NGU-Web/CLAUDE.md
Zaldimmar 32d04b63e9 Add commands section to CLAUDE.md and fix role/logo details
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 03:24:00 -05:00

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: 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/seed.js: rebuild content tables from src/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-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 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.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.