Commit Graph

2 Commits

Author SHA1 Message Date
9e8ebfe49c Degrade plugin content gracefully in HTML, PDF, and office exports (#79)
All checks were successful
CI / Lint, typecheck, test (push) Successful in 2m57s
CD / Promote to Int (push) Successful in 9s
CI / Auth e2e pack (push) Successful in 5m2s
CI / Import/export fidelity gate (push) Successful in 46s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m10s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m12s
Completes M7: exports and the public read view no longer show raw plugin
placeholders (ADR 0008/0009).

- PluginFallbackRenderer (api): replaces each plugin-block placeholder in
  content-cache HTML with its best static form — the block's stored SVG
  snapshot (block data is author-controlled, so it passes the same
  DOMPurify sanitizer as uploaded SVG files before entering host HTML),
  else the manifest fallback from the stored snapshot (text, or an image
  inlined as a data URI so network-isolated renderers work; tombstone-safe
  for uninstalled plugins), else the literal '[plugin content]' marker.
- Office exports (docx/odt): the export markdown is degraded before
  pandoc — GFM knows neither the dorfteich-plugin fence nor the section
  fenced div, so blocks become their fallback text and sections plain
  quoted blocks (shared replacePluginNodesForExport, AST-level so nesting
  and embedded blocks inside sections survive).
- PDF export applies the HTML fallback pass before building the Gotenberg
  document — resolving the TODO left in #67.
- Public read view: the same fallback pass plus the pond's active
  section-style CSS inlined as a <style> block, so public pages show
  styled sections and static plugin content without any plugin runtime.
- Covered in export.service.db.test (snapshot SVG sanitized — hostile
  <script> stripped; manifest text; tombstone text; quoted sections and
  no fence artifacts in the pandoc input) and shared export-fallbacks
  tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-11 14:13:09 +02:00
fc41c91003 Add public read access and server-rendered page HTML (#56)
All checks were successful
CD / Build and push images (push) Successful in 3m13s
CI / Lint, typecheck, test (push) Successful in 2m30s
CI / Auth e2e pack (push) Successful in 3m21s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
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
2026-07-09 23:46:25 +02:00