f500198c5d
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| f500198c5d |
Add per-pond fonts: catalog, build, application, and admin UI (#66)
All checks were successful
CD / Build and push images (push) Successful in 3m24s
CI / Lint, typecheck, test (push) Successful in 3m6s
CI / Auth e2e pack (push) Successful in 4m8s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
Self-hosted Google Fonts with per-pond selection (ADR 0016), the GDPR "zero external requests" posture (security.md, CSP `font-src 'self'`). - Catalog: a curated 15-family OFL/Apache list in shared (family, weights, category, license, google-webfonts-helper id). `deploy/fonts/build-fonts.mjs` validates every entry has license info (fails the build otherwise), downloads the WOFF2 weights into apps/web/public/fonts/ (gitignored), and generates the @font-face stylesheet — run at image build time from the web Dockerfile (with retries), never from a visitor's browser. - Application: PondFontScope sets --font-heading/body/mono (+ weights) from pond.settings.fonts on the editor + read view; the existing global CSS already reads those custom properties, so headings/body/code re-resolve to the pond's fonts. A pond with no settings arrives with the defaulted values (Roboto 400 / Roboto 200 / Fira Code), so the vision defaults always render. - Admin UI: pond-settings 'Appearance' section — three slots (family + weight) with a live preview, Pond-Admin-gated (fonts added to updatePondInputSchema and merged in PondsService.update); a font catalog attribution page (/fonts) listing families and licenses. New `font` i18n namespace (de+en). - CSP: strict Content-Security-Policy in nginx.conf (default-src 'self'; font-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; …) — the app's scripts are all external files, inline styles cover CSS variables. - Tests: shared catalog-integrity unit test (the invariant the build enforces); e2e fonts pack — no request leaves the origin when rendering a pond (the GDPR network assertion), a font choice applies to a page and persists, and a pond without settings renders the defaults. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 0c6494f209 |
Enforce permissions in API guards and retire interim access (#52)
All checks were successful
CD / Build and push images (push) Successful in 2m54s
CI / Lint, typecheck, test (push) Successful in 2m25s
CI / Auth e2e pack (push) Successful in 2m58s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m12s
CD / Promote to Int (push) Successful in 12s
Every route now declares its access rule explicitly and is enforced through the shared resolution algorithm (permissions.md): - PermissionGuard + decorators (@RequiresPondRole, @RequiresPagePermission, @RequiresAttachmentPermission, @AuthenticatedOnly) applied to every route; a route-enumeration test proves full coverage alongside @Public()/Site-Admin-guarded routes. - 404/403 policy (documented in README conventions): denied reads answer 404 (existence hiding), denied writes on readable things answer 403; trash views need write capability (ADR 0013). - PermissionService resolves page/pond questions via the shared resolver, with an in-process pond-context cache (grants + label parents) that is invalidated on every grant/label-tree change and TTL-bounded as a multi-process safety net. Grant changes also fire pond_access_changed for collab revalidation (#39/#53). - shared: pond-scope resolution (hasPondRole, canSeePond) next to the page resolver; grant wire schemas + GrantView. - Owner Pond-Admin grants: migration backfill for all existing ponds, created transactionally with every new pond (shared + personal + seed). - Grant CRUD under /ponds/:id/grants (pond_admin-gated) with structural and referential validation, last-admin protection, audit logs. - InterimAccessService deleted; page lists, search, backlinks, phantom links, and trash listings are filtered per page through the resolver; collab tokens are now truly ro for readers. - Fixture-matrix e2e (reader/editor/pond admin/foreign, label-deny, authenticated-subject, revoke-then-immediate-deny cache test). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY |
|||
| fa7ae033b5 |
Add permission-revocation handling for live and offline sessions (#39)
All checks were successful
CD / Build and push images (push) Successful in 2m58s
CI / Lint, typecheck, test (push) Successful in 1m55s
CI / Auth e2e pack (push) Successful in 2m25s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m12s
CD / Promote to Int (push) Successful in 11s
Revoking write access must terminate live sessions and let a user with pending offline edits export them rather than lose them silently. Backend (generic, reused by M5 grants #53): - packages/shared: POND_ACCESS_CHANGED_CHANNEL, the LISTEN/NOTIFY channel shared by api and collab. - api: PondAccessNotifier emits pg_notify(pond_access_changed, pondId) on a permission-relevant change; the single generic seam for revocation. Wired into pond soft-delete as the interim trigger (see==modify until #53). - collab: a dedicated-connection LISTEN listener (LISTEN is connection- bound, not pooled) that, on a notification, closes every open connection to the pond's open pages. Clients then reconnect and the api re-issues a token reflecting current access (downgrade to ro, or 403/404). Reconnects and re-LISTENs if its connection drops. Frontend: - use-collab-provider: a refused token (403/404) on (re)connect sets accessRevoked and stops the reconnect loop; exposes discardLocal. - AccessRevokedDialog: keeps local content visible and offers Markdown copy/download (derived from the live editor doc, so offline edits are included) and an explicit discard that clears IndexedDB. de+en strings. Tests: collab DB-backed integration test proves a direct NOTIFY closes a live session within seconds (AC1) and leaves unrelated ponds untouched; listener unit tests; api test asserts soft-delete fires the notifier; web test for the export Markdown derivation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY |
|||
| 64928f0ac0 |
Quota foundation: overrides, resolution, race-safe consumption (#22)
All checks were successful
CD / Build and push images (push) Successful in 1m46s
CI / Lint, typecheck, test (push) Successful in 1m17s
CI / Auth e2e pack (push) Successful in 1m39s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m6s
CD / Promote to Int (push) Successful in 10s
- quota_overrides + pond_usage models (BigInt values, unique per
subject+key); migration 20260705185146_quotas
- instance-default quota keys in the settings registry (editors 5,
readers 50, additional ponds 0, storage 1 GiB, max file 25 MiB)
- QuotaService: getEffective with pond → user → instance resolution
(zero counts as a value, not a gap); assertCanCreateSharedPond and
checkAndConsume serialize via pg_advisory_xact_lock inside the guarded
write's transaction; release never drops below zero
- pond creation enforces additional_ponds (personal ponds don't count);
quota errors carry code quota_exceeded + {quotaKey, limit}, localized
- seed grants fixtures an additional_ponds override (default is 0)
- table-driven resolution tests, parallel-consumption test, e2e for the
pond-creation limit
Closes #22
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpQz6ypHJsLfMf4S6fyQEB
|
|||
| f0850eecd3 |
Ponds: data model, CRUD API, personal pond on verification (#21)
All checks were successful
CD / Build and push images (push) Successful in 1m46s
CI / Lint, typecheck, test (push) Successful in 1m19s
CI / Auth e2e pack (push) Successful in 1m42s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m5s
CD / Promote to Int (push) Successful in 10s
- Pond model with pond-level trash columns (ADR 0013) and settings jsonb holding only deviations from the defaults (sidebar sort, font slots per ADR 0016); migration 20260705090100_ponds - shared: pond schemas/views and slugify (German transliteration, URL-safe, length-capped); deterministic -2/-3 suffixes for collisions - InterimAccessService: single place answering pond access questions until the real role model lands in M5 - POST/GET /ponds, GET /ponds/:slug, PATCH/DELETE /ponds/:id, Site-Admin trash + restore; personal pond auto-created on e-mail verification and for active seed fixtures; personal ponds cannot be trashed - e2e pack covering verify-flow pond creation, slug suffixes, rename, foreign-pond 404s, trash/restore; slugify unit tests Closes #21 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UpQz6ypHJsLfMf4S6fyQEB |