Add categories for motifs and entries #6

Closed
opened 2026-06-20 01:12:50 +00:00 by rbrooks · 3 comments
Owner

It would be very handy to have categories for motifs and entries to make them easier to navigate.

It would be very handy to have categories for motifs and entries to make them easier to navigate.
rbrooks self-assigned this 2026-06-20 01:12:50 +00:00
Author
Owner

Progress — building in 3 slices

Design (decided):

  • Many categories per item (junction tables, like tags), one shared set used by both entries and motifs.
  • Project-scoped (project_id NOT NULL), distinct from tags (which are user-scoped + freeform). Tags label; categories file.
  • Flat for now (a parent_id hierarchy can come later). User-created, with an optional #rrggbb color (also sets up the v6.2 #7 charts).

Slice 1 — data model + CRUD API — DONE (merged in #50)

  • Migration 040 (api/src/db/migrations/040_categories.sql): categories (UNIQUE(project_id, name), optional color) + entry_categories / motif_categories junction tables (cascade both sides).
  • api/src/routes/categories.ts: GET /api/categories (active-project scoped, returns entry_count/motif_count), POST, PATCH (rename/recolor), DELETE. Hex color normalized (invalid → null), 409 on dup name, project-access checks on mutate. Mounted in api/src/app.ts at /api/categories.
  • Tests: api/src/test/integration/categories.test.ts (8). categories added to clearEntryData in helpers.ts. Also fixed admin.test.ts to assert getInstalledVersion() instead of a hard-coded version.

Slice 2 — assign categories on entries/motifs + display

  • API: extend entry & motif create/update to accept category_ids: string[] (replace-set into the junction tables, inside the existing write transaction); include categories (id/name/color) in entry & motif read payloads.
  • UI: a category multi-select on app/src/pages/Entries/EntryForm.tsx and the motif form; category badges (colored) on EntryCard, EntryDetailPage, motif list/detail. New GET /api/categories already provides the options list.

Slice 3 — filter + management UI

  • Filter entry list (and motif list / timeline) by category_id — extend the existing list query params (GET /api/entries already takes filters) and the filter panel UI.
  • Category-management section in Settings (create/rename/recolor/delete, showing usage counts) — mirror the existing TagManagement component.

Resume notes (for a fresh machine)

  • Branch off main; verify on the dev server via ssh docker-test (Node at $HOME/.nvm/versions/node/v24.14.1/bin). Test DB applies migrations on run; if the API suite shows cascading 429/"Too Many Requests" failures, docker exec tealeaves-redis-test-1 redis-cli FLUSHALL (rate-limit exhaustion, not a real failure).
  • Each slice = its own PR. CHANGELOG entry deferred until the feature is user-visible (end of slice 3), rolling into the eventual v6.2.0 release notes.
  • main is green and deployed at v6.1.0; migration 040 is in main but not yet on the deployed image (no redeploy needed until the UI slices land).

🤖 Tracked by Claude Code

## Progress — building in 3 slices **Design (decided):** - **Many categories per item** (junction tables, like tags), **one shared set** used by both entries and motifs. - **Project-scoped** (`project_id NOT NULL`), distinct from tags (which are user-scoped + freeform). Tags label; categories file. - **Flat** for now (a `parent_id` hierarchy can come later). User-created, with an optional `#rrggbb` color (also sets up the v6.2 #7 charts). ### ✅ Slice 1 — data model + CRUD API — **DONE** (merged in #50) - **Migration 040** (`api/src/db/migrations/040_categories.sql`): `categories` (`UNIQUE(project_id, name)`, optional `color`) + `entry_categories` / `motif_categories` junction tables (cascade both sides). - **`api/src/routes/categories.ts`**: `GET /api/categories` (active-project scoped, returns `entry_count`/`motif_count`), `POST`, `PATCH` (rename/recolor), `DELETE`. Hex color normalized (invalid → null), `409` on dup name, project-access checks on mutate. Mounted in `api/src/app.ts` at `/api/categories`. - Tests: `api/src/test/integration/categories.test.ts` (8). `categories` added to `clearEntryData` in `helpers.ts`. Also fixed `admin.test.ts` to assert `getInstalledVersion()` instead of a hard-coded version. ### ⬜ Slice 2 — assign categories on entries/motifs + display - API: extend entry & motif create/update to accept `category_ids: string[]` (replace-set into the junction tables, inside the existing write transaction); include `categories` (id/name/color) in entry & motif read payloads. - UI: a category multi-select on `app/src/pages/Entries/EntryForm.tsx` and the motif form; category **badges** (colored) on `EntryCard`, `EntryDetailPage`, motif list/detail. New `GET /api/categories` already provides the options list. ### ⬜ Slice 3 — filter + management UI - Filter entry list (and motif list / timeline) by `category_id` — extend the existing list query params (`GET /api/entries` already takes filters) and the filter panel UI. - Category-management section in **Settings** (create/rename/recolor/delete, showing usage counts) — mirror the existing `TagManagement` component. ### Resume notes (for a fresh machine) - Branch off `main`; verify on the dev server via `ssh docker-test` (Node at `$HOME/.nvm/versions/node/v24.14.1/bin`). Test DB applies migrations on run; if the API suite shows cascading 429/"Too Many Requests" failures, `docker exec tealeaves-redis-test-1 redis-cli FLUSHALL` (rate-limit exhaustion, not a real failure). - Each slice = its own PR. CHANGELOG entry deferred until the feature is user-visible (end of slice 3), rolling into the eventual v6.2.0 release notes. - `main` is green and deployed at **v6.1.0**; migration 040 is in `main` but not yet on the deployed image (no redeploy needed until the UI slices land). 🤖 Tracked by Claude Code
Author
Owner

Slice 2 — assign categories on entries/motifs + display — DONE (merged in #51)

  • APIlib/categoryAssign.ts: applyCategories (project-contained replace-set into entry_categories / motif_categories, audited by category name, no-ops on absent field), fetchCategoriesFor, and categoriesJsonSql. Entry & motif create/update accept category_ids: string[]; reads (entry list/detail, motif list/detail, entries-within-motif) include categories (id/name/color) via a correlated subquery (no JOIN → no cartesian blow-up of the tag agg). Only ids in the owner's own project are applied (IDOR-safe); unknown/malformed ids are dropped.
  • UICategoryBadge (contrast-aware colored chip) + CategorySelect (multi-select with create-on-the-fly via POST /api/categories, mirroring the tag input). Wired into EntryForm and the motif edit form; badges on EntryCard, EntryDetailPage, MotifListPage, MotifDetailPage (incl. entries-within). categories added to the Entry/MotifSummary/MotifEntry types; public graph adapters + test fixtures updated.
  • Tests — 11 new integration tests (categoryAssign.test.ts). Verified on the dev server: 168 api / 63 app passing, both production builds green. CI green on #51.
  • Deployed — pulled to the dev server and rebuilt; migration 040 applied (✓), /health ok, /api/categories gated (401 unauth).

Slice 3 — filter + management UI (remaining)

  • Filter entry list / motif list / timeline by category_id (extend existing list query params + filter panel).
  • Category-management section in Settings (create/rename/recolor/delete with usage counts) — mirror TagManagement.
  • CHANGELOG entry lands here, rolling into the v6.2.0 release notes.

🤖 Tracked by Claude Code

### ✅ Slice 2 — assign categories on entries/motifs + display — **DONE** (merged in #51) - **API** — `lib/categoryAssign.ts`: `applyCategories` (project-contained replace-set into `entry_categories` / `motif_categories`, audited by category name, no-ops on absent field), `fetchCategoriesFor`, and `categoriesJsonSql`. Entry & motif create/update accept `category_ids: string[]`; reads (entry list/detail, motif list/detail, entries-within-motif) include `categories` (id/name/color) via a correlated subquery (no JOIN → no cartesian blow-up of the tag agg). Only ids in the owner's own project are applied (IDOR-safe); unknown/malformed ids are dropped. - **UI** — `CategoryBadge` (contrast-aware colored chip) + `CategorySelect` (multi-select with **create-on-the-fly** via `POST /api/categories`, mirroring the tag input). Wired into `EntryForm` and the motif edit form; badges on `EntryCard`, `EntryDetailPage`, `MotifListPage`, `MotifDetailPage` (incl. entries-within). `categories` added to the `Entry`/`MotifSummary`/`MotifEntry` types; public graph adapters + test fixtures updated. - **Tests** — 11 new integration tests (`categoryAssign.test.ts`). Verified on the dev server: **168 api / 63 app** passing, both production builds green. CI green on #51. - **Deployed** — pulled to the dev server and rebuilt; migration 040 applied (✓), `/health` ok, `/api/categories` gated (401 unauth). ### ⬜ Slice 3 — filter + management UI (remaining) - Filter entry list / motif list / timeline by `category_id` (extend existing list query params + filter panel). - Category-management section in **Settings** (create/rename/recolor/delete with usage counts) — mirror `TagManagement`. - CHANGELOG entry lands here, rolling into the v6.2.0 release notes. 🤖 Tracked by Claude Code
Author
Owner

Slice 3 — filter + management UI — DONE (merged in #53). Feature complete.

  • Filter APIGET /api/entries and GET /api/motifs accept a UUID-validated, parameterized category_id.
  • Filter UICategoryFilterSelect on the entry list, motif list, and timeline filter panel (timeline URL-synced); hidden when a project has no categories.
  • Management — Settings → Categories: create/recolor/rename/delete with live usage counts.

All three slices merged, CHANGELOG updated for v6.2.0, deployed to the dev server (health ok, no errors). Closing — categories for entries & motifs is done. 🎉

### ✅ Slice 3 — filter + management UI — **DONE** (merged in #53). Feature complete. - **Filter API** — `GET /api/entries` and `GET /api/motifs` accept a UUID-validated, parameterized `category_id`. - **Filter UI** — `CategoryFilterSelect` on the entry list, motif list, and timeline filter panel (timeline URL-synced); hidden when a project has no categories. - **Management** — Settings → Categories: create/recolor/rename/delete with live usage counts. All three slices merged, CHANGELOG updated for v6.2.0, deployed to the dev server (health ok, no errors). Closing — categories for entries & motifs is done. 🎉
rbrooks referenced this issue from a commit 2026-06-29 17:48:48 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rbrooks/TeaLeaves#6
No description provided.