NGU-Web/CLAUDE.md
2026-09-25 03:20:11 -05:00

4.4 KiB

NGU-Web

Website for NGU (Next Generation of Unity), a Unity movement organization with regional chapters in the US and internationally. Full-stack app with a public site and a role-based admin panel.

Stack

  • Frontend: React, TypeScript, Tailwind CSS, React Router, Vite (in src/)
  • Backend: Hono on Node.js, SQLite (WAL mode, STRICT tables) via better-sqlite3 / node:sqlite (in server/)
  • Package manager: pnpm only (never npm or yarn)

Deployment (production)

  • Ubuntu VPS, nginx reverse proxy, systemd service ngu-api
  • App deployed to /srv/ngu-api; database at /var/lib/ngu/ngu.db
  • Debugging: check journalctl -u ngu-api -n 40 --no-pager first. Make sure rsync ran from the repo (not the deployed copy) before restarting the service.
  • Never run commands against the production server or database unless explicitly asked.

Git workflow

  • Remote is a self-hosted Forgejo server, not GitHub. Do not use gh.
  • Open pull requests with tea: tea pr create --base main --head <branch> --title "..." --description "..."
  • Never commit directly to main. Create a branch per change, push it, open a PR.
  • Versions are marked with annotated tags (v1.0, v1.3...). Don't create or move tags unless asked.
  • server/dev.db and other *.db files are local only and never committed.

How to work in this repo

  • Read the relevant existing files before writing anything. Follow existing patterns exactly: descriptors, field syntax, extension shape, import conventions.
  • Ask questions up front before implementing non-trivial features.
  • Prefer targeted edits when surrounding code is stable; full rewrites only when a component is being substantially reworked.
  • Fix root causes. No redirect shims or workarounds.
  • Keep data logic in the database and presentation logic in code. Make things configurable via constants, not hardcoded in components.
  • Name components for what they do, not what they currently filter.

Project layout

  • All pages use PageShell.tsx as the wrapper unless explicitly noted otherwise.
  • Pages live in src/pages/; section-level components go in src/pages/sections/.
  • src/data/ holds only hardcoded data shared across multiple section files (e.g. historyDecades.ts, map grid). Everything else comes from SQLite.
  • navConfig.js is the single source of truth for navigation, routes, and actions (header, footer, pages).
  • api.js is the shared caching client used by frontend data hooks.
  • Logos: org logos in /org-logos/, event logos in public/event-logos/. The <Logo> component hides itself on load error.

Rules and gotchas

  • Role checks must use ladder comparisons, never equality. Roles rank viewer → editor → admin → superadmin. Use roleCanWrite(user) / roleCanDelete(user) from lib/roles and the minimum-rank helpers (canWrite, canDelete, isSuper, atLeast). role === "admin" silently excludes higher roles and has caused repeated bugs.
  • Imports need explicit extensions (.ts, .tsx, .js) everywhere.
  • Vite resolves .js before .ts, so a .js and .ts file with the same base name will import the wrong one. Give new hooks distinct names.
  • Don't use fallback: EMPTY in api.js hooks. It silently returns empty arrays and hides server errors; let the error state surface.

Admin CRUD engine

Descriptor-driven: server/admin-crud.js and admin-schema.js (server) and adminSchema.js (client) generate SQL and form fields from declarative entity configs. Adding an entity should mean adding a descriptor, not new CRUD code.

  • Child collections are deleted and reinserted wholesale. Unsafe for entities referenced by foreign keys elsewhere.
  • reindex: false prevents cross-entity sort order collisions.
  • The OMIT sentinel distinguishes unsent fields from deliberate clears.
  • admin-schema-sync.js runs at boot and throws if descriptors don't match live PRAGMA table_info. If boot fails after a schema change, update the descriptor or migration so they agree.
  • admin-cli.js imports ROLES and destroyAllSessionsFor from auth.js. Keep it that way to prevent drift.

Migrations

  • Sequential files: 001_, 002_, ...
  • The runner may drop statements after a BEGIN...END trigger body. Put each CREATE VIEW in its own migration file with no BEGIN...END block.
  • PRAGMA foreign_keys = OFF must be set outside transactions when cascading constraints are involved.

Integrations

  • Church Center (ngu.churchcenteronline.com): Planning Center embeds for giving and the calendar.