- The 80ms left/top transition moved out of .chart-tooltip: it eased the
snap-positioned goal tooltip but made cursor-following tooltips
(sankey, time-series) trail the pointer by a frame. Goal projection
opts back in via inline style; the component comment documents the
split.
- setRelation reuses _draw()'s targetAmount const instead of declaring
a local 'target' that shadowed the target-date const.
- Sankey context line gets max-w-64 so truncate has a constraint to
fire against on deep flows.
- Component comment now says 10x12 padding, matching the declaration.
font-mono in this codebase marks code, keys, and admin surfaces; money
is sans + tabular-nums everywhere else (cards, KPIs, tables). Keep the
tabular figures for scrub stability, drop the mono.
Chart tooltips answer three stacked questions: context (what am I
looking at), value (how much), relation (vs what). Time-series already
had all three; the other two were missing rows.
- Sankey links showed a bare "$X (Y%)" with no indication of which
flow was hovered. Links now lead with a swatch-dotted
"Source → Target" context line; nodes get the same dot + name
treatment, tying the card to the ribbon color. The context builder
escapes node names centrally (they're user-named categories).
- Goal projection adds a tertiary relation line — "52% of $20K
target" — computed from the payload the chart already carries, for
both the saved and projected segments. Hidden when the goal has no
positive target. Template is i18n-wired like the existing tooltip
strings (goals.show.projection.tooltip_target_relation).
Verified with Playwright against the running app: all three surfaces
pass computed-style and content assertions.
The previous pass approximated the reference with utility guesses
(rounded-2xl, p-4, shadow-xl, dark ring). The actual spec is a hairline
border ring composed with a soft 0 8px 24px drop shadow, 10px radius,
12x14 padding, and an 80ms left/top glide. Tailwind shadow utilities
can't compose a ring with a custom drop shadow, so the surface moves
into the design system as .chart-tooltip (theme-aware: dark swaps the
ring to alpha-white and lets it carry the edge).
Money/numeric figures also pick up the reference's mono treatment:
font-mono + tabular-nums on every value across time-series, sankey,
and goal-projection, so digits don't jitter while the scrubber moves.
One visual contract for all three D3 tooltip surfaces, matching the
design reference: p-4, rounded-2xl, shadow-xl, no edge ring in light
mode. Dark mode keeps a 1px alpha-white ring since a shadow alone
disappears against dark surfaces.
- goal_projection_chart_controller drops its hand-copied class string
(it still carried the old bordered recipe — the drift this util
exists to prevent) and builds its two lines through the shared
factory: secondary date line, tabular value line.
- New content conventions exported alongside the container contract:
context line = text-xs text-secondary, values = font-medium
tabular-nums. Time-series and sankey adopt them.
- Sankey node titles now escape before .html(); user-named categories
were previously interpolated raw into the tooltip markup.
Closes#2011.
time-series and sankey each created their cursor-following tooltip with a
duplicated className literal that had already drifted apart: time-series was
missing `text-primary` and `z-50`. Move the visual contract into
app/javascript/utils/chart_tooltip.js as CHART_TOOLTIP_CLASSES and have both
controllers reference it. Each keeps its own behavioural classes (time-series
its initial `opacity-0`; both `top-0`; sankey toggles opacity via inline
style). `privacy-sensitive` stays bundled so future copies can't drop it.
Also exports a createChartTooltip factory for the raw-DOM idiom.
goal_projection_chart_controller is not in main yet (it lands with the goals
work in #1798); it migrates to the same symbol there.
* fix(views): DS drift sankey tooltip and imports icon token
Replace raw palette classes on the cashflow Sankey tooltip with functional tokens (aligned with time_series_chart). Use bg-surface for the YNAB import option icon background.
Refs #1971, #1951
* fix(views): add privacy-sensitive sankey tooltip class
Align the sankey tooltip with privacy mode masking by appending privacy-sensitive while keeping the DS tokenized tooltip styling.
---------
Signed-off-by: glorydavid03023 <glorydavid03023@gmail.com>
* feat(dashboard): zoom into cashflow sankey categories
Click a category node on the dashboard cashflow Sankey to focus on it and
its descendants only; a back button restores the full view. Clicking the
Cash Flow node zooms to the expense (outbound) side.
- Pure utility (app/javascript/utils/sankey_zoom.js) computes the
descendant subgraph from a clicked node, with direction inferred by
reachability from the cash flow node (outbound for expense, inbound
for income).
- Stable node ids emitted from the controller so the JS can identify
nodes across re-renders.
- Stimulus controller adds chart + zoomOutButton targets, fade
transition, and only sets a pointer cursor when a node has children.
- Node:test coverage for expense, income, cash-flow, and malformed-data
cases; \"type\": \"module\" added to package.json so the .js util is
ESM-compatible under Node.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(dashboard): extract cashflow sankey chart partial
Deduplicate sankey chart markup between inline and expanded dialog views,
and reset zoom state when chart data changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(js): rename sankey_zoom util to .mjs to drop project-wide ESM flag
Removes "type": "module" from package.json to avoid implicitly switching
every .js file in the project to ESM (a future footgun for any .js config
file added by Biome, Vite, etc.). Renames the utility to .mjs so node --test
can import the ES module directly, and adds an explicit importmap pin since
pin_all_from only globs .js/.jsm.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(assets): register .mjs MIME type for Propshaft
Propshaft derives Content-Type from Mime::Type.lookup_by_extension, which
returns nil for :mjs by default. Browsers refuse to execute ES modules
served with an empty Content-Type, breaking the sankey_zoom util loaded
via importmap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: move safe-area padding from body/HTML to navbars. Add script to compute app height dynamically.
* fix: Initialize sankey tooltip with top-0 to avoid overflow
* fix: add fallback to HTML height
* fix: properly set bottom spacing and use position fixed for bottom navbar
* fix: move viewport controller initialization
* feat: implement expandable view for cashflow sankey chart
* refactor: migrate cashflow dialog sizing to tailwind utilities
* refactor: declarative draggable restore on cashflow dialog close
* refactor: localized title and use Tailwind utilities
* refactor: update dialog interaction especially on mobile
* refactor: add global expand text to localization
* fix: restore draggable immediately after dialog close
* Whitespace noise
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* feat: Add subcategory breakdown to Cash Flow and Reports
Implements Discussion #546 - adds hierarchical category/subcategory
visualization to both the Sankey chart and Reports breakdown tables.
Sankey chart changes:
- Income: subcategory → parent category → Cash Flow
- Expense: Cash Flow → parent category → subcategory
- Extracted process_category_totals helper to DRY up income/expense logic
Reports breakdown changes:
- Subcategories display nested under parent categories
- Smaller dots and indented rows for visual hierarchy
- Extracted _breakdown_table partial to eliminate duplication
* fix: Dynamic node padding for Sankey chart with many nodes
- Add dynamic nodePadding calculation to prevent padding from dominating
chart height when there are many subcategory nodes
- Extract magic numbers to static constants for configuration
- Decompose monolithic #draw() into focused methods
- Consolidate duplicate tooltip/currency formatting code
- Modernize syntax with spread operators and optional chaining
* fix: Hide overlapping Sankey labels, show on hover
- Add label overlap detection by grouping nodes by column depth
- Hide labels that would overlap with adjacent nodes
- Show hidden labels on hover (node rectangle or connected links)
- Add hover events to node rectangles (not just text)
* fix: Use deterministic fallback colors for categories
- Replace Category::COLORS.sample with Category::UNCATEGORIZED_COLOR
for income categories in Sankey chart (was producing different colors
on each page load)
- Add nil color fallback in reports_controller for parent and root
categories
Addresses CodeRabbit review feedback.
* fix: Expand CSS variable map for d3 color manipulation
Add hex mappings for commonly used CSS variables so d3 can manipulate
opacity for gradients and hover effects:
- var(--color-destructive) -> #EC2222
- var(--color-gray-400) -> #9E9E9E
- var(--color-gray-500) -> #737373
* test: Add tests for subcategory breakdown in dashboard and reports
- Test dashboard renders Sankey chart with parent/subcategory transactions
- Test reports groups transactions by parent and subcategories
- Test reports handles categories with nil colors
- Use EntriesTestHelper#create_transaction for cleaner test setup
* Fix lint: use Number.NEGATIVE_INFINITY
* Remove obsolete nil color test
Category model now validates color presence, so nil color categories
cannot exist. The fallback handling in reports_controller is still in
place but the scenario is unreachable.
* Update reports_controller.rb
* FIX trade category
---------
Co-authored-by: sokie <sokysrm@gmail.com>
* Enhance cash flow dashboard with new cash flow period handling and improved Sankey diagram rendering. Update D3 and related dependencies for better performance and features.
* Fix Rubocop offenses
* Refactor Sankey chart controller to use Number.parseFloat for value formatting and improve code readability by restructuring conditional logic for node shapes.