Skip to content

feat: waterfall charts — financial bridges with running totals - #196

Merged
ethenotethan merged 1 commit into
mainfrom
feat/waterfall-chart
Jul 22, 2026
Merged

feat: waterfall charts — financial bridges with running totals#196
ethenotethan merged 1 commit into
mainfrom
feat/waterfall-chart

Conversation

@ethenotethan

Copy link
Copy Markdown
Contributor

What

The flow-shaped gap from the chart-type audit: "where did it go" answers — revenue bridges, token budgets, headcount changes — as a native ```chart type.

```chart
{"type": "waterfall", "title": "Q2 Bridge", "series": [{"name": "Bridge", "points": [
  {"x": "Revenue", "y": 500},
  {"x": "COGS", "y": -180},
  {"x": "Gross profit", "total": true},
  {"x": "Opex", "y": -220},
  {"x": "Net", "total": true}
]}]}
```
  • Client-side running totals — the model emits signed deltas in order; {"total": true} marks checkpoint bars (y optional). ChartWaterfall.segments computes the levels, so the model never emits running sums (same raw-data discipline as histogram/boxplot).
  • Floating bars: rises in Theme.success, falls red (financial convention, matching the cron chart's OK/Error language), totals as neutral zero-anchored bars, dashed hairline connectors carrying the level between adjacent bars.
  • Hover readout: gain/loss delta + running level; totals show the total. No legend (identity is the rise/fall color), no zoom (categorical axis).
  • Duplicate step labels disambiguate instead of colliding on the categorical axis.
  • Formatting prompt documents the contract.

Testing

3 new tests: parsing with total markers (optional y), segment math (rise/fall/total spans), duplicate-label disambiguation. Chart + PDF regression suites green; both mac & iOS targets build; swiftlint --strict clean.

Built in an isolated worktree off current main — no entanglement with in-flight branches.

🤖 Generated with Claude Code

The flow-shaped gap from the chart-type audit: "where did it go"
answers (revenue bridges, token budgets, headcount changes) as a
native ```chart type.

- {"type": "waterfall"}: one series of signed deltas in point order;
  {"x": "Gross profit", "total": true} marks running-total checkpoints
  (y optional on totals). ChartWaterfall.segments computes the levels
  client-side — the model never emits running sums.
- Floating BarMarks: rises Theme.success, falls red (financial
  convention, same OK/Error language as the cron chart), totals as
  neutral zero-anchored bars; dashed hairline connectors carry the
  level between adjacent bars.
- Hover readout: gain/loss delta + running level; totals show the
  total. Categorical selection, no zoom (owns its axis), no legend
  (identity is the rise/fall color).
- Duplicate step labels disambiguate instead of colliding on the
  categorical axis.
- Prompt documents the contract; segment math + parsing + label
  disambiguation covered by tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ethenotethan
ethenotethan merged commit 51c8b08 into main Jul 22, 2026
4 of 6 checks passed
ethenotethan added a commit that referenced this pull request Jul 23, 2026
The recurring CI test-job crash (NSInternalInconsistencyException,
'bundleProxyForCurrentProcess is nil') was NotificationService.shared
being initialized from a real code path during tests — the crash-run
stack shows ChatViewModel.applySessionEvent → notifyTurnComplete →
UNUserNotificationCenter.current(), which throws in bundle-less test
runners. Two suites set isTestEnvironment manually, so whether CI
crashed depended on suite ordering (why it was flaky: also hit #196).

isTestEnvironment now defaults to auto-detection via a shared
ProcessInfo.isTestProcess (XCTest/swift-testing/xctest-bundle/SwiftPM
runner checks); ArtifactStore's duplicate detection now uses the same
helper. 4/4 consecutive full-suite runs clean (previously ~2 of 3
crashed locally).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ethenotethan added a commit that referenced this pull request Jul 23, 2026
* feat: composite map artifacts — map + status table as one surface

In artifact hosts, a map artifact now renders BOTH views of its one
content body: the spatial map on top and a sortable entry table below
— label, group, and the union of data fields riding on markers
(status, reached_out, rent…), with the declared action controls
inline per row. Fields named by declared actions always get a column,
even before the first mark.

Selection is shared: click a row → the pin highlights and the camera
recenters; tap a pin → the row highlights (note revealed on the
selected row, since notes don't fit a column). Sort is numeric-aware
with string fallback.

Chat transcript blocks keep the compact entry list (snapshots, not
work surfaces). The expanded/fullscreen sidebar widens to fit the
table. entryRow's inline action controls moved into the table — one
owner for controls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: UNUserNotificationCenter crash under the SwiftPM test runner

The recurring CI test-job crash (NSInternalInconsistencyException,
'bundleProxyForCurrentProcess is nil') was NotificationService.shared
being initialized from a real code path during tests — the crash-run
stack shows ChatViewModel.applySessionEvent → notifyTurnComplete →
UNUserNotificationCenter.current(), which throws in bundle-less test
runners. Two suites set isTestEnvironment manually, so whether CI
crashed depended on suite ordering (why it was flaky: also hit #196).

isTestEnvironment now defaults to auto-detection via a shared
ProcessInfo.isTestProcess (XCTest/swift-testing/xctest-bundle/SwiftPM
runner checks); ArtifactStore's duplicate detection now uses the same
helper. 4/4 consecutive full-suite runs clean (previously ~2 of 3
crashed locally).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant