feat(lists): add Queries + Parameters count columns to list tables#436
Conversation
Two cohesive list-summary additions (feat_list_count_columns):
1. /query-sets gains a "Queries" column showing query_count.
- QuerySetSummary gains query_count: int (the schema previously
omitted it "to avoid N+1 counts at list time").
- New batched repo.count_queries_for_sets(ids) — one GROUP BY
aggregate per page, NOT a per-row count. Mirrors the no-N+1
pattern repo.count_trials_for_studies established for the
studies-list trial_count field (feat_studies_convergence_visibility,
PR #421). The list endpoint stays at a fixed 2 queries regardless
of page size.
- The aggregate column is labeled `query_count` (NOT `count`):
SQLAlchemy Row is tuple-like and exposes a built-in `.count()`
method, so `row.count` would resolve to the bound method, not the
labeled column (caught by mypy).
2. /templates gains a "Parameters" column showing param_count.
- QueryTemplateSummary gains param_count: int = len(declared_params).
- Free to compute — declared_params is a JSONB column already loaded
on the row (not a child relationship), so no extra query, no N+1.
The full dict stays on QueryTemplateDetail.
- Surfaces each template's tuning surface at a glance (0 =
non-tunable; 6-8 = rich search space).
Regenerated ui/openapi.json + ui/src/lib/types.ts via the canonical
scripts/regen-generated-artifacts.sh, so the frontend types pick up
both new fields automatically — and the generated-artifacts-fresh CI
gate (shipped in the prior PR) validates the snapshot on this PR.
Tests (14 new): 2 contract (schema shape: both fields integer +
required), 5 integration (query_count: 3/0/per-set; param_count: 3/0
— verified against real Postgres in the one-shot container), 7 vitest
(both columns: exists, value render, zero, thousands-separator
formatting). 74 related existing backend tests + 1032 vitest still green.
No migration (Alembic head unchanged). No new product surface.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
Tangential discovery during feat_list_count_columns: ui/public/guides/03_create_query_template/01-templates-list.png + ui/public/guides/04_create_query_set/01-query-sets-list.png screenshot the affected list views and will show old 4-column / 3-column tables once the new query_count / param_count columns ship. In-PR regen attempted on an empty DB produced empty-state screenshots (net negative vs. previous populated-list illustrations). Reverted the regen + filed this chore for a populated-stack regen pass. PR ships unblocked; screenshots catch up later (3-5 min on a populated stack). CLAUDE.md tangential rubric row justifying deferral: "Fix requires an operator-environment change you can't make" — needs a populated demo DB via `make seed-demo`, which is an operator decision. Includes regenerated MVP2/roadmap dashboards (pre-commit hook). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
The operator brought up a populated stack (make up + make seed-demo: 5 query-sets × 5 queries each, 7 templates with 1-3 declared params), so the in-PR guide regen the earlier empty-DB attempt couldn't do is now possible. Regenerated guides 03 (create_query_template) + 04 (create_query_set) against the populated stack: - 03/01-templates-list.png: shows the new PARAMETERS column with real values (3,2,2,1,1,2,2) across 7 templates. - 04/01-query-sets-list.png: shows the new QUERIES column with 5 per set across 5 sets. - The create-modal + detail screenshots in both guides regenerated in lockstep (the list shows behind the modal); all populated + current. This obsoletes chore_guide_regen_after_list_count_columns (filed in 027ad78 when the DB was empty) — removed the idea folder since the work it deferred is done here. The dashboard-regen hook drops it from the MVP2/roadmap dashboards in lockstep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
The prior commit (4bc7c3f) removed the obsolete chore_guide_regen_after_list_count_columns folder but its dashboard- regen hook didn't fire on the deletion, leaving DASHBOARD.md / MVP2_DASHBOARD.md / *.html / website/docs/roadmap.md still referencing the deleted folder. Regenerated all of them via `make dashboard` + `scripts/build_public_roadmap.py` so the generated docs no longer point at a folder that doesn't exist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
The regenerated 03 + 04 guides (4bc7c3f) updated the screenshots but the .webm walkthroughs were left stale (playwright test was run directly, not via `pnpm capture-guides` which chains promote-videos.mjs). Promoted the fresh recordings from this run's test-results so the walkthrough videos match the populated-stack screenshots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
There was a problem hiding this comment.
Code Review
This pull request introduces list-summary count fields to optimize performance and surface metrics at a glance. Specifically, it adds a query_count field to QuerySetSummary (resolved via a single batched GROUP BY query to avoid N+1 queries) and a param_count field to QueryTemplateSummary (derived from the already-loaded declared_params JSONB column). Corresponding columns ("Queries" and "Parameters") are added to the frontend tables, accompanied by comprehensive contract, integration, and frontend tests. A review comment suggests using a fallback default dictionary when calculating param_count to prevent a potential TypeError if declared_params is None.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| version=row.version, | ||
| # declared_params is a JSONB column already loaded on the row, so | ||
| # len() is free — no extra query, no N+1 (see QueryTemplateSummary). | ||
| param_count=len(row.declared_params), |
There was a problem hiding this comment.
If row.declared_params is None (which can happen if the JSONB column contains a NULL value in legacy database rows), calling len() directly will raise a TypeError: object of type 'NoneType' has no len(). To prevent potential 500 Internal Server Errors on the list endpoint, it is safer to use a fallback default dictionary.
| param_count=len(row.declared_params), | |
| param_count=len(row.declared_params or {}), |
Review adjudication (Gemini Code Assist)Gemini Code Assist (1 finding)
Outcomes
All 17 CI checks green (including |
Final cross-model review (GPT-5.5) — adjudicationGPT-5.5 returned 2 findings, both rejected as false positives caused by the review input excluding the (large, bot-generated)
Outcomes (all reviews)
All 17 CI checks green. Ready for human review + merge. |
…shness-gate framing (#437) - Prepend feat_list_count_columns (PR #436, 606d43d) to Last 5 merges. - Correct the infra_generated_artifact_freshness_gate entry: it merged as PR #433 (c5c36c6) + docs PR #435 — the prior text was committed on the feature branch before the PR number existed and still said "PR forthcoming". - Drop feat_studies_convergence_visibility to the older-entries pointer (keeps the list at 5). - Reset "Current branch / execution context" + "In flight" to main / nothing-in-flight (both 2026-06-03 features merged). Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All three findings accepted: 1. column-config.tsx — forward-compat guard `if (!badge) return <em-dash>` for an unmapped convergence_verdict. Unlike the unreachable declared_params NULL case (rejected on PR #436 — DB NOT NULL), this is REACHABLE: convergence_verdict is a backend-COMPUTED classification (backend/app/domain/study/convergence.py), not a fixed DB enum, so a newer backend could emit a verdict this snapshot doesn't map during a rolling deploy — and an unmapped value would throw on `badge.variant`, crashing the whole table render. Matches the codebase's existing forward-compat stance (StatusBadge `?? 'secondary'`; the best_metric column's rolling-deploy comment in this same file). + regression test (cast an out-of-union verdict → asserts em-dash, no crash). 2+3. convergence-column.test.tsx — import `type ReactNode` from 'react' explicitly instead of the ambient `React.ReactNode` namespace; matches the rest of the codebase's import style. 8/8 convergence-column tests green; tsc + 0 lint errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
* feat(studies-ui): restore lost Trials + Convergence list columns The studies list backend already returns trial_count + convergence_verdict (feat_studies_convergence_visibility, PR #421 Story 1.1 / b90d547), but the Story 1.2 *frontend* columns (commit ed5ca27) never reached main — they were dropped in the PR #421/#422 rebase that de-duplicated Epic 1 commits. So the fields sat returned-but-unsurfaced; the /studies list ran with only its original 6 columns. This restores ed5ca27 by cherry-pick (the reviewed original is better than a fresh reimplementation — header tooltipKey pattern, hideable, `satisfies Record<ConvergenceVerdict>` exhaustiveness, the study.trial_count glossary key, a dedicated unit test, and an E2E spec): - studies-table.column-config.tsx: Trials column (trial_count) + Convergence column (convergence_verdict → Converged / Improving / Too few trials badge, em-dash when null). Both use header tooltipKey + are hideable. - studies-table-convergence-column.test.tsx (restored): 7 cases. - studies-convergence-columns.spec.ts (restored): E2E. - glossary.ts: study.trial_count + convergence_verdict short entries. Caught a gap the lost commit never exercised: the header tooltipKey renders an <InfoTooltip> in the column header, whose Radix Tooltip needs a TooltipProvider ancestor. The app shell (layout.tsx) provides one in prod, but page.test.tsx renders <StudiesPage> in isolation — so it now wraps in TooltipProvider (mirroring the layout). That ed5ca27 didn't already carry this fix confirms it never ran the full suite on main. types.ts conflict resolved to main's generated version (the fields are already present + freshness-gate-canonical). ceiling-badge fixture conflict merged (trial_count + convergence_verdict both present). 1039 vitest green; tsc + build clean; freshness gate clean (no backend change). No migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> * docs: correct the false "Story 1.2 columns shipped" claim (lost in rebase) state_history.md + the feat_studies_convergence_visibility implementation plan both marked the frontend Trials + Convergence columns as shipped via PR #421 (commit ed5ca27). They did not — ed5ca27 was dropped in the #421/#422 de-duplication rebase; only the backend fields landed. Annotate both with a CORRECTION pointing at the 2026-06-03 restoration (feat_studies_list_trial_convergence_columns). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> * docs(guides): regenerate guide 06 studies-list screenshot for new columns The /studies list now carries Trials + Convergence columns (restored in this PR), so guide 06's 01-studies-list.png was stale. Regenerated against the populated stack (make up + make seed-demo): the list screenshot now shows the TRIALS column (50/200/200/15/...) + the CONVERGENCE column (CONVERGED / TOO FEW TRIALS badges) with their header tooltips. 03-create-study-modal (list behind the modal) + 04-study-detail (a richer completed study) regenerated in lockstep; walkthrough video promoted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> * fix(studies-ui): adjudicate Gemini PR #438 review (3 accepts) All three findings accepted: 1. column-config.tsx — forward-compat guard `if (!badge) return <em-dash>` for an unmapped convergence_verdict. Unlike the unreachable declared_params NULL case (rejected on PR #436 — DB NOT NULL), this is REACHABLE: convergence_verdict is a backend-COMPUTED classification (backend/app/domain/study/convergence.py), not a fixed DB enum, so a newer backend could emit a verdict this snapshot doesn't map during a rolling deploy — and an unmapped value would throw on `badge.variant`, crashing the whole table render. Matches the codebase's existing forward-compat stance (StatusBadge `?? 'secondary'`; the best_metric column's rolling-deploy comment in this same file). + regression test (cast an out-of-union verdict → asserts em-dash, no crash). 2+3. convergence-column.test.tsx — import `type ReactNode` from 'react' explicitly instead of the ambient `React.ReactNode` namespace; matches the rest of the codebase's import style. 8/8 convergence-column tests green; tsc + 0 lint errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> --------- Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds a count column to two list tables, surfacing at-a-glance metadata operators previously had to click into each row to see:
/query-setsgains a Queries column (query_count) — how many queries are in each benchmark set./templatesgains a Parameters column (param_count) — how many declared params each template exposes, i.e. its tuning surface (0 = non-tunable; 6–8 = rich search space).Both are additive
intfields on the existing list-summary schemas. No migration, no new product surface.Why two different implementations
query_countcounts childqueriesrows, so it uses a new batchedGROUP BYaggregate (repo.count_queries_for_sets) — one query per page, NOT per-row. This is the same no-N+1 patternrepo.count_trials_for_studiesestablished for the studies-listtrial_countfield (feat_studies_convergence_visibility, PR docs(research): complementary-architecture one-pager (three-layer handoff) #421).QuerySetSummaryhad previously omitted the count specifically "to avoid N+1 counts at list time"; the batched aggregate removes that objection.param_countislen(declared_params)— free, becausedeclared_paramsis a JSONB column already loaded on the template row (not a child relationship). No extra query, no N+1.A subtle bug caught by mypy during implementation: the aggregate column is labeled
query_count, NOTcount— SQLAlchemyRowobjects are tuple-like and expose a built-in.count()method, sorow.countwould resolve to the bound method rather than the labeled column. Commented inline.Generated artifacts + guides
ui/openapi.json+ui/src/lib/types.tsviascripts/regen-generated-artifacts.sh, so the frontend types pick up both new fields. Thegenerated-artifacts-freshCI gate (shipped PR infra(freshness-gate): CI gates for generated artifacts (copy-docs + openapi.json + types.ts) #433) validates the snapshot on this PR.create_query_template) + 04 (create_query_set) against a populated stack — the list-view screenshots now show the new PARAMETERS (values 1–3 across 7 templates) and QUERIES (5 per set across 5 sets) columns with real data. Walkthrough.webmvideos promoted to match.Test coverage
backend/tests/integration/test_list_count_fields.pybackend/tests/contract/test_list_count_fields_contract.pyui/src/__tests__/components/common/list-count-columns.test.tsx74 related existing backend tests + 1032 vitest still green; no exact-shape assertion broke.
Test plan
pnpm --dir ui test→ 1032/1032;pnpm typecheckclean;pnpm buildOKmake lint && make typecheckclean;ruff format --checkcleangit statusclean (openapi.json + types.ts current)🤖 Generated with Claude Code