Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions state.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

> Read this first. Snapshots the active branch, what just shipped, what's in flight, what's queued, and where the project currently sits in the MVP1 → GA roadmap. Updated whenever a feature lands or a priority shifts.

**Last updated:** 2026-05-17 (after `infra_e2e_seed_completed_study` merged as PR #130dev-only `POST /api/v1/_test/studies/seed-completed` endpoint + `seedStudyCompletedWithDigest` Playwright helper enabling deterministic E2E coverage of completed-study surfaces. Strict env guard returns 404 outside `ENVIRONMENT=development`. 2 commits-of-followup captured `infra_e2e_wire_seed_helper_into_studies_spec/idea.md` for the deferred Playwright tests that broke the smoke lane on first push).
**Last updated:** 2026-05-17 (after `chore_data_table_primitive_followups` items 1+2+4+6 of 6 merged as PR #132test mock helper extraction + `useLocalStorageSet` docstring lock + `?limit=` allowlist coercion + URL-state validation in `useDataTableUrlState`. Items 3 + 5 remain open per the chore's idea file. UI vitest **368 passing across 56 files** — +11 cases. Defense-in-depth: direct URLs with `?status=invented` or `?sort=garbage:asc` now hydrate as empty matchers instead of being sent to the backend and 422-ing).

---

## Current branch / execution context

- **Branch:** `main` (post-merge of PR #130 squash commit `13b3383` 2026-05-17). Finalization docs PR off `docs/finalize-e2e-seed-completed-study`. Prior squashes: PR #128 `73459d2` 2026-05-17 (bug_cursor_decode_value_validation), PR #126 `d6115b3` 2026-05-16 (feat_data_table_primitive). `v0.1.0` annotated tag still on `main` commit `d099536` 2026-05-13; GitHub Release at https://github.com/SoundMindsAI/relyloop/releases/tag/v0.1.0.
- **Branch:** `main` (post-merge of PR #132 squash commit `ee4c8d4` 2026-05-17). Finalization docs PR off `docs/finalize-data-table-followups`. Prior squashes: PR #130 `13b3383` 2026-05-17 (infra_e2e_seed_completed_study), PR #128 `73459d2` 2026-05-17 (bug_cursor_decode_value_validation), PR #126 `d6115b3` 2026-05-16 (feat_data_table_primitive). `v0.1.0` annotated tag still on `main` commit `d099536` 2026-05-13; GitHub Release at https://github.com/SoundMindsAI/relyloop/releases/tag/v0.1.0.
- **Active feature:** none in flight. **Three PRs shipped 2026-05-15:** PR #122 (Phase 1, 16th MVP1 feature — Tooltip primitive + 26 placements on create-study modal + study detail), PR #123 (Phase 1 finalization docs), PR #124 (Phases 2 + 3 — 17th MVP1 feature; 21 additional tooltips on judgments + proposals + cluster registration + 2 new first-run components: chat ExamplePrompts strip + Stripe-style StartHereChecklist on home page). The original "MVP1 Phase 1 only" scope-lock was reversed mid-day: operator decided to ship Phases 2 + 3 together with a Stripe-style design call rather than wait for MVP2. PR #124 took 2 hours from idea-folder reuse to merge. 47 total tooltip placements + 2 new first-run components live in `main`. **PR #122 shipped 2026-05-15 morning** — `feat_contextual_help` Phase 1 (16th MVP1 feature). Adds the first Tooltip primitive (`@radix-ui/react-tooltip@~1.2.8` + shadcn-style wrapper at `ui/src/components/ui/tooltip.tsx`), two glossary-backed wrappers (`InfoTooltip` standalone + asChild modes; `HelpPopover` click-to-open with `react-markdown` safety filter), and a 49-key glossary source-of-truth at `ui/src/lib/glossary.ts` (8 enum groups parity-tested against `enums.ts`). 26 tooltip placements across the create-study modal (Step 1 target + Step 3 template + 9 Step 5 inputs), study-header (status badge dynamic key + Best metric + Trials), trials-table (5 column headers + Sort label), and digest panel (5 section labels + Open PR enabled + Open PR disabled). The disabled Open PR button refactored from native `disabled` to `aria-disabled="true"` so it stays focusable and the tooltip reveals on focus (AC-11). Gemini Code Assist: 2 findings (1 accepted + fixed, 1 rejected with cited counter-evidence). Final GPT-5.5 review: 1 Medium accepted-framing-but-deferred. Spec converged at GPT-5.5 cycle 3 (24 findings, 23 accepted + 1 rejected); plan converged at cycle 2 (12 findings, 10 accepted + 1 rejected + 1 spec patch). UI vitest now **279 passing across 48 files** (was 249 across 45 — +3 new test files, +30 cases). Playwright E2E **8 passing** (was 5 — +3 new contextual-help tests). One follow-up filed: `infra_e2e_seed_completed_study/idea.md` tracks the E2E gap for digest-panel triggers + AC-11 (cross-subsystem helper for seeding a completed study with digest + proposal; component-level coverage is in place). Phases 2 + 3 deferred to MVP2 via `feat_contextual_help_mvp2/` (judgments + proposals tooltips; chat + cluster + home onboarding; the home-page "Start here" panel is the only product-design-shaped item).

**Earlier — seven PRs shipped 2026-05-14:** `feat_judgments_periodic_resume_sweep` (PR #104, 14th MVP1 feature), `bug_query_inline_crud_since_filter_uuidv7_ms_collision` (PR #106 — UUIDv7 ms-collision test flake), `infra_dashboard_regen_pre_commit_conflict §2+§4` (PR #108 — dashboard regen idempotency + relative-link rewriting), `infra_make_targets_split_backend_only` (PR #110 — `make backend-fmt/lint/typecheck` + symmetric `ui-fmt` so Node-18 contributors aren't blocked), `chore_digest_worker_narrow_except` (PR #112 — narrowed `except Exception` allowlist to `(ValueError,)` + ERROR-level `digest_importance_failed_unexpected` event), `infra_structlog_test_helpers` (PR #114 — factored the two structlog test-assertion patterns into `backend/tests/_log_helpers.py`), and `chore_chat_last_message_preview` (PR #117 — `last_message_preview` + `last_message_at` on `ConversationSummary` via LATERAL JOIN; frontend shows preview under title + swaps displayed timestamp from `created_at` to `last_message_at`). Plus PR #116 dropped `chore_studies_ui_shadcn_polish` as won't-do (forward-compat audit on NavigationMenu primitive + ClusterFilterSelect precedent on native `<select>` for page-level controls). The 7-PR day drained the operational-friction surfaces and the last `/bug-fix`-shaped item. **MVP1 alpha + 17 features shipped (all 3 phases of feat_contextual_help on 2026-05-15) + 26 backlog items drained.** Manual maintainer steps still pending from [`release-checklist.md`](docs/03_runbooks/release-checklist.md): §4 fresh-VM hosted-OpenAI walkthrough, §5 local-LLM walkthrough, §8 feedback Discussion + design-partner channel shares. **Remaining MVP1 backlog: zero actionable items** — the MVP1 dashboard now reads `Path to MVP1: 0`. Two ideas held for MVP2 (visible on the new `MVP2_DASHBOARD.md`): `infra_arq_subprocess_test_mvp2` (folder renamed 2026-05-14 to mirror the `_mvp2` precedent — trigger-locked: arq pin bump, 3rd cron, or MVP3 hardening opt-in) and `bug_chat_long_conversation_truncation_mvp2` (paired with the just-shipped chat preview but kept on MVP2 hold per its preflight — needs `/pipeline` for migration + new LLM round-trip). Plus 4 keep-deferred items by operator decision + `infra_dashboard_regen_pre_commit_conflict §3` follow-up (runbook addendum, MVP1-eligible but low-leverage).
Expand All @@ -19,6 +19,8 @@

## Most recent meaningful changes (newest first)

- **2026-05-17 — `chore_data_table_primitive_followups` items 1+2+4+6 merged into `main`** as PR #132 (squash commit `ee4c8d4`). Shipped via direct implementation → `/impl-execute --ad-hoc`. 4 of 6 follow-ups from `feat_data_table_primitive` (PR #126) review cycles. **Item 1 (test helper extraction):** new `ui/src/__tests__/helpers/data-table-url-mock.ts` factors the ~25-line `searchParamsSubscribers` + `applyUrl` mock pattern duplicated across 3 test files (`proposals/page.test.tsx`, `judgments/[id]/page.test.tsx`, `query-sets/queries-table.test.tsx`). Uses async `vi.mock` factory + dynamic import to sidestep Vitest's hoist trap (a vi.mock factory that references same-file imports fails because hoisting puts the factory above the import statements). **Item 2 (`useLocalStorageSet` shape lock):** plan §Story 2.10 had sketched `{ value: string[], ... }`; shipped impl uses `Set<string>` for O(1) membership + `.has()` / `.toggle()` matching the consumer pattern. Docstring updated to canonicalize the shipped contract; no code change. **Item 4 (`?limit=` allowlist):** when `options.pageSizeOptions` is provided, out-of-allowlist `?limit=` values fall back to `defaultPageSize`. Backward-compatible — current consumers omit `pageSizeOptions`. **Item 6 (URL-state validation):** centralizes validation for enum filters (`column.filter.wireValues`) and sort tokens (`column.sortable` + `column.sortDirections`) inside `useDataTableUrlState`. Direct URLs like `?status=invented` or `?sort=garbage:asc` now hydrate as empty matchers instead of round-tripping to the backend and 422-ing. fk-select filters pass through (their option IDs load async — can't be hook-validated). Did NOT auto-clean URLs via `replace()` — silently drop to avoid the "flash of empty state" UX surprise. **Gemini Code Assist:** 2 Medium findings, both accepted: (1) docstring on sort fallback said "first allowed direction" but impl hardcoded `'asc'` — rewrote docstring to lock in the deliberate "no synthesizing direction" semantic (mirrors backend `parse_sort` at `_sort.py:50-56`) + regression test for desc-only edge case; (2) `Number(limitRaw)` → `parseInt(limitRaw, 10)` so fractional URLs like `?limit=10.5` truncate to `10` + regression test. **CI:** all 7 checks green on both cycles (first commit + Gemini fixes). **Tests:** UI vitest 357 → 368 (+11 cases — 7 for item 6 validation, 4 for item 4 coercion). Lint + typecheck clean. **Items 3 (`DataTableProps` URL state aggregate prop) + 5 (TanStack `state.columnVisibility` wire-up) still open** in the chore's idea.md — both touch 8 DataTable consumers each; too big for one ad-hoc PR.

- **2026-05-17 — `infra_e2e_seed_completed_study` merged into `main`** as PR #130 (squash commit `13b3383`). Shipped via direct implementation → `/impl-execute --ad-hoc` (no `/bug-fix` because this is `infra_`-prefixed, not a bug). Adds a development-only endpoint that inserts a completed study + 2 trials + digest + (optional) pending proposal in one transaction so future Playwright E2E coverage of digest-panel surfaces can run against real backend rows instead of mocked component data. **Endpoint:** `POST /api/v1/_test/studies/seed-completed`. **Security model:** new `Settings.environment: str = "development"` field; endpoint gates on `environment == "development"` (strict equality — not `!= "production"`) and returns 404 `RESOURCE_NOT_FOUND` outside development so the staging/production behavior is indistinguishable from "route not registered." Pydantic `extra="forbid"` on the request schema blocks tampered payloads from smuggling extra columns into the insert path. **Implementation:** new `backend/app/api/v1/_test.py` router with `_require_development_env` dependency, new `backend/app/services/test_seeding.py` service helper that drives the study through legal state-machine transitions via `study_state.start_study → complete_study` so the FR-7 protection listener does not raise, plus `seedStudyCompletedWithDigest` Playwright helper at `ui/tests/e2e/helpers/seed.ts`. **Tests:** 13 contract cases in `backend/tests/contract/test_test_endpoint_guard.py` covering env-guard HTTP-layer + dependency-layer behavior across 5 non-dev environment values + schema validation; 2 integration smoke cases in `backend/tests/integration/test_test_seeding.py` exercising the live-DB write path; `# pragma: no cover` on the integration-only function bodies (matching the precedent from `feat_github_pr_worker` PR #45 `git_pr.py`). **Gemini Code Assist:** 2 Medium findings, both accepted + fixed in commit `b617ca1` (`timedelta` import + reorder `start_study` BEFORE trial inserts so the seeded flow mirrors the real orchestrator + anchor trial timestamps off `study.started_at` with `ended_at = started_at + timedelta(milliseconds=duration_ms)`). **CI fight:** 5 push cycles before green — (1) initial push `580edf1`: backend coverage gate failed at 80% because the new ~290 LOC weren't covered; (2) `b617ca1` Gemini fixes: same coverage gate; (3) `6964924` added integration test: still failing because... (4) `a68007b` added `# pragma: no cover` to integration-only functions: STILL failing — turned out the root cause was actually mypy `--strict` errors in `test_test_endpoint_guard.py` (two issues: `test_router.HTTPException` was importing through a re-export that didn't exist, and `assert isinstance(detail, dict)` was unreachable because FastAPI types `HTTPException.detail` as `str`); diagnostic blind spot was running mypy on touched files only instead of `mypy backend/` per CLAUDE.md "Build, Test, and Lint Commands"; (5) `2615aae` fixed the mypy errors — backend lane green, smoke lane then failed because the 2 new Playwright tests in `studies.spec.ts` broke `make up`-driven E2E; root cause undiagnosed (agent execution environment has no authenticated access to GitHub Actions logs or Playwright report artifacts); dropped both tests + captured resume work as `infra_e2e_wire_seed_helper_into_studies_spec/idea.md` in commit `3203787`. **Final state:** all 7 CI checks green; endpoint + helper + integration + contract tests ship as primary deliverable; the 2 dropped E2E tests are additive coverage that future session with log access can restore in <60min via the captured idea (the helper, endpoint, and seed function are all already wired correctly — just need to debug the Playwright failure pattern). **Architecture doc impact:** none; `Settings.environment` documented inline at `backend/app/core/settings.py:295-302` referencing CLAUDE.md §Environments.

- **2026-05-17 — `bug_cursor_decode_value_validation` merged into `main`** as PR #128 (squash commit `73459d2`). Shipped via `/bug-fix` (Default mode) → `/impl-execute --ad-hoc`. Defense-in-depth fix on [`backend/app/db/repo/_sort.py`](backend/app/db/repo/_sort.py) `decode_cursor` — adjudicated the Medium GPT-5.5 finding deferred from PR #126 final review. **Root cause:** decode performed `json.loads → decoded[0] → str(decoded[1])` with no shape or type validation. A tampered cursor whose value-half was a non-primitive (dict / list / bool) silently passed through when `value_is_datetime=False`, flowed into `keyset_predicate`'s SQL comparison, and surfaced as 500 INTERNAL_ERROR instead of the intended 422 VALIDATION_ERROR that all 9 cursor-consuming routers wrap `_sort_decode_cursor` to produce. **Fix:** assert 2-element list payload; assert value-half is `None | str | int | float` (bool rejected via explicit isinstance check since `isinstance(True, int)` is True); assert row-id is str (Gemini High finding — `str(decoded[1])` would coerce `None` → `"None"` / `[]` → `"[]"` and surface as 500 on UUID id columns). All decode failures normalize to `ValueError`; routers' existing `except Exception` translation fires unchanged. **Locked-and-deferred design forks:** (1) skip per-column-type cross-check (4-type allowlist already covers every legitimate value); (2) keep 2-tuple cursor payload — defer 3-tuple `[col_name, value, row_id]` for cross-sort replay detection (backward-incompat with issued cursors, addresses non-crash concern); (3) reuse `VALIDATION_ERROR` code instead of adding `INVALID_CURSOR` (9-router precedent; new code would add noise without changing client behavior). **Tests:** +12 unit cases in `backend/tests/unit/db/test_parse_sort.py` (7 for the original bug surface — dict / list / bool value-half, single-element / three-element / non-list payload, int-with-datetime-sort — plus 5 parametrized for Gemini's row_id finding covering None / int / list / dict / bool). All 12 verified to fail on `main` (proven via `git stash`) and pass with the fix; broader 855 unit + 157 contract suite green. **Gemini Code Assist:** 3 findings, all 3 accepted + fixed (High row-id type check + Medium row_id regression test + Medium stale bug_fix.md line ranges). **GPT-5.5 final review:** skipped — agent session has no `.env`/`OPENAI_API_KEY` in remote execution environment; diff is small (+192/-4 across 3 files) and bounded to a single helper module so recommended-but-not-required per impl-execute Step 7 threshold. **CI:** both push cycles green on first attempt (no flakes); 7 checks each cycle. **Scope discipline:** no router edits, no migration, no wire-format change, no `api-conventions.md` change — pre-fix and post-fix cursors decode identically for legitimate payloads.
Expand Down