M17–M19: #133–#137 (Kommentare inline, Statuszeile, Transklusion, Excalidraw, Checkbox-Fix) #138

Merged
fable-5 merged 10 commits from m17-m19-issues into main 2026-07-19 09:38:38 +02:00
Collaborator

Implementiert die fünf Issues der Milestones M17–M19.

  • #137 Checkbox-Ausrichtung
  • #134 Statuszeile
  • #133 Kommentare fest inline (+ öffentlicher Read-only-Endpoint)
  • #135 Seiten-Einbettung !Seite (Transklusion)
  • #136 Excalidraw-Block-Plugin

PR öffnet, um CI zu triggern (Branch-Pushes lösen ci.yml nicht aus). Runtime-Verifikation von #135/#136 folgt auf Test.

🤖 Generated with Claude Code

Implementiert die fünf Issues der Milestones M17–M19. - #137 Checkbox-Ausrichtung - #134 Statuszeile - #133 Kommentare fest inline (+ öffentlicher Read-only-Endpoint) - #135 Seiten-Einbettung ![[Seite]] (Transklusion) - #136 Excalidraw-Block-Plugin PR öffnet, um CI zu triggern (Branch-Pushes lösen ci.yml nicht aus). Runtime-Verifikation von #135/#136 folgt auf Test. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fable-5 added 8 commits 2026-07-19 05:47:32 +02:00
`graphify claude install` im Monorepo: graphify-Abschnitt an (neue)
CLAUDE.md angehängt und PreToolUse-Hooks in .claude/settings.json
registriert (Graph-Check vor Such-/Lesetools, Auto-Rebuild nach
Code-Änderungen). Der generierte graphify-out/ (AST-Graph über 741
Code-Dateien, ~10 MB) wird nicht versioniert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Die Task-List-CSS war auf `.editor-content` gescoped und griff daher in
keinem Lese-Container (public/comment/legal/home/history-preview), wo
docToHtml sein `<li><input><p>`-Markup einspeist — dort blieb der Bullet
sichtbar, die Checkbox lag inline und das Block-`<p>` mit Default-
`margin: 1em 0` versetzte den Text in die nächste Zeile.

Fix: Task-List-Regeln über das eindeutige `data-type='task_list'`-Attribut
(nur von docToHtml und der Editor-NodeView erzeugt) entscopen, sodass sie
im Editor UND in allen Lese-Containern greifen; Checkbox per kleinem
margin-top auf die erste Textzeile ausrichten und Ober-/Untermarge des
Item-Absatzes neutralisieren. Deckt beide DOM-Formen ab: Lesemodus
`li > input` + `li > p`, Editor `li > label > input` + `li > div > p`.

Visuell verifiziert (Vorher/Nachher, beide Pfade).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
`graphify claude install` erzeugte CLAUDE.md und .claude/settings.json,
die nicht Prettier-konform sind und `pnpm lint`/CI rotmachen würden.
Diese Dateien sind tool-generiert (bei Re-Install neu geschrieben), daher
per .prettierignore ausgenommen statt von Hand formatiert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Neue schlanke Statuszeile (letzte Aktualisierung · Wortzahl · geschätzte
Lesezeit) zwischen Seitenkopf und Artikel — im authentifizierten
Lesemodus und in der öffentlichen Ansicht.

- Geteilte Komponente `PageStatusBar` (Datum via Intl in der aktiven
  Sprache, Lesezeit = ceil(Wörter/200), Singular/Plural, Lesezeit
  ausgeblendet bei 0 Wörtern).
- `countWords`/`htmlToText`-Helfer in lib/word-count.ts.
- Authentifiziert (`PageEditorPage`, nur Lesemodus): Wortzahl aus dem
  vorhandenen Markdown-Export (geteilter Query-Key ['page-markdown']),
  `updatedAt` direkt von `page.data`.
- Öffentlich (`PublicPageView`): Wortzahl aus dem server-gerenderten HTML
  per DOMParser — kein Editor-Bundle nötig; kein Backend-Change.
- i18n common.statusbar (de+en), CSS `.page-statusbar` (middot-getrennt,
  gedämpft). Gates grün (typecheck/lint/i18n:check); visuell verifiziert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Kommentare erscheinen jetzt fest im Lesefluss zwischen Backlinks und
lokalem Graph statt in einem ein-/ausblendbaren Panel. Der
Kopfleisten-Toggle (Icon + Unread-Badge) entfällt.

Frontend:
- CommentsPanel → CommentsSection (Inline-Sektion, ohne Panel-Chrome/
  Close-Knopf; markiert beim Sichtbarwerden als gelesen). Neue
  Read-only-Variante PublicComments für die anonyme öffentliche Ansicht.
- Umzug auf die äußere Ebene in PageEditorPage (view-Modus, zwischen
  BacklinksPanel und LocalGraphPanel). Das Schreibrecht (collab rw) wird
  per onWriteAccess aus dem inneren PageEditor hochgereicht, damit die
  äußere Ebene den Composer bei commentPolicy=editors korrekt zeigt/
  verbirgt.
- Deep-Link ?comments=1 scrollt jetzt zur Inline-Sektion statt ein Panel
  zu öffnen. Resolve/Unresolve-Knöpfe zusätzlich an mayComment gekoppelt
  (früher nur an isRoot) — Leser sehen keine 403-Knöpfe mehr; Read-only
  blendet alle Aktions-Controls aus.
- CSS comments-panel* → comments-section*; tote Unread-Badge-Regeln raus.

Backend:
- GET /public/:pondSlug/:pageSlug/comments (@Public), read-only. Nutzt den
  vorhandenen resolve()-Pfad (erzwingt ggf. anonymen Lesezugriff → nicht
  öffentliche Seiten 404en) und CommentsService.list. PublicModule
  importiert CommentsModule.

Tests: public.e2e.db.test.ts um anonymen Kommentar-Lesezugriff + 404-Fälle
ergänzt (grün gegen frische Test-DB); comments.spec.ts auf die Inline-UI
umgestellt. typecheck/lint/i18n:check grün.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
body.threads[0] ist unter noUncheckedIndexedAccess möglicherweise
undefined; per Destrukturierung + Non-null-Assertion nach dem
toHaveLength(1)-Check geglättet. (Der Fehler rutschte durch, weil der
#133-Commit nach dem Nachtragen des Tests nicht erneut getypecheckt
wurde.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Obsidian-Syntax `![[slug]]` (optional `![[slug|Anzeige]]`) als Seiten-
Einbettung. Im Lese- und öffentlichen Modus wird der Inhalt der Zielseite
inline gerendert; im Editier-Modus zeigt die NodeView eine Platzhalter-
Karte (Titel + Öffnen-Link).

Shared (Vorbild plugin_block):
- Neuer Block-Atom-Node `transclusion` (targetSlug + optional displayText).
- Markdown: Block-Regel für eine reine `![[…]]`-Zeile (vor `paragraph`
  registriert; mitten im Absatz greift sie bewusst nicht), Token→Node-
  Mapping, Serializer — Round-Trip stabil.
- html.ts: Platzhalter `<div class="dt-transclusion" data-transclusion>`.
- extractWikilinkSlugs erfasst jetzt auch Transklusionen → Einbettung
  zählt als Backlink/Graph-Kante.

Backend (zentraler Render-Pfad):
- PublicService expandiert Platzhalter zur gerenderten Body-HTML der
  Zielseite: SELBER Pond, read-permission-geprüft, Tiefe ≤2 + Zyklen-
  Guard (visited); Fehlend/unlesbar/zyklisch → neutraler Wikilink. Medien
  werden EINMAL über den ganzen Baum aufgelöst (kein Doppel-Processing).
- Neuer authentifizierter Endpoint GET /read/:pondSlug/:pageSlug (nicht
  @Public) liefert dieselbe gerenderte HTML — für die NodeView im
  authentifizierten Lesemodus, auch bei nicht-öffentlichen Seiten.

Web:
- NodeView `transclusion.tsx`: Editier-Modus → Karte; Lesemodus → holt
  /read/:pond/:slug und rendert den (server-sanitisierten) Inhalt inline.
- WikilinkAutocomplete unterstützt `![[` → fügt einen Transklusions-Block
  ein (statt Wikilink).
- CSS für Karte (.dt-transclusion-card) und Embed (.dt-embed), i18n de+en.

Tests: shared Round-Trip-Unit (5), public-DB-Test um Embed-Expansion
(zyklus-sicher, Fehlend→Link) erweitert — grün. typecheck/lint/i18n grün.
Visuelle Editor-Verifikation folgt auf dem Test-Stage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
#136 Excalidraw-Block-Plugin
Some checks failed
CI / Lint, typecheck, test (pull_request) Failing after 4m19s
CI / Auth e2e pack (pull_request) Has been skipped
CI / Import/export fidelity gate (pull_request) Has been skipped
CI / Build container images (pull_request) Has been skipped
c164a031e4
Neues Referenz-Block-Plugin „Excalidraw" (handgezeichnete Whiteboard-
Skizzen), analog zum draw.io-Plugin. Anders als draw.io (vendored Webapp)
ist Excalidraw eine React-npm-Lib: esbuild bündelt Controller + React +
Excalidraw in plugin.js, die Font-/Locale-/Data-Assets werden aus
node_modules in den ZIP-Root kopiert und zur Laufzeit über
EXCALIDRAW_ASSET_PATH (Plugin-Asset-Basis) geladen — nichts spricht mit
excalidraw.com, die Sandbox-CSP pinnt jede Anfrage auf self.

- manifest.json: kind=code, Block-Extension-Point diagram,
  permissions blockData+ui, fallback "[Excalidraw]".
- src/plugin.tsx: Render-Modus zeigt gespeichertes SVG; Edit-Modus zeigt
  Snapshot + Bearbeiten-Knopf (leerer Block öffnet direkt); Vollbild via
  host.ui.enterFullscreen mountet <Excalidraw> (React), „Speichern &
  Beenden" exportiert per exportToSvg, persistiert {scene, svg} über
  host.blockData.setData → Fallback-Renderer bedient Lese-/Public-Ansicht
  + Exporte ohne Backend-Änderung.
- build.mjs: esbuild (jsx automatic, css→text, production-conditions) +
  fflate-ZIP. Build erzeugt excalidraw-1.0.0.zip: 15,5 MiB zip /
  22,3 MiB unpacked (Limits 64/256 MiB — passt).
- i18n de+en, globals.d.ts (CSS-Modul-Deklaration).

pnpm-Override @floating-ui/react-dom@2.1.2: Excalidraw 0.18.1 zieht sonst
@floating-ui/dom@^1.8.0, das (noch) nicht im Registry ist und `pnpm
install` repo-weit bricht (dokumentiert in pnpm-workspace.yaml).

VERIFIZIERT: typecheck/lint, Manifest-Validierung (SDK), Build+ZIP-Größe.
NICHT lokal verifiziert (braucht Preview/Test-Stage): Laufzeit —
Excalidraw-Rendering + Speichern unter Sandbox-CSP, Font-Laden vom
Asset-Pfad. Prod-Installation macht Stefan als Site-Admin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
fable-5 added 1 commit 2026-07-19 06:01:48 +02:00
#135 Fix: /read-Route deklariert Zugriffsregel explizit (@AuthenticatedOnly)
Some checks failed
CI / Lint, typecheck, test (pull_request) Failing after 4m20s
CI / Auth e2e pack (pull_request) Has been skipped
CI / Import/export fidelity gate (pull_request) Has been skipped
CI / Build container images (pull_request) Has been skipped
d5b895ada2
route-permissions.e2e.db.test.ts (#52) verlangt, dass JEDE Route ihre
Zugriffsregel explizit deklariert (PERMISSION_KEY, @Public oder
SiteAdminGuard). Der neue GET /read/:pond/:slug hatte keinen Decorator
(verließ sich auf den Default-Guard) → Coverage-Test rot in CI.
@AuthenticatedOnly() ergänzt (Session erforderlich; per-Page-Recht prüft
weiterhin der Service via resolve→canAccessPage→404).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
fable-5 added 1 commit 2026-07-19 06:12:58 +02:00
Fix latenten Flake in links.service.db.test (Phantom-Sortierung)
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m24s
CI / Build container images (pull_request) Successful in 3m45s
CI / Auth e2e pack (pull_request) Successful in 6m48s
CI / Import/export fidelity gate (pull_request) Successful in 50s
CI / Lint, typecheck, test (push) Successful in 4m31s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 6m45s
CI / Import/export fidelity gate (push) Successful in 54s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
5255cdce06
pondGraph-Test sortierte die EMPFANGENEN Phantom-Slugs, verglich aber
gegen ein UNsortiertes Literal. Da beide Slugs (ghost-<sfx>,
ghost-secret-<sfx>) den Zufalls-Suffix teilen, kippt ihre Sortierreihen-
folge auf ~1/5 der Suffixe (wenn sfx[0] > 's') → nicht-deterministischer
Fehlschlag. In CI-Lauf 372 traf es zu (Suffix „vpxwe…"). Fix: beide
Seiten sortieren. Vorbestehender Bug, unabhängig von M17–M19.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
fable-5 merged commit 5255cdce06 into main 2026-07-19 09:38:38 +02:00
Sign in to join this conversation.
No description provided.