feat(studies-ui): restore lost Trials + Convergence list columns#438
Conversation
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>
…base) 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>
…umns 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>
There was a problem hiding this comment.
Code Review
This pull request restores the frontend 'Trials' and 'Convergence' columns in the /studies list that were lost during a previous rebase. It adds the column configurations, glossary tooltips, unit tests, and an E2E test, while also wrapping the page in a TooltipProvider to prevent Radix tooltip errors. The review feedback suggests adding a defensive check in the column configuration to handle unmapped convergence verdicts gracefully during rolling deployments, and importing ReactNode explicitly in the new test file to avoid referencing the global React namespace.
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.
| const badge = VERDICT_BADGE[verdict]; | ||
| return ( | ||
| <span | ||
| className="inline-flex items-center" | ||
| data-testid={`convergence-verdict-${row.original.id}`} | ||
| data-verdict={verdict} | ||
| > | ||
| <Badge variant={badge.variant} className="text-[10px] uppercase tracking-wide"> | ||
| {badge.label} | ||
| </Badge> | ||
| </span> | ||
| ); |
There was a problem hiding this comment.
During rolling deployments or API updates, the backend might return a new or unexpected convergence_verdict value that is not yet mapped in VERDICT_BADGE. To prevent runtime crashes (e.g., TypeError: Cannot read properties of undefined (reading 'variant')), add a defensive check to handle cases where badge is undefined.
const badge = VERDICT_BADGE[verdict];
if (!badge) return <span className="text-muted-foreground">—</span>;
return (
<span
className="inline-flex items-center"
data-testid={`convergence-verdict-${row.original.id}`}
data-verdict={verdict}
>
<Badge variant={badge.variant} className="text-[10px] uppercase tracking-wide">
{badge.label}
</Badge>
</span>
);
| import { render, screen } from '@testing-library/react'; | ||
| import { describe, expect, it } from 'vitest'; | ||
|
|
There was a problem hiding this comment.
Import ReactNode from 'react' to type-safe the cell renderer return type and avoid referencing the global React namespace.
| import { render, screen } from '@testing-library/react'; | |
| import { describe, expect, it } from 'vitest'; | |
| import { render, screen } from '@testing-library/react'; | |
| import { type ReactNode } from 'react'; | |
| import { describe, expect, it } from 'vitest'; |
| if (!column?.cell || typeof column.cell !== 'function') { | ||
| throw new Error(`${columnId} column or its cell renderer not found`); | ||
| } | ||
| const cell = column.cell as (ctx: { row: { original: StudySummary } }) => React.ReactNode; |
There was a problem hiding this comment.
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>
Review adjudication (Gemini Code Assist)Commit landing accepted fixes: Gemini Code Assist (3 findings — all accepted)
Outcomes
Note: this PR also corrects a doc-drift bug it uncovered — the studies-list columns were marked "shipped" in PR #421 but were actually lost in the #421/#422 rebase (see PR description + the All 17 CI checks were green on |
Final cross-model review (GPT-5.5) — adjudicationGPT-5.5 returned 1 Low-severity finding, rejected:
Outcomes (all reviews on this PR)
Ready for human review + merge once CI is green on |
…438) (#439) Prepend the #438 entry to Last 5 merges (noting it restored the lost PR #421 Story 1.2 columns + corrected the doc-drift); drop the now-6th entry to the older-entries pointer; update branch context + in-flight to reflect all three 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>
Summary
Adds Trials + Convergence columns to the
/studieslist table — answering the operator request to surface trial count on the studies page.This is a restoration of lost work, not a new feature. The backend already returns
trial_count+convergence_verdictonStudySummary(shipped infeat_studies_convergence_visibility, PR #421, Story 1.1 /b90d5477). The matching frontend columns (Story 1.2, commited5ca276) were authored + reviewed but never reachedmain— they were dropped in the PR #421/#422 rebase that de-duplicated Epic 1 commits. So the two fields sat returned-but-unsurfaced; the/studieslist ran with only its original 6 columns.This PR cherry-picks
ed5ca276to restore the reviewed original (better than a fresh reimplementation: headertooltipKeypattern,hideable,satisfies Record<ConvergenceVerdict>exhaustiveness, thestudy.trial_countglossary key, a dedicated unit test, + an E2E spec):trial_count(the non-baseline count, resolved server-side via the batchedcount_trials_for_studiesaggregate — no N+1).convergence_verdict→Converged/Improving/Too few trialsbadge, em-dash when null (in-flight / <5 complete trials). ReusesCONVERGENCE_VERDICT_VALUES+ theconvergence_verdictglossary key.Bug caught during restoration
The header
tooltipKeyrenders an<InfoTooltip>in the column header, whose RadixTooltipneeds aTooltipProviderancestor. The app shell (layout.tsx) provides one in prod, butpage.test.tsxrenders<StudiesPage>in isolation — so it now wraps inTooltipProvider, mirroring the layout. Thated5ca276didn't already carry this fix confirms it never ran the full suite onmain(consistent with being lost pre-merge).Doc-drift corrected
state_history.mdand thefeat_studies_convergence_visibilityimplementation plan both marked Story 1.2 as shipped via PR #421. They didn't. Both now carry aCORRECTION (2026-06-03)annotation pointing here.Guide regen
Guide 06's
01-studies-list.pngwas stale (old 6-column list). Regenerated against the populated stack — now shows the TRIALS column (50/200/200/15/…) + the CONVERGENCE column (CONVERGED / TOO FEW TRIALS badges).03-create-study-modal+04-study-detailregenerated in lockstep; walkthrough video promoted.Test coverage
studies-table-convergence-column.test.tsx(restored)studies-table-ceiling-badge.test.tsx(fixture extended)studies/page.test.tsx(TooltipProvider + mock fields)studies-convergence-columns.spec.ts(restored)1039 vitest green; tsc + build clean; freshness gate clean (no backend change). No migration.
Test plan
pnpm --dir ui test→ 1039/1039pnpm --dir ui typecheck+pnpm --dir ui buildcleanpnpm --dir ui lint→ 0 errorsgit statusclean after canonical regen🤖 Generated with Claude Code