NGU-Web/CLAUDE.md
Zaldimmar 625329fa7c Consolidate migrations 001–023 into one baseline schema
023_schema.sql is the whole database as the 23 migrations left it:
every table in its final shape (columns added by ALTER folded into
their CREATE), its indexes, the five views, the seed rows (event
scopes, the shipped front page) and the triggers, last. The reasoning
from the old files that still applies sits next to what it explains;
the rest is in git history.

A fresh database runs it and lands at v23; one already at v23 skips
it. migrate() now treats the first file as a baseline and refuses a
database between v1 and v22 with a message saying to upgrade it on
an older release first, rather than failing halfway on CREATE TABLE.
Production is at v19, so the previous commits on this branch
(020–023) have to be deployed before this one.

Checked by building a database from the old files and one from this
file and comparing them: strictness, columns, types, defaults,
nullability, keys, foreign keys and their actions, CHECK constraints,
index definitions, view and trigger SQL, and seed rows all match.

Comments that cited migration numbers now point at the schema, and
CLAUDE.md describes the baseline and where new migrations go.

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

78 lines
No EOL
6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
- `server/src/migrations/023_schema.sql` is the baseline: the whole schema, consolidated from the old 001–023. It only runs on an empty database; the runner refuses a database between v1 and v22. It's the place to read the schema, not to change it: a database that already exists never re-runs it.
- Changes go in new sequential files after it: `024_`, `025_`, ...
- The runner may drop statements after a `BEGIN...END` trigger body. Keep triggers last in a file, and put each `CREATE VIEW` before any trigger or in its own file.
- 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. When views or triggers name the table being rebuilt, wrap the drop-and-rename in `PRAGMA legacy_alter_table = ON` ... `OFF`, then recreate the rebuilt table's own indexes and triggers.
## Integrations
- Church Center (ngu.churchcenteronline.com): Planning Center embeds for giving and the calendar.