Commit Graph

265 Commits

Author SHA1 Message Date
Ben Stull bc60ad97be fix(deps): Patchwatch security floors for tqdm/idna/vitest (v0.55.1)
Clears Patchwatch advisories #36–#40 on the OHM deployment (affecting
ohm-rfc-app + rfc-app-ppe) by forcing the vulnerable dependencies to
patched versions:

- backend/requirements.txt: floor-pin tqdm>=4.66.3 (#37/#39/#40, resolves
  4.68.2) and idna>=3.15 (#38, resolves 3.18) — both arrive transitively
  with no lockfile, so a floor-pin is the robust fix.
- frontend/package.json: vitest ^3.0.0 -> ^4.1.0 (#36, resolves 4.1.8;
  dev/test-only, GHSA-5xrq-8626-4rwp UI-server file read/exec). Lockfile
  regenerated.

No behavior change; backend (685), frontend (66), and localhost E2E (5)
suites pass. Issues are closed after the change is deployed and re-scanned,
not on merge. Patchwatch Phase 2 remediation, session ohm/0095.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:49:39 -07:00
Ben Stull c3efe893c3 Merge pull request 'fix(§22/registry): prune projects absent from the registry on reconcile (v0.55.0)' (#51) from fix-provision-reconcile into main v0.55.0 2026-06-09 12:29:56 +00:00
Ben Stull 9275348c45 fix(§22/registry): prune projects absent from the registry on reconcile (v0.55.0)
The registry mirror was additive-only — it upserted the projects/collections a
deployment's projects.yaml declares but never removed ones it had dropped.
Re-pinning to a different registry stranded the old project's collections + its
cached entries as dead rows that, at scale, starved writes (PPE accumulated
~1.2k orphaned entry rows and had to be reset by hand).

`registry.refresh_registry(prune=True)` now calls `projects.prune_absent_projects`,
deleting a removed project with its collections and every project-scoped row
(the 7 project_id-keyed tables + 13 collection_id-keyed entry-corpus tables +
the non-keyed thread_messages descendant) in one FK-off transaction with a
`PRAGMA foreign_key_check` backstop that rolls back rather than leave a dangling
ref — mirroring restamp_default_project / reconcile_default_collection_id.

Scoped for safety: whole-project granularity (a present project is untouched);
the default project is never pruned; prune runs ONLY on the full-reconcile
triggers (startup + the periodic sweep), never on incidental refreshes (collection
create, webhook), so an in-app refresh can't wipe a project. Reached only after a
successful parse of a non-empty registry (parse_registry rejects empty; the read
raises on transport error), so a transient read can't trigger a wipe.

Second of the two §9-surfaced framework fragilities. No migration. backend 685
green (+5: prune removes-all/no-op/never-default/empty-rejected/incidental-no-prune
with FK-integrity backstop).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 05:28:50 -07:00
Ben Stull 242831a4a0 Merge pull request 'fix(§6/auth): reconcile by email in provision_user so OTC→OAuth doesn't 500 (v0.54.1)' (#50) from fix-provision-reconcile into main v0.54.1 2026-06-09 12:01:49 +00:00
Ben Stull f0533dc073 fix(§6/auth): reconcile by email in provision_user so OTC→OAuth doesn't 500 (v0.54.1)
The Gitea-OAuth callback's `auth.provision_user` matched an existing `users` row
only by `gitea_id`. A human who signed in first via OTC owns an email-only row
(`gitea_id` NULL); a later Gitea-OAuth sign-in with the same email missed that
row and INSERTed a new one, colliding on the `idx_users_email` case-insensitive
unique index → IntegrityError → 500 callback.

Fix (mirror of `otc.provision_or_link_user`): when no `gitea_id` row exists,
reconcile by email and link the OAuth identity (gitea_id/gitea_login/profile)
onto the existing email row. Owner-zero (§6.1) bootstrap applied on link
(matching the fresh-insert path); `permission_state` preserved so linking never
changes admission status as a side effect.

One §9-surfaced framework fragility of two. backend 680 green (+3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 05:01:02 -07:00
Ben Stull f7b93d797c Merge pull request 'feat(§8.12/§8.3): main-view Ask cuts an edit branch and runs the question there (v0.54.0)' (#49) from fix-main-view-ask into main v0.54.0 2026-06-09 11:20:30 +00:00
Ben Stull 24596842ea feat(§8.12/§8.3): main-view Ask cuts an edit branch and runs the question there (v0.54.0)
The AI "Ask" affordance (selection tooltip + prompt bar) had no response
surface on an entry's canonical `main` view: RFCView renders the human-
discussion panel there, not the chat panel, so a chat turn posted from the
reading view had nowhere to render — asking-while-reading silently did
nothing. AI chat is an editing activity (a turn can emit <change> proposals)
and only runs on an edit branch.

Option B: when Ask is invoked from main, transparently cut an edit branch via
the same dispatch as Start Contributing (promote-to-branch for active,
start-edit-branch for super-draft; idempotent — reuse an existing edit
branch), navigate onto it so the chat panel mounts, and run the question
(text + selected quote) as the branch's first chat turn once its view and
main_thread_id resolve. The turn fires from the message-load effect's
continuation (not a parallel effect) so a late message load can't clobber the
optimistic turn; a live ref keeps the latest submitChatTurn closure in reach.

Degrades gracefully: signed-out → login; a viewer who can't contribute has the
cut rejected server-side and the error surfaced (no spurious branch); asking
from a branch already, and Flag on main (human discussion thread), unchanged.

Frontend-only; the branch chat endpoints already work on a branch. SPEC §8.3
records the behavior; new RFCView unit tests + an e2e spec cover the flow.
backend 677 / frontend 66 / e2e 5 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 04:19:20 -07:00
Ben Stull ff88be2e91 Merge pull request 'fix(§22/G-15): three-tier-aware branch/edit/body subsystem (v0.53.0)' (#47) from fix-g15-three-tier-write-paths into main v0.53.0 2026-06-09 05:40:59 +00:00
Ben Stull 4e7410f90b fix(§22/G-15): make the branch/edit/body subsystem three-tier aware (v0.53.0)
§22 migrated the READ/catalog path to the three-tier (project/collection)
model but the WRITE/branch/body subsystem still hardcoded the default
project's default collection — resolving every meta-resident entry to the
default content repo at rfcs/<slug>.md, ignoring the entry's project (its own
content repo) and collection (a <subfolder>/rfcs/ prefix). An entry outside
the default collection rendered a blank canonical body and every edit/PR/
body-write path hit the wrong file.

- New single resolver: projects.content_repo_for_collection() +
  projects.entry_location(config, cid, slug) -> (org, repo, md_path)
  (collection -> project -> content_repo + subfolder; falls back to the
  default repo for a legacy/unknown collection).
- Every entry write/read path resolves via it: api_branches (body GET + all
  branch write paths), api_prs (pr-draft/open/merge/withdraw/review/
  resolution-branch), api_graduation (graduate/claim/retire/unretire +
  orchestrator + state-flip), api.py mark-reviewed + idea-PR merge/decline/
  withdraw + proposal preview, api_metadata. bot.open_metadata_pr and
  bot.mark_entry_reviewed gained a file_path param. refresh_meta_branches,
  the webhook corpus-refresh dispatch, and the hygiene branch-delete now
  span every project's content repo, not just the default.
- New additive collection-scoped body-read routes:
  GET /api/projects/{pid}/collections/{cid}/rfcs/{slug}/main and
  .../branches/{branch} disambiguate a slug across collections (G-5) and read
  the entry's own repo. Slug-only routes kept (now collection-aware via the
  cached row). Frontend getRFCMain/getBranch take optional pid+cid; RFCView
  threads them (mirrors the v0.52.1 entry-detail fix).

No migration, no config change. Existing default-collection entries
unaffected. Tests: backend resolver + collection-scoped branch/body + graduate-
in-subfolder write path; frontend api unit. backend 677 / frontend 60 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 22:39:46 -07:00
Ben Stull afa8d26378 Merge pull request 'fix(routing): 404 page for unmatched routes (v0.52.3)' (#45) from fix-404-unmatched-routes into main v0.52.3 2026-06-09 03:14:39 +00:00
Ben Stull 948ee88160 fix(routing): render a 404 page for unmatched routes (v0.52.3)
The top-level catch-all silently redirected unknown paths to "/", and the
nested /admin/*, /p/:projectId/*, and /docs/* route groups had no catch-all
(invalid subpaths rendered a blank pane). Add a shared NotFound component and
wire it into all four route groups so a bad/typo'd URL shows a clear 404 with
a link home. Client-side 404 UI (SPA still serves HTTP 200).

Patch 0.52.2 → 0.52.3; CHANGELOG updated. Caught by the operator on PPE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:14:25 -07:00
Ben Stull 7bcf784d06 Merge pull request 'fix(admin): absolute sidebar nav links (v0.52.2)' (#44) from fix-admin-nav-relative-links into main v0.52.2 2026-06-09 03:08:25 +00:00
Ben Stull 8e207a60e6 fix(admin): absolute sidebar nav links so /admin URLs don't accumulate (v0.52.2)
The /admin left-rail NavLinks used relative targets (to="users", etc.). Under
the /admin/* nested route a relative link resolves against the current URL, so
each click appended a segment — Users→Allowlist→Graduation yielded
/admin/users/allowlist/graduation instead of /admin/graduation. Use absolute
targets (/admin/<tab>) + `end` for exact active matching.

Patch 0.52.1 → 0.52.2; CHANGELOG updated. Caught by the operator on PPE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:07:44 -07:00
Ben Stull fbaa975b5c Merge pull request 'fix(§22.4a): scope RFCView entry-detail fetch to its collection (v0.52.1)' (#43) from fix-collection-scoped-entry-detail into main v0.52.1 2026-06-08 13:45:42 +00:00
Ben Stull ba37da927a fix(§22.4a): scope RFCView entry-detail fetch to its collection (v0.52.1)
The §9 deployed-environment E2E harness (0.52.0), run against a PPE host
with per-collection-isolated content, surfaced a latent multi-collection
bug: RFCView computed the collection id from the route but called
getRFC(pid, slug) without it, so a named-collection entry was always
fetched via the project default-collection route — which 404s for an entry
that exists only in a named collection ("Error: Not found"; metadata panel
absent). Local/Tier-1 stacks masked it (same slug also reachable via the
default collection). Thread cid through all three getRFC call sites; re-run
the load effect on collection change.

Harness/test-infra (not in the deployed artifact):
- e2e: pre-record cookie consent via addInitScript (lib/fixtures.js) so the
  bottom-fixed consent banner can't intercept catalog row-select clicks on
  the slower deployed edge.
- testing/seed-ppe.sh: fail loudly on any non-2xx Gitea response (a
  swallowed 403 org-repo create had reached the deploy as a 502).
- testing/ppe-deploy-and-test.sh: seed via the Keychain admin token
  (write:organization needed to create the PPE repos); store the E2E secret
  newline-free; read EXPECT_VERSION from VERSION.

Patch bump 0.52.0 → 0.52.1; CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 06:45:01 -07:00
Ben Stull 9c8035bdbd test(e2e): one-shot PPE deploy+E2E resume script
Runs the whole §9 PPE stage non-interactively after the operator's gcloud
reauth: bot-token-seed the PPE repos -> ensure E2E secret -> deploy ->
wait for health=0.52.0 + bdd-collection sync -> run metadata.spec.js
against the deployed host. Idempotent; secrets fetched from SM, never
echoed. Test/ops infra (not in the deployed artifact).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 00:01:19 -07:00
Ben Stull fd123da6a3 test(e2e): harden harness for deployed runs (retries, serialize, banner)
Test-infra only (e2e/ is not in the deployed artifact). Refines the
v0.52.0 deployed-env harness:
- retries:2 + on-first-retry trace now meaningful (was dead: retries
  defaulted to 0); de-risks timing flakes over the public edge.
- workers:1 — the metadata specs run in order and write real commits;
  parallel workers would race on shared state and concurrent bot pushes.
- deployed timeouts bumped (60s/20s) when E2E_TEST_AUTH_SECRET is set.
- dismissCookies forcibly removes any lingering consent-banner node so it
  can't intercept catalog-footer checkbox clicks (the recurring flake).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 23:56:32 -07:00
Ben Stull 96e2214213 Merge pull request 'v0.52.0 — deployed-environment E2E harness + gated test-auth' (#42) from ppe-e2e-deployed-harness into main v0.52.0 2026-06-08 06:36:18 +00:00
Ben Stull 83eafe72ee feat(e2e): deployed-environment E2E harness + gated test-auth (v0.52.0)
The §9 pipeline's PPE+E2E stage was unreachable: e2e/metadata.spec.js was
bound to Tier-1-only scaffolding (docker-seeded faceted collection,
SQLite-injected owner, Mailpit OTC sink). This makes the same suite run
against a deployed host.

- backend: POST /auth/test/login — fail-closed, secret-gated, single-
  identity owner test-login (404 unless E2E_TEST_AUTH_SECRET +
  E2E_TEST_AUTH_EMAIL both set; constant-time compare; loud startup warn).
  6 vertical tests; backend 665 green. Documented in backend/.env.example.
- e2e/lib/auth.js: branch on E2E_TEST_AUTH_SECRET (deployed test-login vs
  local Mailpit OTC); OWNER_EMAIL from E2E_OWNER_EMAIL. Spec unchanged so
  the localhost Tier-1 path keeps working.
- testing/seed-ppe.sh: seed a dedicated, prod-untouching PPE registry +
  content repo (faceted bdd collection) — real OHM content never touched.
- docs/design/2026-06-07-deployed-env-e2e-harness.md; CHANGELOG; VERSION
  + frontend/package.json -> 0.52.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 23:35:53 -07:00
Ben Stull dd9ceff69e Merge pull request 'v0.51.1 — collection-id divergence fix + faceted-catalog scoping fix + metadata E2E' (#41) from fix-mig029-collection-divergence into main 2026-06-08 05:30:47 +00:00
Ben Stull 52f465b4dd release: v0.51.1 — collection-id divergence + faceted-catalog scoping fixes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:30:09 -07:00
Ben Stull 2fc7029bd9 test(e2e): §22-current Tier-1 harness + metadata E2E (SLICE-3/4/5)
Modernize the Tier-1 stack to the three-tier app and add browser coverage for
the §22.4a metadata UI, closing the E2E gap deferred across SLICE-3/4/5:
- seed-gitea.sh: create a REGISTRY_REPO with projects.yaml + a faceted named
  collection (.collection.yaml fields: priority enum + tags) seeded with three
  metadata-bearing entries; register content+registry webhooks; self-guarding
  (skip if a prior token still works) so a dependency-triggered re-run can't
  remint and invalidate the backend's token.
- .env.tier1: REGISTRY_REPO/DEFAULT_PROJECT_ID; disable OTC cooldown + lift the
  per-IP auth limiter for the single-IP test runner.
- docker-compose: pin backend image; backend-seed inserts a granted owner the
  OTC path can sign in as (write paths need contributor+).
- Makefile: two-phase tier1-up (seed to completion, then create backend so it
  reads the populated token env); robust down; e2e-fresh = down+up+e2e (the
  canonical run, since the edit/bulk specs mutate the seeded corpus).
- metadata.spec.js: SLICE-3 faceted filter (anon), SLICE-4 edit panel (owner),
  SLICE-5 bulk bar (owner). 4 passed against a fresh stack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:29:11 -07:00
Ben Stull 9e1b7ce34f feat(ratelimit): env-tunable per-IP auth limiter budgets
Add RATELIMIT_OTC_REQUEST_MAX / RATELIMIT_VERIFY_MAX / RATELIMIT_CHECK_MAX
(default to the existing secure values; non-positive/unparseable → default).
Lets a test/PPE stack that drives the auth endpoints repeatedly from one IP
lift the budget; production leaves them unset. Mirrors the existing
OTC_REQUEST_COOLDOWN_SECONDS knob.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:28:57 -07:00
Ben Stull cbaba76345 fix(§22): catalog scopes to the named collection in the URL (E2E-caught)
useCollectionId read useParams().collectionId, but the Catalog renders at
/p/:projectId/* — outside the c/:collectionId route — so it always fell back
to the default collection. The faceted filter (SLICE-3) and bulk action bar
(SLICE-5) therefore never scoped to a named (fields-bearing) collection in the
deployed app; the Catalog unit test had mocked useCollectionId, hiding it.
Resolve the /c/<id>/ segment from the pathname when the route param isn't in
scope. Caught by the new Playwright metadata suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:28:57 -07:00
Ben Stull 620926b834 fix(§22): heal mig-029 vs registry-mirror collection-id divergence
On a deployment that already had >=2 projects when migration 029 ran, 029
seeds the default project's collection id as the project id (e.g. 'ohm'),
but the registry mirror expects 'default' -> it inserted a duplicate empty
'default' collection, orphaning the entries.

Adds projects.reconcile_default_collection_id() -- the collection-grain twin
of restamp_default_project -- run at startup BEFORE the mirror so it merges
onto the canonical 'default' collection instead of duplicating. Renames the
divergent collection id and cascades collection_id across all keyed tables
(FK-off atomic rename + foreign_key_check). Idempotent; no-op on fresh /
single-project / already-aligned deploys. Seam tests show the duplicate forms
without the fix and merges with it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:54:52 -07:00
Ben Stull 4ac3955056 Merge pull request 'v0.51.0 — SLICE-5: bulk tag/untag metadata (§22.4a PUC-2)' (#35) from slice5-bulk-metadata into main 2026-06-08 04:04:21 +00:00
Ben Stull 7886840362 fix(slice5): validate raw metadata input + prune stale bulk selections
Code-review follow-ups:
- Validate the raw submitted value before apply_values coerces it, in both
  the single-edit and bulk endpoints — a scalar set onto a tags field now
  rejects (422 / rejected) instead of silently char-splitting into a list.
- Catalog prunes bulk selections to the currently-visible (filtered) entries
  after each list fetch, so the bulk bar can't act on rows the user has
  filtered out of view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:03:29 -07:00
Ben Stull 8eee907893 docs(slice5): record SLICE-5 bulk edit shipped in SPEC §22.4a status
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:52:48 -07:00
Ben Stull a2b55f94ce release(slice5): v0.51.0 — bulk tag/untag metadata (§22.4a PUC-2 SLICE-5)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:52:16 -07:00
Ben Stull edbf68909a feat(slice5): catalog multi-select + bulk action bar (§22.4a PUC-2)
Faceted catalog rows are selectable for contributors; a sticky bulk bar
(BulkActionBar) drives set/add/remove gestures from the collection fields
schema, calls bulkEntryMeta (one commit), toasts applied/skipped counts,
and re-fetches. Non-contributors and legacy (no-fields) collections see
no selection (INV-5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:51:48 -07:00
Ben Stull 282706d7ef feat(slice5): POST .../meta/bulk one-commit bulk metadata edit (§22.4a PUC-2)
set/add/remove ops reusing the SLICE-4 sidecar write-through; per-entry
partial-rejection; contributor+ gated (INV-4); validated at the write
boundary. Tests cover one-commit, add/remove, partial reject, authz,
and op/field/empty guards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:48:54 -07:00
Ben Stull eaf69cd05c Merge pull request 'v0.50.0 — SLICE-4: single-entry metadata edit (§22.4a)' (#34) from worktree-metadata-slice4 into main 2026-06-08 02:18:21 +00:00
Ben Stull 0d2fdfacf2 release(slice4): v0.50.0 — single-entry metadata edit + sidecar-aware writes (§22.4a SLICE-4)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:17:25 -07:00
Ben Stull abd17a6cc8 feat(slice4): schema-driven metadata edit panel in RFCView (§22.4a PUC-1 §5.2)
MetadataFieldsPanel renders one control per declared field (enum→select,
tags→chips, text→input), read-only without contribute access, saving changed
values via saveEntryMeta (direct sidecar commit). Wired into the canonical
main view, gated on the collection declaring fields (INV-5). saveEntryMeta
API client added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:14:13 -07:00
Ben Stull 46c957cff5 feat(slice4): Owner-gated collection migrate endpoint (§22.4a PUC-5)
POST .../collections/{cid}/migrate — now safe to ship since all write paths
are sidecar-aware. Idempotent, one commit per collection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:01:18 -07:00
Ben Stull d687a65470 feat(slice4): POST .../meta single-entry edit endpoint + GET RFC meta/can_edit_meta (§22.4a PUC-1)
Direct-commit to the sidecar (D7), contributor+ gated (INV-4), schema-validated
at the write boundary, lazy-migrates a legacy entry, re-ingests. GET RFC now
returns the per-entry meta mapping and a can_edit_meta capability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:00:40 -07:00
Ben Stull aee9b582e5 fix(slice4): make all entry write paths sidecar-aware (§22.4a carried from SLICE-1)
graduate, claim, retire/unretire, _read_meta_entry, mark_entry_reviewed,
body extract/wrap (api_branches + api_prs replay) now dual-read and write
metadata to the sidecar via write_entry_files + bot.commit_entry_files/
open_entry_pr — a migrated body-only .md no longer crashes entry.parse or
re-grows frontmatter; legacy entries lazy-migrate on first metadata write.
Existing tests updated to assert the sidecar (INV-2 clean docs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:57:54 -07:00
Ben Stull 734290f344 feat(slice4): bot.commit_entry_files + open_entry_pr multi-file primitives (§22.4a)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:46:09 -07:00
Ben Stull 49981e2d6e feat(slice4): metadata sidecar git read/write helpers — dual-read + lazy-migrate ops (§22.4a)
apply_values, EntryGitState, read_entry_from_git, write_entry_files, sidecar_path_for.
Plus the SLICE-4 implementation plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:44:14 -07:00
Ben Stull 7ece6d348b Merge pull request 'v0.49.0 — SLICE-3: faceted left-pane filtering (§22.4a)' (#33) from worktree-metadata-slice3-facets into main 2026-06-08 00:44:54 +00:00
Ben Stull 6bb6d654fa release(slice3): v0.49.0 — faceted left-pane filtering (§22.4a SLICE-3) 2026-06-07 17:43:56 -07:00
Ben Stull 276a625997 test(slice3): Catalog faceted-pane component test (§22.4a PUC-3) 2026-06-07 17:41:04 -07:00
Ben Stull ae3afe2f48 feat(slice3): faceted catalog pane gated on collection fields (§22.4a §5.1) 2026-06-07 17:40:29 -07:00
Ben Stull ae083bfcaa feat(slice3): FacetGroups faceted-pane component (§22.4a §5.1) 2026-06-07 17:38:35 -07:00
Ben Stull bcce40d2cb feat(slice3): listRFCs accepts facet selections, returns {items,facets} (§22.4a) 2026-06-07 17:38:16 -07:00
Ben Stull 7b269e11c4 test(slice3): integration tests for faceted list endpoint (§22.4a PUC-3) 2026-06-07 17:37:46 -07:00
Ben Stull 27061c30b0 feat(slice3): collection list endpoint returns facets + honours filters (§22.4a) 2026-06-07 17:35:26 -07:00
Ben Stull 14ea3c0cce feat(slice3): pure facet field-set + filter/count helper (§22.4a) 2026-06-07 17:34:33 -07:00
Ben Stull 644bf35d89 feat(slice3): persist entry metadata to cached_rfcs.meta_json at ingest (§22.4a) 2026-06-07 17:33:47 -07:00
Ben Stull 3636fa5afd feat(slice3): migration 034 — cached_rfcs.meta_json for facet values (§22.4a) 2026-06-07 17:33:11 -07:00