- 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]>
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]>
- 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]>
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]>
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]>
- 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]>
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]>
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]>
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]>
- 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]>
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]>
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]>
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]>
- 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]>
- 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]>
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]>
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]>