Commit Graph

9 Commits

Author SHA1 Message Date
63fe6af6b0 Add remote cursors and a presence strip (#37)
All checks were successful
CD / Build and push images (push) Successful in 2m54s
CI / Lint, typecheck, test (push) Successful in 1m58s
CI / Auth e2e pack (push) Successful in 2m10s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m15s
CD / Promote to Int (push) Successful in 11s
Seeing other participants live (ADR 0003/0004, realtime-collaboration.md
§Awareness):

- The collaboration-caret extension renders remote carets and selections
  with a name flag and a per-user colour. Colours come from a small,
  hand-picked palette hashed by user id (FNV-1a), so they are stable across
  sessions; a unit test asserts each palette colour clears WCAG AA contrast
  (4.5:1) against the white label text.
- A presence strip at the top of the page shows an avatar (initials) per
  connected participant, deduplicated by user id, with an overflow count.
  Read-only participants appear in the strip (with a marker) but broadcast
  no caret — the caret render suppresses read-only users — so the same
  awareness feed drives both cursors and presence. Own identity (id +
  display name) comes from the auth context into the awareness `user` field.
- Presence updates on every awareness change, so a disconnect drops the
  participant within seconds.

The collab e2e pack gains a test: two browsers see each other in the
presence strip, one participant's named caret appears in the other's editor,
and disconnecting removes them. Validated locally against the full stack.
de + en strings and cursor/presence styles added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
2026-07-08 18:22:41 +02:00
7d04c0b594 Switch the editor to live collaboration (#36)
All checks were successful
CD / Build and push images (push) Successful in 2m59s
CI / Lint, typecheck, test (push) Successful in 2m0s
CI / Auth e2e pack (push) Successful in 2m10s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
The editor now edits over the collaboration server instead of REST — the
moment Dorfteich becomes collaborative (ADR 0003, realtime-collaboration.md).

Web:
- New `useCollabProvider` hook binds a page's Y.Doc to a HocuspocusProvider.
  The document loads and persists through the collab server (#35); there is
  no REST autosave and no REST seed (a REST seed would fork the doc lineage
  and duplicate content). The collab token is fetched lazily on every
  (re)connect via an async token function, so an expired token is replaced
  transparently and a permission change takes effect on the next reconnect.
- Connection-state UI replaces the save indicator: connecting / connected
  ("Live") / reconnecting / offline, driven by provider status + navigator
  online state. Read-only (`ro`) tokens make the editor non-editable with a
  reason; an oversize-document stateless error (#35) surfaces a banner.
- Removed `use-page-autosave.ts` and `yjs-base64.ts` (no longer used).

API:
- `PUT /pages/:id/state` is retired and returns 410 `rest_state_write_retired`
  (the criterion deferred here from #35). Collab is the sole writer of page
  state; the read paths remain. Removed the now-dead `saveState` service.

e2e / CI:
- The e2e static server proxies the `/collab` WebSocket upgrade (mirrors
  Caddy); vite dev gains a `/collab` ws proxy. The auth-e2e CI job starts the
  collab server and runs a new collab pack.
- New `collab.spec.ts`: two browsers converge on one page (the milestone
  headline), and offline edits continue locally and sync on reconnect. The
  read-only live assertion is a `test.fixme` until real read-only grants
  exist — under interim access seeing and modifying coincide, so no `ro`
  token is issued yet (that arrives with #53). Reworked the api/trash tests
  and the content editor-basics test off the retired REST write path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
2026-07-08 18:00:19 +02:00
b5cc4c34b8 Add link UX: edit URL and open in new tab (#29)
All checks were successful
CD / Build and push images (push) Successful in 2m2s
CI / Lint, typecheck, test (push) Successful in 1m41s
CI / Auth e2e pack (push) Successful in 1m49s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m9s
CD / Promote to Int (push) Successful in 9s
A bubble menu on link selection offers "edit URL", "open in new tab",
and "remove link"; Mod-k opens the same editor for the current
selection (creating a link if there isn't one yet), and the toolbar
button does the same. Invalid protocols (e.g. javascript:) show a
localized inline error instead of silently no-oping. Pasting a URL
over selected text links it instead of replacing the text.

Links always render with target="_blank" so read mode opens them in a
new tab by default; edit mode suppresses the resulting navigate-on-
click (Mod-click still follows it), since a plain click there should
place the cursor instead.

Closes #29
2026-07-08 11:50:34 +02:00
076883a9a6 Add TipTap page editor with REST persistence (#25)
All checks were successful
CD / Build and push images (push) Successful in 2m0s
CI / Lint, typecheck, test (push) Successful in 1m42s
CI / Auth e2e pack (push) Successful in 1m50s
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 10s
TipTap is bound to the canonical ProseMirror schema (packages/shared,
#24) via a generic bridge (spec-utils.ts) that re-derives every
node/mark's attrs/parseDOM/toDOM from editorSchema instead of
duplicating them, so the editor's schema stays byte-for-byte identical
to what the api decodes Yjs states against — guarded by a schema-
fidelity + real Yjs round-trip test (@tiptap/y-tiptap client encoding
against y-prosemirror server decoding).

Route /p/:pondSlug/:pageSlug (RequireAuth) resolves the page via a new
GET /ponds/:pondId/pages/:slug endpoint, binds a local Y.Doc via
@tiptap/extension-collaboration (fragment "default"), and offers a
view/edit mode toggle (sidebar auto-hides in edit mode via a small
AppLayout context). Page state saves debounced to PUT /pages/:id/state
with a truthful saving/saved/error(retrying) indicator; title saves
separately via PATCH /pages/:id.

Toolbar covers headings, marks, lists, blockquote, code block, hr,
table (insert/row/column/header ops via prosemirror-tables), a minimal
link mark, and an image placeholder (real upload is #27/#28).

Closes #25

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 11:08:43 +02:00
0bc80c9f93 Add auth, settings, and admin UI to the SPA
The web app grows its account surface: login (with next-redirect,
unverified-hint + resend), signup (react-hook-form + shared Zod
schemas, field-level api errors, closed-registration state fed by the
new public GET /auth/registration), e-mail verification, forgot/reset
password; a settings page with profile (locale applies immediately),
password change, and active-session management; a Site-Admin page for
instance name, default locale, and registration mode. AuthProvider
holds /auth/me, applies the profile locale, and backs route guards
(RequireAuth/RequireAnonymous/RequireSiteAdmin); the top bar gains a
user menu. All strings ship in the new auth/settings namespaces (de+
en); the exception filter now preserves handler-specific error codes.
Verified live: signup → Mailpit → verify → login → profile through
the Vite proxy.

Closes #16
Closes #17
Closes #18
Closes #19

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 05:35:56 +02:00
fb1422f56f Add CD workflow: build-push, deploy Test, smoke suite, promote Int
Some checks failed
CD / Build and push images (push) Failing after 5s
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
CI / Lint, typecheck, test (push) Failing after 7s
CI / Build container images (push) Has been skipped
On every push to main: build both images once (SHA + moving `test`
tag), push to the Gitea registry, SSH-deploy the Test stage, wait for
readiness, run the new Playwright smoke suite (SPA shell, web
liveness, api healthz/readyz) against https://test.dorfteich.cloud,
and on green retag the identical SHA images as `int` and deploy Int.
The CI image-build job becomes PR-only to avoid double builds on main.

Part of #8

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:57:16 +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
300a418e85 Add React SPA shell with routing, layout, and API status
apps/web becomes a Vite + React application: React Router with home
and 404 routes, base layout (top bar, collapsible sidebar remembered
per user via localStorage, main area), CSS design tokens including the
three font slots from ADR 0016, TanStack Query, and a typed fetch
helper showing live API health on the home page. All UI strings go
through a t() stub that issue #5 replaces with i18next. The Vite dev
server proxies /api to the api dev port (3001).

Closes #4

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:19:12 +02:00
b16d23297e Scaffold pnpm monorepo with lint, format, and test tooling
pnpm workspace with apps/web, apps/api, apps/collab, and
packages/shared; strict TypeScript base config, repo-wide ESLint (flat)
+ Prettier, Vitest per package, and root scripts lint/typecheck/test/
build. @dorfteich/shared ships a first health-response helper consumed
by apps/api to prove workspace linking. Existing markdown docs are
reformatted once by the new Prettier setup.

Closes #1

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