Skip to content

UI Polish, Persistent Layout, and Data Correctness - #94

Closed
terryaney wants to merge 12 commits into
davidfowl:mainfrom
terryaney:feature/ui-tweaks
Closed

UI Polish, Persistent Layout, and Data Correctness#94
terryaney wants to merge 12 commits into
davidfowl:mainfrom
terryaney:feature/ui-tweaks

Conversation

@terryaney

Copy link
Copy Markdown
Contributor

Review Order Note

If PR #91 is accepted, it should be merged first. After that lands, I'll get notification and I'll fix the known merge conflict that'll appear in this branch. This PR will then be resubmitted. Reviewers should treat this PR as the follow-on to #91 and should be merged afterwards.

Short Summary

This PR improves the generated HTML spending report across correctness, navigation, and daily usability.

There was one headline fix to data correctness: report-side charts, Filtered View totals, and Section View percentages now classify each transaction by its own tags instead of using a merchant-wide tag union.

On top of that, the report gains a richer date filter, a contained Transaction Details panel, persistent layout settings, clearer rule-provenance popups, blank-safe memo/report-field display, chart polish, mobile control wrapping, and a cleaner loading experience.

The branch is intentionally presentation-heavy. It does not change merchant rule matching semantics, and existing user configs keep their configured titles and behavior.

Mobile View Note: Given this is a locally generated html file, I feel the vast majority of users will be desktop/large display. There were several pre-existing mobile issues that I plan to address in a follow-on PR that I have planned for some improvements to charting.

At a Glance

NOTE: Screenshots from simulated data are included below to demonstrate changes without need to merge/test the branch.

  • Corrected chart/KPI classification so mixed-tag merchants no longer poison every transaction at that merchant.
  • Corrected Section View percentage denominators by using per-transaction spending totals.
  • Reworked charts into readable, reconciled Cash Flow and Spending views.
  • Added Month / Quarter / Year / Custom date filtering with URL hash restoration.
  • Added a collapsible Transaction Details container with sticky headers, pinned totals, and view-aware summaries.
  • Persisted report layout preferences in localStorage, with a reset control.
  • Added report_fields configuration so captured fields like memo can appear in report details without per-rule passthroughs.
  • Suppressed blank report-field badges, avoiding phantom +1 Memo indicators.
  • Improved transaction row layout, column sizing, and rule provenance popups.
  • Updated starter config title to Tally Spending Analysis to support multi-year reports.

Important Data Correctness Fixes

Transaction tags now drive chart and KPI classification

Previously, the JavaScript report classified transactions using merchant.tags, which is the union of tags across all transactions for a merchant. That meant one income, transfer, refund, or investment transaction could cause unrelated transactions at the same merchant to be classified incorrectly.

This PR changes the rendered report paths to classify with each transaction's own txn.tags, matching the Python-side KPI logic.

Affected report surfaces:

  • Filtered View KPI tile
  • Cash Flow Trend
  • Spending by Category
  • Spending by Category Trend

Evidence from the chart-polish notes shows the reference dataset reconciled to Python totals after the fix:

Metric Python KPI JS before JS after
Income $213,613 $263,212 $213,613
Spending $253,781 $243,654 $253,781
Credits $45,595 $28,616 $45,595

Section View percentages now use the correct spending denominator

Section View percentages previously depended on whole-merchant include/exclude decisions derived from merchant tag unions. In mixed-tag datasets, that could drop ordinary spending from the denominator. The notes record a denominator of $215,322 before the fix versus the correct $253,781 after the fix.

The rendered Section View denominator now comes from filteredViewTotals.value.spending, which is already calculated per transaction.

Correct totals in the default report view

The report now classifies each transaction with its own tags instead of using the merchant-wide tag union. In the default unfiltered view, the rendered KPI totals and charts now reconcile with the Python-computed report totals.

This is the strongest correctness story in the PR. It shows that the report is not just visually improved; the numbers shown in the browser now match the underlying analysis.

Default report totals now reconcile with Python-computed KPIs.
image

Chart Improvements

Screenshot directly above shows each of the following changes.

Cash Flow Trend

The old Monthly Trend chart is now Cash Flow Trend. It plots spending, income, credits, and investment, while keeping transfers out of cash flow. Credits were already part of the report's cash-flow formula, but the chart previously discarded them.

Empty series are omitted from the dataset and legend, so an all-zero Investment series no longer leaves a phantom legend entry.

Spending by Category

The old By Category chart is now Spending by Category. It uses the same category ranking as the category trend chart (top 10 categories) and rolls smaller categories into Other, keeping legends readable while preserving totals.

Spending by Category Trend

The old Category Trends by Month chart is now Spending by Category Trend. It no longer includes synthetic Income or Investment category datasets that could create filters for categories that do not exist.

Other chart fixes

  • Functionality Change: Category charts share a top-10-plus-Other ranking and color mapping (previously all categories where shown).
  • Functionality Change: Category chart data-click filters were removed; legend clicks behave as visibility toggles for all charts (previously, some charts added filters).
  • Empty months are dropped per chart instead of rendering blank columns.
  • Cash Flow Trend bar clicks now index into the actual plotted months rather than the unfiltered month list.
  • Chart containers can shrink correctly after resize because grid items no longer lock to min-width: auto.

Multi-Granularity Date Filtering

The date filter changed from a single month dropdown into an interactive popover with multiple date selection modes.

New date filter capabilities:

  • This Month, This Quarter, This Year presets
  • Last Month, Last Quarter, Last Year presets
  • Year tabs for the most recent data years
  • Month grid for the active year
  • Custom Start / End range inputs
  • Drill-down calendar with day, month, and year views
  • Greedy chip aggregation on Apply: full years collapse to one year chip, full quarters collapse to one quarter chip, remaining months stay individual
  • URL hash round-trip for month, quarter, year, and custom range chips
  • Additive date semantics across month/quarter/year/custom selections

The underlying month source was also corrected. Available months now come from categoryView, so a month is not lost just because its merchant was excluded from every configured view.

Date filtering now supports months, quarters, years, and custom ranges in one popover.
image

Selected months aggregate into concise quarter/year chips, while custom ranges stay day-precise.
image

Multi-Year Report Polish

The default starter title now uses Tally Spending Analysis instead of hardcoding a year. This better reflects multi-year reports while preserving existing user-configured titles.

Transaction rows also now show a year to remove cross-year transaction ambiguity.
image

Transaction Details Container

The report now wraps all detail views in a single Transaction Details card. It mirrors the chart container pattern and collapses cleanly to header only.

User-visible changes:

  • Contained Transaction Details card around view toggles and all category/view sections
  • Header-only collapse state, with no empty padded band
  • Left-aligned view buttons
  • Single state-aware collapse-all / expand-all control
  • View-aware summary counts, such as category/merchant or view/merchant counts
  • Collapse-all folds categories and open transaction lists; expand-all reopens categories without reopening every transaction list
  • Merchant/Subcategory/View modes now use one keyed render path, reducing DOM churn and preserving state on view toggles

Transaction Details are now contained, collapsible, and easier to scan.
image

Collapsed sections no longer leave an empty padded band.
image

Transaction Details Table Polish

The section tables inside Transaction Details received a focused usability pass.

Improvements:

  • Bordered per-section table frames
  • Sticky opaque table headers
  • Pinned opaque total footers
  • Rows-only scrolling with the existing skinny scrollbar style
  • Ring-glow category dots
  • Category labels use normal text color instead of always-blue styling
  • Count column centered
  • Total and percentage columns right-aligned
  • Opaque sticky backgrounds prevent scrolled rows from bleeding through headers/footers
  • Mobile view-toggle wrapping keeps controls usable at narrow widths

Headers and totals stay readable while only transaction rows scroll.
image

Transaction Details styling is consistent across light and dark themes.
image

Persistent Layout Settings

The report now saves layout preferences in localStorage so a monthly report can reopen the way the user left it.

Persisted settings include:

  • Transaction Trends collapsed state
  • Transaction Details collapsed state
  • Current details view: Merchant, Subcategory, or View
  • Include Negatives toggle
  • Per-section collapsed state
  • Per-row expanded transaction state
  • Per-section sort settings

The implementation cross-references stored keys against the current report data on load. Stale items are pruned, and new items default to collapsed. A reset-layout-settings icon near the theme toggle clears saved report layout state without clearing the theme preference.

Local session history confirms this grew out of the earlier discussion that cookies were the wrong fit for generated local reports, and that Edge/Chrome localStorage support was sufficient for the intended usage.

Saved layout preferences can be reset without changing the theme.
image

Report Fields and Memo Display

The new report_fields setting lets captured CSV fields, such as memo, appear in transaction details without adding a per-rule passthrough directive.

Behavior summary:

  • report_fields can be configured globally or per source.
  • New starter configs include report_fields: [memo] by default.
  • Global report fields ignore sources that do not capture the field.
  • Source-level overrides reject unknown fields.
  • Rule-level field: directives still win on collision.
  • Blank values are suppressed so empty memos do not create phantom +1 Memo badges.
  • Meaningful values such as 0 and False are retained.

Docs were updated in the reference command output and generated docs.

Real memo fields appear in transaction details, while blank values no longer create phantom badges.
image

Rule Provenance and Row UX

Transaction rows and rule popups received several explainability and layout improvements.

Improvements:

  • Merchant rows now expose explicit info and filter links, making the main actions discoverable without relying on implicit row interactions.
  • Expanded transaction rows have clearer date, account, description, tag, and amount regions.
  • Date/account/amount columns are measured and sized dynamically.
  • Column sizing recalculates on resize with debouncing.
  • Rule provenance popup data now includes ruleName.
  • Pattern text wraps more reliably.
  • Tag-only fallback can promote the first matching tag rule when no categorization rule matches, giving the popup useful provenance.
  • Popup tag display matches the main grid's merchant-level tag display.
  • Hardcoded theme-unsafe colors were replaced with theme variables where identified in prior session work.
  • Search input and date-filter button height were aligned during the UI tweak follow-up.

Expanded rows now separate transaction details into scannable columns.
image

Merchant rows now include explicit info and filter links for easier discovery and consistency.
image

Rule provenance popups now show the actual rule path more clearly.
image

Include Negatives Badge

The Include Negatives control is now hidden when there are no negative-total merchants to include. When negatives are hidden, the control shows a count badge based on the currently filtered view, capped at 99+.

The Include Negatives badge shows how many negative-total merchants are currently hidden.
image

Loading and Mount Polish

The generated report now shows a clean loading shell while Vue mounts. This hides uninitialized template content and gives the page a progress indicator during startup.

Report shows a clean loading state while the app initializes.
image

Config and Documentation Changes

Confirmed changed files include starter config, docs, and reference output.

Notable changes:

  • Starter title is now Tally Spending Analysis.
  • Starter settings include report_fields: [memo].
  • Report subtitles deduplicate source names.
  • docs/formats.html, docs/reference.html, and the tally reference command were updated for report fields.
  • config/settings.yaml.example includes the new report field setting.

Tests and Verification Evidence

Automated coverage added or cited by the notes:

  • New analyzer tests for report_fields behavior and blank-field suppression.
  • New Playwright/report tests for date filtering, multi-year transaction rows, coverage-based date pill highlighting, and Transaction Details container behavior.
  • Exported session notes cite 898 passed during chart/report-fields work.
  • Transaction Details notes cite a later full suite result of 906 passing.
  • UI tweaks notes cite 91/91 report tests and 37/37 CLI tests at that point.
  • Manual Playwright verification was recorded against real C:\BTR\TallySpending\tally\config data for chart correctness, date filtering, Transaction Details, sticky headers/footers, and both themes.

Current branch diff summary:

  • 18 files changed
  • 2674 insertions
  • 511 deletions
  • Largest touched files: spending_report.js, spending_report.css, spending_report.html, tests/test_report_html.py, and tests/test_analyzer.py

terryaney added 12 commits July 13, 2026 15:05
1. Removed year from title to support multi-year data/reporting and made page title match content title
2. Data from... subtitle is distinct list of source
3. Include Negatives buttons hides if no negatives or has badge count
	showing how many negative merchants exist
4. Revamped date filter to be more functional than simply just a month list
Both bugs are the same root cause: the JS classified transactions using
merchant.tags — the union of every tag across all of a merchant's
transactions (analyzer.py:122 builds it that way) — instead of the
transaction's own txn.tags, which report.py already emits per transaction.
categorizeAmount() is a strict income -> investment -> transfer -> sign
chain, so one tagged transaction re-bucketed every other transaction at
that merchant. This changed reported dollar amounts; it is not cosmetic.

Bug 1 — charts and the Filtered View tile (filteredViewTotals,
chartAggregations). Every number in all three charts and in the Filtered
View KPI tile was wrong. A merchant whose union is
[monthly-bill, refund, transfer] had all of its ordinary purchases counted
as transfers and vanish from spending. Now classified per transaction, the
JS reconciles with the Python KPIs to the dollar.

Both KPI tiles now agree line-for-line on unfiltered data.

Bug 2 — Section View percentages (grossSpending). grossSpending was
grandTotal + creditsTotal, and both inputs drop a whole merchant when its
tag union is excluded from spending. It feeds the (X%) badge beside each
section header, which renders whenever typeTotals is absent — that is,
Section View always (Category/Subcategory pass server-computed typeTotals
and were never affected). The denominator ran 15% low: $215,322 against a
true $253,781, hiding $6,910 of Amazon spending, $2,448 of Minnesota
Department of Revenue, and more. grossSpending now derives from the same
per-transaction pass and equals Python's spendingTotal exactly. Monthly
Bills reads 23.8% (was 28.0%), Food & Dining 20.0%.

Chart identity
- Renamed: "Monthly Trend" -> "Cash Flow Trend", "By Category" -> "Spending
  by Category", "Category Trends by Month" -> "Spending by Category Trend".
- Cash Flow Trend now plots Spending, Income, Credits, and Investment.
  Credits were previously discarded despite being part of the cash-flow
  definition (income - spending + credits). Transfers stay excluded.
- Removed the synthetic Income/Investment datasets from the category trend
  chart; it is now purely spending by category. Those datasets were built
  from tags, not real categories, so clicking one filed a category filter
  that matched nothing and blanked all three charts.

Consistent interactions
- Legends are visibility toggles on all three charts. The category trend
  legend previously added a category filter on click.
- Removed the data-click filter handlers from both category charts. The
  Cash Flow Trend bar click (month filter) is retained.

Correctness and rendering
- Both category charts share one ranking: top 10 categories plus an "Other"
  rollup. The pie previously listed all 16 categories (legend overflowed and
  clipped) while the trend chart silently dropped everything past the top 8,
  under-reporting spending. "Other" is omitted when 10 or fewer categories
  are present.
- Series with no data are no longer emitted, so an all-zero Investment
  series no longer leaves a phantom legend entry.
- Months with nothing to plot are dropped per chart (a transfer-only month
  drew an empty column).
- Cash Flow Trend bar clicks index into monthlyChartMonths — the months
  actually plotted — instead of availableMonths. Under a date filter the two
  diverged and a bar click filed the wrong month.
- Added min-width: 0 to .chart-container; the grid default of min-width:
  auto locked tracks open and clipped canvases on re-widen.

Documented, not fixed: creditMerchants has the same merchant-union bug. It
is unrendered — the "Credits Applied" section was dropped from the template
in ad9477e while docs/reference.html still documents it — so a comment on
the computed records the bug and the two ways to fix it when the section is
restored.
1. Introduce **containment** — wrap the button row + all category collapsers in one **Transaction Details** card that collapses to **header only** (fixing the "empty padded band when collapsed" that Trends shows today).
2. Left-align the view buttons; add collapse/expand controls on the right.
3. Polish each collapser: anchored header + rows-only skinny scroll, bordered table, glowing color dot, non-blue label, and Count-centered / Total-right / %-right columns.
- Unify Merchant/Subcategory category views into one keyed v-for so Vue
  patches <section> nodes in place instead of remounting on a groupByMode
  toggle (preserves collapse state, no DOM churn).
- Remove the staggered fadeIn entrance animation. The details <section> is the
  only <section> in the report, so those global `section { animation }` +
  nth-child rules only ever animated it, replaying as a cascade every time the
  view toggle remounted the section-view sections.
Wrap the toggle on mobile (<=640px) — view modes on line 1, actions on line 2;
Collapse becomes icon-only to fit. Verified at 360/390px; 8 container tests pass.
UI and interaction polish

- Reworked merchant row action presentation: explicit info and filter links
- Refined expanded transaction row structure to better separate date,
  account badge, description, tags, and amount.
- Fixed miscellaneous CSS issues for hard coded colors, popup layout
  and transaction details buttons.  Removed dead CSS rules
- Hide app components (and added progress bar) while VUE was mounting/initializing

Adaptive column sizing architecture

- Introduced dynamic transaction column sizing via CSS variables for
  date, account, and amount tracks.
- Added startup-time width profiling and debounced resize recalculation
  for desktop-first responsiveness.

Rule Info Fixes

- Added explicit rule provenance into popup data (ruleName, pattern, source)
  so "Why This Matched" reflects the actual classification path.
- Improved fallback behavior: when no categorization rule matches, promote
  the first matching tag rule so popup rule details remain meaningful.
- Switched popup tag display to merchant-level union tags (matching the
  main grid) so tags like transfer are not dropped when first-transaction
  match_info is incomplete.
- Cleaned up tag-source reporting to avoid redundant/misleading "from [rule]"
  lines when the displayed rule already explains the tag.

Outcome

- Delivers a cleaner, more consistent UI with stronger alignment and spacing.
- Makes transaction detail rows feel more intentional and legible across real data variance.
- Transaction Trends collapsed state
- Transaction Details collapsed state
- Details view mode (Merchant, Subcategory, View)
- Include Negatives toggle
- Per-section collapse state
- Per-item expansion state (merchant/subcategory rows)
- Per-section sort settings
getFilterDescriptor() used item.id (category-specific) instead of
item.displayName for merchant-type filters, breaking cross-category
merchant filtering when the same merchant appears in multiple categories.

Also brings the test suite in line with two intentional behavior changes
already on this branch that its own tests hadn't caught (no CI runs on
this branch's pushes):
- Category sections now default to collapsed on a fresh load ("Save
  Layout Settings to Local Storage"), so tests need to expand sections
  before interacting with rows. Added an autouse fixture instead of
  touching every test.
- Transaction dates always show the year now (row UX polish), so the
  multi-year-only assertions were stale.
@github-actions

Copy link
Copy Markdown

PR Build Available

Version: 0.0.94-2538433

Install from this PR

Linux / macOS:

curl -fsSL https://raw.githubusercontent.com/davidfowl/tally/main/docs/install-pr.sh | bash -s -- 94

Windows PowerShell:

iex "& { $(irm https://raw.githubusercontent.com/davidfowl/tally/main/docs/install-pr.ps1) } 94"

Manual download: View workflow run and download artifacts.

Requirements
  • GitHub CLI (gh) must be installed and authenticated
  • Run gh auth login if not already authenticated

@terryaney

Copy link
Copy Markdown
Contributor Author

Closing to better maintain my repo and convert to a stacked pattern (with this one later in the stack and more important ones higher in stack)

@terryaney terryaney closed this Jul 31, 2026
@terryaney
terryaney deleted the feature/ui-tweaks branch August 1, 2026 03:15
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