diff --git a/AGENTS.md b/AGENTS.md index 285fce1..58d6ee0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,11 @@ What the app is and how to run it: [`README.md`](README.md). UI rules: [`docs/de backend's `people.*` responses with `people.contract.ts` and, in agent mode, uses the in-memory fake (`AGENT_PERMISSIONS`, `AGENT_PERSON` pick the viewer). `/dashboard` admits `people:self` without `admin:access`; Capo Admin and Team Lead come from the person's own record (`peopleRoles` in the route context) and only open their - sections. Handoff notes, decisions and the plan: `docs/people/`. + sections. Fees and receipts (`docs/people/fees.md`): `people:fees:write` reviews, marks paid and reminds; + `people:receipts:sign` downloads and uploads signed receipts for the officer on the caller's record. + Preparing CIE requests additionally requires `esign:sign` and reuses the existing eSign UI and agent; the backend + owns the durable receipt/request association and completion (the People backend contract is still pending). Handoff notes, + decisions and the plan: `docs/people/`. - **Shared UI**: `src/components/shell`, `src/components/primitives`, `src/components/ui`. ## Conventions diff --git a/README.md b/README.md index 2af26db..b5ca4ee 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,10 @@ requires `azure:members:create`. Groups require `azure:groups:read`; adding and Telegram › Capo Admin and Account › My area for volunteers themselves. The dashboard talks to a typed `people.*` contract (`src/features/people/people.contract.ts`, documented in `docs/people/contract.md`); until the backend implements it, agent mode serves sample data from `people.fake.server.ts`. Permissions: `people:read`, `people:write`, -`people:members:read`, `people:members:write`, `people:appoint`, and `people:self`, which alone admits non-admins to My -area and, by the role on their record, Capo Admin or their team. See `docs/people/README.md`. +`people:members:read`, `people:members:write`, `people:appoint`, `people:fees:write` (review transfer receipts, mark fees +paid, reminders), `people:receipts:sign` (the President and Vice-president sign receipts), and `people:self`, which alone +admits non-admins to My area and, by the role on their record, Capo Admin or their team. See `docs/people/README.md` +and, for fees and receipts, `docs/people/fees.md`. Built with TanStack Start (React 19), Vite+, Nitro, Tailwind CSS v4 and shadcn/Base UI. The server is an OIDC BFF: only it holds the IdP access/refresh/ID tokens. Browsers hold an opaque, HttpOnly session cookie; sessions live in a @@ -29,23 +31,23 @@ Signing in as a real user needs a development client registered in the IdP. Set the following runtime variables (the build does not need secrets): -| Variable | Meaning | -| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `APP_URL` | Dashboard's public origin. Required outside development; HTTPS in production. Development defaults to `http://localhost:$PORT` (3001). Used for exact Origin CSRF validation and callback/logout URLs. | -| `BACKEND_URL` | tRPC backend origin; use the in-cluster service address in production. Defaults to `http://localhost:3000` in development. | -| `IDP_URL` | Public IdP origin; defaults to `https://auth.polinetwork.org`. Issuer is `$IDP_URL/api/auth`. Browser redirects retain this public origin. | -| `IDP_INTERNAL_URL` | Optional internal IdP origin, e.g. `http://auth-service.auth.svc.cluster.local:3000`. Rewrites discovery, token, JWKS and revocation transport only; issuer, JWT assertion audiences (public token/revoke endpoints) and browser URLs remain public. | -| `OIDC_CLIENT_ID` | Registered confidential dashboard client, with authorization code and refresh grants, scopes `openid profile email offline_access backend:admin`, and backend resource link. | -| `OIDC_CLIENT_PRIVATE_JWK` | Private signing JWK JSON with `kid` and `alg` (`EdDSA`/Ed25519 or `ES256`/P-256); inject from this service's Key Vault. Register only its public JWKS in the IdP. | -| `OIDC_CLIENT_SECRET` | Alternative `client_secret_basic` credential; set exactly one client credential mode. | -| `BACKEND_RESOURCE` | Backend OAuth resource/audience; defaults to `https://backend.internal.polinetwork.org`. Sent on every authorize, code exchange and refresh request. | -| `REDIS_URL` | Dedicated dashboard Redis connection. Required outside development; development defaults to `redis://localhost:6379`. Never point to shared backend Redis. | -| `SESSION_SECRET` | Independent random secret of at least 32 characters, encrypting the 10-minute state/nonce/PKCE cookie. Rotation invalidates pending login attempts. | -| `AGENT_PERMISSIONS` | Development only. Comma-separated permissions for the agent-mode user (default: all). `people:self` alone previews the restricted People view. | -| `AGENT_PERSON` | Development only. The agent-mode user's People record (default `P-0007`, Direttivo). `P-0098` is a Capo Admin, `P-0034` the IT Team Lead, `P-0115` an IT member; `match:P-0115` previews the first sign-in, `none` a sign-in without a record. | -| `AGENT_MODE` | Fake all-permission user in development only. Backend must be a local fake or local legacy test instance. Production ignores this mode. | -| `PORT` | Local listening port (3001 by default); use a port ≥10000 for agent previews. | -| `TEST_REDIS_URL` | Opt-in disposable Redis for integration tests. Tests create/delete only UUID session keys. | +| Variable | Meaning | +| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `APP_URL` | Dashboard's public origin. Required outside development; HTTPS in production. Development defaults to `http://localhost:$PORT` (3001). Used for exact Origin CSRF validation and callback/logout URLs. | +| `BACKEND_URL` | tRPC backend origin; use the in-cluster service address in production. Defaults to `http://localhost:3000` in development. | +| `IDP_URL` | Public IdP origin; defaults to `https://auth.polinetwork.org`. Issuer is `$IDP_URL/api/auth`. Browser redirects retain this public origin. | +| `IDP_INTERNAL_URL` | Optional internal IdP origin, e.g. `http://auth-service.auth.svc.cluster.local:3000`. Rewrites discovery, token, JWKS and revocation transport only; issuer, JWT assertion audiences (public token/revoke endpoints) and browser URLs remain public. | +| `OIDC_CLIENT_ID` | Registered confidential dashboard client, with authorization code and refresh grants, scopes `openid profile email offline_access backend:admin`, and backend resource link. | +| `OIDC_CLIENT_PRIVATE_JWK` | Private signing JWK JSON with `kid` and `alg` (`EdDSA`/Ed25519 or `ES256`/P-256); inject from this service's Key Vault. Register only its public JWKS in the IdP. | +| `OIDC_CLIENT_SECRET` | Alternative `client_secret_basic` credential; set exactly one client credential mode. | +| `BACKEND_RESOURCE` | Backend OAuth resource/audience; defaults to `https://backend.internal.polinetwork.org`. Sent on every authorize, code exchange and refresh request. | +| `REDIS_URL` | Dedicated dashboard Redis connection. Required outside development; development defaults to `redis://localhost:6379`. Never point to shared backend Redis. | +| `SESSION_SECRET` | Independent random secret of at least 32 characters, encrypting the 10-minute state/nonce/PKCE cookie. Rotation invalidates pending login attempts. | +| `AGENT_PERMISSIONS` | Development only. Comma-separated permissions for the agent-mode user (default: all). `people:self` alone previews the restricted People view. | +| `AGENT_PERSON` | Development only. The agent-mode user's People record (default `P-0007`, Direttivo and President). `P-0098` is a Capo Admin and the Vice-president, `P-0034` the IT Team Lead, `P-0115` an IT member, `P-0045` has a fee receipt to review, `P-0021` a rejected one; `match:P-0115` previews the first sign-in, `none` a sign-in without a record. | +| `AGENT_MODE` | Fake all-permission user in development only. Backend must be a local fake or local legacy test instance. Production ignores this mode. | +| `PORT` | Local listening port (3001 by default); use a port ≥10000 for agent previews. | +| `TEST_REDIS_URL` | Opt-in disposable Redis for integration tests. Tests create/delete only UUID session keys. | Before review run `pnpm check`, `pnpm typecheck`, `pnpm test` and `pnpm build`. Run real Redis tests with `TEST_REDIS_URL=redis://127.0.0.1: pnpm test`. The OIDC integration test uses a local fake provider with @@ -78,3 +80,8 @@ Logout deletes the server session, attempts refresh revocation, and redirects to requests require an `Origin` exactly equal to `APP_URL`'s configured origin. Sessions and identity responses are not cacheable. Production cookies are host-only, `Secure`, `HttpOnly`, `SameSite=Lax`; tokens and client keys never appear in browser storage or route loader output. + +People receipt signing reuses PoliNetwork eSign's CIE popup and keeps ZIP download/signed-file upload as an option. +The People backend (including the durable receipt/eSign bridge) is still pending; see +[`docs/people/fees.md`](docs/people/fees.md#integration-with-esign). Agent mode previews the manual receipt flow and +reports the backend requirement for CIE signing. diff --git a/docs/design.md b/docs/design.md index d72ac40..79fea11 100644 --- a/docs/design.md +++ b/docs/design.md @@ -117,7 +117,7 @@ service title ("PoliNetwork Admin" on Overview), Search, the theme toggle and th as horizontally scrolling pills that scroll away with the page. The current pill stays in view after navigation, font loading and changes to the bar's width, including when it first appears below 760px. A 64px bottom nav lists the visible services (current one accent with a top bar, Reports with the open-reports dot). The device's bottom safe area adds space below the 64px -bar. Below 360px, WhatsApp uses "WA" so all labels remain readable. Section pills and bottom nav are the only navigation; there +bar. Below 360px, WhatsApp uses "WA" so all labels remain readable. With seven services, WhatsApp uses "WA" below 440px and Telegram uses "TG" below 360px. Section pills and bottom nav are the only navigation; there is no sheet. **Command palette** (`⌘K`/`Ctrl+K`, the rail and top bar Search, the Overview search): sections, "Find “…” in @@ -264,7 +264,9 @@ first/last buttons); on narrow tables "‹ 1 of 34 ›". Clickable rows and rows with actions hover on `--pn-accent-soft`, using the brand blue in both themes. **Row actions** (`RowActions`, `IconButton`). Order: other actions → edit → delete → `⋮` menu, the menu always far -right. Always visible, 2px apart, monochrome `--pn-fg-muted` ghosts. Every action uses brand blue on hover, +right. A `⋮` menu holds at least two actions: a single one is its own icon button, and none leaves an empty slot so the +icons of every row align. Menus never repeat the row's own link (Open record) or an action already visible on the row, +never start with a separator, and keep destructive actions last after one. Always visible, 2px apart, monochrome `--pn-fg-muted` ghosts. Every action uses brand blue on hover, including destructive actions; `tone="info"` marks an "on" state in accent (the visibility eye). Group row actions keep their semantic colors at rest: labels use `--pn-warning-fg`, edit uses `--pn-success-fg`, and leave/delete use `--pn-danger-fg`. Visibility, labels, edit and leave/delete all hover in brand blue (`--pn-accent` on `--pn-accent-soft`); @@ -329,11 +331,14 @@ the language name over a field-like box that holds the `bare` input. Single-line **Forms** (`FormField`, `fieldControl`). Label above, real `