Commit Graph

11 Commits

Author SHA1 Message Date
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
2026-07-20 00:34:15 +02:00
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
2026-07-19 01:04:11 +02:00
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
2026-07-16 12:03:55 +02:00
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
2026-07-15 13:09:31 +02:00
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>
2026-07-14 10:40:08 +02:00
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>
2026-07-14 10:17:36 +02:00
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
2026-07-12 19:15:34 +02:00
6c4f37ef91 Make the sidebar width drag-resizable and persistent (#99)
Some checks failed
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Successful in 3m35s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m45s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m18s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Has been cancelled
- SidebarResizer: pointer-drag handle on the sidebar's right edge,
  keyboard-adjustable (arrows, Home/End), double-click resets to the
  16rem default; width clamped to 12-32rem
- AppLayout persists the width via usePersistentState (ui.sidebar.width)
  and sets --sidebar-width inline on .app-body, so collapse/force-hide
  keep animating from/to the chosen width
- localized aria-label (de+en), handle hidden while collapsed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-12 04:16:56 +02:00
69b00fcf2f Add manual page ordering with drag-and-drop (#45)
All checks were successful
CD / Build and push images (push) Successful in 3m1s
CI / Lint, typecheck, test (push) Successful in 2m13s
CI / Auth e2e pack (push) Successful in 2m36s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
Enable the third sidebar sort mode — a freely defined order.

- api: `PATCH /pages/:id/position` (before/after neighbour) recomputes only
  the moved page's fractional `sort_key`. Pure `sort-key.ts` helpers
  (`nextKeyOrRebalance`, `evenlySpacedKeys`) decide between the cheap
  single-key path and a full pond rebalance to evenly-spaced keys when a key
  would exceed MAX_SORT_KEY_LENGTH or the client's neighbours are stale;
  rebalance runs in one transaction. Order is server-authoritative.
- web: enable 'manual' in the sort-mode switch; in manual mode the owner can
  reorder via native drag-and-drop (drop above/below by pointer half) or the
  keyboard (per-row up/down buttons), each announced through an aria-live
  region. Reordering is hidden while a label filter narrows the list. New
  pages already append at the end (create uses generateKeyBetween(last, null)).
  Pure `reorder.ts` neighbour helpers, unit-tested.
- i18n: manual sort mode + reorder strings (de + en).
- tests: sort-key property test (10.000 adversarial reorders never collide or
  overflow — rebalance verified); reposition db test (persist, server-order,
  sort-mode switch keeps manual order); reorder e2e pack (keyboard reorder
  persists across reload + identical on a fresh read; aria-live announced).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
2026-07-09 12:18:44 +02:00
49beb45b3e Add pond sidebar with page list, sort modes, and pond switcher (#26)
All checks were successful
CD / Build and push images (push) Successful in 1m59s
CI / Lint, typecheck, test (push) Successful in 1m38s
CI / Auth e2e pack (push) Successful in 1m48s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 9s
GET /ponds/:id/pages lists a pond's pages ordered by the pond's
persisted sidebarSort setting (alpha/created; manual arrives with #45).
The sidebar consumes it to show the page list with an active-page
highlight, an owner-only sort switch (persists via the existing
PATCH /ponds/:id), and an inline "new page" flow. The top bar gains a
pond switcher; a new /p/:pondSlug route gives it somewhere to land,
redirecting to the pond's first page once loaded. Sidebar collapse
gains a Ctrl/Cmd+\ shortcut and a slightly refined transition.

Closes #26
2026-07-06 12:37:44 +02:00
e855192d23 Add i18n with i18next, German and English, and a key-parity check
Translation resources live in packages/shared/i18n/<lang>/<ns>.json
(common, errors) and ship with de and en. The web app initializes
react-i18next with bundled resources (?lng= wins, then the browser
language); all shell components use useTranslation and the temporary
t() stub is gone. The api localizes its uniform error bodies via a
minimal i18next instance negotiated from Accept-Language. `pnpm
i18n:check` fails CI when any key is missing in any language, backed
by tested helpers in @dorfteich/shared.

Closes #5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:23:45 +02:00