Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ 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. Handoff notes,
decisions and the plan: `docs/people/`.
- **Shared UI**: `src/components/shell`, `src/components/primitives`, `src/components/ui`.

## Conventions
Expand Down
40 changes: 21 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:<test-port> pnpm test`. The OIDC integration test uses a local fake provider with
Expand Down
19 changes: 15 additions & 4 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
Expand Down Expand Up @@ -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 `<label for>`. Required is unmarked; optional fields say
"(optional)". Hints 12px under the field. Validate on submit, then on blur for fields that have errored; never per
keystroke. Errors: danger border, 12px danger text with an icon, `aria-invalid` + `aria-describedby`. Read-only values
are text in a `KeyValueList`, never disabled inputs. Length counters (`FieldCounter`) appear only in the last 20% of the
are text in a `KeyValueList`, never disabled inputs. A single choice among a few explained options is a
`RadioCardGroup` of `RadioCard`s (custom radios, arrow keys move the choice), never native radio inputs; files are
chosen with `FileButton` or `FilePickerButton` (several files). Length counters (`FieldCounter`) appear only in the last 20% of the
limit.

Calendar dates use a custom picker at every viewport, display the same day–month–year format as read-only dates,
and retain ISO values for the server. Month and year selectors are custom controls; calendars start on Monday.
Calendar dates use `DateField`: a custom picker at every viewport that shows the same day–month–year format as read-only
dates and keeps ISO values for the server. Its month and year selectors are custom controls (the years cover the last
ten and the next one); calendars start on Monday.
Grant date/time fields use the calendar and time wheels on touch devices too, with no native date/time fallback.

**Alerts and toasts.** `InlineAlert` for what persists or needs action: load errors, dialog submit errors, partial
Expand Down Expand Up @@ -452,6 +457,12 @@ Rules that the templates above don't imply.
use `FormDialog` with `submitTone="danger"`; plain confirmations stay `ConfirmDialog`.
- **Member register**: status segments scope the table; Number leads on desktop, the name on mobile. Add member from a
record opens the register's dialog via `?add=P-…`. Export confirms before downloading.
- **Fees and receipts** (`docs/people/fees.md`): the register's first row action follows the fee (Review receipt,
Send reminder, or an empty slot); its header keeps one outline action (Send reminders), with Export register as a boxed
icon. People › Receipts is a queue with the pipeline as segments and the officer's to-sign count in the panel.
Review receipt is an `lg` `FormDialog` whose Reject switches it to a required reason (`submitTone="danger"`, Back on
the left). The fee status shows once per page: the record and My area say it in their fee card, not in Member or My
roles. Amounts are `formatCents` ("€10.00") with tabular numbers.
- **Admins**: Groups, Capo Admin of reference, Member and Census never hide; the rest follow the table width. The four
quick filters scroll sideways on phones.
- **Teams**: index cards then all members; a Team Lead without `people:read` sees only their team, as the section
Expand Down
1 change: 1 addition & 0 deletions docs/people/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Everything an agent needs to build, or continue building, the People area of the
| [`brief.md`](brief.md) | The original brief, verbatim |
| [`decisions.md`](decisions.md) | What was settled after the brief: owner decisions, navigation, per-page rules, roles |
| [`contract.md`](contract.md) | The `people.*` backend contract the dashboard codes against |
| [`fees.md`](fees.md) | Fees and receipts: renewal flow, signing, reminders, screens and the receipt PDF |
| [`plan.md`](plan.md) | Phases, checklist and progress log: **start here to know what is done and what is next** |
| [`prototype/`](prototype/) | The clickable prototype's source (structure and copy reference only) |

Expand Down
Loading
Loading