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]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5fa59310f3
commit
c2e8c83f86
@@ -16,7 +16,7 @@ user layer or files a Changes-inbox event.
|
||||
|---|---|---|
|
||||
| `apps/server` | `main.ts` boots (config → `createDb` → `migrateToLatest` → `loadPlugins` → worker + scheduler + daily backup → `Bun.serve`). `app.ts` `createApp(deps)` = `/api/v1` route table + `/ingest/*` mount (404 in Phase 1), testable via `app.fetch`. `http.ts` = `route()`, auth (session cookie / Bearer app token), CSRF header, body caps, rate limit, error mapping. `routes/<area>.ts` one file per area; `openapi.ts` builds `/api/v1/openapi.json` from the table | exists |
|
||||
| `apps/web` | React 19 app served by the server as an HTML import (`index.html` → `src/main.tsx`, `src/theme.css`). wouter routes in `main.tsx` (`/login`, `/register`, `/`, `/entries/:id`; other sections are placeholders until Task 18); `api.ts` = fetch client (`request`, `ApiError`, cookie auth, CSRF header, mirrored wire types); `format.ts` = pure display helpers and `friendlyError`; `edits.ts` = next-value builders for entry edits; `hooks.ts` = TanStack Query hooks incl. optimistic `useEntryPatch`; `settings.ts` = preferences; `components/` (shell, detail boxes, history drawer, rating control) and `routes/`. Never imports `packages/*` at runtime | exists |
|
||||
| `packages/core` | domain services, one module per area (import by path, no barrel): `auth`, `crypto`, `works`, `matching`, `entries` (entries, library listing, quick add, per-user moves, match suggestions), `engagements`, `history` (`user_history`, restore), `lists`, `copies`, `custom-fields`, `attachments`, `preferences` (per-user rating scale and default visibility), `export` (per-user bundle, CSV, restore), `backup` (scheduled bundles + SQLite `VACUUM INTO`), `sync/` (the change engine: `snapshot` hashing and versions, `apply` per-record diff and apply, `run` = `runSync`, `changes` = Changes inbox `listChanges`/`resolveChange`, `listens` = `ingestListens`), `jobs` (queue, worker, scheduler), `registry` (`loadPlugins`), `connections` (CRUD, plugin context, `syncHandler`) | exists (partial) |
|
||||
| `packages/core` | domain services, one module per area (import by path, no barrel): `auth`, `crypto`, `works`, `matching`, `entries` (entries, library listing, quick add, per-user moves, match suggestions), `engagements`, `history` (`user_history`, restore), `lists`, `copies`, `custom-fields`, `attachments`, `preferences` (per-user rating scale and default visibility), `log` (the Log feed: engagements and listens, keyset-paged), `titles` (batched override-aware title lookups for list views), `export` (per-user bundle, CSV, restore), `backup` (scheduled bundles + SQLite `VACUUM INTO`), `sync/` (the change engine: `snapshot` hashing and versions, `apply` per-record diff and apply, `run` = `runSync`, `changes` = Changes inbox `listChanges`/`resolveChange`, `listens` = `ingestListens`), `jobs` (queue, worker, scheduler), `registry` (`loadPlugins`), `connections` (CRUD, plugin context, `syncHandler`) | exists (partial) |
|
||||
| `packages/db` | Kysely over one Bun.SQL dialect (SQLite + Postgres), ids, JSON helpers, migrations, `forEachDialect` test harness | exists (schema pending) |
|
||||
| `packages/plugin-sdk` | plugin contract, `SourceRecord`, http/rate-limit helpers, fixture test harness | planned |
|
||||
| `plugins/<id>` | one source integration each (hardcover, trakt, mangabaka, listenbrainz, lastfm, csv) | planned |
|
||||
|
||||
@@ -4,6 +4,22 @@ Append-only. Never edit or delete an entry. Supersede it with a new one that lin
|
||||
One entry per decision a future maintainer would otherwise have to reverse-engineer.
|
||||
No index: `grep -n '^## ' docs/DECISIONS.md` lists every entry (date, title, line number).
|
||||
|
||||
## 2026-10-30: Log feed, titles on Changes, work titles on copies (Task 18 API additions)
|
||||
**Status:** accepted
|
||||
**Context:** Task 18's Log and Changes screens had nothing to read from (controller ruling).
|
||||
**Decision:**
|
||||
- **`GET log?cursor=&limit=`** (any:read) → `{ items, nextCursor }` from `packages/core/src/log.ts` `listLog`: the user's
|
||||
live engagements on live entries plus their listens, newest first. Each item's `at` is a listen's `listened_at`, or an
|
||||
engagement's finish date, else start date, else `created_at`. Partial dates sort as strings, never through `Date`.
|
||||
Keyset cursor over (`at`, `kind`, `id`) all descending, base64url JSON; default 50, max 200. Two queries (one per
|
||||
kind, each `limit + 1` past the cursor) are merged in code, so no dialect-specific UNION is needed.
|
||||
- **`listChanges` items carry `title`** (an entry's title with the user's override; an engagement resolves to its entry; a
|
||||
list to its name; null when gone) **and, on merged events, `intoTitle`**: the title of the entry that the same
|
||||
connection's `intoKey` record maps to, else null. Titles come from a fixed number of batched queries
|
||||
(`packages/core/src/titles.ts`), not one per event.
|
||||
- **`listCopies` items carry `workTitle`** (override-aware, null while unmatched) so Owned needs no per-row work lookups.
|
||||
The export bundle is unchanged.
|
||||
|
||||
## 2026-10-30: Web app (Task 17): preferences, first run, optimistic edits, AA on orange, self-hosted fonts
|
||||
**Status:** accepted
|
||||
**Context:** Task 17 and its fix round 1 (controller rulings).
|
||||
|
||||
Reference in New Issue
Block a user