Skip to content

feat(cost): filter rail, provider marks and drill-down on the Cost page - #2548

Merged
Makisuo merged 1 commit into
mainfrom
cost/filter-rail-and-marks
Nov 12, 2026
Merged

Makisuo merged 1 commit into
mainfrom
cost/filter-rail-and-marks

feat(cost): filter rail, provider marks and drill-down on the Cost page

0b4b4eb
Select commit
Loading
Failed to load commit list.
Maple Review Bot / Maple / review completed Nov 12, 2026 in 6m 24s

Confidence 6/10 · 1 issue to address

🟡 Confidence 6/10 · needs attention. Held at 6 because a correctness warning is open.
1 warning −1 · 3 notes · tests partial −1 · risk medium

Moves the /cost filters into the shared rail, adds provider marks in the table, legend, chips and rail, and makes a row click regroup instead of keeping the grouping. The rail ships a dead Only action, and a few labels and kit reads need tightening.

  • FilterSection gains formatCount; BarChart gains seriesMarks; chips gain mark
  • FetchCostFacets reads one breakdown per dimension under the other dimensions' filters
  • Row clicks regroup via drilledInto/drillTarget; usage_type leaves the URL and grouping
  • marks.ts names AWS/OpenRouter accounts and picks a mark per dimension

Findings

🟠 Warning · F1 · onOnly without onExclude renders no Only button, so the rail has none

correctness · apps/web/src/page/cost/view-filters.ts:69

filter-section.ts:270 draws the Only/Exclude hover overlay only when onExclude is set, and this section passes onOnly alone. No rail row ever shows Only: Message.ClickedOnlyFacetValue is never sent and onlyFilter is dead. Every other rail (services/list/view-filters.ts:39) passes both.

Pass an `onExclude` too, or in `filter-section.ts:270` render the overlay when either action exists: `...(config.onExclude === undefined && config.onOnly === undefined ? [] : [...])`.
🔵 Note · F2 · keyDetail repeats the id accountLabel already appended

correctness · apps/web/src/page/cost/marks.ts:146-148

With two OpenRouter sources accountLabel returns OpenRouter org_second (marks.ts:95), and keyDetail returns the id unconditionally, so the table row and the rail's title read OpenRouter org_second · org_second.

Return `key` only while the label is the bare name: `accountLabel(key, sources) === COST_PROVIDERS.openrouter.name ? key : null`.
🔵 Note · F3 · One facet key re-reads all five breakdowns on every filter change

performance · apps/web/src/page/cost/update.ts:124-128

requestKey covers the whole facets input, so a checkbox click re-runs all five FetchCostFacets reads (command.ts:104). facetFilters leaves the toggled dimension out of its own read, so only four of them changed and the fifth is re-read unchanged on every click.

Key and store each dimension's facet on its own (`costFacet.${dimension}` plus a `Keyed` slot), so the untouched one is reused.
🔵 Note · F4 · seriesMarks indexed without Rec.has, so a constructor series crashes the legend

correctness · packages/views/src/charts/bar-chart.ts:88

A series column named constructor reads back Object.prototype.constructor, so mark is not undefined, mark.icon is undefined, and the legend's icon(h, undefined) throws instead of drawing a plain entry. marks.ts:57 and ai-vendor.ts:68 guard exactly this read with Rec.has.

`mark: Rec.has(inputs.seriesMarks, definition.rawKey) ? inputs.seriesMarks[definition.rawKey] : undefined`
What was checked
  • Every IconName in CATEGORY_ICONS and the account/region glyphs resolves in icons.generated.ts
  • rawKey is the row's column name, so legend marks keyed by label resolve (timeseries.ts:43)
  • sourcesOf is exported at model.ts:69 and every keyLabel/keyMark caller passes it

0b4b4eb · Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

Annotations

Check warning on line 69 in apps/web/src/page/cost/view-filters.ts

See this annotation in the file changed.

@maple-review-bot maple-review-bot / Maple / review

correctness: `onOnly` without `onExclude` renders no Only button, so the rail has none

`filter-section.ts:270` draws the Only/Exclude hover overlay only when `onExclude` is set, and this section passes `onOnly` alone. No rail row ever shows Only: `Message.ClickedOnlyFacetValue` is never sent and `onlyFilter` is dead. Every other rail (`services/list/view-filters.ts:39`) passes both.

Check notice on line 148 in apps/web/src/page/cost/marks.ts

See this annotation in the file changed.

@maple-review-bot maple-review-bot / Maple / review

correctness: `keyDetail` repeats the id `accountLabel` already appended

With two OpenRouter sources `accountLabel` returns `OpenRouter org_second` (`marks.ts:95`), and `keyDetail` returns the id unconditionally, so the table row and the rail's title read `OpenRouter org_second · org_second`.

Check notice on line 128 in apps/web/src/page/cost/update.ts

See this annotation in the file changed.

@maple-review-bot maple-review-bot / Maple / review

performance: One facet key re-reads all five breakdowns on every filter change

`requestKey` covers the whole `facets` input, so a checkbox click re-runs all five `FetchCostFacets` reads (`command.ts:104`). `facetFilters` leaves the toggled dimension out of its own read, so only four of them changed and the fifth is re-read unchanged on every click.

Check notice on line 88 in packages/views/src/charts/bar-chart.ts

See this annotation in the file changed.

@maple-review-bot maple-review-bot / Maple / review

correctness: `seriesMarks` indexed without `Rec.has`, so a `constructor` series crashes the legend

A series column named `constructor` reads back `Object.prototype.constructor`, so `mark` is not undefined, `mark.icon` is undefined, and the legend's `icon(h, undefined)` throws instead of drawing a plain entry. `marks.ts:57` and `ai-vendor.ts:68` guard exactly this read with `Rec.has`.