Repository navigation
UN-4401 [FEAT] Add v1 Line, Area, Bar, StackedBar and Sparkline charts with a token ordinal ramp - #2446
Conversation
Line, Area, Bar, StackedBar and Sparkline under components/ui/chart, sharing one props model: ramp or tone colours from tokens, tooltip (hover and arrow keys), legend, loading and empty states, and a text alternative (summary sentence plus a visually hidden data table). Markers are drawn as a dot-only series, never a Recharts ReferenceLine. Adds --chart-1..5 to the v1 tokens, each reaching 3:1 against card in every mode, enforced by a unit test. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 1 — tip 5306b7065 (review-deep)
Review chosen: /unstract:review-shallow was tried first and refused on its pattern gate (auth_authz_secrets from "token", attack_surface from a test regex's .exec(, onprem_rollout). Per the loop rules this round ran /unstract:review-deep. Mode: INITIAL. Base ed53cfded (fetched), 25 files / 2,969 lines, count verified against GitHub.
Verdict — REQUEST CHANGES
Summary — Critical: 0 · High: 0 · Medium: 9 · Low: 7 · Lenses run: 19/19 (11 N/A or not covered, with reasons, below)
Signals: lenses 16 and 17 raised (README and comments; colour-token count changed 30→35), plus the React lifecycle slice. Security deep-dive: done (0 findings, filtering done; scope excludes tests and docs). boundary-sweep: not triggered — the diff adds no outbound client, endpoint, log line or error path. rule-storage-check: 0 to verify, 3 dismissed (--chart-1, --chart-5, --page-aspect are CSS custom properties, not script flags). count-enum-check: not covered — the script could not resolve paths (realpath --no-symlinks failed on macOS).
Findings
-
[Medium] [Lens 3 — Correctness] — A marker on a grouped BarChart, or on a right-axis stack, is drawn at the category centre instead of over its bar
- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L173-L185 - Failure mode: markers are a
Line, which plots at the centre of the band. Side-by-side bars are offset from that centre, so with 2 series the dot floats in the gap, and with 3 it sits on the wrong bar. - Evidence: in
__snapshots__/markup.mdthe bar variant draws row 1's bars at x=148.8–173.8 and 177.8–202.8, while the marker is atcx=176. - Suggested fix: on bar kinds, draw the marker from the marked
Baritself (aLabelListcontent at the bar's x + width/2, y). Add position tests. - Confidence: High · Verification: VERIFIED — snapshot coordinates.
- Location:
-
[Medium] [Lens 3/16 — Correctness, docs] — The summary announces markers the plot drops
- Location:
frontend/src/v1/components/ui/chart/describe.ts:L103 - Failure mode:
planMarkersdrops a marker when its series is unknown, its index is not an integer, or the value is a gap.describeChartonly checks the index range, so screen readers hear "Marked: …" for a dot nobody sees. This also contradicts the comments atmarkers.ts:L33andtypes.ts:L42. - Suggested fix: one validity predicate shared by both, plus summary tests for the gap, fractional and unknown-series cases.
- Confidence: High · Verification: VERIFIED — both filters read side by side (5 agents independently).
- Location:
-
[Medium] [Lens 7 — Contract] —
ChartDatum's index signature rejectsinterfacerows and rows with non-chart fields- Location:
frontend/src/v1/components/ui/chart/types.ts:L4-L6 - Failure mode: consumers' API row types won't type-check (TS2322), which forces a cast that erases key checking. This is cheap to change now and breaking once consumers depend on it.
- Suggested fix:
Row extends object, and constrain only the keys that are read. - Confidence: High · Verification: VERIFIED — tsc probe.
- Location:
-
[Medium] [Lens 7 — Contract] —
series[].keyandmarkers[].seriesaccept non-numeric fields (evenxKey)- Location:
frontend/src/v1/components/ui/chart/types.ts:L22 - Failure mode:
key: "day"compiles. The series draws nothing, and the table and summary say "no values". - Suggested fix: a
NumericKey<Row>mapped type. - Confidence: High · Verification: VERIFIED — tsc probe.
- Location:
-
[Medium] [Lens 3 — Correctness] — Numeric strings are plotted by Recharts but treated as gaps by the tooltip, table, summary and markers
- Location:
frontend/src/v1/components/ui/chart/describe.ts:L35-L38 - Failure mode: a DRF Decimal serialised as
"12.5"draws a line, while every accessible channel says there is no data. - Suggested fix: restrict series values to
number | null | undefinedat the type level (folds into #4). - Confidence: Medium · Verification: UNVERIFIED — does a string-valued series plot? Render to confirm.
- Location:
-
[Medium] [Lens 3/16 — Correctness, docs] — No error path: the README's canonical example turns a failed query into "No data for this period"
- Location:
frontend/src/v1/components/ui/chart/README.md:L25 - Failure mode:
state={isPending ? "loading" : "ready"}, withdata=[]on error, renders as empty and offers no retry. - Suggested fix: the README says to render
InlineAlertwithonRetryinstead of the chart on error. - Confidence: High · Verification: VERIFIED — reading.
- Location:
-
[Medium] [Lens 3 — Correctness] — A lone point (one row, or a value between two gaps) is invisible in Line, Area and Sparkline
- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L133-L135 - Failure mode: d3 emits a zero-length
M…Z, so nothing paints, while the summary says "1 point". - Suggested fix: draw a dot for any point with no defined neighbour.
- Confidence: Medium · Verification: UNVERIFIED — does a one-row LineChart paint any mark?
- Location:
-
[Medium] [Lens 13 — Testing] — The stacked marker "on top of its segment" is not pinned
- Location:
frontend/src/v1/components/ui/chart/describe.test.ts:L123-L136 - Failure mode: the mutant
.slice(0)survives (63/63), so a lower-segment marker could float to the top of the bar unnoticed. - Suggested fix: test a marker on a lower segment.
- Confidence: High · Verification: VERIFIED — mutation run.
- Location:
-
[Medium] [Lens 13 — Testing] — Right-axis series forming their own stack is untested
- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L163 - Failure mode:
stackId: "stack"survives. - Suggested fix: render a right-axis stacked series and assert its baseline.
- Confidence: Medium · Verification: VERIFIED that the mutant survives; UNVERIFIED whether Recharts renders it differently.
- Location:
-
[Low] [Lens 2 — Lifecycle/perf] —
data={[...rows]}gives Recharts a new array every render, sosetChartDatachurns- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L189,Sparkline.tsx:L66 - Suggested fix: memoise the plan and pass a stable reference.
- Confidence: Medium · Verification: UNVERIFIED (render count). (Rated Low here: a wasted re-render, with no wrong output.)
- Location:
-
[Low] [Lens 2] — Tooltip
contentis an inline arrow, which Recharts mounts as a new component type each render- Location:
CartesianChart.tsx:L234-L246,Sparkline.tsx:L82-L104 - Suggested fix: pass an element.
- Location:
-
[Low] [Lens 3] — Duplicate React keys when labels repeat (legend, table header)
- Location:
frontend/src/v1/components/ui/chart/ChartFrame.tsx:L105,L143 - Suggested fix: key by index.
- Location:
-
[Low] [Lens 3] — The tooltip vanishes, heading included, on a category where every series is a gap
- Location:
frontend/src/v1/components/ui/chart/ChartTooltip.tsx:L46-L48 - Suggested fix: keep the heading and show "–".
- Location:
-
[Low] [Lens 7] — One
axisFormatserves both Y axes- Location:
frontend/src/v1/components/ui/chart/types.ts:L86-L87 - Note: an additive gap, not a break.
- Location:
-
[Low] [Lens 13] — Tooltip exclusion of marker series and the marker dot fill are untested
- Location:
frontend/src/v1/components/ui/chart/charts.test.tsx:L142-L172
- Location:
-
[Low] [Lens 16/17 — Docs] — Stale comments
- Locations:
tokens.json:L330andpalette.ts:L3imply dark orders the ramp differently.README.md:L5-L6says every chart has a tooltip and a legend (Sparkline doesn't).tokens.ts:L11defines colour as "the Midnight Bloom collection minus its palette entries", which no longer holds now the chart ramp is in it. Made stale by:tokens.json:L285.
- Locations:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — See findings #10, #11
- Correctness & edge cases — See findings #1, #2, #5, #6, #7, #12, #13
- Security — Clean (security deep-dive done, 0 findings; assessed by orchestrator)
- Data integrity & migrations — N/A — no persistence, schema or data migration
- Concurrency — N/A — no concurrency primitives; synchronous rendering only
- API & contract compatibility — See findings #3, #4, #14 (consumer search: no consumers outside the chart folder yet, orchestrator)
- Reliability & resilience — N/A — no I/O, network or retries
- Performance & cost — See findings #10
- Observability — N/A — UI components with no logging or metrics surface
- Operational safety — N/A — v1 is not mounted; no flags, infra or rollout config
- LLM/agent-specific — N/A — no LLM or agent code
- Testing — See findings #8, #9, #15
- Dependencies & build — Clean (no dependency change; recharts 3.7.0 already in bun.lock; orchestrator)
- Code quality — Clean (pinned Biome 2.3.13 check on changed files: clean; orchestrator)
- Doc & comment accuracy — See findings #2, #6, #16
- Cross-document rule consistency — See findings #16 (merge-result sweep ran, exit 1, hits classified; orchestrator)
- On-prem deployment experience — N/A — frontend v1 components only; no chart, values, schema or on-prem build path
- Scaling & workload lifecycle — N/A — no workloads or scaling config
Unanchored findings
- PR title
UN-4401 [FEAT] …matches the workspace convention (CLAUDE.md "PR titles"), so there is nothing to flag.
Open questions for the author — none beyond the UNVERIFIED items above.
Assumptions made — consumers will pass API response row types directly (this drives #3/#4).
cost: agents=8 wall=04:13 lenses-examined=11 of 19 files=25 lines=2969
Unanchored findings
frontend/src/v1/styles/tokens.ts:11(outside the diff hunk): [Low] [Lens 16/17] — #16 Stale docs: this header no longer covers the chart ramp; alsotokens.jsonchart-5 note andpalette.ts:3imply dark orders differently; README intro says every chart has tooltip+legend. Made stale by:tokens.json:L285.
- Markers: one rule for which markers count (charted series, integer row, a value there), used by the plot and the summary alike. Bar markers are drawn from the bar itself (LabelList), so they sit over their own bar in a group and on top of their own segment in a stack. - Types: rows may be any object type (interfaces, API types with extra fields); xKey must name a string/number field and series keys a numeric one, so numeric strings are rejected at compile time. - A point with no defined neighbour is drawn as a dot. - Tooltip keeps gap entries and shows the category with "–" on an all-gap row; passed as an element so it is not remounted each render. - Rows passed to Recharts are memoised; legend and header keyed by index and series key; rightAxisFormat added. - Docs: error handling via InlineAlert, ramp order, sparkline legend and tooltip, tokens.ts colour family header. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 2 — tip 8ed588ab5 (review-deep, FOLLOWUP)
Review chosen: review-deep (lite's whole-PR gate fires the same pattern triggers as round 1). Delta under judgement: 5306b7065..8ed588ab5.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0 (6 of 6). Since boundary: 5306b7065.
Scope change — NO
Summary — Critical: 0 · High: 1 · Medium: 5 · Low: 7 · Lenses run: 19/19 (N/A ones with reasons below)
Signals: lenses 3, 7, 13 and 16 raised by the delta (marker placement, row and key types, new tests, docs). Security deep-dive: done (0 findings, after examination; filtering done). boundary-sweep: not triggered (no outbound client, endpoint, log line or error path). Pinned Biome 2.3.13 on the delta's files: clean.
Prior findings status
- #1 [Medium] Bar marker at category centre → RESOLVED in 8ed588a. It is drawn from the bar (
marks.tsx:barMarker) and the position is tested incharts.test.tsx. A gap case remains, filed as new #17. - #2 [Medium] Summary announces dropped markers → RESOLVED in 8ed588a. One rule (
markers.ts:keptMarkers) now drives both; the mutant is killed. - #3 [Medium]
ChartDatumrejected interfaces → RESOLVED in 8ed588a (types.ts:8,ChartDatum = object). A residual edge with index-signature rows is filed as new #20. - #4 [Medium] Series and marker keys accepted non-numeric fields → RESOLVED in 8ed588a (
NumericKey,types.ts:22). - #5 [Medium] Numeric strings → RESOLVED in 8ed588a. They are now rejected by type (
NumericKey). - #6 [Medium] No error path in the docs → RESOLVED in 8ed588a (README
InlineAlertsection;types.tsstate doc). - #7 [Medium] Lone point invisible → RESOLVED in 8ed588a (
marks.tsx:isolatedDot, tested, neighbour mutants killed). - #8 [Medium] Stacked marker segment not pinned → RESOLVED in 8ed588a. The marker comes from the segment's geometry, and the lower-segment test asserts it.
- #9 [Medium] Right-axis own stack untested → RESOLVED in 8ed588a. The no-overlap and baseline assertions kill the shared-
stackIdmutant. - #10 [Low] New data array every render → PARTIALLY RESOLVED in 8ed588a. The rows are memoised, but on dependencies that inline
series/markersliterals (the README's own example) change on every render, and the spread discards identity. Residual tracked as new #18. - #11 [Low] Inline tooltip content remounts → RESOLVED in 8ed588a. It is now an element, which Recharts clones.
- #12 [Low] Label keys → RESOLVED in 8ed588a (series key and index).
- #13 [Low] Tooltip vanishes on an all-gap category → RESOLVED in 8ed588a (
filterNull={false}, "–"; tested for CartesianChart). Sparkline is untested, new #29. - #14 [Low] One axis format → RESOLVED in 8ed588a (
rightAxisFormat, tested). - #15 [Low] Tooltip marker exclusion and marker fill untested → RESOLVED in 8ed588a (both tested; mutants killed).
- #16 [Low] Stale docs → RESOLVED in 8ed588a (
tokens.json,palette.ts, README intro,tokens.tsheader).
New findings
- [High] [Lens 3] — On a non-stacked BarChart, a gap before a marked row shifts its marker onto a later bar, or drops it from the plot while the summary still reads it
- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L66-L91 - Failure mode: Recharts'
LabelListpassesindexas the position among the drawn rectangles (Bar.jsfilters out null bars), not the data row. So[10, null, 30, 40]with a marker on row 2 draws it over row 3, and a marker on the last row is never drawn. - Suggested fix: resolve the row from the entry's
payload(or carry a row-index field), not the position. Add a gap test. - Confidence: High · Verification: VERIFIED — two independent source traces of recharts 3.7.0
Bar.js/LabelList.jsplus a d3 scale probe (null maps to undefined).
- Location:
- [Medium] [Lens 2/9] — The rows memo loses identity in the documented usage
- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L75-L84 - Failure mode: the memo depends on
series/markers(inline literals in the README) and always spreads, so Recharts gets a new array every render. The bar path doesn't even readseries. The same spread is inSparkline.tsx:64. - Suggested fix: pass
datathrough when nothing is marked; memoise on key and index content; document the stable-reference expectation. - Confidence: High · Verification: VERIFIED by reading the deps against the README.
- Location:
- [Medium] [Lens 7] — An inline
data={[]}infersRow = never, rejectingxKeyand every series key with an error that never mentionsdata- Location:
frontend/src/v1/components/ui/chart/types.ts:L71-L73 - Suggested fix: infer
Rowfromdataonly (NoInferon keys) and documentdata={[] as Row[]}. - Verification: VERIFIED — tsc probe.
- Location:
- [Medium] [Lens 7] — Rows typed
Record<string, string | number>(pivoted, dynamic series) makeNumericKeynever- Location:
frontend/src/v1/components/ui/chart/types.ts:L22-L25 - Suggested fix: fall back to
stringkeys whenRowhas a string index signature. - Verification: VERIFIED — tsc probe.
- Location:
- [Medium] [Lens 13] — Number-format expectations depend on the runtime locale
- Location:
frontend/src/v1/components/ui/chart/describe.ts:L14-L18 - Failure mode:
Intl.NumberFormat(undefined)with tests hard-coding "2,981" fails on ade_DErunner. - Suggested fix: pin the locale in the tests, or derive the expectations from the formatter.
- Confidence: High · Verification: UNVERIFIED — does
LANG=de_DE.UTF-8fail the suite?
- Location:
- [Medium] [Lens 13] — The compile-time key constraints have no guard
- Location:
frontend/src/v1/components/ui/chart/describe.test.ts:L117-L125 - Failure mode: loosening
NumericKeyto accept strings passes both tsc and vitest (mutation run). The old@ts-expect-errorwent with the rework. - Suggested fix:
@ts-expect-errorcases for a numeric-string key and an unknown key, plus a positive interface-row case. - Verification: VERIFIED — mutation.
- Location:
- [Low] [Lens 3] —
keptMarkersdrops invalid markers with no dev signal- Location:
frontend/src/v1/components/ui/chart/markers.ts:L39-L49 - Suggested fix: a dev-only
console.warn, aslib/pdf/highlights.tsdoes.
- Location:
- [Low] [Lens 16] —
keptLabelssays "series then row order" but follows the order of themarkersprop- Location:
frontend/src/v1/components/ui/chart/markers.ts:L57-L62 - Suggested fix: sort by series index.
- Verification: VERIFIED — run.
- Location:
- [Low] [Lens 2] — After a data change, the first frame can place an isolated dot or bar marker from the new rows at the old point's position
- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L99-L127 - Verification: UNVERIFIED — does the first frame show stale positions?
- Location:
- [Low] [Lens 2] —
barMarkeris a fresh component type each render (LabelusescreateElement)- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L160
- Location:
- [Low] [Lens 7] — Key errors name the alias (
NumericKey<Api>), not the allowed keys- Location:
frontend/src/v1/components/ui/chart/types.ts:L11-L25 - Note: the doc comments surface on hover; accepted as is.
- Location:
- [Low] [Lens 7] —
CategoryKey/NumericKeynot exported, so generic wrappers can't name them- Location:
frontend/src/v1/components/ui/chart/index.ts:L14-L22
- Location:
- [Low] [Lens 13] — Sparkline's
filterNull={false}is untested- Location:
frontend/src/v1/components/ui/chart/Sparkline.tsx:L86
- Location:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — See findings #18, #25, #26
- Correctness & edge cases — See findings #17, #23
- Security — Clean (security deep-dive done, 0 findings; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — See findings #19, #20, #27, #28 (consumer search: still no consumers outside the chart folder; orchestrator)
- Reliability & resilience — N/A — no I/O
- Performance & cost — See findings #18
- Observability — N/A — UI components, no logging or metrics surface
- Operational safety — N/A — v1 not mounted; no flags or infra
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #21, #22, #29
- Dependencies & build — Clean (no dependency change; orchestrator)
- Code quality — Clean (pinned Biome on the delta: clean; orchestrator)
- Doc & comment accuracy — See findings #24
- Cross-document rule consistency — Clean (merge-result sweep exit 1, all hits consistent; stale terms
planMarkers/"running total"/"finite number is a gap" gone; orchestrator) - On-prem deployment experience — N/A — frontend v1 components only
- Scaling & workload lifecycle — N/A — no workloads
Open questions / assumptions — consumers may build pivoted dynamic-key rows (drives #20).
cost: agents=8 wall=05:28 lenses-examined=11 of 19 files=27 lines=3400
- Bar markers read the row index carried in the row (LabelList's own index counts drawn bars and drifts after a gap); BarMarker is an element. - Isolated-point dots decide from Recharts' plotted points, not the closure. - Rows passed to Recharts are `data` itself unless something is marked, and memoised on what series and markers say, not their identity. - Key types accept index-signature rows; CategoryKey/NumericKey exported; empty-literal data inference documented. - Dropped markers warn with the reason; summary marker order is by series. - numberFormats(locale); tests pin en-US so they hold in any locale. - Type-level guards for the key constraints; tests for gap bars, sparkline gap tooltip, marker order and warnings. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 3 — tip 4965a6ed8 (review-deep, FOLLOWUP)
Delta under judgement: 8ed588ab5..4965a6ed8.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0. Since boundary: 8ed588ab5.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 6 · Low: 3 · Lenses run: 19/19 (N/A ones with reasons below)
Security deep-dive: done (0 findings, after examination; no candidates, so nothing to filter). boundary-sweep: 1 sink traced (the new console.warn in markers.ts), clean — it carries a marker label and a row index only. Pinned Biome on the delta: clean.
Prior findings status
- #10 (round 1) → RESOLVED in 4965a6e. Rows are
dataitself unless something is marked, and are memoised on series/marker content. - #17 [High] Bar marker drifts after a gap → RESOLVED in 4965a6e. Each bar row carries its own row index, and the gap test would fail on the old code.
- #18 [Medium] Memo identity → RESOLVED in 4965a6e. Untested; see new #34.
- #19 [Medium]
data={[]}infers never → PARTIALLY RESOLVED. The new index-signature fallback makesCategoryKey<never>=string, so an empty literal now accepts any key. The doc's "rejects every key" is false, see #33. - #20 [Medium]
Recordrows → PARTIALLY RESOLVED. The fallback ignores the index's value type, soRecord<string, string>is accepted, see #32. - #21 [Medium] Locale-dependent tests → RESOLVED in 4965a6e.
en-USis pinned and the suite passes underLANG=de_DE. The cost to default-formatter coverage is tracked as #35. - #22 [Medium] Key constraints unguarded → RESOLVED in 4965a6e. The
@ts-expect-errorguards are mutation-checked with tsc. TheCategoryKeybranch is still unguarded, see #37. - #23 [Low] Dropped markers silent → RESOLVED in 4965a6e. They now warn, but see #30 and #31.
- #24 [Low]
keptLabelsorder → RESOLVED in 4965a6e (sorted by series, tested). - #25 [Low] Stale first frame → PARTIALLY RESOLVED.
isolatedDotnow readspoints, but marker labels still come from the closure, see #36. - #26 [Low]
barMarkercomponent churn → RESOLVED in 4965a6e (it is passed as an element). - #27 [Low] Key errors name the alias → WAIVED in round 2 by this reviewer (doc comments show on hover).
- #28 [Low] Key types not exported → RESOLVED in 4965a6e.
- #29 [Low] Sparkline
filterNulluntested → RESOLVED in 4965a6e (tested; mutant killed).
New findings
- [Medium] [Lens 3] — The drop warning fires during loading and empty states
- Location:
frontend/src/v1/components/ui/chart/markers.ts:L41-L53 - Failure mode:
keptMarkersruns before the state check, sostate="loading"withdata=[]warns for every marker. It repeats on every new[], and twice under StrictMode, because the side effect sits insideuseMemo. The README example triggers it. - Suggested fix: warn only when ready, from an effect keyed on the marker spec.
- Verification: VERIFIED by reading.
- Location:
- [Medium] [Lens 3] — A second marker on the same series and row silently replaces the first
- Location:
frontend/src/v1/components/ui/chart/markers.ts:L55-L57 - Failure mode: the first marker is neither drawn nor read out, and nothing is logged. This contradicts the docstring's warn-on-every-drop promise.
- Location:
- [Medium] [Lens 7] — The
NumericKeyindex-signature fallback acceptsRecord<string, string>- Location:
frontend/src/v1/components/ui/chart/types.ts:L30-L35 - Failure mode: this reopens the numeric-string hole the type documents as closed.
- Suggested fix: make the fallback depend on the index value type (
number extends Row[string & keyof Row]). - Verification: VERIFIED — tsc probe.
- Location:
- [Medium] [Lens 16] — The docs say
data={[]}rejects every key; it now accepts any key- Location:
frontend/src/v1/components/ui/chart/types.ts:L81-L85,README.md:L75 - Failure mode:
keyof neverincludesstring, so the fallback branch fires. Typos compile unchecked. - Suggested fix: reject
neverfirst ([Row] extends [never] ? never). - Verification: VERIFIED — tsc probe.
- Location:
- [Medium] [Lens 13] — The content-keyed memo has no test that a changed marker or series redraws
- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L85-L98 - Failure mode: deps reduced to
[data]and a label dropped frommarkerSpecboth pass 77/77, with the lint rule suppressed. - Verification: VERIFIED — mutation.
- Location:
- [Medium] [Lens 13] — Pinning the locale removed all coverage of the default formatters
- Location:
frontend/src/v1/components/ui/chart/charts.test.tsx:L53-L54 - Failure mode: replacing the defaults with
Stringpasses 77/77. - Verification: VERIFIED — mutation.
- Location:
- [Low] [Lens 2/16] — Marker labels come from the render closure, so for a frame they can sit on the old rows' geometry; the
marks.tsxheader claims otherwise- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L3-L9 - Suggested fix: carry the label in the row.
- Location:
- [Low] [Lens 13] — The
CategoryKeyindex-signature branch has no type guard- Location:
frontend/src/v1/components/ui/chart/types.ts:L16-L21
- Location:
- [Low] [Lens 3] — Injected
__row/__marker_ifields can overwrite a same-named field on index-signature rows- Location:
frontend/src/v1/components/ui/chart/markers.ts:L89
- Location:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — See findings #36
- Correctness & edge cases — See findings #30, #31, #38
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — See findings #32 (still no consumers outside the chart folder; orchestrator)
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean (rows keep identity; reviewed by lifecycle agent)
- Observability — Clean (new console.warn reviewed; see #30)
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #34, #35, #37
- Dependencies & build — Clean (no dependency change; orchestrator)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #33, #36
- Cross-document rule consistency — Clean (merge-result sweep exit 1, hits consistent; orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=04:32 lenses-examined=12 of 19 files=27 lines=3700
- Marker labels travel in the rows (markerLabelKey), so markers stay paired with their point in every frame; renderers read only what Recharts hands them. - Dropped-marker warnings are emitted from an effect, only once the chart shows data, once per distinct problem; a second marker on the same point and series keys using the reserved __marker prefix now warn too. - Key types reject never (an untyped []) and string-only index rows. - Tests for marker updates on rerender, warnings, default formatters, and type guards for the new branches. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 4 — tip 0d79da9c8 (review-deep, FOLLOWUP)
Delta under judgement: 4965a6ed8..0d79da9c8.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0. Since boundary: 4965a6ed8.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 4 · Low: 5 · Lenses run: 19/19 (N/A ones with reasons below)
Security deep-dive: done (0 findings, after examination). boundary-sweep: 1 sink traced (the console.warn of marker labels and series keys), clean. Pinned Biome on the delta: clean. Code-reviewer core: clean.
Prior findings status
- #30 [Medium] Warnings while loading → RESOLVED in 0d79da9. They are now emitted from an effect, only when the chart is ready. The repeat nuances are tracked as #43.
- #31 [Medium] A duplicate marker overwrote the earlier one silently → RESOLVED in 0d79da9 (the duplicate is dropped with a warning; tested).
- #32 [Medium] String-only index rows → RESOLVED in 0d79da9 (
Record<string, string>is rejected; tested). - #33 [Medium] The
data={[]}doc was wrong → RESOLVED in 0d79da9 (neveraccepts no key; tested). - #34 [Medium] Memo untested → RESOLVED in 0d79da9 (rerender test; both deps mutants killed).
- #35 [Medium] Default formatters untested → PARTIALLY RESOLVED. The defaults are now asserted, but the test's name claims the viewer's locale, which it can't tell from en-US. See #41.
- #36 [Low] Labels came from the closure → PARTIALLY RESOLVED. Labels now travel in the rows, but the fields are keyed by series index. See #40.
- #37 [Low]
CategoryKeybranch unguarded → RESOLVED in 0d79da9. - #38 [Low] Reserved-key collision → PARTIALLY RESOLVED. It now warns, but the plot is still corrupted, and only while ready. See #44 and #45.
New findings
- [Medium] [Lens 7/16] —
CategoryKey<Record<string, unknown>>isnever, whileNumericKeyof the same row accepts any key- Location:
frontend/src/v1/components/ui/chart/types.ts:L20-L25 - Failure mode:
Extract<unknown, string | number>isnever, so loosely typed JSON rows can't take anyxKey. The docstring says otherwise. Three agents found this independently. - Verification: VERIFIED — tsc probe.
- Location:
- [Medium] [Lens 2] — Marker fields are keyed by series index, so dropping or reordering a series can pair a stale row's label with another series for a frame
- Location:
frontend/src/v1/components/ui/chart/markers.ts:L22-L29 - Suggested fix: key the fields by series key.
- Verification: UNVERIFIED (traced in the recharts source).
- Location:
- [Medium] [Lens 13] — "formats with the viewer's locale by default" passes when the default is hard-coded to en-US
- Location:
frontend/src/v1/components/ui/chart/charts.test.tsx:L625-L639 - Verification: VERIFIED — mutation.
- Location:
- [Medium] [Lens 13] — No test distinguishes the row-carried labels from the old closure labels
- Location:
frontend/src/v1/components/ui/chart/charts.test.tsx:L567-L599 - Failure mode: the rerender test passes against the round-2 code.
- Verification: VERIFIED. Can happy-dom observe the intermediate frame at all? UNVERIFIED.
- Location:
- [Low] [Lens 2/16] — "Warn once per distinct problem" is false
- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L100-L108 - Failure mode: StrictMode doubles the warnings in dev, the whole set is re-emitted when it changes, and a label containing a newline is split.
- Location:
- [Low] [Lens 3] — A reserved-prefix series key still corrupts its own plot
- Location:
frontend/src/v1/components/ui/chart/markers.ts:L122-L131 - Note: only the warning guards it.
- Location:
- [Low] [Lens 3] — The reserved-key warning appears only when the chart has data
- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L102-L103
- Location:
- [Low] [Lens 7] —
NumericKeyrejects numeric-subtype index rows (Record<string, 0 | 1>)- Location:
frontend/src/v1/components/ui/chart/types.ts:L43
- Location:
- [Low] [Lens 13/16] — The
[Row] extends [never]guards are redundant, and two doc sentences are wrong- Location:
frontend/src/v1/components/ui/chart/types.ts:L33-L36 - Detail: the
neverassertions pass without the guards. TheNumericKey"otherwise checked as above" and thechartRowsdocstring's field description are both wrong.
- Location:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — See findings #40, #43
- Correctness & edge cases — See findings #44, #45
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — See findings #39, #46 (no consumers outside the chart folder; orchestrator)
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — See findings #43
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #41, #42, #47
- Dependencies & build — Clean (no dependency change; orchestrator)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #39, #47
- Cross-document rule consistency — Clean (merge-result sweep, hits consistent; orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=04:15 lenses-examined=12 of 19 files=27 lines=3840
- Marker fields are named by series key, so a series keeps its own fields when another is removed or reordered. - A reserved (__marker) series key warns in any state and the chart draws no markers; dropped-marker warnings still only while showing data, logged when the set changes. - Key types: an unknown/any index accepts any key; numeric-subtype index rows are accepted; redundant never guards removed; docs corrected. - Renderer unit tests pin the row-carried labels; locale test renamed to what it checks; two-series marker test. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 5 — tip 45c4e8f17 (review-deep, FOLLOWUP)
Delta under judgement: 0d79da9c8..45c4e8f17.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0. Since boundary: 0d79da9c8.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 2 · Low: 2 · Lenses run: 19/19 (N/A ones with reasons below)
Security deep-dive: done (0 findings, after examination). boundary-sweep: 1 sink traced (console.warn), clean. Pinned Biome: clean. The code-reviewer, silent-failure, lifecycle, type-design and test reports were all clean. Mutation: 12 of 12 mutants killed.
Prior findings status
- #39 [Medium]
CategoryKeyonunknown→ RESOLVED in 8c13e10 (anunknown/anyindex accepts any key; tested by tsc). - #40 [Medium] Index-keyed fields → RESOLVED in 8c13e10 (named by series key; literal-name and two-series tests).
- #41 [Medium] Locale test overclaims → RESOLVED in 8c13e10 (renamed to what it checks, with a comment on why).
- #42 [Medium] Row-carried labels untested → RESOLVED in 8c13e10.
marks.test.tsxpins that each renderer reads its label from the row orvalue. The intermediate frame can't be observed in happy-dom, and the test header says so. - #43 [Low] Warn-once claim → RESOLVED in 8c13e10 (comment reworded; JSON-keyed, so a newline no longer splits a warning).
- #44 [Low] Reserved key corrupts its plot → RESOLVED in 8c13e10 (no markers drawn when a key is reserved).
- #45 [Low] Reserved-key warning only while ready → RESOLVED in 8c13e10 (warns in any state; tested).
- #46 [Low] Numeric-subtype index → RESOLVED in 8c13e10.
- #47 [Low] Redundant guards and docs → RESOLVED in 8c13e10.
New findings
- [Medium] [Lens 3] — Marker labels on the first or last point of a line or area chart are centred on the plot edge, so they are likely clipped by the SVG or run over the Y axis
- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L30 - Failure mode: a category axis without bars uses a point scale, so the end points sit on the plot's edges.
- Suggested fix: anchor the label start or end at the extremes.
- Verification: UNVERIFIED — would a browser clip a last-row label?
- Location:
- [Medium] [Lens 16] — The
keptMarkersdocstring ("the one rule") and the README markers row omit the reserved-key drop- Location:
frontend/src/v1/components/ui/chart/markers.ts:L48-L55,README.md:L94 - Verification: VERIFIED.
- Location:
- [Low] [Lens 16] — "would be overwritten" overstates the reserved-key collision (it "could" be), and so does the test title
- Location:
frontend/src/v1/components/ui/chart/markers.ts:L63-L64
- Location:
- [Low] [Lens 16] — The
marks.test.tsxheader claims the tests pin that a renderer "is given nothing else"- Location:
frontend/src/v1/components/ui/chart/marks.test.tsx:L6-L14
- Location:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — See findings #48
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean (type probes broad and clean; orchestrator consumer search: none)
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — Clean (12/12 mutants killed)
- Dependencies & build — Clean (orchestrator)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #49, #50, #51
- Cross-document rule consistency — See findings #49 (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=04:47 lenses-examined=13 of 19 files=28 lines=4043
- A line or area marker on the first or last point anchors its label inward, so it is not cut off at the plot edge or drawn over the axis. - Docs: keptMarkers' rule includes the reserved-key drop (README too); "could be overwritten"; marks.test header no longer overclaims. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 6 — tip a9662c565 (review-deep, FOLLOWUP)
Delta under judgement: 45c4e8f17..a9662c565.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0. Since boundary: 45c4e8f17.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 1 · Low: 4 · Lenses run: 19/19 (N/A ones with reasons below)
Security deep-dive: done (0 findings, after examination). boundary-sweep: not triggered (no new sink in the delta). Pinned Biome: clean. The core, silent-failure and lifecycle reviews were clean.
Prior findings status
- #48 [Medium] Edge labels clipped → PARTIALLY RESOLVED in a9662c5. Row 0 and the last row now anchor inward, but a long label one row in still overflows. See #52.
- #49 [Medium] Drop rule omitted the reserved key → RESOLVED in a9662c5.
- #50 [Low] "would be overwritten" → RESOLVED in a9662c5.
- #51 [Low]
marks.testheader → RESOLVED in a9662c5.
New findings
- [Medium] [Lens 3/16] — Anchoring keys on the row index, not the distance to the edge, so a long label one row in from either edge still overflows
- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L86-L94 - Failure mode: on a 30-day chart, a label on row 1 or row 28 runs into the axis or is clipped. The docstring claims the label stays inside the chart.
- Suggested fix: anchor by distance, from the estimated half-width of the label to the plot edge.
- Verification: UNVERIFIED, since happy-dom does not measure text.
- Location:
- [Low] [Lens 16] — The
MarkerMarkdocstring says "first and last point", while the code works by row and centres a single-row chart- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L15-L19
- Location:
- [Low] [Lens 7] — The public
ChartMarkerdoc points at a module consumers can't import, and no type doc mentions the reserved__markerprefix- Location:
frontend/src/v1/components/ui/chart/types.ts:L79-L84
- Location:
- [Low] [Lens 13] — The label's 4px x nudge is untested; dropping it or flipping its direction survives
- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L45
- Location:
- [Low] [Lens 13] — No real-chart test pins anchoring; it relies on Recharts passing
pointsfor every row- Location:
frontend/src/v1/components/ui/chart/charts.test.tsx:L293-L317
- Location:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — See findings #52
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — See findings #54
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #55, #56
- Dependencies & build — Clean (orchestrator)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #53
- Cross-document rule consistency — Clean (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=03:21 lenses-examined=13 of 19 files=28 lines=4091
- A line/area marker label reads inward whenever, centred, it would cross the plot's edge (estimated from its length), not only on the first and last row. - Public type docs state the marker rules and the reserved __marker prefix. - Tests: label x offset and anchoring in unit tests, and anchoring through a real line and area chart. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 7 — tip 0d2f1c1c9 (review-deep, FOLLOWUP)
Delta under judgement: a9662c565..0d2f1c1c9.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0. Since boundary: a9662c565.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 2 · Low: 6 · Lenses run: 19/19 (N/A ones with reasons below)
Security deep-dive: done (0 findings, after examination). boundary-sweep: not triggered. Pinned Biome: clean. The core and lifecycle reviews were clean.
Prior findings status
- #52 [Medium] Row-index anchoring → RESOLVED in 0d2f1c1 (anchored by distance to the plot edge; tested).
- #53 [Low] Docstring rows vs points → RESOLVED in 0d2f1c1.
- #54 [Low] Public marker doc → RESOLVED in 0d2f1c1 (rules and reserved prefix in the type docs). The warning clause is inexact, see #60.
- #55 [Low] Nudge untested → RESOLVED in 0d2f1c1.
- #56 [Low] No real-chart anchor test → RESOLVED in 0d2f1c1.
New findings
- [Medium] [Lens 3] — Bar and stacked-bar marker labels are always centred, so on the first or last bar a long label crosses the plot edge
- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L173-L189 - Note: LabelList content receives
parentViewBox, which supplies the bounds.
- Location:
- [Medium] [Lens 13] — The anchor test doesn't pin the half-width threshold
- Location:
frontend/src/v1/components/ui/chart/marks.test.tsx:L72-L97 - Failure mode: comparing the full width, or a char width of 14, survives.
- Verification: VERIFIED — mutation.
- Location:
- [Low] [Lens 3] — A label wider than the space on both sides of the dot reads
start, which overflows more than centring would- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L92-L99
- Location:
- [Low] [Lens 16] — The
ChartMarkerdoc says every drop warns, but warnings wait for the ready state- Location:
frontend/src/v1/components/ui/chart/types.ts:L83-L90
- Location:
- [Low] [Lens 16] — "so it is not cut off" overstates what the anchoring guarantees
- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L15-L20
- Location:
- [Low] [Lens 7] —
labelAnchoris exported but nothing imports it- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L77
- Location:
- [Low] [Lens 7] — The reserved prefix is only enforced at runtime
- Location:
frontend/src/v1/components/ui/chart/types.ts:L72 - Note: optional compile-time
Exclude.
- Location:
- [Low] [Lens 13] — The strict
<vs<=boundary is unpinned- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L94
- Location:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — See findings #57, #59
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — See findings #62, #63
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #58, #64
- Dependencies & build — Clean (orchestrator)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #60, #61
- Cross-document rule consistency — Clean (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=04:06 lenses-examined=13 of 19 files=28 lines=4163
- labelAnchor takes plot bounds: line/area read them from the first and last row, bars from Recharts' SVG box less the margin and Y-axis insets, so bar marker labels read inward near the edges as well. - A label wider than the room on both sides stays centred. - Docs: drop warnings only once the chart shows data; reserved prefix is a runtime check by design; labelAnchor is module-private. - Tests pin the half-width threshold, the exact boundary, both-sides case, bar insets, and anchoring through a real bar chart. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 8 — tip 8720d2895 (review-deep, FOLLOWUP)
Delta under judgement: 0d2f1c1c9..8720d2895.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0. Since boundary: 0d2f1c1c9.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 2 · Low: 5 · Lenses run: 19/19 (N/A ones with reasons below)
Security deep-dive: done (0 findings, after examination). boundary-sweep: not triggered. Pinned Biome: clean. The core and lifecycle reviews were clean.
Prior findings status
- #57 [Medium] Bar labels not anchored → RESOLVED in 8720d28 (tested through a real bar chart).
- #58 [Medium] Threshold unpinned → RESOLVED in 8720d28 (half-width and the exact left boundary pinned).
- #59 [Low] Wider-than-both-sides case → RESOLVED in 8720d28. A narrower variant remains, see #65.
- #60 [Low] Warning clause → RESOLVED in 8720d28.
- #61 [Low] "Not cut off" overstated → RESOLVED in 8720d28.
- #62 [Low]
labelAnchorexport → RESOLVED in 8720d28. - #63 [Low] Reserved prefix only checked at runtime → WAIVED by this reviewer. Runtime check by design, documented at
types.tsChartSeries.key. - #64 [Low]
<vs<=→ RESOLVED for the left edge in 8720d28. The right edge is still unpinned, see #71.
New findings
- [Medium] [Lens 3] — An inward-anchored label can overflow the opposite edge by more than centring would
- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L85-L93 - Failure mode: the flip checks the half-width but not the full width it then needs.
- Suggested fix: choose the anchor with the least overflow.
- Verification: VERIFIED by trace (found by two agents).
- Location:
- [Medium] [Lens 13] — The right-axis term of the bar-marker inset is untested
- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L195 - Failure mode: removing, inverting or always adding it all survive.
- Verification: VERIFIED — mutation.
- Location:
- [Low] [Lens 7] — The bar plot bounds are rebuilt from layout constants plus Recharts' undocumented
parentViewBoxmeaning- Location:
frontend/src/v1/components/ui/chart/CartesianChart.tsx:L191-L197 - Suggested fix:
usePlotArea()would read the bounds directly.
- Location:
- [Low] [Lens 7] —
labelAnchortypes its bounds asunknown, wider than either caller needs- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L75-L82
- Location:
- [Low] [Lens 16] — The
MarkerMarkdocstring overstates when the label moves off-centre- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L16-L17
- Location:
- [Low] [Lens 16] — "once the chart shows data" can be read as a count
- Location:
frontend/src/v1/components/ui/chart/types.ts:L90
- Location:
- [Low] [Lens 13] — The right-edge boundary is unpinned
- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L85
- Location:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — See findings #65
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — See findings #67, #68
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #66, #71
- Dependencies & build — Clean (orchestrator)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #69, #70
- Cross-document rule consistency — Clean (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=03:44 lenses-examined=13 of 19 files=28 lines=4282
…ts (round 8) - labelAnchor picks whichever of middle/start/end leaves the least of the label outside the plot (ties centred), so reading inward never spills further off the far edge than centring would. - BarMarker reads the plot area from Recharts' usePlotArea() instead of restating margins and axis widths; the inset prop is gone. - Bounds typed number | null | undefined; doc wording fixes. - Tests: right-axis plot area through a real bar chart, narrow plots, both exact boundaries, and the label nudge. Co-Authored-By: Claude Opus 5.5 <[email protected]>
for more information, see https://pre-commit.ci
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 9 — tip a30a61b76 (review-deep, FOLLOWUP)
Delta under judgement: 8720d2895..a30a61b76, plus the pre-commit.ci formatting commit 15796d64a (test file only). The review is pinned to that head.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0. Since boundary: 8720d2895.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 1 · Low: 3 · Lenses run: 19/19 (N/A ones with reasons below)
Security deep-dive: done (0 findings, after examination). boundary-sweep: not triggered. Pinned Biome: clean. The core, silent-failure, lifecycle and type-design reviews were clean. Mutation: 15 of 17 mutants killed; the 2 survivors are #74.
Prior findings status (thread replies already posted)
- #64 [Low]
<vs<=boundary → RESOLVED in a30a61b (both edges pinned). - #65 [Medium] Inward flip overflowed the opposite edge → RESOLVED in a30a61b (least-overflow anchoring).
- #66 [Medium] Right-axis inset untested → OBSOLETE. The inset is gone; the plot area comes from
usePlotArea(), and a right-axis test covers it. - #67 [Low] Bounds restated from constants → RESOLVED in a30a61b.
- #68 [Low]
unknownbounds → RESOLVED in a30a61b. - #69 [Low]
MarkerMarkdocstring → RESOLVED in a30a61b. - #70 [Low] "once" → "when" → RESOLVED in a30a61b. Two other restatements remain, see #73.
- #71 [Low] Right boundary → RESOLVED in a30a61b.
New findings
- [Medium] [Lens 16] — The
labelAnchordocstring says a label too wide for either side "stays centred", but the code and a test pickstartnear an edge- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L82-L84
- Location:
- [Low] [Lens 17] — "once" remains in
markers.ts:55andREADME.md:94- Location:
frontend/src/v1/components/ui/chart/README.md:L94 - Made stale by:
types.ts:L90
- Location:
- [Low] [Lens 13] — The
endcandidate's nudge is unpinned- Location:
frontend/src/v1/components/ui/chart/marks.tsx:L102
- Location:
- [Low] [Lens 13] — The right-axis bar test passes by only 1.3px
- Location:
frontend/src/v1/components/ui/chart/charts.test.tsx:L753-L777
- Location:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — Clean
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean (usePlotArea public across ^3.5.1; barrel unchanged)
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #74, #75
- Dependencies & build — Clean (orchestrator)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #72
- Cross-document rule consistency — See findings #73 (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=05:42 lenses-examined=13 of 19 files=28 lines=4293
- labelAnchor docstring states only the least-overflow rule (the summary sentence contradicted it); "when", not "once", in markers.ts and README. - Pin the end label's nudge; widen the right-axis bar test's margin. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 10 — tip 0749bfe1d (review-deep, FOLLOWUP) — circuit breaker reached
Delta under judgement: 15796d64a..0749bfe1d (a docstring, two doc wordings and tests).
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0. Since boundary: 15796d64a.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 1 · Low: 1 · Lenses run: 19/19 (N/A ones with reasons below)
The delta itself is clean. The core, silent-failure, type-design, lifecycle, comment and test reviews all found nothing, and both new tests kill their mutants. Security deep-dive: done (0 findings). Pinned Biome: clean. The whole-diff cold read raised two findings on code from round 1 that no earlier round flagged.
Prior findings status
- #72 [Medium] Docstring summary contradicted the rule → RESOLVED in 0749bfe.
- #73 [Low] "once" in two restatements → RESOLVED in 0749bfe.
- #74 [Low] End nudge unpinned → RESOLVED in 0749bfe (mutants killed).
- #75 [Low] Right-axis test margin was 1.3px → RESOLVED in 0749bfe (about 4–5px).
New findings
- [Medium] [Lens 3] — Loading and empty states are only the plot's height, but a ready chart adds the legend row and gap, so it grows about 28px when data arrives and shifts the content below
- Location:
frontend/src/v1/components/ui/chart/ChartFrame.tsx:L56-L100 - Suggested fix: reserve the legend row in the loading and empty branches when a legend is configured.
- Verification: VERIFIED by reading the branches.
- Location:
- [Low] [Lens 3/16] — Sparkline tooltip's
labelOfreads the category from props, contrary to the rulemarks.tsxstates for renderers- Location:
frontend/src/v1/components/ui/chart/Sparkline.tsx:L88-L92 - Failure mode: for a frame after a data change, the heading can name another row.
- Location:
Circuit breaker
This is round 10 of 10, and the ready test is not met: #76 is above the floor. Per the self-review rules this run stops here. The PR stays a draft, and the fix is described rather than made, since a fix landed at the cap would itself be unreviewed. Case: a fix is still owed. #76 is a small, contained change: a placeholder legend row in the ChartFrame loading and empty branches. #77 is a one-line change: read the category from payload[0].payload. A human should decide whether to authorise one more fix-and-review round.
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — See findings #76, #77
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — Clean (mutants killed)
- Dependencies & build — Clean (orchestrator)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #77
- Cross-document rule consistency — Clean (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=04:11 lenses-examined=13 of 19 files=28 lines=4305
…he hovered row (round 10) - Loading and empty lay out the legend invisibly (aria-hidden) where the ready chart has it, so a card does not jump when data arrives. - Sparkline tooltip reads its category from the row Recharts hands over (categoryKey), so heading and values always describe the same row. - Tests: footprint across states and positions, ChartTooltip unit tests. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 11 — tip 6dfc936d5 (review-deep, FOLLOWUP; rounds 11–15 authorised by Ritwik)
Delta under judgement: df4e457a0..6dfc936d5 (the fixes for #76 and #77). The merge of origin/main (df4e457a0) brought in no src/v1 changes.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0. Since boundary: 0749bfe1d.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 2 · Low: 3 · Lenses run: 19/19 (N/A ones with reasons below)
Security deep-dive: done (0 findings, after examination). boundary-sweep: not triggered. Pinned Biome: clean. The core, silent-failure, type-design and lifecycle reviews were clean.
Prior findings status
- #76 [Medium] Loading/empty footprint → RESOLVED in 6dfc936. The legend is laid out invisibly and the footprint is tested. The claim is only true for fixed series, see #78.
- #77 [Low] Sparkline heading read from props → RESOLVED in 6dfc936. It now reads from the hovered row; unit-tested.
New findings
- [Medium] [Lens 16] — "Keeps the ready chart's footprint" holds only when
seriesis known before the data- Location:
frontend/src/v1/components/ui/chart/README.md:L165-L166,ChartFrame.tsx:L65-L67 - Failure mode: series built from the response (pivoted rows) reserve nothing while loading.
- Location:
- [Medium] [Lens 13] — No test fails on the pre-change Sparkline tooltip
- Location:
frontend/src/v1/components/ui/chart/ChartTooltip.test.tsx:L10-L31 - Failure mode: the one-frame divergence it closes can't be observed in happy-dom. Either state that, or describe the change as a refactor.
- Location:
- [Low] [Lens 13] — The footprint test doesn't pin "one legend"; a duplicated reserved legend survives
- Location:
frontend/src/v1/components/ui/chart/charts.test.tsx:L795-L800
- Location:
- [Low] [Lens 13] — The non-object-row guard in
ChartTooltipis unexercised- Location:
frontend/src/v1/components/ui/chart/ChartTooltip.tsx:L57
- Location:
- [Low] [Lens 16] — The footprint test's name and order comment claim more than its assertions check
- Location:
frontend/src/v1/components/ui/chart/charts.test.tsx:L780
- Location:
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — Clean
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean (barrel unchanged)
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #79, #80, #81
- Dependencies & build — Clean (orchestrator; main merged, no dependency change)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #78, #82
- Cross-document rule consistency — Clean (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=04:09 lenses-examined=13 of 19 files=29 lines=4421
…ightened - Footprint docs (README, ChartFrame) hold only when series are known before the data; say so and how to keep a card still otherwise. - ChartTooltip tests state they pin the mechanism (the frame is not observable in happy-dom) and cover a missing hovered row. - Footprint test asserts exactly one legend on the ready chart's side. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ooltip key guard tested Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 12 — tip e04a78761 (review-deep, FOLLOWUP; rounds 11–15 authorised by Ritwik)
Delta under judgement: 6dfc936d5..e04a78761 (the round-11 fixes for #78–#82: docs, comments and tests). Posted against head f7bcfcc60, which carries this round's fixes; those are round 13's delta.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0, with --prev-sha 6dfc936d5.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 3 · Low: 2 · Lenses run: 19/19 (the N/A lenses give reasons below)
Security deep-dive: done (0 findings, after examination). Boundary sweep: not triggered. Pinned Biome: clean. Suite: 95/95, then 96/96 with this round's fixes. The core, silent-failure, type-design and lifecycle reviews were clean.
Prior findings status
- #78 Footprint claim scoped to series known before the data → RESOLVED in e04a787.
- #79 Tooltip test stated as a mechanism test → RESOLVED in e04a787.
- #80 Exactly one legend asserted → RESOLVED in e04a787 (mutation-verified: a duplicated legend is killed).
- #81 Missing-row guard exercised → RESOLVED in e04a787 (mutation-verified).
- #82 Test name and comment narrowed → RESOLVED in e04a787.
New findings
- [Medium] [Lens 13] — "Never from the label" is untested for a row that lacks the category key
- Location:
frontend/src/v1/components/ui/chart/ChartTooltip.tsx:L58 - Failure mode: the mutant
row[categoryKey] ?? labelsurvives all four tooltip tests, so a row without the key would show the row index as the heading. Fixed in f7bcfcc with a new case, which kills that mutant.
- Location:
- [Medium] [Lens 3, 16] — Ramp steps are separable only by lightness, 1.24–1.40:1 between neighbours
- Location:
frontend/src/v1/components/ui/chart/README.md:L125 - Failure mode: untoned series that are not an order (this week and last week) on a line or a grouped bar are hard to tell apart, and "do not go past five" implies five are separable. Ratios were recomputed from tokens.json with the WCAG luminance formula. Fixed in f7bcfcc (README guidance: give such series tones; stacks stay on the ramp). The ramp values themselves are still pending lane C and designer sign-off.
- Location:
- [Medium] [Lens 16] — The README recommends an
EmptyStatecomponent that does not exist, in a box that does not grow- Location:
frontend/src/v1/components/ui/chart/README.md:L82 - Failure mode: the slot is centred in a fixed
heightbox (40px on a sparkline) and replaces the screen-reader title prefix. Fixed in f7bcfcc: the docs now say the slot must fit and must name the chart.
- Location:
- [Low] [Lens 16] — The tooltip test header reads as if it covers every test —
ChartTooltip.test.tsx:L15. Fixed in f7bcfcc ("the category-key tests below"). - [Low] [Lens 16] — "No chart-level test can see it" is stated without evidence —
ChartTooltip.test.tsx:L14. Kept: rounds 9–10 tried a chart-level data swap under happy-dom and could not observe the frame. That is the same basis as themarks.test.tsxheader.
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — See findings #84
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean (barrel and types unchanged)
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #83
- Dependencies & build — Clean (orchestrator; no dependency change)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #84, #85, #86, #87
- Cross-document rule consistency — Clean (orchestrator; README matches types.ts and the v1 README)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=06:22 lenses-examined=13 of 19 files=29 lines=4454
…table data, emptySlot JSDoc, null-row test Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 13 — tip f7bcfcc60 (review-deep, FOLLOWUP; rounds 11–15 authorised by Ritwik)
Delta under judgement: e04a78761..f7bcfcc60 (the round-12 fixes for #83–#86). Posted against head 59e21f566, which carries this round's fixes; those are round 14's delta.
Verdict — REQUEST CHANGES
Prior context fetched — followup-fetch.sh exited 0, with --prev-sha e04a78761.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 4 · Low: 2 · Lenses run: 19/19 (the N/A lenses give reasons below)
Security deep-dive: done (0 findings, after examination). Boundary sweep: not triggered. Pinned Biome: clean. Suite: 96/96, then 97/97 with this round's fixes. The lifecycle and silent-failure reviews were clean.
Prior findings status
- #83 → RESOLVED in f7bcfcc. Mutation-verified: the
?? labeland key-presence fallbacks are now killed. - #84 → RESOLVED in f7bcfcc (README); refined by #89.
- #85 → RESOLVED in f7bcfcc (README); the JSDoc half is #88.
- #86 → RESOLVED in f7bcfcc.
- #87 → WAIVED: the claim rests on the round 9–10 attempt, as recorded in round 12.
New findings
- [Medium] [Lens 16] — The
emptySlotJSDoc still recommends the nonexistentEmptyStateand omits the fit and naming rules- Location:
frontend/src/v1/components/ui/chart/types.ts:L134 - Fixed in 59e21f5.
- Location:
- [Medium] [Lens 16, 2] — "A
toneeach" leaves only meaning-bearing tones past two series- Location:
frontend/src/v1/components/ui/chart/README.md:L132 - Fixed in 59e21f5. Two non-ordered series use
primaryandmuted; with more, chart them separately or group. "Or use tones" is dropped from the five-step rule.
- Location:
- [Medium] [Lens 3] — In the Unstract theme,
primary(#072b4d) is 1.45:1 from--chart-5and 1.79:1 from--chart-4- Location:
frontend/src/v1/components/ui/chart/README.md:L140 - Ratios recomputed with the WCAG formula from tokens.json. Documented in 59e21f5 (keep
primaryoff ramp charts). Changing the token values is for the designer and lane C, and is added to the PR's sign-off list.
- Location:
- [Medium] [Lens 16] — The README says inline
seriesandmarkersare safe but says nothing aboutdata, whose identity matters- Location:
frontend/src/v1/components/ui/chart/README.md:L104 - Fixed in 59e21f5 with the mechanism already stated in
CartesianChart.tsx: a new array recomputes the rows and resets Recharts' data store. No claim is made about the tooltip state.
- Location:
- [Low] [Lens 3] — A lone untoned series gets the faintest step in light mode —
palette.ts. Not changed: this is a design choice for the ramp sign-off, and it is added to that list. - [Low] [Lens 13] — The
row !== nullguard was untested —ChartTooltip.tsx:L57. Fixed in 59e21f5 (apayload: nullcase, which kills the mutant).
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — See findings #89
- Correctness & edge cases — See findings #90, #92
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean (no exported change)
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #93
- Dependencies & build — Clean (orchestrator; no dependency change)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #88, #89, #91
- Cross-document rule consistency — See findings #88
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 wall=04:22 lenses-examined=13 of 19 files=29 lines=4475
[Low impact] The PR appears safe to merge, with no new actionable issue found.
|
Greptile: Sparkline accepted ChartSeries.axis but drew every line on one hidden Y axis, so a small-valued series beside a large one went flat. A right-axis series now scales on its own hidden axis, as on the axis charts. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Deepak-Kesavan
left a comment
There was a problem hiding this comment.
🤖 Automated review
Automated review by Unstract PR review kit (Claude Code). These are starting points, not verdicts: the maintainer decides what to act on, closing any of them is a legitimate outcome, and nothing here blocks the merge. Each finding was checked by an independent verifier; anything tagged [unverified] had its facts confirmed but is a judgement call.
26 inline comment(s) · 1 finding(s) not on changed lines (below).
Findings off the diff
frontend/scripts/check-v1-styles.mjs:104
[minor] CONFIRMED · (pre-existing, file not in this diff) The S2 checker lets a named CSS colour through as a Recharts prop when the name is not on its 31-name list, so the PR's new colour surface (fill/stroke/activeDot props) is only partly guarded.
A later chart edit writes , , activeDot={{ stroke: "darkblue" }} or const TICK = { fill: "steelblue" }. lint:v1 passes and v1 ships a raw colour that ignores light, dark and Unstract modes. This PR's README and v1/README.md say chart colours come only from --chart-1..5 and the semantic tokens.
Suggested fix: List all 148 CSS named colours in NAMED_COLOURS, at least for the whole-literal check (a string that is only a named colour). Or, in chart/, require fill/stroke values to match var(--token) or none/transparent/currentColor.
🤖 Unstract PR review kit (Claude Code) · review-pr-bot:f25e16b969cb
review-pr-bot:review
# Conflicts: # frontend/src/v1/README.md # frontend/src/v1/styles/__snapshots__/token-table.md # frontend/src/v1/styles/index.css # frontend/src/v1/styles/tokens.css # frontend/src/v1/styles/tokens.json # frontend/src/v1/styles/tokens.test.ts # frontend/src/v1/styles/tokens.ts
From Deepak's review on #2446: - stacked bars use stackOffset="sign", so a negative segment stacks down from zero instead of inside the positive one (4510266843); - a marker on a negative bar reads its label under the tip, clamped to the plot so it does not hit the X labels (4510266848); - a ready chart whose every series value is a gap shows the empty state, in CartesianChart and Sparkline (4510266955). Also pins, with tests: gaps break the line/area/sparkline (connectNulls), xFormat reaches the X ticks, className on every state, grid on/off, the empty state's screen-reader title, and the sparkline right axis's dataMin..dataMax domain (a fixture whose minimum is far from zero). Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ot "no data" Self-review of dc35a40: - a negative bar whose tip is on the plot's bottom edge clamped its label onto its own dot; it now reads beside the dot, on whichever side fits; - hasValues counts any present value (an unparsed numeric string too), so only null, missing and non-finite series values read as the empty state; pinned directly in describe.test.ts; - marker docs no longer say "on top" for bars below zero. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 23 — tip dc35a4084 (review-deep, FOLLOWUP; Ritwik's go-ahead of 10 Nov)
Delta under judgement:
f6e4d18dd..dc35a4084: the merge oforigin/main(a09a3d5, with conflicts resolved infrontend/src/v1/styles/*andfrontend/src/v1/README.md) and the fixes for Deepak's review 5822680995.- The fixes are the mixed-sign stack (4510266843), the negative-bar marker (4510266848) and the all-gap empty state (4510266955), plus test-only gaps.
Posted against head ae87a9f52, which holds this round's fixes; round 24 reviews them.
Verdict — REQUEST CHANGES (two Mediums, fixed in ae87a9f).
Prior context fetched — followup-fetch.sh exited 0, with --prev-sha f6e4d18dd.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 2 · Low: 3 · Lenses run: 19/19 (the N/A lenses give reasons below)
Security deep-dive: done (0 findings, after examination). Boundary sweep: not triggered. Pinned Biome: clean. The v1 suite passes 560/560 (after bun install --frozen-lockfile for main's new @tanstack/react-virtual).
Merge resolution was verified by three reviewers: both sides kept, and 45 colour tokens (30 + 10 code/diff + 5 chart), matching the test. The lifecycle and type reviews were clean. Recharts 3.7.0 confirms that a negative bar's y is its lower tip, for plain bars and for stackOffset="sign".
Every new fix and test is mutation-verified:
- stack offset;
- marker label position and clamp;
hasValuesin each chart;connectNullson line, area and sparkline;- the xFormat ticks,
className, the grid and the right-axis domain.
New findings
- [Medium] [Lens 3, 16] — At the plot's bottom edge the clamp put a negative bar's label baseline on its own dot, and the test accepted it
- Location:frontend/src/v1/components/ui/chart/marks.tsx:L223
- Found by four reviewers.
- Fixed in ae87a9f: where there is no room under the tip, the label reads beside the dot, on whichever side fits. The tests require the glyphs to clear the dot, check that the fixture's tip really is at the edge, and cover both sides. Mutants for no side branch, always the clamp, a zero gap, either side forced, and an ignoredlabelXare all killed. - [Medium] [Lens 3] —
hasValuesread unparsed numeric strings (DRF Decimals in pivoted rows, whichNumericKeyallows) as gaps, so a chart with data showed "No data for this period"
- Location:frontend/src/v1/components/ui/chart/describe.ts:L57
- Fixed in ae87a9f: any present value counts. Only null, missing and non-finite series values are gaps.
- Unit tests indescribe.test.tspin that only the series' own keys are read (closes #139).
- Follow-up, recorded in the PR body: a dev warning when a series key is missing from every row. - [Low] [Lens 13] — Nothing pinned that
hasValuesreads only series keys. Fixed in ae87a9f. - [Low] [Lens 16, 17] — The marker prose said "on top" / "label above", which is wrong for bars below zero —
README.md,marks.tsx,markers.ts. Fixed in ae87a9f. - [Low] [Lens 16] — The test comment's "40 minus 4 + 4" read as 40. Fixed in ae87a9f.
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — See findings #137, #138
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean (
hasValuesinternal; token contract intact after merge) - Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #139
- Dependencies & build — Clean (main's new dependency installs from the lockfile; nothing added here)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #141
- Cross-document rule consistency — See findings #140
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 lenses-examined=13 of 19 files=29 lines=4971
…y in the plot; one gap rule Self-review of ae87a9f: - a beside-the-dot label now takes the side where less of it leaves the plot (as labelAnchor does) and its baseline is held within the plot's bottom, so a tip exactly on the edge does not push it into the X labels; - hasValues goes back to numberAt, so the empty state, tooltip, table, summary and markers share one rule for a gap (an unparsed string is one; parse first, as the README now says); - marker docs say "on its tip" rather than "over the bar". Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 24 — tip ae87a9f52 (review-deep, FOLLOWUP)
Delta under judgement: dc35a4084..ae87a9f52 (beside-the-dot label for bottom-edge negative bars; hasValues counting strings; doc wording). Posted against head 05ac6c317, which holds this round's fixes; round 25 reviews them.
Verdict — REQUEST CHANGES (fixed in 05ac6c3).
Prior context fetched — followup-fetch.sh exited 0, with --prev-sha dc35a4084.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 4 · Low: 4 · Lenses run: 19/19 (the N/A lenses give reasons below)
Security deep-dive: done (0 findings, after examination). Boundary sweep: not triggered. Pinned Biome: clean. The v1 suite passes 562/562. The lifecycle review was clean. On the type review: no exported contract change.
Prior findings status
- #137 → RESOLVED in ae87a9f, then completed in 05ac6c3 (#142, #143).
- #138 → OBSOLETE: ae87a9f counted strings as data, which split the gap rule (#144). 05ac6c3 restores one rule and documents "parse first".
- #139, #140, #141 → RESOLVED in ae87a9f.
New findings
- [Medium] [Lens 3] — The beside label was not clamped: a tip exactly on the bottom edge (a minimum on a nice tick) put its baseline 4px into the X axis gutter
- Location:frontend/src/v1/components/ui/chart/marks.tsx:L249
- Fixed in 05ac6c3 (Math.min(y + LABEL_MIDLINE, bottom)). A ±20 fixture puts the tip on the edge. - [Medium] [Lens 3] — When the label did not fit on the right it always went left, even where left overflowed more (into the Y ticks)
- Location:frontend/src/v1/components/ui/chart/marks.tsx:L243
- Fixed in 05ac6c3: the label takes the side with less overflow, aslabelAnchordoes.
- Tests cover a narrow chart (right side), the third of four bars (left side) and the rightmost bar (left side). - [Medium] [Lens 3, 16] — Two meanings of "gap":
hasValuescounted strings as data while the tooltip, table, summary and markers read them as gaps
- Location:frontend/src/v1/components/ui/chart/describe.ts:L59
- An all-string chart was "ready" with a summary of "no values".
- Fixed in 05ac6c3:hasValuesusesnumberAtagain. The READMEseriesrow says what a gap is and to parse numeric strings first, because Recharts may still draw them. A dev warning for unparsed values is a follow-up. - [Medium] [Lens 13] — The beside label's vertical position and the end side's anchor were unpinned (dropping
yor the"end"anchor survived)
- Fixed in 05ac6c3: the tests assert the baseline sits between the dot's centre and 4px below it, and check the anchor on each side. - [Low] [Lens 16] — "The lowest bar, whose tip sits on the bottom edge" and "on whichever side it fits" overstated the code. Reworded in 05ac6c3.
- [Low] [Lens 16] —
markers.tsandBarMarkerstill said "over its own bar". Fixed in 05ac6c3. - [Low] [Lens 2] — The
placetype admits anxwithout ay. Module-local, with three construction sites; left as is. - [Low] [Lens 16] — The test comment's dot extent says "+6" where the ring's outer edge is 5. The assertion is stricter than needed and still sound; left as is.
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — See findings #148
- Correctness & edge cases — See findings #142, #143, #144
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean (no exported change)
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #145
- Dependencies & build — Clean (orchestrator; no dependency change)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #146, #147, #149
- Cross-document rule consistency — Clean (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 lenses-examined=13 of 19 files=29 lines=5020
…ward bottom-edge label Self-review of 05ac6c3: - a series holding values that are not numbers (an unparsed DRF Decimal) now logs a console warning naming it, in any state, from CartesianChart and Sparkline: those values are gaps, so a forgotten parse would otherwise show "No data for this period" with nothing to say why; - the rightward bottom-edge label is pinned beside the dot and within the plot (a tip exactly on the edge), as the leftward one already was; - BarMarker's docs and a test comment describe the placement accurately. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 25 — tip 05ac6c317 (review-deep, FOLLOWUP)
Delta under judgement: ae87a9f52..05ac6c317. It holds three changes: the least-overflow side and the clamp for bottom-edge labels, one gap rule (hasValues on numberAt), and the docs. The review is posted against head 8f5bd9a78, which holds this round's fixes. Round 26 reviews them.
Verdict — REQUEST CHANGES (fixed in 8f5bd9a).
Prior context fetched — followup-fetch.sh exited 0, with --prev-sha ae87a9f52.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 2 · Low: 4 · Lenses run: 19/19 (the N/A lenses give reasons below)
Security deep-dive: done (0 findings, after examination). Boundary sweep: not triggered. Pinned Biome: clean. The v1 suite passes 563/563.
The core, lifecycle and type reviews were clean. The core review confirmed that the side choice, the clamp and the single gap rule hold, and that the docs match. The test runner killed 25 of 28 mutants: one is equivalent, and the two start-branch mutants are #151.
Prior findings status
- #142 → RESOLVED in 05ac6c3. The rightward side was pinned in 8f5bd9a (#151).
- #143 → RESOLVED in 05ac6c3.
- #144 → RESOLVED in 05ac6c3. The silent side of it is #150.
- #145 → RESOLVED in 05ac6c3.
- #146, #147 → RESOLVED in 05ac6c3.
New findings
- [Medium] [Lens 3] — With one gap rule, a payload the page forgot to parse shows "No data for this period" with nothing in the console
- Location:frontend/src/v1/components/ui/chart/describe.ts:L60
- Raised by the cold read and the silent-failure review.
- Fixed in 8f5bd9a:unparsedWarningslogs one warning per series that holds a value that is not a number. It fires in any state, from CartesianChart and from Sparkline. The single gap rule stays.
- Tests: a chart-level test covers both charts, and a unit test checks it never warns for gaps. Removing the warning from either chart fails. - [Medium] [Lens 13] — The rightward bottom-edge label was not pinned beside the dot or within the plot
- Location:frontend/src/v1/components/ui/chart/marks.tsx:L253
- Two mutants survived:yat the dot's centre, and the clamp dropped.
- Fixed in 8f5bd9a: a stricty > cyon the narrow-chart case, plus a rightward case with the tip exactly on the edge ([-20, 20, 20, 20]). Both mutants are killed. - [Low] [Lens 2] — The
NumericKeyJSDoc lists fewer places that read a string as a gap than the README does. The two do not contradict each other. Follow-up. - [Low] [Lens 16] — The
BarMarkerJSDoc said edge labels read "as on a line", and the paragraph was badly wrapped. Rewritten in 8f5bd9a. - [Low] [Lens 16] — A test comment called a tip 13px above the bottom "on the bottom edge". Fixed in 8f5bd9a: "within 16px; the domain bottoms out at -45".
- [Low] [Lens 3] — The second guard on
overRight(Math.max) is an equivalent mutant. No action.
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — See findings #152
- Correctness & edge cases — See findings #150, #155
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean (no exported change)
- Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — Clean
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #151
- Dependencies & build — Clean (orchestrator; no dependency change)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #153, #154
- Cross-document rule consistency — Clean (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 lenses-examined=13 of 19 files=29 lines=5125
…es whenever a value is unparsed Co-Authored-By: Claude Opus 5.5 <[email protected]>
ritwik-g
left a comment
There was a problem hiding this comment.
Self-review round 26 — tip 8f5bd9a78 (review-deep, FOLLOWUP)
Delta under judgement: 05ac6c317..8f5bd9a78. It adds an unparsedWarnings console warning in CartesianChart and Sparkline, pins the rightward bottom-edge label, and rewrites the docs.
Posted against head e8f4830af. That commit holds the two wording fixes below: one comment and one README sentence, with no code. They were checked by diff and by lint/Biome.
Verdict — APPROVE WITH NITS. No finding above Low; the loop ends here.
Prior context fetched — followup-fetch.sh exited 0, with --prev-sha 05ac6c317.
Scope change — NO
Summary — Critical: 0 · High: 0 · Medium: 0 · Low: 5 · Lenses run: 19/19 (the N/A lenses give reasons below)
Security deep-dive: done (0 findings, after examination). Boundary sweep: not triggered. Pinned Biome: clean. The v1 suite passes 565/565.
The core, lifecycle, type and silent-failure reviews found nothing above Low. The checks behind that:
- every unparsed value that empties a chart now warns;
- the effects re-fire only when the set of problems changes;
- hook order is intact;
- no contract changed.
The test runner killed 13 of 17 mutants. Of the survivors, one is equivalent for strings, and the other two are Lows below.
Prior findings status
New findings
- [Low] [Lens 16] — The Sparkline comment said the warning appears "in development", but nothing gates it. Raised by six reviewers. Fixed in e8f4830 ("in the console").
- [Low] [Lens 16] — The README tied the warning to the empty state, but it fires whenever any value is unparsed. Fixed in e8f4830.
- [Low] [Lens 13] — Nothing pins that the Sparkline warning logs once per change (it is keyed on a JSON string). Follow-up.
- [Low] [Lens 13] — The CartesianChart warning in the loading state is unpinned. Follow-up.
- [Low] [Lens 10] — The warning names the series but not which chart it came from. Follow-up.
Lens checklist
- Spec & intent — Clean
- Architectural fit & precedent — Clean
- Correctness & edge cases — Clean
- Security — Clean (security deep-dive done; orchestrator)
- Data integrity & migrations — N/A — no persistence
- Concurrency — N/A — synchronous rendering only
- API & contract compatibility — Clean (
unparsedWarningsinternal) - Reliability & resilience — N/A — no I/O
- Performance & cost — Clean
- Observability — See findings #160
- Operational safety — N/A — v1 not mounted
- LLM/agent-specific — N/A — no LLM code
- Testing — See findings #158, #159
- Dependencies & build — Clean (orchestrator; no dependency change)
- Code quality — Clean (pinned Biome clean; orchestrator)
- Doc & comment accuracy — See findings #156, #157
- Cross-document rule consistency — Clean (orchestrator)
- On-prem deployment experience — N/A — frontend v1 only
- Scaling & workload lifecycle — N/A — no workloads
cost: agents=8 lenses-examined=13 of 19 files=29 lines=5180
praveen-formido
left a comment
There was a problem hiding this comment.
Approving at e8f4830. The three points from Deepak's review (mixed-sign stacked bars, negative-bar marker labels, all-empty charts) each have a test, CI is green and the colours follow the LLMWhisperer design as agreed.
Non-blocking follow-ups from a read-through (not run against Recharts, so treat as things to check, not confirmed bugs):
- Hover/arrow-key moves re-render the whole chart body through the live-region setState, which re-runs the describe and table-row work; memoising on data/series would help on large series.
- A series with a constant value gives the sparkline a zero-width domain, so it may draw on the axis edge.
- Charts with only right-axis series still render the left axis and grid.
- Six or more untoned series reuse a ramp colour with no console warning.
Frontend Lint Report (Biome)✅ All checks passed! No linting or formatting issues found. |
|
Unstract test resultsPer-group results
Critical paths
|



What
frontend/src/v1/components/ui/chart/:LineChart,AreaChart,BarChart,StackedBarChartandSparkline(P1-T17).data,xKey,xLabel,series[{ key, label, tone?, axis?, format? }],title,summary?,state,emptySlot?,emptyMessage?,height,width?,valueFormatandxFormat.markers,legend,axisFormat,rightAxisFormatandgrid.aria-describedby, plus a visually hidden data table.--chart-1to--chart-5, is added to the v1 tokens for every mode and mapped through@theme inline. Its values are the LLMWhisperer design's--mode-*colours.components/ui/chart/README.md: props, examples, colour, the marker rule, error handling, the text alternative and testing tips.src/v1/README.mdpoints to them.Why
How
--chart-1..5are the LLMWhisperer design's--mode-native-text…--mode-tablevalues, exactly as Figma defines them for each mode (figq tokens llmw; table below).palette.test.tspins them to the design and checks lightest-to-darkest order. Per Ritwik's decision there is no contrast rule: an earlier 3:1-against-cardrule was proposed by Claude, not the spec, and has been dropped. The README rules follow the designs:toneby meaning, as in DS D01 "Throughput and failures" (primaryanddestructive).primary, or the tone for its meaning.Rowcan be any object type, including interfaces and API types with extra fields.xKeymust name a string or number field. Series and marker keys must name anumber | null | undefinedfield, so a numeric string (a DRF Decimal) is rejected at compile time. Rows with an index signature, such as pivoted dynamic series, accept any key whose field could hold the right type.@ts-expect-errorguards pin each of these rules intypecheck:v1.ReferenceLine/ReferenceDot. I reproduced the defect against recharts 3.7.0: on a categorical axis with a repeated label, a reference element silently draws nothing.{ index, series, label }, addressed by row.data, a gap, or an already-labelled point are dropped. So are all markers when a series key starts with the reserved__marker.console.warn, but only once the chart shows data.usePlotArea()(bars).role="application", focusable, arrow keys) and takes its name fromtitle. The active point is read out through one polite, visually hidden live region (data-slot="chart-live").ChartFramemounts it with the chart, outside Recharts' tooltip wrapper, and the tooltip fills it viaonAnnounce. The data table is hidden by ansr-onlywrapper, not on the table itself. A sparkline without a tooltip isrole="img"and stays out of the tab order. A point with no plotted neighbour is drawn as a dot, so a single row stays visible. The tooltip keeps gap rows and names the category, showing "–" for missing values. axe runs on every variant.dataitself unless something is marked. They are memoised on whatseriesandmarkerssay, so inline literals do not reset Recharts' store. The tooltip is passed as an element.InlineAlertwithonRetryin place of the chart.Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)
src/v1, which nothing mounts yet. The only edits to existing files add five tokens and their@thememapping, update the colour-family count assertion and the token-table snapshot, and add one sentence to thetokens.tsheader.Database Migrations
Env Config
Relevant Docs
frontend/src/v1/components/ui/chart/README.mdRelated Issues or PRs
Dependencies Versions
recharts(^3.5.1, locked at 3.7.0).usePlotAreais public across that range.Notes on Testing
bun run typecheck:v1,bun run lint:v1and the v1 Vitest project pass locally (565 tests, after mergingmainon 10 Nov). The chart suite also passes underLANG=de_DE, because the tests pinen-US.mainwas merged in and rounds 23–26 reviewed the fixes in row 33. Each round's review is on this PR.Gap analysis: spec vs this PR
Origin tags: [Original spec] the P1-T17 page · [Existing v0] current v0 behaviour · [Gap] something neither the spec nor the landed code covers · [Proposed by Claude] a default chosen here.
--chart-1..5, blue (the design's mode colours)--mode-*values for every mode--mode-*)card--chart-1(2.1:1) and--chart-2, and dark--chart-5(2.2:1), sit below 3:1 againstcardtoneper seriesrightAxisFormatSkeletonemptySlottestId, ref forwarding)data-slot)InlineAlertnumberFormats(locale))warning, Documents in#5f8ad0(not a token)primaryuntil the design names a tokentooltiptrue→false test that would fail without the cleanup, plus relabelling the loading test and the comment atChartTooltip.tsxL88 (#133/#134); (b) README: an Escape dismissal keeps the last sentence (#135); (c) "Pages no data" may be announced for an active tooltip with an empty payload while Recharts keeps it hidden; whether that is reachable is unverified (#136); (d) each active-point change re-renders the whole chart (unverified cost)stackOffset="sign"(4510266843); (b) a negative bar's marker label reads under its tip, or beside the dot (on the side that leaves the plot less, within the plot's bottom) where there is no room under it (4510266848); (c) a ready chart whose every series value is a gap shows the empty state, in CartesianChart and Sparkline, with one gap rule (numberAt) shared by the tooltip, table, summary and markers, and a console warning naming any series whose values are not numbers (4510266955). Also pinned with tests: gaps break the line/area/sparkline,xFormatreaches the X ticks,classNamein every state,grid, the empty state's screen-reader title, the sparkline right axis'sdataMin..dataMaxdomain,toHaveLengthdropReasonandcategoryOfhelpers,.at(-1), the range-sentence helper) and S6479/S7770 to be marked accepted in Sonar; the--swatchcustom-property rule in v1/README S1 (4510266961); the S2 named-colour list incheck-v1-styles.mjs(off-diff); theNumericKeyJSDoc lists fewer gap readers than the README; theplacetype admitsxwithouty; label width is a 7px-per-character estimate; a dev warning for a series key missing from every row; pin that the Sparkline unparsed-value warning logs once per change, and the CartesianChart one in the loading state; name the chart in that warning--chart-1(the palest step) with no warning; the README's single-series rule is guidance, not a defaultprimary, and add palette warnings (untoned outside a stack, more than five untoned, tones mixed with the ramp)Ramp values (LLMWhisperer design
--mode-*, fromfigq tokens llmw)--mode-native-text#86b6ef#9ec5f4#86b6ef--mode-low-cost#5598e7#6da7ec#5598e7--mode-high-quality#2a78d6#3987e5#2a78d6--mode-form#1c5cab#256abf#1c5cab--mode-table#104281#184f95#104281Screenshots
components/ui/chart/__snapshots__/markup.md. Local harness renders per theme were shared with the Frontend v1 command session.Checklist
I have read and understood the Contribution Guidelines.
🤖 Generated with Claude Code