NGU-Web/CLAUDE.md
Zaldimmar 25e592bfea Clean up the schema: drop unused tables, rename scopes, publishable awards
Migrations 020–023, with the code that reads each:

- 020 drops people_lists, people_list_members, v_chapters and
  v_person_affiliations. Nothing queried any of them.
- 021 renames event_sections to event_scopes and events.section_id
  to scope_id, finishing what 015 described. The API sends `scopes`
  and `scope_id`, and useEvents, EventListCards and EventCalendar
  take `scope`. It also inserts national/regional/partner, which only
  the retired seed ever created: a database built from migrations
  alone had no scope for the Retreats bands.
- 022 drops events.sort_order and people.sort_order. Events now sort
  by date (upcoming soonest first, past latest first, undated last)
  on /events, org pages and the countdown. People were only ever
  sorted by sort_name on the site. Every other sort_order stays.
- 023 rebuilds teams with created_at and updated_at plus a touch
  trigger, so the teams editor gets the same optimistic concurrency
  as the other entities.

Awards can be drafted: is_published (added in 011) is on both
descriptor halves with a Publishing group, and the award list, award
page, org awards and event awards leave drafts out.

The migration runner now turns foreign keys off around the per-file
transactions and runs foreign_key_check before each commit. PRAGMA
foreign_keys is a no-op inside a transaction, so 009's warning was
right and a rebuild of a referenced table (023) couldn't be written
otherwise. CLAUDE.md is updated to match.

Also removes the stray src/App.tsx.save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 17:09:58 -05:00

5.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

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)

Commands

Frontend (repo root):

  • pnpm dev / pnpm build / pnpm preview: Vite
  • pnpm format: oxfmt
  • pnpm exec tsc: type-check (noEmit; there is no separate lint or typecheck script)
  • There is no test suite.

Backend (server/, Node >= 22). The server reads HOST (default 127.0.0.1), PORT (default 3001) and DB_PATH (default ./ngu.db); locally, use DB_PATH=./dev.db:

  • DB_PATH=./dev.db pnpm dev: run with node --watch
  • DB_PATH=./dev.db pnpm migrate: apply migrations without starting the server
  • DB_PATH=./dev.db node src/admin-cli.js add|list|passwd|role|disable|enable ...: the only way accounts are created

In dev, Vite proxies /api to the target set in vite.config.ts, so the API must listen on that port.

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.ts is the single source of truth for navigation, routes, and actions (header, footer, pages).
  • api.ts is the shared caching client used by frontend data hooks.
  • Logos: org logos in public/org-logos/ (served at /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 the minimum-rank helpers from src/lib/roles.ts (canWrite, canDelete, isSuper, atLeast). Where a local variable shadows the name, import with an alias, e.g. canWrite as roleCanWrite. 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.ts 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.ts (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.
  • The runner turns foreign keys off around every migration (it can't be done inside the file's transaction) and runs PRAGMA foreign_key_check before committing, so a table rebuild needs no PRAGMA foreign_keys of its own. A rebuild whose table is named by views or triggers wraps the rename in PRAGMA legacy_alter_table = ON ... OFF (see 023).

Integrations

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