Skip to content

Release 0.3.1 — collections as first-class DB entity#20

Open
tylernash01 wants to merge 15 commits intomasterfrom
master-0.3.1
Open

Release 0.3.1 — collections as first-class DB entity#20
tylernash01 wants to merge 15 commits intomasterfrom
master-0.3.1

Conversation

@tylernash01
Copy link
Copy Markdown
Collaborator

Summary

  • Promotes collections from localStorage-only strings to a first-class DB entity with full CRUD API
  • Empty collections now appear in the CLI picker (skillnote-pick) at session start
  • Auto-migrates existing localStorage-meta entries to the API on first load of /collections

Root cause

The CLI picker fetches /v1/collections, which was implemented as SELECT unnest(collections) FROM skills ... GROUP BY name — a collection only surfaced once ≥1 skill referenced it. NewCollectionModal wrote only to browser localStorage, so newly-created empty collections were invisible to the CLI picker. Adding one skill would suddenly make the collection appear.

What changed

Backend (9 commits):

  • Migration 0011_collections_table: new collections table keyed by name, with description + timestamps
  • Collection SQLAlchemy model + CollectionCreate/Update/Detail/ListItem Pydantic schemas
  • validate_collection_name — free-form text (to preserve "lp assessment"), max 128 chars, non-empty, no newlines/XML
  • GET /v1/collections now UNIONs skill-derived names with empty rows from the table; response gains an additive description field (CLI keeps working)
  • POST /v1/collections (409 on duplicate), PUT /v1/collections/{name} (description-only; no rename in 0.3.1), DELETE /v1/collections/{name} (409 if any skill still references it)
  • Fix: replaced fragile db.refresh(col) after commit with explicit re-query — db.refresh fails intermittently under connection-pool reuse (same latent pattern exists in skills.py:318, out of scope)

Frontend (4 commits):

  • src/lib/api/collections.ts — typed API client
  • NewCollectionModal POSTs to the API, falling back to localStorage + error toast on network failure
  • /collections page reads from the API and auto-migrates stale skillnote:collections-meta entries (POST missing ones, then clear on success; retry next page load on partial failure)
  • Inline CollectionPicker (skill editor) fetches from the API with localStorage fallback

Test plan

  • pytest tests/unit/test_collection_validator.py — 8/8 passing
  • pytest tests/integration/test_collections_api.py — 11/11 passing (covers shape, POST + dup + validation + whitespace, PUT + 404, DELETE + 404 + 409 skill-ref)
  • 50-cycle POST→PUT→DELETE stress test on a long-running container — 50/50 passing after the db.refresh fix
  • API smoke: POST empty collection → appears in GET with count: 0, description → CLI picker's fetch_collections() sees it → DELETE returns 204
  • Edge-case matrix: validation 422s (empty, whitespace, XML, newline), duplicate 409, 404s, skill-ref 409, GET shape — all green
  • npx tsc --noEmit — no new errors in `src/` (pre-existing `cli/` errors unrelated)
  • UI smoke: create empty collection in web UI on a deployed build → appears in CLI picker at next `claude` session start (requires rebuilding the web container; recommended as a deploy step)

Notes

  • No auth changes (still disabled per migration 0004)
  • No rename support in 0.3.1 (YAGNI — PUT updates description only)
  • `deriveCollections()` in `src/lib/derived.ts` preserved for now; `deriveCollectionsFromApi()` added alongside

🤖 Generated with Claude Code

tylernash01 and others added 15 commits April 15, 2026 22:38
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds CollectionListItem, CollectionCreate, CollectionUpdate, and
CollectionDetail schemas, wiring the collection_validator into
field_validator for name and description constraints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the list_collections endpoint to UNION skill-derived collection names
with explicitly-created empty collections from the collections table, and adds
a description field to every response item.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds POST /v1/collections to create empty named collections with
duplicate detection (409 COLLECTION_EXISTS) and whitespace trimming
via Pydantic validator. Fixes error key in duplicate test (detail→error)
to match codebase's global exception handler format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Returns 409 COLLECTION_IN_USE if any skill still references the collection,
404 COLLECTION_NOT_FOUND for missing collections, 204 on success.
Includes three new integration tests covering all three branches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace deriveCollections(skills) with deriveCollectionsFromApi(skills, apiCollections)
on the /collections page; on first load, migrate any lingering localStorage-meta entries
to the API and remove them on success.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace synchronous getAllCollections with async version that fetches
from the API (offline fallback: localStorage meta). Creating a new
collection via the __create__ sentinel now POSTs to the API first,
falling back to persistCollection when offline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
db.refresh(col) after commit was failing intermittently with
'Could not refresh instance' once the API container had been running for
a while — a stale-session/connection-pool interaction. Explicit re-query
is deterministic and idiomatic. Verified with a 50-cycle POST/PUT/DELETE
stress test on a long-running container.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant