feat(ux): humanize backend wire values in the UI#616
Conversation
UX audit finding: several surfaces rendered raw snake_case/lowercase enum
values to users. Add a single source of display labels (ui/src/lib/labels.ts)
and apply it:
- StatusBadge now renders a human label (explicit LABEL_TABLE for acronym
casing like `pr_opened` -> "PR opened"/`pr_merged` -> "PR merged", else a
snake_case -> Title-case humanizer). Raw value stays in data-value.
- Judgment "Source" column: new `judgment_source` StatusBadge kind maps
llm/human/click -> "LLM-as-judge"/"Human"/"Click (UBI)". Fixes a latent
color bug too — it previously used kind="judgment_list", whose variant
table has no source keys, so every source fell through to the secondary
variant.
- Study wizard metric/sampler/pruner dropdowns show "NDCG"/"TPE (learns from
prior trials)"/"Median (early-stop weak trials)" instead of ndcg/tpe/median
(wire values still come from @/lib/enums via *_VALUES.map — form-select
discipline preserved).
- Shared formatMetricLabel so a metric reads "NDCG@10" everywhere (confidence
panel + wizard caption) instead of "ndcg" one place, "NDCG@10" another.
- Fix stale judgments empty-state copy ("via the calibration modal" ->
generate from a query set or UBI).
Tests: labels.ts unit tests (humanizer, formatMetricLabel, enum-coverage);
StatusBadge tests updated for labels + a regression test that judgment_source
gets a real variant. Full suite 1315 green; tsc + lint clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
There was a problem hiding this comment.
Code Review
This pull request centralizes the management of human-readable labels for backend wire values into a new labels.ts utility, ensuring consistent formatting across components like StatusBadge, CreateStudyModal, and ConfidencePanel, supported by comprehensive unit tests. The review feedback is highly constructive, pointing out opportunities to simplify the code by removing a redundant mapping in LABEL_TABLE that is already handled by the fallback humanizer, and cleaning up an unnecessary type assertion and unreachable fallback in CreateStudyModal.
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 LABEL_TABLE: Record<string, Record<string, string>> = { | ||
| proposal: { | ||
| pr_opened: 'PR opened', | ||
| pr_merged: 'PR merged', | ||
| }, | ||
| proposal_pr: { | ||
| open: 'Open', | ||
| closed: 'Closed', | ||
| merged: 'Merged', | ||
| }, | ||
| judgment_source: JUDGMENT_SOURCE_LABELS, | ||
| }; |
There was a problem hiding this comment.
The proposal_pr mapping in LABEL_TABLE is redundant because humanizeWireValue already converts 'open', 'closed', and 'merged' to 'Open', 'Closed', and 'Merged' respectively. Removing this redundant mapping simplifies the code and reduces maintenance overhead.
const LABEL_TABLE: Record<string, Record<string, string>> = {
proposal: {
pr_opened: 'PR opened',
pr_merged: 'PR merged',
},
judgment_source: JUDGMENT_SOURCE_LABELS,
};
| {METRIC_LABELS[metric as ObjectiveMetric] ?? metric.toUpperCase()}{' '} | ||
| evaluates the full ranked list — no cutoff used. |
There was a problem hiding this comment.
Since metric is already typed as ObjectiveMetric (watched from FormValues), the type assertion as ObjectiveMetric is redundant. Additionally, because METRIC_LABELS is a complete mapping of ObjectiveMetric to string, the value is guaranteed to be defined, making the nullish coalescing fallback ?? metric.toUpperCase() unreachable. We can simplify this expression to just METRIC_LABELS[metric].
| {METRIC_LABELS[metric as ObjectiveMetric] ?? metric.toUpperCase()}{' '} | |
| evaluates the full ranked list — no cutoff used. | |
| {METRIC_LABELS[metric]}{' '} | |
| evaluates the full ranked list — no cutoff used. |
- Drop the redundant proposal_pr entry from StatusBadge LABEL_TABLE — humanizeWireValue already yields Open/Closed/Merged. - Simplify METRIC_LABELS[metric] access — `metric` is already typed ObjectiveMetric and the map is total, so the cast + fallback were dead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai>
Review adjudicationGemini — 2 findings, both accepted + fixed in
Pre-existing red check (not this PR): the |
…621) * docs(ux): capture deferred UX-audit polish as a tracked idea file Follow-up remainder of the 2026-07-11 UX audit whose high/medium findings shipped in PRs #616-#620: dark-mode long-tail color sweep + chart theming + human dark-mode QA, empty-state CTA parity, breadcrumb consistency, reverse "used by" links, wizard form-error association, and a centralized date formatter. All low-severity; split out to keep each fix PR reviewable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> * docs(ux): clarify date vs number formatting in follow-up idea (Gemini review) Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> --------- Signed-off-by: SoundMindsAI <eric.starr@soundminds.ai> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
First of a series of UX-audit fix PRs. Scope: wire-value humanization — several surfaces rendered raw snake_case/lowercase enum values to users.
Changes
ui/src/lib/labels.ts— single source of display labels for backend wire values (never widens types; wire values still come from@/lib/enums).LABEL_TABLEfor acronym casing (pr_opened→"PR opened",pr_merged→"PR merged"), else asnake_case→Title-case humanizer. Raw value preserved indata-valuefor tests/automation.judgment_sourcebadge kind mapsllm/human/click→"LLM-as-judge"/"Human"/"Click (UBI)". Also fixes a latent color bug: it usedkind="judgment_list"whose variant table has no source keys, so every source fell through to thesecondaryvariant.ndcg/tpe/median. Wire values still come from*_VALUES.map— form-select discipline preserved.formatMetricLabel— a metric now reads "NDCG@10" everywhere (dedup'd the private copy in the confidence panel).Testing
labels.tsunit tests (humanizer,formatMetricLabel, enum-coverage so no value can leak unlabeled); StatusBadge tests updated + a regression test thatjudgment_sourcegets a real (non-secondary) variant. Full suite 1315 green;tsc+eslintclean.🤖 Generated with Claude Code