Anonymous visitors read what `public` grants allow, via the SPA and a
server-rendered HTML endpoint for crawlers / PDF export (ADR 0005/0009).
- api `public/`: `GET /public/:pondSlug/:pageSlug` returns a self-contained
HTML document (content cache + minimal chrome + canonical link, no
session-dependent content), and `…/content` returns JSON for the SPA. Both
are `@Public()` and resolve the `public` subject through the shared resolver
(PermissionService) — denied or missing → 404, so non-public pages never
reveal their existence (security.md). Cached image nodes (`data-file-id`)
are resolved to `/api/v1/media/:fileId` for the static render.
- media: `GET /media/:fileId` is `@Public()` too, so embedded images on a
public page stream to anonymous visitors; the attachment guard still gates
on the `public` grant (non-public → 404).
- web: a lightweight read-only `PublicPageView` at `/public/:pondSlug/:pageSlug`
(outside the auth guard) renders the server HTML — deliberately without
importing the collaborative editor, so anonymous readers load no editor
bundle. New `public` i18n namespace (de+en).
- tests: `public.e2e.db.test.ts` (HTML + JSON served for a public page; a
non-public page never resolves; removing the grant 404s both) and a browser
`public` pack (anonymous reads a public page and its image via the SPA;
a non-public page shows "not found") with its own CI step.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1