ee6a11f9b0
254 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 0bc36aa58c |
#194: orphan-file sweep, drop the unused Attachment.deletedAt
Nightly sweep with two directions: attachments still unclaimed (pageId null) after a 24 h grace period - claimed by no collab persist, page upload, or import - are reclaimed (row, file, quota released); files on the uploads volume without a database row (drift after a crashed upload) are removed once older than the grace period. The grace period protects the paste-then-insert window. Deliberate deviation from the issue's content-reference idea, documented in schema comment and operations.md: claimed attachments whose page content no longer embeds them are NOT auto-deleted. The page attachments panel lists claimed files as user-managed objects (inserting into the document is optional there), so 'not embedded' is not 'unused' - an auto-delete would destroy panel assets. Humans clean those up in the panel or the pond file manager, which flags orphans already. Attachment.deletedAt is removed by migration - deletion is hard everywhere (sweep, purge, manual), there is no soft-delete state; the never-true deletedAt:null filters in files/export queries went with it. Refs #194 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 402b22e05f |
#193: pond purge — retention job and manual Site-Admin endpoint
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m58s
CI / Build container images (pull_request) Successful in 2m47s
CI / Auth e2e pack (pull_request) Successful in 7m46s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 28s
CD / Smoke tests against Test (push) Successful in 1m20s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 5m4s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m38s
CI / Import/export fidelity gate (push) Successful in 56s
Deletion now actually deletes: a trashed pond past the trash retention (same clock as pages, extended trash-purge job) or purged manually via DELETE /ponds/:id/purge (Site-Admin-only, like pond restore) is removed with everything it holds. Files go first (idempotent rm, resumable on a crash), then one transaction ordered around the FK actions: attachments and labels (Restrict) precede the pond; the page delete cascades versions, comments, content cache incl. the search vector, update log, mentions, label assignments, favorites, outgoing links and open collab sessions; the pond delete cascades grants, usage counters (that is the quota correction), pond-plugin opt-ins and conversion jobs; polymorphic watches and pond quota overrides are deleted explicitly. A purge racing a restore or another purge is a no-op; both paths record a pond.purged audit event. Known residues by design, documented in operations.md: target_slug in other ponds' page links (#235) and backups within their retention. Refs #193 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 394d1c811d |
#192: deploy-level backup target allowlist
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m52s
CI / Build container images (pull_request) Successful in 3m54s
CI / Auth e2e pack (pull_request) Successful in 8m4s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 5m0s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m41s
CI / Import/export fidelity gate (push) Successful in 56s
BACKUP_ALLOWED_TARGETS (comma-separated destination hosts) constrains where backups may go, enforced twice: the api rejects settings writes and connection tests towards non-allowlisted hosts with admin-visible error codes and resolves a non-allowlisted configured target to null, and the sidecar enforces the same policy at the point of egress for the WebDAV upload and the rsync mirror alike (shared policy helpers in packages/shared/src/backup-target-policy.ts). BREAKING: the empty default disables every remote target - backups stay local only, the VS-NfD reference configuration (ADR 0026). Existing deployments with a remote target must list its host or uploads and mirror stop. The admin UI distinguishes unavailable-by-policy from unconfigured (i18n de+en) and shows the permitted hosts. Refs #192 (ADR 0026) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| afef45732a |
#191: feeds.enabled instance switch, feed-token log masking
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m52s
CI / Build container images (pull_request) Successful in 3m55s
CI / Auth e2e pack (pull_request) Successful in 7m52s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m55s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m34s
CI / Import/export fidelity gate (push) Successful in 58s
Chosen path: an instance master switch following the api.enabled/ mcp.enabled pattern — while off, both feed routes AND the feed-token management answer 404 (existence hidden). Default ON: feeds predate the switch, existing instances and their subscribed readers keep working; the VS-NfD reference configuration (#227) turns it off. Admin UI gets the toggle next to the API/MCP switches (i18n de+en). Moving the token out of the query string is documented as rejected: a path segment lands in the same proxy and request logs, and feed readers cannot send headers — that is why the credential is in the URL at all. What DID leak was our own request log (pino logs req.url): the req serializer now masks ?token= values (common/mask-token-param.ts), so no code path logs the credential. Refs #191 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| db4c5ce9ca |
#190: configurable session lifetime with a server-side idle timeout
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m53s
CI / Build container images (pull_request) Successful in 3m55s
CI / Auth e2e pack (pull_request) Successful in 7m53s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m17s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m53s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m37s
CI / Import/export fidelity gate (push) Successful in 52s
SESSION_ABSOLUTE_HOURS (default 168 h) caps a session's total lifetime from login: expiresAt is set once at creation and never extended — the old sliding 30-day renewal is gone. SESSION_IDLE_HOURS (default 72 h) ends sessions unused for that long, enforced server-side against lastSeenAt with a write throttle scaled to the idle bound so short idle windows still renew. Expired rows are removed on validation and the session list applies both bounds, so idle-dead sessions never show as active. The cookie maxAge follows the configured absolute bound. Documented in .env.example (with the VS-NfD reference values for the upcoming hardening guide #227), compose passes the variables through, security.md and ADR 0007 record the amendment. Refs #190 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 214e707102 |
fix flaky tampered-token test: flip a significant signature character
All checks were successful
CI / Build container images (pull_request) Successful in 3m27s
CI / Auth e2e pack (pull_request) Successful in 7m51s
CI / Lint, typecheck, test (pull_request) Successful in 4m49s
CI / Import/export fidelity gate (pull_request) Successful in 1m1s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m19s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 4m54s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m51s
CI / Import/export fidelity gate (push) Successful in 54s
The tampered-token case flipped the LAST base64url character of the signature. Its low bits are padding that decoders ignore, so whenever a signature ends in 'A' (~1/16 of tokens) the flip to 'B' decodes to the same bytes and the token verifies — jose compares decoded bytes, unlike the pre-#188 homegrown code that compared encoded strings. Reproduced deterministically (20/20 A-ending signatures accepted the flip); CI run 477 and one local full-suite failure were this, not load. Flipping the first character makes the tamper always significant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| d32c8c3730 |
#189: make the CSRF origin check fail closed
A cookie-carrying mutation without Origin and Referer (or with an unparsable one) is now rejected with 403 csrf_origin_mismatch instead of passing unchecked. The exception for non-browser clients stays structural: PAT/bearer requests carry no session cookie and never reach the check, and a request that does carry the cookie is always checked. The test harness injects the matching Origin (supertest simulates a browser page of this instance) with an explicit suppression header for the negative cases; the Playwright fixture contexts send the header on their manual seeding calls; release-qa.sh pins APP_BASE_URL and sends the matching Origin. Dedicated spec covers: missing headers 403, mismatch 403, unparsable 403, match passes, GETs untouched, PAT mutation without headers passes, cookie+bearer still checked. Refs #189 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| 3d1f4fda53 |
#188: purpose-bound token keys via HKDF, jose replaces the homegrown JWT
All checks were successful
CI / Build container images (pull_request) Successful in 3m51s
CI / Auth e2e pack (pull_request) Successful in 7m49s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CI / Lint, typecheck, test (pull_request) Successful in 4m43s
CD / Build and push images (push) Successful in 20s
CD / Deploy to Test (push) Successful in 16s
CD / Smoke tests against Test (push) Successful in 1m19s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m54s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m39s
CI / Import/export fidelity gate (push) Successful in 59s
COLLAB_TOKEN_SECRET becomes a root key: every purpose derives its own HKDF-SHA-256 subkey (deriveTokenKey), and no code path signs with the root key directly. Collaboration tokens are signed and verified by jose with HS256 as an explicit allowlist; the sign/verify API turns async at its three call sites. Unsubscribe tokens move from a purpose-prefix string to the structural subkey, with a documented dual-verify window (legacy derivation accepted until 2026-11-01, covering the 90-day TTL of links in already-sent mail). The cross-runtime property that justified the homegrown implementation is now proven by a test: the built CJS and ESM dist artefacts round-trip tokens in both directions in child processes (jose v6 reaches CJS via Node's require(esm), pinned Node 22 images). Negative tests cover cross-purpose subkeys, root-key-signed tokens, alg:none and RS256. Refs #188 (ADR 0020) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ |
|||
| b5d2a436e0 |
#186: pond accent theming — scoped derivation, cascade pond > user > default
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m44s
CI / Build container images (pull_request) Successful in 4m2s
CI / Auth e2e pack (pull_request) Successful in 10m50s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 17s
CD / Smoke tests against Test (push) Successful in 4m2s
CI / Lint, typecheck, test (push) Successful in 4m47s
CI / Build container images (push) Has been skipped
CD / Promote to Int (push) Successful in 14s
CI / Auth e2e pack (push) Successful in 10m7s
CI / Import/export fidelity gate (push) Successful in 56s
Release / Build release images and notes (push) Successful in 1m11s
Release / Release-candidate operations QA (push) Successful in 1m0s
Prod deploy / Deploy the released images to Prod (push) Successful in 17s
pondSettingsSchema gains theme = { accent: '#rrggbb' | null } (null =
inherit the viewer's theme), exposed as a top-level key of the flat
updatePondInputSchema and included in the PondsService settings merge
(the known silent-no-op pitfall). The server validates only the hex;
conformance arises at render time: PondThemeScope (mounted around the
page content next to PondFontScope) derives the accent pair for the
EFFECTIVE mode via useEffectiveTheme and sets it as inline custom
properties — inline beats both tokens.css and the user-theme <style>,
which IS the cascade precedence pond > user > default.
Pond settings get a PondThemeSection (inherit | presets | custom color
with per-mode preview swatches, explicit save like the font manager);
AccentSwatches extracted for reuse; i18n de+en. The no-JS public shell
stays deliberately un-themed (ADR 0018 amendment).
Tests: pond DB test (theme merge keeps fonts, invalid hex 400), e2e
pond-theme.spec (scope boundary content vs. chrome, per-mode
re-derivation, axe on the pond settings page; resets the fixture pond).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
|
|||
| 83a2fe470e |
#184: user accent theming — presets and free color as one mechanism
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m41s
CI / Build container images (pull_request) Successful in 4m4s
CI / Auth e2e pack (pull_request) Successful in 11m40s
CI / Import/export fidelity gate (pull_request) Successful in 52s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
apply-theme.ts derives BOTH modes' accent tokens from the stored choice
(ui.theme.accent: preset id or {custom:'#hex'}) and writes them as
<style id="user-theme"> with :root:root + :root:root[data-theme='dark']
blocks — the doubled :root beats tokens.css regardless of document
order, since theme-init.js injects the ui.theme.css cache during <head>
parsing, before the bundle styles. The default preset means NO override
(hand-tuned tokens.css values stay). main.tsx re-derives from the
choice at startup, healing stale caches after app updates.
Settings: accent radiogroup inside the Appearance section (visible
names, color never the only cue) with per-mode preview swatches on
each mode's canonical background, plus a custom color input; i18n
de+en. The second fieldset made bare .settings-fieldset locators
ambiguous — theme specs now scope via input[name] (fence stays).
Tests: apply-theme unit pack, BASE_PALETTE<->tokens.css drift fence in
theme-contrast.test.ts, e2e theme-accent.spec (instant apply, pre-paint
persistence, default removes override, axe smoke with garish yellow in
both modes). ADR 0018 amendment documents the stage-B details.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
|
|||
| b799ad180b |
#182: top-bar theme toggle — cycle light/dark/system without a menu
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m40s
CI / Build container images (pull_request) Successful in 4m1s
CI / Auth e2e pack (pull_request) Successful in 8m30s
CI / Import/export fidelity gate (pull_request) Successful in 54s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
An IconButton between the notifications bell and the user menu cycles the theme mode in radio order (sun/moon/monitor mirror the CURRENT choice). New useThemeMode() hook is the single write path (persist + apply + same-document event), so the settings radios and the toggle stay in sync; AppearanceSection now uses it too. Also rendered for signed-out visitors — the mode is a device-local preference. i18n de+en; unit tests for cycle/setter, theme.spec covers cycling, radio sync, persistence, and the signed-out top bar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX |
|||
| 77df813f16 |
#180: settings jump-nav fence — nine sections since the Appearance section
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m38s
CI / Build container images (pull_request) Successful in 1m29s
CI / Auth e2e pack (pull_request) Successful in 7m44s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 2m36s
CD / Promote to Int (push) Successful in 21s
CI / Lint, typecheck, test (push) Successful in 4m46s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 9m18s
CI / Import/export fidelity gate (push) Successful in 56s
CI run 453 caught it: settings-nav.spec.ts pins the user-settings section count, which #180's Appearance section raised from 8 to 9. Verified locally against a fresh e2e environment (both pack tests green). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX |
|||
| 4e0ad82220 |
#180: dark-mode test fence, both-scheme a11y pack, theme e2e, ADR 0018
theme-contrast.test.ts parses tokens.css and asserts every real UI colour pairing (4.5:1 text, 3:1 UI) for BOTH palettes, so palette drift fails unit tests instead of review. theme.test.ts covers resolve/apply logic (Node >= 22 ships a shadowing undefined localStorage global — the test brings its own in-memory storage). The a11y pack now runs its four scans in light AND dark via emulateMedia; the new theme pack exercises the three-way switch end to end (instant apply, reload persistence, live OS follow in system mode, override beats OS). ADR 0018 records the theming model broadly: modes now, accent themes by derivation later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX |
|||
| 2c571f9f5e |
#180: dark mode — Light/Dark/System setting with token-based dark palette
The dark palette lives as a single :root[data-theme='dark'] block in tokens.css; theme.ts and the pre-paint public/theme-init.js (external file because the prod CSP forbids inline scripts) always resolve the stored ui.theme.mode to a concrete data-theme, so 'system' needs no @media duplicate and follows live OS changes via matchMedia. color-scheme flips per theme (native controls/scrollbars), paired theme-color metas track the effective theme, and the new Appearance settings section offers the three-way choice as native radios (device-local, like #170). Label chips gain a chip-outline ring so arbitrary user colors stay separated on the dark canvas; useEffectiveTheme() is exported for the later pond-scoped theming stage (ADR 0018). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX |
|||
| 5034b7a80f |
#180: promote remaining hardcoded base.css colors to design tokens
New tokens (light values, dark arrives with the theme block): surface-muted (was fallback-only), danger-contrast, danger-strong, badge ok/error/warn pairs, chip-outline. Replace the phantom --color-primary with the real --color-accent and unify the three danger reds (#a02818/#b91c1c/#ab091e) on --color-danger. attachments-panel maps to bg-subtle, matching its old 6%-grey fallback. Light rendering is visually unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX |
|||
| bdadfce6b9 |
#177: Admin-Personenliste — Aktions-Zelle bleibt Table-Cell, Flex auf Innen-Wrapper
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m39s
CI / Build container images (pull_request) Successful in 1m10s
CI / Auth e2e pack (pull_request) Successful in 7m43s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m50s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m24s
CI / Import/export fidelity gate (push) Successful in 55s
Release / Build release images and notes (push) Successful in 1m11s
Release / Release-candidate operations QA (push) Successful in 45s
Prod deploy / Deploy the released images to Prod (push) Successful in 16s
Die Trennlinie unter der Aktionen-Spalte endete auf Höhe der Icon-Reihe statt am Zeilenende: display:flex direkt auf dem td nahm der Zelle ihr table-cell-Verhalten, sie wuchs nicht mehr auf Zeilenhöhe. Das Flex- Layout liegt jetzt auf einem Innen-Wrapper (.user-row__actions-inner); gemessen: 0 px Bottom-Delta über alle Zellen jeder Zeile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 3283affa67 |
#175: Admin-Personenliste — Aktions-Icons statt Textlinks, Reihenfolge Admin/Deaktivieren/Löschen
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m41s
CI / Build container images (pull_request) Successful in 1m9s
CI / Auth e2e pack (pull_request) Successful in 7m32s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m41s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m46s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m18s
CI / Import/export fidelity gate (push) Successful in 55s
Die Zeilen-Aktionen der Personenverwaltung sind jetzt IconButtons (lucide): MailCheck (Bestätigung erneut senden, nur bei Ausstehend), ShieldPlus/ShieldMinus (Zum Admin machen / Admin entfernen), UserX/UserCheck (Deaktivieren/Aktivieren), Trash2 (Löschen) — in dieser Reihenfolge. Das zweistufige Löschen bleibt: die Bestätigung ist weiterhin ein roter Text-Button und erhält beim Umschalten den Fokus (kein Fokusverlust, ADR 0017). Lokalisierte Namen kommen unverändert aus users.json via IconButton (aria-label+title), Icons aria-hidden. Der Admin-Bereich ist neu im a11y-CI-Pack (axe WCAG A/AA auf /admin). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 0a26572933 |
#171: A11y-Tooling — axe-Smoke-Pack in CI
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m38s
CI / Build container images (pull_request) Successful in 4m0s
CI / Auth e2e pack (pull_request) Successful in 7m31s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 17s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m24s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m50s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m32s
CI / Import/export fidelity gate (push) Successful in 55s
@axe-core/playwright als devDependency (exakt +2 Lockfile-Pakete, axe-core hat null Runtime-Dependencies; Freigabe durch Stefan im Chat). Neuer e2e-Pack a11y.spec.ts scannt Login, Lesemodus, aktiven Editor und Nutzer-Einstellungen gegen WCAG 2.1 A/AA — jede neue Verletzung bricht den Build (Allowlist bewusst leer, nur mit Begründung erweiterbar); Best-Practice-Regeln bleiben außen vor. In ci.yml als eigener Schritt mit Rate-Limit-Reset nach dem Muster der übrigen Packs verdrahtet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| 31b59f0fb6 |
#170: Statusmeldungen, Einzeltasten-Shortcuts, Bewegung
Toast-Standzeit 2,5s auf 6s (WCAG 2.2.1 — für Screenreader-/Zoom-Nutzer kaum erfassbar). Neue Einstellungs-Sektion Bedienung mit dem Schalter Einzeltasten-Kürzel deaktivieren (lokale Geräte-Einstellung); die Handler von e und / prüfen sie beim Tastendruck (WCAG 2.1.4). prefers-reduced-motion: CSS-Transitions kollabieren auf instant, die Graph-Simulation rechnet ihr Layout synchron zu Ende statt zu animieren (WCAG 2.2.2). settings-nav-Spec auf 8 Sektionen nachgeführt. Bewusst KEIN zusätzliches role=status (legal.spec-Locator-Falle). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| 58c19abfdd |
#169: Nicht-Text-Inhalte — Task-Checkboxen, Wissensgraph
Task-Checkboxen tragen in beiden Renderpfaden einen Namen: docToHtml setzt aria-label aus dem Aufgabentext, die Editor-NodeView ebenso. Die NodeView rendert ihr Host-Element jetzt selbst als li (ReactNodeView- Renderer as/attrs) — TipTaps zusätzliches div-Host-Element zwischen ul und li brach die Listensemantik; der Wrapper flacht per display:contents ab, die #137-Pixel-Abstimmung bleibt erhalten (Selektor auf die neue Tiefe nachgeführt, Ausrichtung nachgemessen: 1px-Versatz unverändert). Der Wissensgraph-SVG bekommt ein beschreibendes aria-label inklusive Verweis auf die Backlinks als gleichwertige Listenform. Der Bild-Alt-Editor existierte bereits (Bild-Controls bei Auswahl) — kein Änderungsbedarf. Hinweis: gecachte Seiten übernehmen das Checkbox-Label wie bei jeder docToHtml-Änderung erst mit dem nächsten Persist ihrer Inhalte. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| 8719b0ee1e |
#168: Formulare — Fehler-Verdrahtung und Namenslücken
Der Field-Baustein verdrahtet Hinweis/Fehler jetzt per aria-describedby und aria-invalid mit dem Eingabefeld (cloneElement auf das einzelne Kind; Fragmente bleiben unangetastet) — Screenreader nennen den Fehler damit auch beim Feld-Fokus. Quota-Typ-Select mit Namen; die leeren Aktions-/Erledigt-Spaltenköpfe in API-Tokens, Feed-Tokens, Sitzungen und der Aufgabenübersicht (NodeView UND Server-Renderpfad) tragen visually-hidden-Beschriftungen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| 4ba7b50336 |
#167: Farbkontraste — Dark-Shell, Wikilink-Unterstreichung, Feld-Ränder
Die öffentliche Server-Shell bekommt AA-geprüfte Dark-Mode-Farben (color-scheme: light dark hatte den UA dunkel rendern lassen, Links fielen durch 1.4.3; Text 14,8:1, Links 10,1:1, Muted 8,5:1). Wikilinks tragen eine permanente Unterstreichung — Farbe allein war das einzige Link-Merkmal bei nur 2,5:1 Abstand zum Fließtext (1.4.1). Neues Token --color-border-input (#7d8a97, 3,5:1/3,3:1) für Eingabefeld-Ränder (1.4.11); Wächter-Kommentar am Favoriten-Gold. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| 2077d92c09 |
#166: Skip-Link, verstecktes Seiten-h1, Resizer in die Nav-Landmarke
Skip-Link als erster Tab-Stopp springt auf #main; die angemeldete Seitenansicht bekommt ein visually-hidden h1 (der sichtbare Titel ist ein Input, der jetzt auch ein aria-label trägt); der Sidebar-Resizer wandert in die nav-Landmarke (absolut an der Kante positioniert), damit kein Inhalt außerhalb von Landmarken liegt. Zwei e2e-Locator auf das Sidebar-Formular gescoped — das Editor-Titelfeld matcht seit dem neuen Label ebenfalls auf /title|titel/i. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| d4d4282c55 |
#165: Reflow bei 320 px und Tastatur-Scrollbarkeit
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m39s
CI / Build container images (pull_request) Successful in 4m2s
CI / Auth e2e pack (pull_request) Successful in 7m17s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
Vier Ursachen des seitenweiten Horizontal-Scrollens behoben: die Topbar saß mit min-content-Breite in der Grid-Spalte (min-width: 0 nach dem #100-Muster) und wickelt auf schmalen Viewports auf eine zweite Zeile (Grid-Zeile minmax, Suchtext wird zum Icon); die aufgeklappte Sidebar liegt unter 40rem als Overlay über dem Inhalt statt ihn auf einen Streifen zu quetschen; Footer wickelt; Titel-Input und Settings-Spalte schrumpfen (min-width bzw. align-items: stretch im Schmal-Layout). Der Haupt-Scrollbereich ist per tabindex=0 tastatur-scrollbar — auf den Rechtstext-Seiten gab es sonst keinen Weg, den Inhalt zu scrollen. Gemessen: 10 Ansichten bei 320 px ohne Dokument-Überlauf (vorher 892 px Inhaltsbreite); e2e content/settings-nav/sidebar/legal/page-tree/search grün. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| 057992faaf |
#164: ARIA-Semantik — Editorfläche, Autocomplete-Listboxen, Sidebar, Toolbar
Die Editorfläche bekommt einen lokalisierten zugänglichen Namen und ist im Lesemodus role=document statt eines unbenannten Textfelds (setOptions im selben Layout-Effekt wie setEditable). Eingeklappte Sidebar zusätzlich inert (aria-hidden allein ließ fokussierbare Kinder im Tab-Weg). Die li-Zwischenknoten der Listboxen (Wikilink-/Mention-Autocomplete, Suchergebnisse) sind role=presentation, damit listbox→option wieder eine gültige Eltern-Kind-Beziehung ist. Toolbar: Pfeiltasten-Navigation über die Controls (native Selects behalten ihre Pfeiltasten) und ein sprechendes Toolbar-Label statt des Absatz-Buttons-Labels. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| 418aafd5ec |
#163: Dokumentsprache und Seitentitel der SPA
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m43s
CI / Build container images (pull_request) Successful in 1m11s
CI / Auth e2e pack (pull_request) Successful in 7m14s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
i18n spiegelt die aktive Sprache auf <html lang> (Init + languageChanged; der User-Locale-Wechsel in auth-context läuft über dasselbe Event). Neuer useDocumentTitle-Hook setzt je Route einen sprechenden Titel (Seite — Teich — Dorfteich), verdrahtet in allen Routen-Komponenten; dynamische Titel folgen den geladenen Daten. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| 1eca7c334c |
#162: Fokus-Management für Dialoge und Such-Palette
Gemeinsamer useModalFocus-Hook: Initialfokus in den Dialog, Tab/Shift-Tab zyklisch gefangen, Fokus-Rückgabe an den Auslöser (bzw. returnFocusRef, wenn der öffnende Menüpunkt mit dem Menü unmountet). Dialoge tragen jetzt aria-labelledby auf ihre Überschrift und tabindex=-1 als Fokus-Fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq |
|||
| db0e563f95 |
#160: Plugin-Block — Bearbeiten-Knopf nach Moduswechsel wieder da
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m39s
CI / Build container images (pull_request) Successful in 1m29s
CI / Auth e2e pack (pull_request) Successful in 7m22s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
Release / Build release images and notes (push) Successful in 1m9s
CI / Lint, typecheck, test (push) Successful in 4m48s
CI / Build container images (push) Has been skipped
Release / Release-candidate operations QA (push) Successful in 52s
Prod deploy / Deploy the released images to Prod (push) Successful in 18s
CI / Auth e2e pack (push) Successful in 7m2s
CI / Import/export fidelity gate (push) Successful in 54s
Die NodeView las editor.isEditable nur beim Mount. Die Seite mountet immer im Lesemodus, und der Moduswechsel läuft über setEditable() — das emittiert in TipTap nur ein update-Event, aber keine Transaction, weshalb React-NodeViews nie neu rendern (geprüft in @tiptap/react 3.27.1: updateProps feuert nur bei Node-Änderung und Selektions- Wechsel). Folge: die Block-Leiste blieb ohne Bearbeiten-Knopf, für alle Block-Plugins (ChordPro, Mermaid, Excalidraw, draw.io). Fix: useEditorEditable abonniert das update-Event und liest isEditable reaktiv; verliert die Seite die Editierbarkeit, während die Editier-UI des Plugins offen ist, fällt der Block auf render zurück (der Lesemodus blendet die Leiste aus, es gäbe sonst keinen Weg mehr heraus). Damit stimmt auch die setData-Schreibrecht-Prüfung (editableRef) wieder. Regressionstest im plugin-blocks-Pack: Block existiert bereits, Seite lädt im Lesemodus, Wechsel in den Edit-Modus zeigt den Knopf (fiel ohne Fix reproduzierbar durch); Rückweg Lesemodus→render mitgeprüft. Die bisherigen Tests fügten Blöcke immer erst nach dem Moduswechsel ein und konnten den Fall nicht sehen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 58f175af32 |
settings-nav robust: Sofort-Sprung statt Smooth-Scroll, Spec wartet auf networkidle
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m37s
CI / Build container images (pull_request) Successful in 1m8s
CI / Import/export fidelity gate (pull_request) Successful in 54s
CI / Auth e2e pack (pull_request) Successful in 7m11s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m42s
CD / Promote to Int (push) Successful in 15s
CI / Lint, typecheck, test (push) Successful in 5m21s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m19s
CI / Import/export fidelity gate (push) Successful in 58s
Der animierte scrollIntoView landete auf einer veralteten Zielposition, wenn Query-Sektionen (Sessions/Tokens) während der Animation noch wuchsen — auf dem CI-Runner deterministisch rot. Jetzt springt die Navigation sofort; der Spec lässt die asynchronen Inhalte vor dem Klick settlen (networkidle) und lief lokal 10× ohne Retry grün. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 5444c39458 |
e2e-Fixes nach CI: section-styles-Selektor eindeutig, settings-nav-Timing
Das Block-Menü ist seit dem eingebauten Aufgabenübersicht-Eintrag (#154) immer sichtbar und teilt die Styling-Klasse editor-toolbar__section-select — der section-styles-Pack adressiert das Abschnitts-Select jetzt per :not(.editor-toolbar__block-select). settings-nav: toBeInViewport bekommt 10 s für Smooth-Scroll auf langsamen Runnern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| f9d23bf8d0 |
#151: Mention-Benachrichtigungen über die Glocke
Neuer Notification-Typ mentioned; abgeleitete Tabelle page_mentions (Migration), vom Collab-Persist transaktional neu geschrieben — der Diff gegen den Vorzustand wird als pg_notify (page_mentions_changed) emittiert, nur NEU Erwähnte lösen aus (kein Spam bei Folge-Saves). Der api-Listener (erweitert um den zweiten Kanal) ruft NotificationsService.fanoutMentions: Zustellung nur nach canAccessPage-Recheck, die Autoren (pending contributors) benachrich- tigen sich nie selbst; Payload wie gehabt mit Actor-Namen. API-seitig erzeugte Seiten seeden page_mentions aus deriveContent. Glocken-Text de+en; DB-Test (Leser ja / Outsider nein / Autor nein); kompletter Loop live verifiziert (Tippen → Persist → NOTIFY → Notification). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| e164370691 |
#154: Aufgabenübersicht als Kern-Block (Seite + Unterseiten)
Neuer Block-Atom task_overview (Markdown-Fence dorfteich-tasks, HTML-Placeholder). Shared extractTaskRows liest Task-Zeilen mit Text, Mentions (#150) und Start-/Zieldaten (#152); TasksService sammelt zur Lesezeit den Teilbaum (rekursiv via collectSubtreeIds, canAccessPage- Filter je Quellseite) aus Basis-State + page_updates-Log — KEINE abgeleitete Tabelle nötig (Teilbäume sind klein, kein Drift). Neuer auth-Endpoint GET /read/:pond/:slug/tasks; die öffentliche Ansicht expandiert den Placeholder serverseitig zur statischen Tabelle (Instanz-Sprache). NodeView mit Live-Tabelle und Rückschreib-Checkboxen (optimistisch, Override bis der debounced Collab-Persist nachzieht); Einfügen über die Block-Auswahl (eingebauter Eintrag). Unit- + DB-Tests, neuer CI-Pack tasks.spec (voller Loop inkl. Rückschreiben end-to-end), User-Guide-Doku en+de. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 3f7190ebcc |
#153: Stabile Task-IDs + Toggle-Rückschreibpfad über den Collab-Server
task_item bekommt ein optionales id-Attr (default null — Bestandsdocs
bleiben gültig), durchgereicht in toDOM/parseDOM und dem Lese-HTML;
der Editor vergibt/entdoppelt IDs lazy per appendTransaction
(TaskItemIds-Extension, auch gegen Copy/Paste). Neuer Kanal
TASK_TOGGLE_CHANNEL; POST /pages/:id/tasks/:taskId {checked} prüft
Schreibrecht, registriert den Toggler als pending contributor und
feuert pg_notify; neuer collab task-toggle-listener (Struktur =
restore-listener) öffnet eine DirectConnection und flippt das
checked-Attribut in einer Transaktion — offene Editoren konvergieren,
unbekannte taskId = geloggter No-op. DB-Test (NOTIFY-Payload,
Attribution, 403/404/400).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
|
|||
| 92a3b2f6d5 |
#152: Datums-Marker >> (Zieldatum) / << (Startdatum)
Neuer Inline-Atom date_marker {kind: due|start, date: ISO}. Markdown
kanonisch ISO (>>2026-12-31), Eingabe-Kulanz dd.mm.yyyy; Block-Guard
vor blockquote hält zeilenführende >>Daten aus dem Zitat-Parser;
ungültige Kalenderdaten bleiben Text. Editor: InputRule beim Tippen
(+Leerzeichen), Anzeige per Intl.DateTimeFormat in Nutzersprache,
Überfällig-Färbung. Die User.locale-Verdrahtung existierte bereits
(auth-context, #17) — keine Änderung nötig. 6 Unit-Tests inkl.
Task-Listen-Zeile mit Marker und Mention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
|
|||
| 7471fc70f7 |
#150: @-Mentions — Inline-Node, instanzweite User-Suche, Autocomplete
Neuer Inline-Atom mention {userId, username}: Markdown-Regel @username
(E-Mail-sicher über Wortgrenzen), Serializer, HTML-Span dt-mention,
Plain-Text für die Suche, Extraktor extractMentionUserIds. Neue
Endpoints GET /users/search (auth, min. 2 Zeichen, Limit 10,
Rate-Limit) und GET /users/brief (Batch-Auflösung für live
Anzeigenamen; gelöschte Nutzer → toter Chip). Editor: MentionView mit
Live-displayName, MentionAutocomplete (Klon des Wikilink-Musters),
Chip-CSS. 5 Unit-Tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
|
|||
| 7252bd16e0 |
#149: Atom-Feeds für Teiche und Seiten, privat via Feed-Token
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m53s
CI / Build container images (pull_request) Successful in 4m1s
CI / Auth e2e pack (pull_request) Successful in 7m12s
CI / Import/export fidelity gate (pull_request) Successful in 1m0s
CD / Build and push images (push) Successful in 14s
CD / Deploy to Test (push) Successful in 16s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 4m35s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Failing after 5m14s
CI / Import/export fidelity gate (push) Has been skipped
GET /public/:pond/feed.xml (zuletzt geänderte Seiten) und GET /public/:pond/:page/feed.xml (Versions-Historie), @Public mit 404-Semantik; öffentliche Teiche anonym, nicht-öffentliche über neues read-only Feed-Token je Nutzer als ?token=dt_feed_… (neue Tabelle feed_tokens + Migration, Verwaltung in den Nutzer-Einstellungen, FeedTokensSection). Öffentliche HTML-Seiten annoncieren den Teich-Feed per link rel=alternate. DB-Tests (anonym/privat/Token-Lifecycle) und User-Guide-Doku en+de. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 89ffbc0e4d |
#147: Eigene Identität in der API klar dokumentiert
GET /api/public/v1/me existiert bereits — OpenAPI-Summary nennt jetzt ausdrücklich die User-ID, api-guide (en+de) ebenso. MCP war bereits paritätisch (list_ponds + Token-Identität); kein neuer Endpoint nötig. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| d73b120d06 |
#148: Seitenlisten filtern nach createdSince/updatedSince
Neues pageListQuerySchema (ISO 8601, Kulanz für Datum ohne Zeit), Query-Parameter auf interner und Public-API-Seitenliste, Prisma-where mit gte; neue Indizes (pondId, createdAt)/(pondId, updatedAt) als Migration. OpenAPI-Parameter, MCP-Parität (list_pages created_since/updated_since), Doku (api-guide, mcp-guide, public-api.md), DB-Test inkl. 400 bei ungültigem Datum. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| b4247f4832 |
#145: Einstellungsseiten mit Sektions-Sprungnavigation
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m27s
CI / Build container images (pull_request) Successful in 3m48s
CI / Auth e2e pack (pull_request) Successful in 6m58s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Failing after 4m47s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
Neue SettingsLayout-Komponente leitet die Navigation per MutationObserver aus den section>h2-Blöcken ab (erfasst konditionale und komponenten-eigene Sektionen ohne Verdrahtung), sticky Leiste neben dem Inhalt, auf schmalen Viewports horizontale Chip-Leiste; aktive Sektion über Scroll-Position, am Seitenende gewinnt die letzte. Auf allen vier Einstellungsseiten verdrahtet; die Admin-Grundeinstel- lungen bekommen dafür eine eigene Überschrift. Neuer CI-Pack settings-nav. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 9bd25f6ce3 |
#146: Transparente Einbettung $[[Seite]] ohne Rahmen und Titel
Neues bare-Attr am transclusion-Node; $-Präfix in Markdown-Regel, Serializer und Autocomplete; HTML-Placeholder trägt data-transclusion-bare, Server-Expansion und NodeView lassen bei bare Rahmen und Titel weg. Gleiche Tiefen-/Zyklen-/Permission-Regeln, zählt weiter als Link. Unit- und DB-Tests ergänzt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 4762cb73d3 |
Public-Ansicht: Plugin-Fallback-SVGs auf Containerbreite skalieren
Vom Fallback-Renderer inline eingesetzte SVGs (z. B. Excalidraw-
Skizzen) tragen feste Pixelmaße und liefen auf schmalen Viewports über
den Rand — die bestehende img-Regel greift für inline-<svg> nicht.
Neue Regel .dt-plugin-fallback svg { max-width:100%; height:auto }.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
|
|||
| 75ec7f6006 |
CSP: data:-Fonts erlauben (eingebettete Fonts in Excalidraw-SVGs)
Excalidraw bettet beim Speichern die verwendeten Schrift-Subsets als data:-URIs ins Snapshot-SVG ein. Die Seiten-CSP (nginx) und die Plugin-Frame-CSP erlaubten aber nur `font-src 'self'` bzw. den Asset-Pfad — die Handschrift fiel in der öffentlichen Ansicht und im Snapshot-Render auf Serifen zurück (auf Prod an der ersten Demo-Skizze sichtbar). Fix: `data:` in beiden font-src-Direktiven. data:-Fonts lösen keinerlei Netzwerk-Request aus — die Zero-Third-Party-Garantie (security.md) bleibt unberührt; fonts.ts-Kommentar entsprechend präzisiert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 60be198e51 |
search.spec: Fehl-Klick entfernt — Reload IST der Weg zurück in den Lesemodus
All checks were successful
CI / Build container images (pull_request) Successful in 1m8s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m35s
CI / Build container images (push) Has been skipped
CI / Lint, typecheck, test (pull_request) Successful in 4m25s
CI / Auth e2e pack (pull_request) Successful in 6m49s
CI / Import/export fidelity gate (pull_request) Successful in 54s
Release / Build release images and notes (push) Successful in 1m12s
Prod deploy / Deploy the released images to Prod (push) Successful in 15s
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 12s
CD / Smoke tests against Test (push) Successful in 1m15s
Release / Release-candidate operations QA (push) Successful in 45s
CI / Auth e2e pack (push) Successful in 6m40s
Der Spec klickte nach page.reload() den Mode-Toggle „um den Edit-Modus zu verlassen" — nach dem Reload ist die Seite aber schon im Lesemodus (React- State resettet), der Klick wechselte also HINEIN. Das passierte jahrelang folgenlos, weil der Fokus auf dem Toggle-Button blieb und „/" die Suche öffnete. Seit dem Editor-Auto-Fokus (PR #140) landet der Fokus im Editor und „/" wird dort zu Text — je nach Ausgang des Rennens gegen den rAF- verzögerten Fokus mal grün (Läufe 381/385), mal rot (383/387/389/390). Fix: den Klick streichen; „/" läuft im Lesemodus als globaler Shortcut. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| d07f8bb8e1 |
#137 Feinjustierung: Checkbox im NodeView 3px höher (Text relativ 3px tiefer)
Stefans Feedback nach dem Nachfahren-Selektor-Fix: „noch 3px weiter runter". Die Zeilenmetrik des Editor-/Auth-NodeViews (label-Wrapper) setzt die Checkbox ~3px tiefer als im öffentlichen docToHtml-Markup — daher NUR für den label-Pfad `margin-top: calc(0.25em - 3px)`; die öffentliche Ansicht (bare input, war korrekt) bleibt bei 0.25em. Live per Injektion auf Test vermessen: Versatz −9 → −6px, Checkbox mittig auf der Textzeile (Zoom-Screenshot). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 3ed3cbb806 |
#137 Nachfix 2: Task-Item-Absätze per Nachfahren-Selektor treffen (NodeView-Tiefe)
Some checks failed
CI / Auth e2e pack (pull_request) Successful in 6m45s
CI / Import/export fidelity gate (pull_request) Successful in 54s
CD / Build and push images (push) Successful in 17s
CD / Smoke tests against Test (push) Successful in 1m15s
CD / Deploy to Test (push) Successful in 13s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 4m32s
CI / Lint, typecheck, test (pull_request) Successful in 4m39s
CI / Build container images (pull_request) Successful in 1m13s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Failing after 5m28s
CI / Import/export fidelity gate (push) Has been skipped
Stefan sah die Checkbox-Verschiebung weiterhin — in der ANGEMELDETEN Lese-/Bearbeiten-Ansicht. Dort rendert der TipTap-ReactNodeView das <p> ZWEI Wrapper tief (`li > div[data-node-view-content] > div > p`), die bisherige Kind-Kette `li > div > p` griff also nur im flachen docToHtml-Markup der öffentlichen Ansicht. Fix: Nachfahren-Selektoren (`li p:first-of-type` / `li p:last-of-type`) — robust gegen die Wrapper-Tiefe beider Renderpfade. Live am echten NodeView-DOM verifiziert (Injektion auf Test: p-marginTop 16px→0, Checkbox bündig; öffentlicher Pfad unverändert ok). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 4ba4ca7ba9 |
Editor: Auto-Fokus beim Wechsel in den Edit-Modus
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m50s
CI / Build container images (pull_request) Successful in 1m16s
CI / Auth e2e pack (pull_request) Successful in 6m44s
CI / Import/export fidelity gate (pull_request) Successful in 54s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m15s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m32s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Failing after 5m29s
CI / Import/export fidelity gate (push) Has been skipped
Beim Umschalten in den Bearbeiten-Modus (Stift-Icon oder Shortcut „e") landet der Cursor jetzt automatisch im Editor — bisher brauchte es einen zusätzlichen Klick, der auf neuen/leeren Seiten zudem pixelgenau den schmalen Inhaltsbereich treffen musste. Effekt feuert, sobald der Editor editierbar wird (nach setEditable), und überspringt den Fokus-Klau, wenn gerade ein Textfeld (z. B. der Seitentitel) den Fokus hält — der Moduswechsel darf den Caret nicht aus dem Titel reißen. TipTaps focus() stellt die letzte Auswahl wieder her bzw. setzt den Caret an den Anfang einer leeren Seite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC |
|||
| 1b0fd21254 |
Editor-Toolbar: bündig an Nav fixen + Overflow-Menü über die Toolbar heben
All checks were successful
CI / Build container images (pull_request) Successful in 1m9s
CI / Auth e2e pack (pull_request) Successful in 6m45s
CI / Import/export fidelity gate (pull_request) Successful in 54s
CI / Lint, typecheck, test (pull_request) Successful in 4m25s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m15s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m33s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 6m38s
CI / Import/export fidelity gate (push) Successful in 1m10s
Zwei im Bearbeiten-Modus gemeldete Layout-Bugs:
1. Die sticky Editor-Toolbar klebte an der Padding-Kante des Scroll-
Containers `.main` (padding-top: --space-6), also mit sichtbarer Lücke
unter der Navigation, durch die die scrollende Seite schien. Fix:
`top: calc(-1 * var(--space-6))` → die Toolbar pinnt bündig an die Nav.
2. Das „…"-Overflow-Menü der Navigation lag HINTER der Toolbar: `.topbar`
steht im DOM vor `.app-body`, hatte aber keinen Stacking-Kontext, also
malte die z-index-20-Toolbar in `.main` darüber und verdeckte
Menüeinträge. Fix: `.topbar { position: relative; z-index: 30 }` (> 20;
Modals mit 1100+ gewinnen weiterhin).
Beide am echten Test-Editor mit langer, scrollbarer Seite verifiziert
(gap_px=0 nach dem Scrollen; Menü vollständig über der Toolbar).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
|
|||
| baea736616 |
#137 Fix: Checkbox-Absatz per :first-of-type ausrichten (nicht :first-child)
All checks were successful
Im Lesemodus-Markup ist das <input> das erste Kind des <li>, also ist das
<p> nie :first-child — die Regel `li > p:first-child { margin-top: 0 }`
griff daher NICHT, das <p> behielt seine ~1em-Obermarge und der Text saß
deutlich tiefer als die Checkbox (auf Test/Int sichtbar, kein Cache-Bug).
Fix: :first-of-type/:last-of-type treffen den ersten/letzten <p>
unabhängig vom vorangehenden <input>. Beide Renderpfade abgedeckt.
Verifiziert per Harness mit exaktem <li><input><p>-DOM (alt vs. neu).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
|
|||
| 5255cdce06 |
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
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 |
|||
| d5b895ada2 |
#135 Fix: /read-Route deklariert Zugriffsregel explizit (@AuthenticatedOnly)
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 |
|||
| 15376d4ac2 |
#135 Seiten-Einbettung ![[Seite]] (Transklusion) im Lesemodus
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 |
|||
| a84e9880bd |
#133 Fix: Typfehler im public-Kommentar-Test (noUncheckedIndexedAccess)
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 |
|||
| f014a61480 |
#133 Kommentare fest inline im Lesemodus (Slide-in-Panel ablösen)
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 |
|||
| c858f12592 |
#134 Statuszeile zwischen Navigation und Artikel
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 |
|||
| d1d98a9575 |
#137 Checkbox-Aufzählungen: Textzeile vertikal ausrichten
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 |
|||
| 4f79a816e4 |
Toast stack: drop role=status from the global live region
All checks were successful
CD / Build and push images (push) Successful in 1m10s
CD / Deploy to Test (push) Successful in 10s
CD / Smoke tests against Test (push) Successful in 1m9s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m24s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 6m32s
CI / Import/export fidelity gate (push) Successful in 49s
Release / Build release images and notes (push) Successful in 1m9s
Release / Release-candidate operations QA (push) Successful in 42s
Prod deploy / Deploy the released images to Prod (push) Successful in 15s
The always-mounted toast container carried role="status", so every
page-scoped getByRole('status') locator suddenly resolved to two
elements — legal.spec failed CI with a strict-mode violation. The
region keeps aria-live="polite" (announcements work the same); the
status role stays with the per-page elements that had it before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn
|
|||
| 6c98a71d34 |
Favorites: personal page stars, golden icons, sidebar filter (#132)
Some checks failed
CD / Build and push images (push) Successful in 3m57s
CD / Deploy to Test (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m32s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Failing after 2m51s
CI / Import/export fidelity gate (push) Has been skipped
Semantics changed from the issue during planning (documented there, comment 1192): favorites are PERSONAL per user, not pond-wide — the sys-fav label approach is dropped entirely. Storage is a page_favorites table (userId+pageId, FK cascade); PUT/DELETE /pages/:id/favorite toggles idempotently and needs read access only (#60 404 semantics — a star is a note-to-self, not a page modification), GET /ponds/:id/favorites lists the account's stars sliced to still-readable pages. Trashed pages keep their rows, so restore keeps the star; purge cascades it away. Web: one shared ['favorites', pondId] query feeds the TopBar star (between labels and history, golden when set), the golden tree icons in the sidebar, and a latching "Favorites" filter button next to the view switch that narrows either view (combinable with the label filter). No public-API/MCP exposure — with the label approach gone, that parity is no longer free; favorites stay UI-only for now. New favorites e2e pack (star toggle, golden icon, filter, per-user isolation) wired into CI; DB suite covers the round-trip, read gating, and the trash/restore/purge lifecycle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| 48d4c60af7 |
Trash: checkbox multi-select with bulk restore and purge (#128)
Each trash row gets a checkbox, a toolbar above the list offers "select all" (native indeterminate for partial selections) and the two bulk actions; bulk purge confirms with the selection count (pluralized). Processing is sequential on purpose — purge promotes leftover children (#107), so concurrent tree mutations would race. Failures don't strand the rest: the loop keeps going, failed pages stay selected for a retry, and an alert banner reports the count. Single-row actions run through the same path, which also fixes their previously unhandled rejection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| 36cdd4fbca |
Editor: confirm snapshots with a toast; wire ui.toast for plugins (#130)
Cmd/Ctrl+S used to snapshot silently. A new app-wide ToastProvider (components/Toast.tsx) owns a bottom-center stack — permanent polite live region, auto-dismiss after 2.5 s, click to dismiss early, error variant. Both snapshot paths (the keyboard chords in PageEditorPage and the save-version TopBar button) now confirm with the version name when there is one, and their failure alert becomes an error toast. The plugin host capability ui.toast (declared since #74, wired nowhere) connects to the same stack: PluginBlockScope carries the showToast handle, plugin-block passes it into the sandbox context. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| d23e5dc10c |
Pond graph: size the canvas to the viewport, not the width ratio (#131)
The SVG scaled to container width with height following the fixed 800×560 viewBox ratio — on wide windows the graph grew taller than the viewport, the page got a scrollbar, and wheel-zoom scrolled along. The graph page is now a flex column filling the main column; the canvas takes the remaining height (flex: 1, min-height: 0), a ResizeObserver feeds its measured size to ForceGraph as width/height, and the SVG fills it exactly. LocalGraphPanel keeps its fixed defaults. Scope deliberately layout-only (issue comment 1187): with no scrollbar there is nothing for the wheel to scroll, so no non-passive listener needed. The graph pack now asserts the main column does not overflow vertically on the graph route. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| 31557f09e5 |
Sidebar: pin the action icon row to the visible bottom (#129)
.sidebar is itself the scroll container, so the footer's margin-top: auto only pinned the icon row to the end of the CONTENT — behind the fold on long page lists. The row is now position: sticky with negative bottom/side margins undoing the sidebar padding, a background, and a top border, so the list scrolls away beneath it and the four actions stay visible at any list length. The import progress list keeps floating above the row (absolute within the sticky footer). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| 1b9dd7d475 |
Give the zip-bomb rejection test headroom against loaded runners
All checks were successful
CD / Build and push images (push) Successful in 2m55s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m14s
CI / Lint, typecheck, test (push) Successful in 4m32s
CI / Build container images (push) Has been skipped
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 6m27s
CI / Import/export fidelity gate (push) Successful in 50s
Release / Build release images and notes (push) Successful in 1m9s
Release / Release-candidate operations QA (push) Successful in 42s
Prod deploy / Deploy the released images to Prod (push) Successful in 17s
Compressing and inflating the 257-MiB zero buffer is CPU-bound and exceeded vitest's 5 s default on a busy CI runner (5.4 s — the M15 push ran CI and CD concurrently). Flaky tests are defects (ADR 0014): the test gets an explicit 30 s timeout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| 78a913c47b |
Vault import: decode ZIP names as UTF-8 and NFC-normalize them
Some checks failed
CD / Build and push images (push) Successful in 4m59s
CD / Deploy to Test (push) Successful in 11s
CI / Lint, typecheck, test (push) Failing after 5m36s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m21s
CD / Promote to Int (push) Successful in 11s
Real vault ZIPs broke umlauts in page titles ("Fußball zum Götzen" →
mojibake, slug fua-ball…goi-tzen): fflate honors only the ZIP UTF-8
flag, which common archivers omit, and decodes unflagged names as
Latin-1. That decoding is byte-lossless, so parseVaultZip now re-reads
any name whose chars all fit one byte as UTF-8 (a strict decoder —
genuine Latin-1 and flag-decoded precomposed chars fall back
unchanged), then NFC-normalizes: macOS zips store umlauts decomposed,
which silently broke slugify's ä→ae digraphs, wikilink matching, and
duplicate-basename detection. slugify itself also precomposes first as
defense in depth for NFD input from other paths.
Unit tests pin both cases: a hand-patched ZIP whose UTF-8 name bytes
carry no UTF-8 flag, and an NFD-named note that must come out
precomposed with an ueber- slug.
Pages already imported with garbled titles stay as they are — delete
the imported subtree and re-import after this lands (or rename by
hand).
Fixes #127
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn
|
|||
| 32d4cd0e4b |
Footer: show the Dorfteich version next to the connection status
The Dockerfile has passed the release tag into the web build as VITE_APP_VERSION since the beginning, but nothing consumed it — the footer now renders it right of the live/offline icon; dev builds show the 0.0.0-dev placeholder. Fixes #126 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| 0428892ef2 |
Editor shortcuts: "e" edits, the platform chord+S snapshots versions
In reading mode a plain "e" (guarded against typing targets) switches to edit mode. In edit mode the platform's native chord — Cmd on macOS, Ctrl elsewhere — +S saves an unnamed manual snapshot in place, and +Shift+S asks for a name and returns to reading mode; both always swallow the browser's save dialog. The shared isTypingTarget guard moves from TopBar into lib/keyboard.ts next to the new modifier helper. Unnamed snapshots needed the API to accept them: the version label is optional now (trigger stays MANUAL, label null), and the history list's existing null-label fallback text becomes "Manueller Schnappschuss" / "Manual snapshot" — it only ever shows for exactly those. DB test for the label-less path, e2e coverage in the CI content pack. Fixes #125 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| 44a90a53ac |
Sidebar: pin the four actions as an icon row at the bottom
Graph, new page, import, and trash collapse from scattered text links into one icon row pinned to the sidebar's bottom edge, in that order, each with a hover hint (the trash reads "Papierkorb anzeigen"). The new-page button now toggles the inline form, which still renders above the footer with the same classes; the import trigger becomes an icon whose progress list floats above the row so the icons stay put. All e2e class hooks (.sidebar__new-page, .sidebar__graph-link, .sidebar__import-*) are unchanged. Fixes #124 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| 1781f12f6e |
Knowledge graph: live Obsidian-like simulation with tunable physics
All checks were successful
CD / Build and push images (push) Successful in 4m2s
CD / Deploy to Test (push) Successful in 9s
CI / Lint, typecheck, test (push) Successful in 4m28s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 6m42s
CI / Import/export fidelity gate (push) Successful in 49s
Release / Build release images and notes (push) Successful in 1m7s
Release / Release-candidate operations QA (push) Successful in 42s
Prod deploy / Deploy the released images to Prod (push) Successful in 21s
The force layout used to run once (tick(250)) and freeze; dragging moved a single node with no reaction from its neighbors. The simulation now stays alive: React renders the SVG structure (testids, edge/ring classes — the e2e contract is unchanged) while each tick writes positions imperatively into the element refs, and it settles to rest via alpha decay, which also keeps Playwright's stability wait happy. Dragging pins the node (fx/fy) and reheats the physics, so the neighborhood gets pulled along; a plain click still just opens the page. Surviving nodes keep their positions across data refreshes, e.g. when a phantom becomes a real page. The graph page gains four sliders — attraction, repulsion, node size, font size — persisted per pond (ui.graph.settings.<pondId>) with a reset; the inner view is keyed by pond id because usePersistentState reads its key only on mount (#108 trap). The local panel adopts those settings (no second set of sliders) and swaps the two fixed hop buttons for a 1–5 depth slider. Fixes #123 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| d4fb2a3c51 |
Sidebar tree: stack parents above children, compact rows, icons
The tree reused .sidebar__page-item's flex ROW from the flat list, so the nested children <ul> sat BESIDE the parent row and align-items: center made the parent float vertically centered next to its subtree. Stack the two in tree view instead: parent first, children indented below (about two characters per level). Rows get compacter (smaller font, tighter padding) so a deep imported vault fits on screen, and folder pages now read differently from leaf pages at a glance: lucide Folder/FolderOpen vs FileText, with the caret glyph upgraded to a ChevronRight that keeps the existing CSS rotation. Caret button, classes, and aria-labels stay untouched for the e2e contract (#101 convention: no page title in the caret label). Fixes #122 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| b8546eb249 |
Vault import: always show the label section, create labels inline
The label multiselect was gated on the pond already having labels — but before a first import that is the common case, so the section silently vanished and no import-wide label could be chosen. Render the fieldset unconditionally (with a hint when empty) and add an inline create field: POST the new label directly to get its id back, refresh the shared label query, and tick it right away. Same pond_admin permission as the dialog itself. The e2e pack now creates its label through the dialog instead of the API, covering exactly the empty-pond path that slipped through. Fixes #121 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| e356d82d51 |
Define the missing --color-surface token; modals were transparent
.modal (and three other rules) referenced --color-surface without a fallback, but the token was never defined — the background declaration was silently dropped and every modal panel rendered see-through over the page. Define the token in tokens.css (white, matching the #fff fallbacks other rules already carried) and pin the vault-import dialog's opaque background in the e2e pack. Fixes #120 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2VzvcoBPHkjh8bZ6PzQn |
|||
| 2d51a55119 |
QA: wire the M13 packs into CI, document the vault import (#119)
All checks were successful
CD / Build and push images (push) Successful in 2m40s
CD / Deploy to Test (push) Successful in 10s
CD / Smoke tests against Test (push) Successful in 1m9s
CI / Lint, typecheck, test (push) Successful in 4m23s
CI / Build container images (push) Has been skipped
CD / Promote to Int (push) Successful in 12s
CI / Auth e2e pack (push) Successful in 6m15s
CI / Import/export fidelity gate (push) Successful in 47s
Release / Build release images and notes (push) Successful in 1m7s
Release / Release-candidate operations QA (push) Successful in 41s
Prod deploy / Deploy the released images to Prod (push) Successful in 16s
CI runs the two new packs after the graph pack (chained, each preceded by the login rate-limit reset): create-missing-page.spec.ts (#115) and import-vault.spec.ts (#117/#118). Docs: the pond-admin guide gains a full 'Import an Obsidian vault' chapter — the three dialog choices, and what happens to folders, links (including the duplicate-name rule: the alphabetically first vault path wins), tags, images, and embeds, plus the limits and the all-or-nothing semantics. The user guide explains following a link to a page that does not exist yet. features.md gets both bullets. German mirrors updated throughout (English stays authoritative). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 704ebe48a6 |
Vault import dialog in the pond settings (#118)
Some checks failed
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Deploy to Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Failing after 1m0s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Has been cancelled
An admin-only 'Import an Obsidian vault' section on the pond settings page opens a dialog with everything the #117 endpoint expects: the ZIP, an indented mount-parent picker over the page tree (the MovePageDialog pattern), a multi-select over the pond's label tree, and the frontmatter radio (strip / keep as code block). Submit uploads and polls the job with a vault-sized budget (600 x 1 s), then invalidates pages, graph, phantom-links, and labels so the sidebar tree, graph, and pickers show the import without a reload — and links to the mount page. apiUploadFile now takes extra multipart fields (the options JSON); existing callers are unchanged. e2e import-vault.spec.ts: an admin imports the fixture vault through the dialog and the app shows the folder tree under the mount page, a rewritten Obsidian link navigates to the right page, the embedded image renders, and the nested tag labels exist next to the dialog's extra label; a plain editor gets no section at all. 3x flake-free locally (CI wiring lands with #119). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 8ae010218e |
Obsidian vault import: endpoint, job orchestration, rollback (#117)
Some checks failed
CD / Build and push images (push) Successful in 4m5s
CD / Deploy to Test (push) Successful in 9s
CI / Lint, typecheck, test (push) Failing after 4m21s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m22s
CD / Promote to Int (push) Successful in 11s
POST /ponds/:pondId/import/vault (pond-admin-gated; a vault import
creates a subtree, uploads files, and creates labels — administration,
not everyday editing) takes the ZIP plus a JSON options field
{parentPageId?, labelIds?, frontmatterMode}. The archive is parsed at
enqueue for fast 400s; the job (new kind import_vault, riding the
existing isImportKind worker routing) re-parses and runs the #116
transform, then: containers top-down → notes (asset placeholders →
uploaded pond files; non-images become page attachments) → tags to
labels (nested tags build a label hierarchy via LabelsService, so
locking and cache invalidation apply) plus the dialog labels.
All-or-nothing: any failure hard-deletes the created pages (children
first) and removes the stored files (quota restored), then surfaces as
import_vault_invalid_zip / import_vault_too_large / quota_exceeded /
conversion_failed — and makes the worker's retry policy safe.
Supporting changes:
- conversion_jobs gains a nullable options jsonb column; enqueue takes
kind-specific options and a maxInputBytes override (the 25 MiB
default protects the pandoc sidecar, which a vault never touches —
vaults use the 64 MiB upload limit).
- insertPage accepts a pre-reserved slug (the batch reserves all slugs
up front against pond ∪ batch).
- NEW: pages born with content seed their outgoing page_links rows
(deriveContent now returns wikilinkSlugs) — imported pages would
otherwise stay invisible to backlinks and the graph until their
first collab save. Collab still rewrites the rows on every save, and
the existing phantom resolution heals batch creation order.
import-vault.e2e.db.test.ts (4 tests, real worker drained): gating +
input rejection, the full fixture import (tree under a mount page,
collision suffixes, link rows incl. phantom, nested tag labels, extra
label everywhere, frontmatter stripped, image embedded + PDF attached),
complete quota rollback, and a clean re-import with fresh suffixes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|||
| 269b36c761 |
Fix the gates the #116 commit skipped past
Some checks failed
CD / Build and push images (push) Successful in 2m53s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m11s
CI / Lint, typecheck, test (push) Failing after 4m22s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Promote to Int (push) Successful in 11s
The asset type lost its name field during the split into path+extension (tsc error), and the fixture's .obsidian/app.json needed Prettier's formatting. Lesson from the /srv move repeated within one day: the gate chain must gate — never '; echo' past a failing typecheck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 09abda3ada |
Obsidian vault transform module (#116)
Some checks failed
CI / Lint, typecheck, test (push) Failing after 52s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Failing after 1m36s
CD / Deploy to Test (push) Has been skipped
CD / Smoke tests against Test (push) Has been skipped
CD / Promote to Int (push) Has been skipped
Pure functions from vault ZIP to import plan — no DB, no DI: - parseVaultZip: fflate unzip with the plugin-package protections (zip-slip rejection, incremental unpacked ceiling 256 MiB, parameterized for tests); dot-directories like .obsidian/ skipped; deterministic ordering. - extractFrontmatter: leading --- block, tags:/tag: in scalar, inline- array, and block-list forms; strip mode drops the block, preserve re-emits it as a fenced yaml code block. - extractInlineTags: fence- and inline-code-aware #tag / #nested/tag extraction and removal (headings and pure numbers untouched). - rewriteLinks: [[Name]], [[Name|Display]], [[Name#Heading]] (fragment stripped), [[folder/Name]] (path match beats basename) → the FINAL slug with the human name as display; unresolvable → slugified phantom; ![[img]] and relative  → vault-asset: placeholders the uploader resolves (#117); non-image embeds → italic filename + page attachment; SVG deliberately stays an attachment (never inline, security.md); note embeds degrade to plain wikilinks. - planFolders: folder chains merged at the deepest levels to fit MAX_PAGE_DEPTH below the mount page (merged titles read c/d). - planSlugs: -n suffixing against existing ∪ batch; duplicate basenames resolve to the lexicographically first vault path. - planVaultImport ties it together into containers + notes + the referenced-asset set. Fixture vault under fixtures/import/obsidian-vault/ (umlauts, duplicate basenames, nested tags, deep folders, embeds, code traps); 13 unit tests colocated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 64e21e9f94 |
Offer creating the page on the not-found screen (#115)
Some checks failed
CD / Build and push images (push) Successful in 4m20s
CD / Deploy to Test (push) Successful in 9s
CI / Lint, typecheck, test (push) Failing after 4m38s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m20s
CD / Promote to Int (push) Has been cancelled
Following a phantom wikilink now ends with a way out instead of a dead end: when the pond resolved and the page 404s as plain not_found, the error screen offers creating the page in place. Title = the URL slug (the PhantomPagesView mechanic), so every wikilink pointing at the address resolves; the invalidated page query then mounts the editor on the same URL. The affordance is deliberately ungated like the sidebar's new-page button — the client cannot tell 'never existed' from 'not readable' (#60), and a reader's POST surfaces as the regular 403 banner. The page_trashed branch (#31) is untouched. Rides along: PhantomPagesView now also invalidates ['pond-links'] — the graph views kept showing a just-created target as a phantom. e2e pack create-missing-page.spec.ts (CI wiring lands with #119): author a phantom link, follow it, create, backlink proves resolution; reader path asserts the 403 banner and no editor mount. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 14711a18c2 |
QA: page-tree and graph e2e packs in CI, manuals updated (#114)
All checks were successful
CD / Build and push images (push) Successful in 1m9s
CD / Deploy to Test (push) Successful in 10s
CD / Smoke tests against Test (push) Successful in 1m12s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m18s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m59s
CI / Import/export fidelity gate (push) Successful in 47s
Two new CI-wired Playwright packs, each in its own shared pond so the fixture ponds stay untouched: - page-tree.spec.ts — create-as-child with the form hint, collapsible folder view (collapse state survives reload), label view grouping, the local view override vs the owner-set pond default (fresh context without localStorage sees the new default), the Move-to dialog with the own subtree disabled, promote vs subtree delete, and a restored orphan re-attaching at the root. - graph.spec.ts — pond graph nodes/edges/legend, node click-through, the phantom-create flow (dashed node turns solid), the local panel with hop toggle and highlight ring, and the permission slice: a label-denied reader sees neither the hidden node nor its edge. Both packs 3× flake-free locally. Manuals: user guide (page tree, moving/deleting with subpages, knowledge graph + local graph), pond admin guide (sidebar view default), features.md (knowledge graph bullet) — with the docs/de mirrors updated (English authoritative). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| a72cb1b1c5 |
Local neighborhood graph on the page (#113)
Some checks failed
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Successful in 2m26s
CD / Deploy to Test (push) Successful in 10s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 4m21s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Has been cancelled
A collapsible 'Local graph' panel joins the backlinks below the page content in read mode: the current page (highlight ring) with its wikilink neighbors in both directions, switchable between direct neighbors and two hops. Computed client-side by BFS over the cached pond-wide graph response — no second endpoint; the TanStack query is shared with the pond graph view. Phantom targets render dashed; a click navigates to the neighbor; pages without any links show no panel at all. Reuses the ForceGraph renderer from #112 unchanged. Verified live: hop toggle reveals the second-hop page, ring on the current page, click-through, and the panel's absence on a lonely page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| dbbe229cb9 |
Pond knowledge graph view (#112)
Some checks failed
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
/p/:pondSlug/graph (static segment ranked above :pageSlug, same documented reserved-slug gap as trash/settings) renders the pond's readable wikilink graph from GET /ponds/:id/links: pages as nodes colored by their first label (legend included, DEFAULT_LABEL_COLOR for unlabeled), resolved links as edges, phantom targets as dashed nodes — clicking one offers to create the page, which resolves its links. Rendering is a self-contained SVG force graph: only d3-force is bundled (no d3 DOM/zoom modules, zero external requests); the layout runs synchronously to rest, zoom/pan/node-drag are plain pointer math. SVG over canvas deliberately — every node carries a data-testid the e2e packs can click. Ponds beyond 500 pages get a capped-view notice. Sidebar footer links every member to the graph (trash stays owner-only). New i18n namespace graph (de+en). Verified live: nodes/edges/legend render, node click opens the page, phantom click creates it and the node turns solid. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| ffcc337ed0 |
Page-tree parity for the public REST API and MCP (#110)
Some checks failed
CD / Build and push images (push) Successful in 3m57s
CD / Deploy to Test (push) Successful in 9s
CI / Lint, typecheck, test (push) Failing after 4m16s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m19s
CD / Promote to Int (push) Successful in 11s
The slug-based machine surfaces now see and shape the hierarchy: - REST: page list/detail carry parent (the parent page's slug, nulled when the token's user may not read it — same no-leak rule as the internal list); create accepts parent; PATCH accepts parent (slug nests, null moves to the top level, appended at the end of the new sibling group via the new PagesService.moveToEnd). Cycle/depth refusals keep their regular error codes. OpenAPI updated. - MCP: list_pages returns parent, create_page takes an optional parent slug, update_page moves with parent (slug|null); tool errors carry the api code (page_cycle covered in the e2e pack). - ZIP export deliberately stays flat — noted in features.md; the hierarchy is organizational only. e2e: REST pack covers nested create, list shape, move/root-move, 409 page_cycle, 404 unknown parent; MCP pack covers nested create, list parent, and the cycle tool error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 0308bc712d |
Drag-onto reparent, Move-to dialog, and the delete decision (#109)
Some checks failed
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Successful in 3m58s
CD / Deploy to Test (push) Successful in 9s
CI / Lint, typecheck, test (push) Successful in 4m23s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Has been cancelled
Sidebar folder view: a row now has three drop bands — the edges keep the within-group reorder, the middle band nests the dragged page under the row (appended to its new sibling group, with a drop-into outline cue). Cycle/depth refusals surface as a translated banner; successful moves are announced for screen readers. The overflow menu gains 'Move to…': a modal parent picker over the page tree (top level first, the page's own subtree disabled) that works in every sort mode. Delete now decides per case: childless pages keep the plain confirm; pages with subpages open a dialog offering promote (default wording: move subpages up) or subtree delete. The children lookup reads the CACHED pages list on purpose: an async fetch before window.confirm broke the click→confirm→DELETE rhythm the content pack (and users) rely on, and a stale childless read errs toward promote — never toward a silent subtree delete. Sidebar caret labels deliberately exclude the page title: accessible names are matched by substring in the specs (#101), and a title like 'Editor…' collided with the edit-mode toggle. Verified live: move dialog (subtree option disabled), promote and subtree delete flows; content/trash/export packs green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| e957c2a28f |
Pond-wide wikilink graph endpoint (#111)
All checks were successful
CD / Build and push images (push) Successful in 4m0s
CD / Deploy to Test (push) Successful in 9s
CI / Lint, typecheck, test (push) Successful in 4m21s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 5m41s
CI / Import/export fidelity gate (push) Successful in 47s
GET /ponds/:pondId/links returns the caller's readable slice of the wikilink graph in one read: nodes (id, title, slug, labelIds for the coloring), resolved edges deduplicated per direction (a rename can leave several slugs pointing at one target), and phantom targets with their referrer ids. An edge survives only when both endpoints are readable; a phantom disappears entirely once its last readable referrer is filtered — a hidden page's existence never leaks through any of the three collections. Trashed pages and their links are excluded. Shared PondGraphView types feed the knowledge-graph views (#112/#113). DB tests cover the owner's full graph, the label-DENY reader slice, edge dedup, trash exclusion, and labelIds on nodes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 15184876bd |
Sidebar folder view, label view, and the view toggle (#108)
Some checks failed
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
The sidebar now presents pages as a collapsible tree built from parentId (folder view) or grouped under the hierarchical label tree (label view, read-only; multi-label pages appear under each label, untagged ones in an 'unlabeled' group). The pond owner sets the default via a new sidebarView pond setting (PATCH-merged like the other keys); every user can override it locally (ui.sidebar.view.<pondId>), and the toggle sits above the page list. Collapse state persists per pond. New pages created while a page is open become its children — the inline form says so and sends parentId. Reordering (buttons and drag-between) now operates within one sibling group; the label filter stays a folder-view feature and falls back to the flat list while active, so the filtered order is never mistaken for a partial tree. SidebarContent is keyed by pond id so the per-pond localStorage hooks mount with the right key. e2e hooks (.sidebar__pages, .sidebar__page, reorder buttons) kept; reorder/labels/content packs green locally, plus a live smoke of nesting, collapse persistence, and both views. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 12ff3c099f |
Delete modes and subtree trash semantics for the page tree (#107)
All checks were successful
CD / Build and push images (push) Successful in 3m59s
CD / Deploy to Test (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m20s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m15s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Successful in 5m42s
CI / Import/export fidelity gate (push) Successful in 47s
DELETE /pages/:id?mode=promote|subtree — promote (the default) moves the page's live children up to its parent; subtree trashes every live descendant with one timestamp and requires write permission on all of them (no partial deletes; trash access is write capability, ADR 0013). Trashed pages keep their parentId. Restore re-attaches to the nearest live ancestor (else root), which makes restore order-independent: restoring a parent afterwards never re-claims an already-restored child. Purge promotes any remaining children to the purged page's parent; the FK's SetNull stays as backstop only. tree-trash.e2e.db.test.ts covers promote, subtree + one-timestamp, the 403 descendant gate (label-DENY editor), order-independent restore, and child promotion on purge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| eb6b0d5d02 |
Page hierarchy: parentId, create-under-parent, reparent (#106)
Some checks failed
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Successful in 3m53s
CD / Deploy to Test (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 4m16s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m19s
CD / Promote to Int (push) Successful in 14s
CI / Auth e2e pack (push) Has been cancelled
Pages form a tree via a nullable parent_id self-relation (SetNull backstop; the real trash/purge semantics follow with #107). Slugs and URLs stay flat and pond-unique, so moving a page never breaks links. - Shared: generic parent-id tree helpers in tree.ts (labels re-export them; buildLabelTree keeps its name-sorted behavior), MAX_PAGE_DEPTH=6, parentId on PageView, createPageInputSchema.parentId (nullish), repositionPageInputSchema.parentId (optional; absent = keep parent). - API: create validates the parent (same pond, live, depth); PATCH /pages/:id/position reparents atomically with the placement, rejecting cycles (page_cycle) and depth violations (page_depth_exceeded); GET /ponds/:id/pages nulls parentId when the caller may not read the parent, so hidden page ids never leak. - New error codes translated de+en; hierarchy.db.test.ts covers create, 404s, depth, cycle, atomic reparent, and the permission nulling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 04abda5724 |
Keep the landing editor off the .legal-editor class
All checks were successful
CD / Deploy to Test (push) Successful in 10s
CD / Build and push images (push) Successful in 1m10s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m10s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m37s
CI / Import/export fidelity gate (push) Successful in 48s
Release / Build release images and notes (push) Successful in 1m6s
Release / Release-candidate operations QA (push) Successful in 41s
Prod deploy / Deploy the released images to Prod (push) Successful in 15s
The landing form reused LegalTextField, whose .legal-editor wrapper the legal e2e selects by index (nth(1) = privacy policy). Placed before the legal form it shifted those indices, so the test drove the imprint field and the published privacy text never appeared. Generalize the component to MarkdownTextField with a wrapperClass prop: legal keeps .legal-editor, the landing editor uses .markdown-field. Verified locally: legal, admin-users, admin-quotas packs green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| abf49c7c0e |
Give the landing-page save button its own label
Some checks failed
CD / Build and push images (push) Successful in 3m53s
CD / Deploy to Test (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m12s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Failing after 2m56s
CI / Import/export fidelity gate (push) Has been skipped
The landing form reused the legal namespace's "Save legal pages" label, so two buttons shared that text and the legal e2e's page-wide button lookup hit a strict-mode violation. Use a dedicated settings-namespace "Save landing page" label instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 9c64166b10 |
Editable landing page for the Site Admin
Some checks failed
CD / Build and push images (push) Successful in 3m50s
CD / Deploy to Test (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m13s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Failing after 2m44s
CI / Import/export fidelity gate (push) Has been skipped
The public home page (/) now renders Markdown the Site Admin stores in the new home.content instance setting, through the same sanitizing pipeline as the legal pages; empty falls back to the built-in welcome text. New public GET /home/content, an Admin → Settings editor with live preview, and an e2e test covering default/configured/escaping/ admin-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| a7760335cb |
Remove an unused eslint-disable directive in main.ts
Some checks failed
CD / Promote to Int (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Failing after 1m11s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 2m48s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Has been cancelled
no-console is not enforced for apps/api, so the directive on the boot-time restore-wait log line was flagged as unused and failed the lint gate (--report-unused-disable-directives). Keep the console.log and its rationale as a plain comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 627f128ab8 |
Pond lifecycle in the UI: create shared ponds, delete from settings
Some checks failed
CD / Build and push images (push) Successful in 3m54s
CD / Deploy to Test (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 4m8s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Failing after 11s
CD / Promote to Int (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m40s
CI / Import/export fidelity gate (push) Successful in 54s
The pond switcher grows a "+ New pond" entry with an inline form (name + optional description, quota errors surfaced translated); the pond settings of shared ponds end in a danger section that moves the pond to the site-level trash after typing its name to confirm. Personal ponds keep hiding the section. .button--danger is now a solid red button (also fixes the admin restore button, which showed red text on the accent-green background). Manuals no longer call these actions API-only; covered by a members-pack e2e test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| c8ec549fa5 |
UI polish: frameless plugin blocks in read mode, sticky toolbar, pinned footer, icon uninstall
All checks were successful
CD / Build and push images (push) Successful in 1m11s
CD / Deploy to Test (push) Successful in 11s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m5s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m35s
CI / Import/export fidelity gate (push) Successful in 47s
Release / Build release images and notes (push) Successful in 1m6s
Release / Release-candidate operations QA (push) Successful in 40s
Prod deploy / Deploy the released images to Prod (push) Successful in 15s
Three refinements from Stefan's review of the plugin work: - read mode integrates plugin output like normal content: no border, no name bar, no selection outline around plugin blocks — same principle as the frameless reading shell (M10) - the editor toolbar pins to the top of the scrolling content area on long articles instead of scrolling away (position: sticky within the main scroll container) - the app footer (connection status + legal links) moved out of the scroll container into a main-column wrapper — always visible at the bottom edge of the window on every view - the plugin uninstall buttons in the admin list are icon buttons now (Trash2, house pattern: aria-label keeps the accessible name) - lint hygiene: eslint/prettier ignore packages/plugins/*/vendor — the unpacked drawio webapp drove eslint out of memory Verified in the browser against a local stack (5/5 scripted checks: icon buttons, toolbar sticky at scroll bottom, footer pinned in edit and read mode, plugin block computed border/outline none in read mode) plus 8 layout-sensitive e2e packs re-run individually, all green (comments, collab, legal, content, plugin-admin, plugin-blocks, page-tools, plugins). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 97f94f247b |
draw.io reference plugin: fullscreen editing, inline SVG rendering
All checks were successful
CD / Build and push images (push) Successful in 3m54s
CD / Deploy to Test (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m9s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Successful in 5m36s
CI / Import/export fidelity gate (push) Successful in 47s
A new block plugin bundling the OFFICIAL draw.io editor — nothing ever
loads from diagrams.net; the sandbox CSP pins every request to the
plugin's own version-pinned asset path (zero-external-network verified
live via a request-capture run).
Plugin (packages/plugins/drawio):
- block data { xml, svg }: xml is the draw.io source (document of
record), svg the rendered snapshot as raw markup — render mode,
office/PDF exports (the existing fallback renderer already inlines
data.svg) and the public view all show the diagram without running
diagram code
- edit mode: snapshot + "edit in fullscreen" (an empty block opens the
editor immediately); the bundled editor runs in a child iframe of the
plugin's own assets and speaks draw.io's JSON embed protocol —
Save & Exit exports xmlsvg, persists { xml, svg } via blockData, and
drops back to the inline size
- build.mjs fetches the pinned release (v30.3.6) into a gitignored
vendor/ cache (fonts-build pattern; skipped in CI — plugin.js still
bundles, the installable ZIP needs a dev machine) and packs a trimmed
webapp subset: no dev sources, no embed.diagrams.net integrations
bundle, no standalone viewers, no MathJax/templates/PWA — 27 MiB ZIP,
85 MiB unpacked, de+en editor languages
Host/SDK extensions (generic, not drawio-specific):
- new ui.enterFullscreen()/exitFullscreen(): the surface's frame becomes
a viewport-covering overlay — same sandboxed iframe, only geometry
changes; destroy removes the frame, so a vanished plugin can never
leave the app covered
- sandbox CSP: connect-src/frame-src now allow the plugin's OWN asset
path (was 'none') — bundled apps lazy-load their resources and run in
a child frame, but the api and external hosts stay unreachable; HTML
assets are served with the same CSP so a packaged page cannot widen
the rules, and child frames inherit the sandbox attribute
- plugin size limits raised (ZIP 5→64 MiB, unpacked 20→256 MiB) for
bundled-app plugins; content types for xml/txt/ico assets
Verified end to end against a local stack (9/9): install via dropzone
(85 MiB validation), block insert, fullscreen entry, bundled editor
boots inside the double sandbox (German UI), shape drawn, Save & Exit
persists, snapshot renders inline, survives reload, zero off-origin
requests throughout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
|
|||
| 52192eb05f |
Backup mirror to BASEL: rsync of the sets after every successful run (#84)
All checks were successful
CD / Build and push images (push) Successful in 3m51s
CI / Lint, typecheck, test (push) Successful in 4m5s
CD / Deploy to Test (push) Successful in 11s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 12s
CI / Auth e2e pack (push) Successful in 5m52s
CI / Import/export fidelity gate (push) Successful in 47s
The operator-level extra beside the admin-configured Nextcloud target (#103), unblocked now that the ONE→BASEL tunnel is stable again. - sidecar: optional mirror step (mirror.ts) driven purely by env — BACKUP_MIRROR_TARGET (rsync-over-ssh), BACKUP_MIRROR_SSH_KEY (private key on the secrets volume, never in image or repo), BACKUP_MIRROR_SSH_PORT. Runs after the prune of every successful run, so --delete aligns the remote retention with the local one (the newest-complete-set guarantee carries over). Only set files travel (db-*.dump, files-*.tar.gz); status files and bundles stay local. Host key pinned via accept-new into .mirror_known_hosts on the backups volume; fixed remote modes (dirs 750, files 640, symbolic --chmod — octal needs rsync ≥ 3, macOS dev machines ship 2.6.9). rsync + openssh-client added to the sidecar image. - status: additive `mirror` block in status.json (outcome, transferred count, lastSuccessAt carried across failures) — shown on the admin backup card; failures alert via a new backupMirrorFailed mail (de+en) while the local run still counts as succeeded. - deploy/backup-basel.md: complete BASEL-side walkthrough — dedicated user dorfteich-backup with a /home/ home and a bash login shell, explicitly avoiding the Debian backup-user (UID 34) pitfalls (nologin shell rejects rsync sessions, /var/backups home), key placement through the api container onto the secrets volume, .env values, on-demand verification. - tests: rsync-arg/stats-parsing units plus an integration suite against the real rsync binary (local target; skips where rsync is absent) — transfer, idempotent re-run (0 files), retention alignment, failure path carrying lastSuccessAt. Verified live against the real BASEL host from a native sidecar run: initial transfer, host-key pinning, retention alignment after a local prune, idempotency, and the failure path (surfaced in status.json while the local run stayed green). BASEL side provisioned per the doc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 04e21a0aac |
Built-in MCP endpoint (Streamable HTTP) on top of the public API (#105)
All checks were successful
CD / Build and push images (push) Successful in 3m50s
CI / Lint, typecheck, test (push) Successful in 4m2s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 11s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Successful in 5m37s
CI / Import/export fidelity gate (push) Successful in 47s
AI clients talk to the instance directly at /api/mcp — under the /api/ path (deviation from the issue's literal /mcp) so every existing reverse proxy already routes it; no deployment changes anywhere. - Transport: official @modelcontextprotocol/sdk server, STATELESS — each POST builds a fresh server+transport pair, no session store, replicas stay trivial; GET/DELETE answer 405. Auth per PAT bearer (#104 tokens), per-token rate limit (429 + Retry-After). - Own switches, independent of REST: instance mcp.enabled (admin settings, default off; off = 404, feature invisible) + pond setting mcpEnabled (pond-settings toggle, default off) — pinned independent in both directions by tests. - Tools (thin wrappers over the #104 services, same permission gates, audit-logged writes): list_ponds, list_pages, read_page, search, create_page, update_page (replace semantics through the collab-owned restore path — open editors converge), add_comment, list_labels, set_page_labels (exact replace), export_pond (link to the REST ZIP). Tool errors carry the api error codes; results carry stable slugs/ids. MCP resources stay the documented stage-2 stretch goal. - Deliberately on the SDK's low-level Server API with a hand-written tool table (mcp-tools.ts): the typed registerTool generics drove tsc out of memory in a program this size; manual Zod validation keeps the wire behavior explicit. - PublicApiService exposure filtering parameterized ('api' | 'mcp', shared pondFeatureEnabled helper) — one implementation, two switches. - Docs: "Connect Claude Code / MCP clients" section in public-api.md (claude mcp add one-liner + mcp-remote bridge for stdio clients). Verification: 8-test e2e pack driving the real MCP SDK client over Streamable HTTP against a listening api (initialize + tools/list, switch independence in both directions, anonymous/garbage 401, opt-in 404 semantics, page roundtrip incl. restore-NOTIFY, labels/comments, read scope blocked from writes with scope_required); live check through the web proxy against the seeded stack (tools list, create, read, update, search — LIVE CHECK PASSED); full api suite 61/61 files green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 52975bad0a |
Deflake the collab restore-listener DB test (poll for the PRE_RESTORE row)
All checks were successful
CD / Build and push images (push) Successful in 1m49s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 3m52s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m32s
CI / Import/export fidelity gate (push) Successful in 47s
The client converges on the restored content via the broadcast INSIDE the document transact — before the listener commits the PRE_RESTORE version row. Asserting the row immediately after convergence is a race that CI lost on the #104 run; poll for it instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 0c85293830 |
Public REST API v1: personal access tokens, instance switch + per-pond opt-in (#104)
Some checks failed
CI / Lint, typecheck, test (push) Failing after 1m39s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m51s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 11s
Token-authenticated machine access at /api/public/v1 — the foundation for the built-in MCP endpoint (#105). Personal access tokens: - api_tokens table (SHA-256 hash, scope read|write, optional pond restriction, expiry, revocation, throttled last-used) + migration; secrets are dt_pat_<random>, shown exactly once - lifecycle endpoints under /users/me/api-tokens (session-only — a leaked token can never mint more tokens) with audit entries api.token_created/api.token_revoked - settings UI section (create with scope/expiry/pond restriction, one-time reveal with copy, list with status + revoke), de+en Activation (404 semantics per #60 on both levels): - instance setting api.enabled (default off, admin settings switch) - pond setting apiEnabled (default off, pond settings toggle; the PondsService settings-merge learned the key — the #92 lesson) Surface (/api/public/v1, excluded from the SPA's global prefix): - me, ponds, pages (list/read as Markdown+HTML, create from Markdown via the shared pipeline, PATCH title/content, DELETE to trash), search (permission-filtered + narrowed to exposed ponds, highlights as **…**), markdown ZIP export, labels (tree, create/rename/recolour/move/delete, assign/unassign), comments (threads, create, resolve/reopen) - content replacement travels the collab-owned document path: the new state lands as a MANUAL version "API update", then the established restore NOTIFY applies it — open editors converge, history stays append-only, no second lineage (VersionsService.replaceContent) - hand-maintained OpenAPI 3.1 document at /openapi.json, pinned to the controller by a route-coverage test in both directions Enforcement: - PublicApiGuard: instance switch → bearer PAT auth (request.user is the token's user) → per-token rate limit (429 + Retry-After) → scope (403 scope_required) → pond opt-in + token restriction - the shared PermissionGuard then applies the unchanged permission model; PageParamSource gained pondSlugParam for the slug+slug routes - no cookies anywhere → no CSRF surface (pinned by a hostile-Origin test) - every write audit-logged as api.write with the token attributed Tests/verification: - 12-test e2e pack: lifecycle, switches, permission matrix (reader/editor/outsider × scopes), restriction, page roundtrip incl. restore-NOTIFY assertion, labels, comments incl. policy, search narrowing, ZIP export, rate limit; full api suite 60/60 green (quota fixture via per-user override — never the instance default) - new collab-pack test proves an open editor converges onto an API content replacement (green against a local seeded stack) - UI smoke against the built SPA: token create/reveal/revoke, pond opt-in persists, admin switch persists (10/10) - docs/self-hosting/public-api.md + README link Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 5cef359b8f |
Nextcloud backup target: admin-configured, manual + scheduled uploads, in-app restore (#103)
All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m45s
CD / Build and push images (push) Successful in 3m49s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m18s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 5m35s
CI / Import/export fidelity gate (push) Successful in 47s
Off-host backups for every self-hoster, configured entirely in the admin UI — supersedes the host-specific mirror plan behind #84. shared: - webdav.ts (new package entry like token-crypto): minimal WebDAV client with basic auth — PROPFIND (tolerant multistatus parser), MKCOL, PUT (streamed), GET, DELETE; Nextcloud DAV path derived from the plain server URL, explicit DAV bases pass through - backup-status.ts: additive remote-upload status in status.json, the restore-status.json contract (running/succeeded/failed + staleness bound), the backup_command/backup_maintenance NOTIFY channels, and the one-bundle-per-set naming (dorfteich-backup-<id>.tar.gz) - backup-set.ts moved here from apps/backup (api lists local sets) backup sidecar: - reads the backup.* instance settings directly from the database (admin changes apply next run; local retention row overrides the env) and the app password from the secret store - after each successful set: bundle dump + files archive + manifest into ONE self-contained tar.gz, upload via WebDAV per schedule (off/daily/weekly; manual runs always upload), prune remote bundles — never the newest — and record the outcome in status.json; upload failures alert via a new backupUploadFailed mail (de+en) - command listener on backup_command (run / restore) with a serial queue against the nightly timer - restore orchestrator: restore-status.json → maintenance NOTIFY → grace → (remote: download + manifest-verify bundle) → terminate other DB connections → shared perform-restore path (same code as restore.sh) → final status + maintenance exit api: - MaintenanceGuard (global, registered before the setup gate): 503 maintenance_mode while restore-status says running; health endpoints and the new public GET /backup/restore-status stay exempt; a stale running state (crashed sidecar) unblocks after 30 min - MaintenanceStateService watches the file and restarts the api after a successful restore (fresh caches, migrate-on-start for older dumps); main.ts refuses to touch the database while a restore runs — a container restarting mid-restore must not race pg_restore with migrate deploy - worker sweeps (conversion, mail outbox, scheduler) catch transient database failures instead of dying on an unhandled rejection — the restore's connection termination crashed the api in verification - backup admin endpoints under /admin/system/backup: settings (live connection test before save, password write-only into the secret store), nextcloud/test, sets (local via the ro backups mount + remote via WebDAV), run + restore (type-to-confirm backstop, source validation) — commands travel as NOTIFY payloads; audit actions backup.settings_changed/run_triggered/restore_requested - readyz: new warning-level backup_remote check while a target is configured (26 h daily / 170 h weekly bound) collab: - maintenance listener: on enter, persist + close every live session and refuse new connections until exit (failsafe timeout 30 min) — no in-memory document may write pre-restore content back afterwards web: - Admin → System backup section: status card with remote facts and a "Back up now" button, the Nextcloud settings form with test button, and the restore picker (local + remote sets, type-to-confirm) - global maintenance screen: any 503 maintenance_mode flips the SPA to a status page polling the exempt endpoint, reloading when the instance returns Verified end-to-end against a live stack (fresh DB, native api + sidecar, fake WebDAV server): configure → test → manual backup → bundle upload → readyz/sets/status surfaces → remote restore with maintenance gate, marker rollback and api restart; suites: shared 21, backup 9, collab 11, api 58 files green, lint + i18n:check + typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 83fa23bbf9 |
Polish round 2: content footer, dismissable menus, manual versions, substring search, icon actions in settings (M10 follow-up)
All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m35s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m44s
CD / Deploy to Test (push) Successful in 15s
CD / Smoke tests against Test (push) Successful in 1m17s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 5m36s
CI / Import/export fidelity gate (push) Successful in 46s
- content footer: the collab status is an icon (wifi/off/refresh, localized tooltip + visually-hidden text, class/data-status hooks kept for e2e) on the left, the legal links right-aligned; read mode drops the editor frame and its inner padding, edit mode keeps it - menus (page overflow, user, notifications bell, pond switcher) close on outside click and Escape via a shared useDismissable hook; the bell got its missing tooltip - side panels (labels, history) stack vertically in one column - edit mode gains a Save-version icon (prompt for the name, POST /pages/:id/versions); the history panel lists contributors by display name — more than three collapse to two plus an expandable ellipsis (PageVersionView.contributors resolved server-side, deleted users drop out) - search finds partial words via a LIKE fallback next to the tsquery (FTS matches still rank first; regression-pinned in the db pack), and the recent-searches list has a clear button - pond owners create labels directly in the label picker (plus a permanent link to the full manager); add/remove/delete buttons across the pond settings (members, access rules, labels, files) and the watch/unwatch toggles in pond/user settings are icon buttons now — class hooks and accessible names unchanged for the e2e packs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| 33121cd73d |
Move pond settings to a TopBar gear, pin the trash link to the sidebar bottom (M10 follow-up)
All checks were successful
CD / Build and push images (push) Successful in 1m8s
CD / Deploy to Test (push) Successful in 11s
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 3m42s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m31s
CI / Import/export fidelity gate (push) Successful in 47s
- owners get a Settings icon next to the pond name while a pond route
is active (same lucide set, localized aria-label/tooltip via the
existing labels:link key); gone on non-pond routes and for non-owners
- the trash stays a text link but pins to the sidebar's bottom
(.sidebar is a flex column now; .sidebar__footer uses margin-top:auto)
- trash.spec: delete flows go through the #101 overflow menu (was
missed in
|
|||
| e740ea6c01 |
Move live presence into the TopBar, signed-in only (#102)
All checks were successful
CD / Build and push images (push) Successful in 1m39s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m11s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 3m33s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m31s
CI / Import/export fidelity gate (push) Successful in 46s
- the TopBar registers a presence slot (only rendered for signed-in users) next to the page-actions slot; PageEditor portals the PresenceStrip into it — behavior unchanged (initials avatars, max 5 + overflow, viewer badge, hidden when empty, both view and edit mode) - pinned guarantee: public.spec asserts the anonymous read path opens no /collab websocket and renders no presence data Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |