Commit Graph
83 Commits
Author SHA1 Message Date
Lucas Winther 5c837a6679 Merge branch 'task/17' into task/18 2026-10-30 22:14:56 +01:00
Lucas WintherandClaude Opus 5.5 4608c977b7 fix(web): keep review text on a failed save; review round 2
- Review draft sync is a pure helper (format.ts): incoming values are
  ignored while a save is in flight, and a failed save restores the
  previous synced value, so the typed text is kept whichever of the
  rollback and the error callback lands first. Tests cover both orders.
- A failed background refetch keeps showing the cached entry instead of
  the error screen (it unmounted the review box mid-outage).
- History restore joins the entry-patch mutation scope.
- updatePreferences: read-modify-write in a transaction, row locked on
  Postgres.
- Invalidate auth-status after register; out-of-range rating errors speak
  in the user's scale.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 22:14:02 +01:00
Lucas WintherandClaude Opus 5.5 53d9e7c9c9 feat(server): plugins list says which sources sign in with a device code
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 22:13:24 +01:00
Lucas WintherandClaude Opus 5.5 c2e8c83f86 feat(core,server): log feed, titles on changes, work titles on copies
GET log (engagements and listens, keyset over at/kind/id), listChanges
title and intoTitle, listCopies workTitle, all via batched lookups.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 22:11:53 +01:00
Lucas WintherandClaude Opus 5.5 5fa59310f3 fix(web): review round 1: optimistic edits, friendly errors, preferences, Gelasio
- Tags and custom fields update the cached entry optimistically and build
  from the latest cache; one entry's PATCHes run in order (mutation scope).
- The review textarea follows server changes (Restore) unless edited.
- friendlyError() maps validation reasons and 413 to plain copy everywhere.
- Rating scale from me/preferences; /login and /register use auth/status;
  Attachments lists from the API.
- Gelasio vendored (official OFL variable TTFs); minors from the review.
- Docs: DESIGN (AA on orange, faint labels), DECISIONS, TODO, ARCHITECTURE.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 22:03:51 +01:00
Lucas WintherandClaude Opus 5.5 d204775235 feat(server): auth status, attachments list, user preferences
GET auth/status ({ hasUsers }), GET attachments?entity=&entityId= (owner
scoped, 404 for foreign targets) and GET/PATCH me/preferences (ratingScale,
defaultVisibility in users.preferences).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:57:08 +01:00
Lucas Winther d547355e5c Merge branch 'feat/phase1' into task/17 2026-10-30 21:55:33 +01:00
Lucas WintherandClaude Opus 5.5 42fea7d54e merge: task 16 server API
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:40:38 +01:00
Lucas WintherandClaude Opus 5.5 439d138967 feat(web): app shell, library, entry detail
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:37:56 +01:00
Lucas WintherandClaude Opus 5.5 70bc6c36d3 feat(web): theme tokens, self-hosted Inter, HTML entry
DESIGN.md tokens as CSS custom properties. Inter (OFL) is vendored so the
app makes no third-party font requests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:37:56 +01:00
Lucas WintherandClaude Opus 5.5 0b34ede62e docs: token actor, connection deleted_at and CSRF decisions; follow-ups
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:30:59 +01:00
Lucas WintherandClaude Opus 5.5 5f7d54b805 fix(server): token actor, login CSRF, 415, cookie decoding, admin invites, production default
- API writes pass token:<name> for Bearer requests and "user" for sessions.
- X-Requested-With: sttracker on every non-Bearer write, login and register included.
- JSON-body routes reject non-JSON content types with 415.
- A malformed stt_session cookie counts as no cookie (401), not 500.
- DELETE me/tokens/:id is 404 for unknown or foreign ids; admin/invites is admin-only.
- Bun.serve development mode only with NODE_ENV=development or --hot.
- OpenAPI documents multipart fields.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:30:43 +01:00
Lucas WintherandClaude Opus 5.5 aa8e4db070 fix(core): token writes keep their actor in history but are user-owned in field_sources
fieldOwner(actor) maps every non-plugin actor to "user" wherever field_sources is written
(entries, engagements, list ownership from history), so syncs never overwrite a field an API
token edited. authenticateAppToken also returns the token name; revokeAppToken reports whether
it revoked; listSuggestions queries the canonical work id.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:29:38 +01:00
Lucas WintherandClaude Opus 5.5 e3a759d768 fix(core): soft-delete source connections with deleted_at
Migration 0003 adds source_connections.deleted_at. deleteConnection sets it; listing, lookups,
updates, the scheduler and the sync handler ignore deleted connections, so a paused
credential-less connection stays visible and can be resumed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:25:59 +01:00
Lucas WintherandClaude Opus 5.5 0bb871cd19 feat(web): api client and display formatters
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:25:41 +01:00
Lucas WintherandClaude Opus 5.5 0a540208e1 test(server): silence the broken fixture plugin warning
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:19:08 +01:00
Lucas WintherandClaude Opus 5.5 348b04e928 docs: server architecture, API decisions, follow-ups
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:17:18 +01:00
Lucas WintherandClaude Opus 5.5 02b7eed5ec feat(server): boot: migrate, load plugins, worker, scheduler, daily backup, web placeholder
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:16:15 +01:00
Lucas WintherandClaude Opus 5.5 6963e297b0 feat(server): REST API, auth, sources, changes, export
createApp() serves /api/v1 from a typed route table (zod bodies and queries), with
session-cookie and Bearer app-token auth, a CSRF header for cookie writes, login/register
rate limiting, body caps, JSON error mapping, OpenAPI generation and a 404 /ingest mount.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:15:01 +01:00
Lucas WintherandClaude Opus 5.5 7b0a6738dd feat(core): API support helpers: token list, suggestions, attachment links, connection runs, device auth, file-mode sync
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:10:39 +01:00
Lucas WintherandClaude Opus 5.5 7f3d0d5c44 merge: task 9 jobs, scheduler, registry, connections
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:02:52 +01:00
Lucas WintherandClaude Sonnet 5.5 f0d41f23c8 fix(core): reaper skips own in-flight jobs, heartbeat, no attempt burned on shutdown
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 20:57:56 +01:00
Lucas Winther 839d9697b0 merge: feat/phase1 into task/9 2026-10-30 20:53:22 +01:00
Lucas WintherandClaude Sonnet 5.5 6f628c4693 fix(core): job failure semantics, stale-lock reaper, run-scoped context, reload
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 20:52:51 +01:00
Lucas WintherandClaude Opus 5.5 cea4afd0de merge: task 8 sync engine
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:48:48 +01:00
Lucas WintherandClaude Opus 5.5 a003c3eb7d fix(core): sync review round 1: lists, supersede, dedupe conflicts, retries
- dedupe files conflicts for disagreeing fields instead of dropping them
- merged-away records never file removals
- matched work committed before apply so retries reuse it (no draft leaks)
- stale pending events superseded by newer values
- multi-source engagement removal only detaches the source
- list records applied with history-derived ownership
- gone targets resolve as ignored; counts only for committed records

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:43:08 +01:00
Lucas WintherandClaude Opus 5.5 0604c323c3 fix(core): re-point source records when a per-user move merges entries
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:43:08 +01:00
Lucas WintherandClaude Opus 5.5 6efb97eb51 fix(core): batch listen ingestion per 500
One existing-key lookup, one resolve per distinct recording, and one
multi-row insert-or-ignore per batch instead of per-listen queries.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:43:08 +01:00
Lucas WintherandClaude Sonnet 5.5 166b8ecb5e feat(core): jobs, scheduler, plugin registry, connections
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 20:39:17 +01:00
Lucas Winther 207c92bb4d merge: task 8 sync engine 2026-10-30 20:35:37 +01:00
Lucas Winther b2e6e1948b Merge branch 'feat/phase1' into task/8
# Conflicts:
#	docs/ARCHITECTURE.md
#	docs/DECISIONS.md
#	docs/TODO.md
2026-10-30 20:35:25 +01:00
Lucas WintherandClaude Opus 5.5 c8ce107f73 merge: task 15 export, restore, backups
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:26:55 +01:00
Lucas WintherandClaude Opus 5.5 1975e971f9 feat(core): sync engine and changes inbox
runSync snapshots and diffs source records, applies new records and
never-edited fields, files pending changed/conflict/removed/merged
events, de-duplicates plays across sources and ingests listens.
listChanges/resolveChange drive the Changes inbox.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:26:03 +01:00
Lucas WintherandClaude Opus 5.5 37a0b0ba94 feat(core): source record snapshots with stable hash and gzipped versions
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:26:03 +01:00
Lucas WintherandClaude Sonnet 5.5 66b155876f fix(core): atomic restore with aliases, snapshot export, hardened backups
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 20:24:10 +01:00
Lucas WintherandClaude Sonnet 5.5 a047020d54 feat(core): export bundle, restore, backups
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 20:17:25 +01:00
Lucas WintherandClaude Opus 5.5 33631aef3c merge: task 6 user layer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:13:03 +01:00
Lucas WintherandClaude Opus 5.5 c6865b2522 fix(core): drill-down lists descendants under untracked intermediates
listEntries({ parentId: X }) now applies the top-level rule relative to X: this user's
live entries on descendants of X (up to three levels) with no live-entry work strictly
between them and X, so it agrees with childSummary. Restoring an engagement's re-parenting
onto a soft-deleted entry throws ValidationError("entryId", "target entry is deleted").

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:05:12 +01:00
Lucas WintherandClaude Opus 5.5 00fc0180b0 docs: record user-layer decisions, core module map and known gaps
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 20:00:14 +01:00
Lucas WintherandClaude Opus 5.5 7154ca0a61 fix(core): race-safe entry upsert and lossless per-user moves
- upsertEntry inserts with ON CONFLICT DO NOTHING and patches through the update path,
  so a concurrent creator never aborts the caller's transaction on Postgres.
- Undoing a move deletes the forward alias and writes no reverse alias.
- Merging into an existing entry keeps 'user' field_sources on combined fields, carries
  the moved entry's field_sources as-is when reviving a deleted target, concatenates
  differing review/private notes, records a dropped rating in history, and writes history
  for re-parented engagements (restorable).
- Moves re-point this user's copies (with history) and list items to the target work.
- rejectSuggestion requires a pending suggestion; engagements of a deleted entry are not
  editable; restoring an entry does not re-check custom fields against current definitions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:59:54 +01:00
Lucas WintherandClaude Opus 5.5 512e6e9464 fix(core): roll up descendants and surface orphaned entries; SQL-derived title cursor
- childSummary counts the user's live entries up to three levels below the work.
- The default library view (parentId null) lists entries with no ancestor (up to three
  levels) carrying a live entry of this user, so episodes of an untracked show still show.
- The title keyset cursor carries the SQL lower(w.title) value, so SQLite no longer skips
  non-ASCII titles.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:59:09 +01:00
Lucas Winther 4c65939d61 merge: feat/phase1 into task/6 2026-10-30 19:54:33 +01:00
Lucas WintherandClaude Opus 5.5 d98b2b9909 chore: ignore agent worktree directory
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:37:04 +01:00
Lucas WintherandClaude Opus 5.5 a13d9cd476 merge: task 5 works and matching
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:35:57 +01:00
Lucas WintherandClaude Opus 5.5 c7471f7761 feat(core): engagements, lists and copies with history restore
Engagements store partial dates as value + precision and track field_sources;
lists (list/grouping/thread, grouping-only rating/review) keep dense item
order and snapshot items in history; copies store price in cents. All three
restore through restoreVersion.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:35:15 +01:00
Lucas WintherandClaude Opus 5.5 13b81b1cd5 feat(core): entries, user history and match suggestion accept/reject
Entries carry per-field field_sources, write user_history diffs, resolve
aliased/merged works, and support quick add, per-user moves (with alias and
draft tombstone) and keyset-paginated library listing with child rollups.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:34:47 +01:00
Lucas WintherandClaude Opus 5.5 50c115b4c7 fix(core): follow merged tombstones in the position lookup
A merged child still holds its (parent_id, position) slot under the unique
index, so skipping tombstones made every retry collide. Follow the tombstone
to the surviving work instead.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:32:27 +01:00
Lucas WintherandClaude Opus 5.5 437ca143e5 fix(core): race-safe matching, visibility-checked ID matches, child-preserving merges
- migration 0002: unique index on works (parent_id, position)
- matchWork retries the whole transaction on a unique violation (3 attempts)
- addMissingExternalIds uses ON CONFLICT DO NOTHING
- ID matches only return shared works or the user's own; private works cannot carry external ids
- adminMergeWorks requires shared works and repoints non-colliding children
- draft is computed for the aliased work id

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:29:48 +01:00
Lucas WintherandClaude Opus 5.5 60876c2930 merge: task 7 attachments and crypto
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:27:30 +01:00
Lucas WintherandClaude Sonnet 5.5 4acb368c87 fix(core): serialise attachment file mutations, atomic writes, reject svg
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 19:26:16 +01:00