5.5 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: Vitepnpm format: oxfmtpnpm 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 withnode --watchDB_PATH=./dev.db pnpm migrate: apply migrations without starting the serverDB_PATH=./dev.db node src/seed.js: rebuild content tables fromsrc/data/. It wipes every content table first (feedback is kept). Run it from the repo, not the deployed copy.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-pagerfirst. 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.dband other*.dbfiles 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.tsxas the wrapper unless explicitly noted otherwise. - Pages live in
src/pages/; section-level components go insrc/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.jsis the single source of truth for navigation, routes, and actions (header, footer, pages).api.jsis the shared caching client used by frontend data hooks.- Logos: org logos in
public/org-logos/(served at/org-logos/), event logos inpublic/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
.jsbefore.ts, so a.jsand.tsfile with the same base name will import the wrong one. Give new hooks distinct names. - Don't use
fallback: EMPTYin 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: falseprevents cross-entity sort order collisions.- The
OMITsentinel distinguishes unsent fields from deliberate clears. admin-schema-sync.jsruns at boot and throws if descriptors don't match livePRAGMA table_info. If boot fails after a schema change, update the descriptor or migration so they agree.admin-cli.jsimportsROLESanddestroyAllSessionsForfromauth.js. Keep it that way to prevent drift.
Migrations
- Sequential files:
001_,002_, ... - The runner may drop statements after a
BEGIN...ENDtrigger body. Put eachCREATE VIEWin its own migration file with noBEGIN...ENDblock. PRAGMA foreign_keys = OFFmust be set outside transactions when cascading constraints are involved.
Integrations
- Church Center (ngu.churchcenteronline.com): Planning Center embeds for giving and the calendar.