Glass metric cards: number on top, label at the bottom (app-wide rule) #1179

Open
opened 2026-10-06 04:55:25 +00:00 by kayg · 20 comments
Owner

Owner design rule (2026-10-06)

"For glass cards: text at the bottom, numbers at the top."

Every glass metric/stat card in the app shows the value (number, date range, status like "1 ✓ matched") at the TOP and the label ("Accounts", "Transactions", "Checks passed") at the BOTTOM. Applies to: Money import wizard result cards (#1140, MoneyImportMetrics), Analytics stat cards (StatRow and widgets), Money budget summary cards, and any shared metric-card component. Use ONE shared metric card component (reuse gate: find the existing one and extend it); value uses the display/stat type token with Torph numeric transitions where values change; label uses the small muted token; consistent padding and baseline alignment across a row (4x crop proof).

Do: add the rule to DESIGN.md (glass/cards section) and a design-rule test that fails if a metric card renders its label before its value. Screenshots of every surface at 390/820/1440, light + dark.

## Owner design rule (2026-10-06) "For glass cards: text at the bottom, numbers at the top." Every glass metric/stat card in the app shows the value (number, date range, status like "1 ✓ matched") at the TOP and the label ("Accounts", "Transactions", "Checks passed") at the BOTTOM. Applies to: Money import wizard result cards (#1140, MoneyImportMetrics), Analytics stat cards (StatRow and widgets), Money budget summary cards, and any shared metric-card component. Use ONE shared metric card component (reuse gate: find the existing one and extend it); value uses the display/stat type token with Torph numeric transitions where values change; label uses the small muted token; consistent padding and baseline alignment across a row (4x crop proof). Do: add the rule to DESIGN.md (glass/cards section) and a design-rule test that fails if a metric card renders its label before its value. Screenshots of every surface at 390/820/1440, light + dark.
Author
Owner

Starting #1179 on job/cards-1179 from d0061ec3df.

Starting #1179 on job/cards-1179 from d0061ec3df127d81c86729d07d899b0bf2b6de91.
Author
Owner

After merging origin/dev at f5fbced3c7, the generic Card primitive exists but no shared metric-card component does. MoneyImport.svelte renders its totals in a paragraph and verification checks in a list. Analytics StatRow, ActivityCard, and HabitsCard each own value/label markup and styling; Money's Ready to Assign card also owns local value and label styles. I will add one shared MetricCard on top of Card, use it for these metric surfaces, and add the import summary result cards. DESIGN §34 defines the glass material but is silent on metric-card structure; the chosen rule is value first at the top, label last at the bottom, with details between them.

After merging origin/dev at f5fbced3c733370b5b37544d7816732628be2fd0, the generic `Card` primitive exists but no shared metric-card component does. `MoneyImport.svelte` renders its totals in a paragraph and verification checks in a list. Analytics `StatRow`, `ActivityCard`, and `HabitsCard` each own value/label markup and styling; Money's `Ready to Assign` card also owns local value and label styles. I will add one shared `MetricCard` on top of `Card`, use it for these metric surfaces, and add the import summary result cards. DESIGN §34 defines the glass material but is silent on metric-card structure; the chosen rule is value first at the top, label last at the bottom, with details between them.
Author
Owner

Finding: the account register balance summary in apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte rendered each label before its value, and those totals were not using a shared metric component. Converted Cleared, Uncleared and Working balance to the shared MetricCard, with value first, label last, and the calculation operators between the cards. The Money month Ready to Assign total and Analytics activity/streak cards also now use the same component.

Finding: the account register balance summary in `apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte` rendered each label before its value, and those totals were not using a shared metric component. Converted Cleared, Uncleared and Working balance to the shared `MetricCard`, with value first, label last, and the calculation operators between the cards. The Money month Ready to Assign total and Analytics activity/streak cards also now use the same component.
Author
Owner

Money production E2E finding: the existing Ready to Assign assertion timed out because it targeted .ready-amount, which the shared MetricCard no longer renders. The expected amount was unchanged. Updated the E2E locator to .metric-value; rerunning the real production flow now.

Money production E2E finding: the existing Ready to Assign assertion timed out because it targeted `.ready-amount`, which the shared MetricCard no longer renders. The expected amount was unchanged. Updated the E2E locator to `.metric-value`; rerunning the real production flow now.
Author
Owner

Money production E2E finding: after the transaction flow, the real server writes an empty notes:: "" field after the transaction row. The existing assertion expects the transaction row to end the file. This is outside #1179, so I kept the existing expectation unchanged and am recording it for review.

Money production E2E finding: after the transaction flow, the real server writes an empty `notes:: ""` field after the transaction row. The existing assertion expects the transaction row to end the file. This is outside #1179, so I kept the existing expectation unchanged and am recording it for review.
Author
Owner

Money screenshot finding: the new 4× crop used the harness capture() helper before the production app installed window.__userStorageTest; its theme assertion then failed with Cannot read properties of undefined (reading getItem). Added the same readiness wait already used by Analytics screenshots before capture.

Money screenshot finding: the new 4× crop used the harness `capture()` helper before the production app installed `window.__userStorageTest`; its theme assertion then failed with `Cannot read properties of undefined (reading `getItem`)`. Added the same readiness wait already used by Analytics screenshots before capture.
Author
Owner

Correction to my prior screenshot finding: the Money crop context never installed the harness installUserStorageTestSeam bridge, so readiness waiting alone could not fix it. I am adding the existing harness init helper to that screenshot context, following Analytics.

Correction to my prior screenshot finding: the Money crop context never installed the harness `installUserStorageTestSeam` bridge, so readiness waiting alone could not fix it. I am adding the existing harness init helper to that screenshot context, following Analytics.
Author
Owner

Money screenshot finding: capture reached theme verification and found mode=light, palette=mono, storedFamily=mono where the screenshot expected the Paper palette. The screenshot helper still wrote preferences to raw localStorage, while the app reads them through tagged User storage. I am updating the test helper to use the existing storage seam so the required light/dark palette screenshots are genuine.

Money screenshot finding: capture reached theme verification and found `mode=light`, `palette=mono`, `storedFamily=mono` where the screenshot expected the Paper palette. The screenshot helper still wrote preferences to raw `localStorage`, while the app reads them through tagged User storage. I am updating the test helper to use the existing storage seam so the required light/dark palette screenshots are genuine.
Author
Owner

Money screenshot run finding: the inferred-balance screenshot fixture timed out waiting for 1 duplicate transaction was merged. The component and request rendered, but that old summary string was absent. I have left the duplicate-flow expectation intact; the screenshot fixture now uses the existing real CSV preview to capture MoneyImportMetrics, and the duplicate behavior check remains in the later functional flow.

Money screenshot run finding: the inferred-balance screenshot fixture timed out waiting for `1 duplicate transaction was merged.` The component and request rendered, but that old summary string was absent. I have left the duplicate-flow expectation intact; the screenshot fixture now uses the existing real CSV preview to capture `MoneyImportMetrics`, and the duplicate behavior check remains in the later functional flow.
Author
Owner

Completed #1179 on job/cards-1179.

Base: f5fbced3c733370b5b37544d7816732628be2fd0
Head: 99ff6002cf9b0f82f4e494cfeda1152e685e7512

Built

  • Added the shared glass MetricCard. It puts each value at the top and its label at the bottom. It uses the stat token and Torph transitions, supports reduced motion, loading, accessible links, detail and accessory content, and page/inner/overlay glass roles.
  • Reused it for Money import results, Ready to Assign, account balances, Analytics stats, activity and habits.
  • Added the rule and design test. Refreshed exact Svelte performance pins without changing the ratchet.
  • Extended the Money import bench profile to measure the real import metric grid. Its measurement was deferred under the rule that perf runs are for performance issues.

Files

  • Shared UI: packages/ui/src/components/Card.svelte, packages/ui/src/components/MetricCard.svelte, packages/ui/src/index.ts.
  • Money: apps/web/src/lib/components/money/MoneyImport.svelte, MoneyImportMetrics.svelte, MoneyImportMetrics.svelte.test.ts, apps/web/src/routes/money/[budget]/[month]/+page.svelte, apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte.
  • Analytics: apps/web/src/lib/components/analytics/ActivityCard.svelte, ActivityCard.svelte.test.ts, HabitsCard.svelte, widgets/StatRow.svelte, widgets/StatRow.svelte.test.ts.
  • Design and tests: docs/DESIGN.md, apps/web/src/calternal-app.css, apps/web/src/lib/components/MetricCard.svelte.test.ts, apps/web/src/lib/themes.test.ts.
  • E2E evidence: apps/web/e2e/money.mjs, apps/web/e2e/analytics-overlay-973.mjs.
  • Perf: bench/money-import-462.mjs, contracts/perf/adoption-1058.json, contracts/perf/exceptions.json.

UX gaps closed

Metric values and labels now share one order, type, spacing and surface across the touched Money and Analytics cards. Loading values keep their shape and announce busy state. Links remain keyboard accessible and name their destination. The screenshot contexts emulate macOS and cover 390, 820 and 1440 px in light and dark palettes. Money has 24 full metric screens and 24 4× crops; Analytics has 6 full overlays and 18 4× crops.

UX gaps left

bun e2e/money.mjs exits 1 after completing its screenshot pass. Its unchanged Markdown assertion expects the final transaction row to end the file; the server writes - notes:: "" after that row. I kept the expectation unchanged. The later unrelated Money import publication and admin-plugin checks do not run after this assertion. An earlier screenshot attempt also found the duplicate-summary text expectation absent; the screenshot fixture now uses the existing real CSV preview, and the duplicate-flow expectation remains unchanged.

svelte-check still reports two existing empty-CSS-rule warnings in AttachmentDeck.svelte:1055 and AgendaList.svelte:1277.

Decisions not covered by DESIGN

  • MetricCard exposes as, surface, tone, optional value links, detail and accessory slots. Card.as supports existing section semantics while reusing one material and layout.
  • A card switches to compact layout below 180 px. StatRow keeps its existing 720 px overlay breakpoint.
  • The value transition uses Torph at 240 ms and follows reduced-motion settings.

Gates and evidence

bun run check exited 0. Output:

PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 22359 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Keyboard focus rings use the shared focus tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
svelte-check found 0 errors and 2 warnings in 2 files

bun run test --maxWorkers=2:

Test Files  276 passed (276)
      Tests  1911 passed (1911)
   Start at  09:56:34
   Duration  595.81s (transform 30%, environment 27%, import 22%, tests 14%, setup 6%)

bun e2e/analytics-overlay-973.mjs exited 0 and captured all six palettes/width combinations and 18 metric crops. The Money screenshot pass captured all metric surfaces before its preserved Markdown assertion failed. node --check bench/money-import-462.mjs and git diff --check passed. No Rust source changed, so Rust fmt/clippy/test gates were not run. cargo build -p calternal-server exited 0 for the production screenshots; its output ended with Finished dev profile [unoptimized + debuginfo] target(s) in 1m 13s. cargo clean was run and web build output was removed.

Screenshot attachments

For the merge round

The profile run is deferred because #1179 is visual-only under the current perf-run policy. If a perf follow-up is requested, build the app and server, then run CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" node bench/money-import-462.mjs --rows=50000. It must report p50/p95 UI render latency, server CPU/RSS for 2,000-row average and 50,000-row worst-case previews, plus the existing four-request burst.

Completed #1179 on `job/cards-1179`. Base: `f5fbced3c733370b5b37544d7816732628be2fd0` Head: `99ff6002cf9b0f82f4e494cfeda1152e685e7512` ### Built - Added the shared glass `MetricCard`. It puts each value at the top and its label at the bottom. It uses the stat token and Torph transitions, supports reduced motion, loading, accessible links, detail and accessory content, and page/inner/overlay glass roles. - Reused it for Money import results, Ready to Assign, account balances, Analytics stats, activity and habits. - Added the rule and design test. Refreshed exact Svelte performance pins without changing the ratchet. - Extended the Money import bench profile to measure the real import metric grid. Its measurement was deferred under the rule that perf runs are for performance issues. ### Files - Shared UI: `packages/ui/src/components/Card.svelte`, `packages/ui/src/components/MetricCard.svelte`, `packages/ui/src/index.ts`. - Money: `apps/web/src/lib/components/money/MoneyImport.svelte`, `MoneyImportMetrics.svelte`, `MoneyImportMetrics.svelte.test.ts`, `apps/web/src/routes/money/[budget]/[month]/+page.svelte`, `apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte`. - Analytics: `apps/web/src/lib/components/analytics/ActivityCard.svelte`, `ActivityCard.svelte.test.ts`, `HabitsCard.svelte`, `widgets/StatRow.svelte`, `widgets/StatRow.svelte.test.ts`. - Design and tests: `docs/DESIGN.md`, `apps/web/src/calternal-app.css`, `apps/web/src/lib/components/MetricCard.svelte.test.ts`, `apps/web/src/lib/themes.test.ts`. - E2E evidence: `apps/web/e2e/money.mjs`, `apps/web/e2e/analytics-overlay-973.mjs`. - Perf: `bench/money-import-462.mjs`, `contracts/perf/adoption-1058.json`, `contracts/perf/exceptions.json`. ### UX gaps closed Metric values and labels now share one order, type, spacing and surface across the touched Money and Analytics cards. Loading values keep their shape and announce busy state. Links remain keyboard accessible and name their destination. The screenshot contexts emulate macOS and cover 390, 820 and 1440 px in light and dark palettes. Money has 24 full metric screens and 24 4× crops; Analytics has 6 full overlays and 18 4× crops. ### UX gaps left `bun e2e/money.mjs` exits 1 after completing its screenshot pass. Its unchanged Markdown assertion expects the final transaction row to end the file; the server writes ` - notes:: ""` after that row. I kept the expectation unchanged. The later unrelated Money import publication and admin-plugin checks do not run after this assertion. An earlier screenshot attempt also found the duplicate-summary text expectation absent; the screenshot fixture now uses the existing real CSV preview, and the duplicate-flow expectation remains unchanged. `svelte-check` still reports two existing empty-CSS-rule warnings in `AttachmentDeck.svelte:1055` and `AgendaList.svelte:1277`. ### Decisions not covered by DESIGN - `MetricCard` exposes `as`, `surface`, `tone`, optional value links, detail and accessory slots. `Card.as` supports existing section semantics while reusing one material and layout. - A card switches to compact layout below 180 px. `StatRow` keeps its existing 720 px overlay breakpoint. - The value transition uses Torph at 240 ms and follows reduced-motion settings. ### Gates and evidence `bun run check` exited 0. Output: ```text PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 22359 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Keyboard focus rings use the shared focus tokens. UI transitions and animation options use shared motion tokens or documented exceptions. svelte-check found 0 errors and 2 warnings in 2 files ``` `bun run test --maxWorkers=2`: ```text Test Files 276 passed (276) Tests 1911 passed (1911) Start at 09:56:34 Duration 595.81s (transform 30%, environment 27%, import 22%, tests 14%, setup 6%) ``` `bun e2e/analytics-overlay-973.mjs` exited 0 and captured all six palettes/width combinations and 18 metric crops. The Money screenshot pass captured all metric surfaces before its preserved Markdown assertion failed. `node --check bench/money-import-462.mjs` and `git diff --check` passed. No Rust source changed, so Rust fmt/clippy/test gates were not run. `cargo build -p calternal-server` exited 0 for the production screenshots; its output ended with `Finished dev profile [unoptimized + debuginfo] target(s) in 1m 13s`. `cargo clean` was run and web build output was removed. ### Screenshot attachments - [Analytics full screens and crops](https://git.kayg.org/attachments/9f2c117b-d689-43e8-8035-55d4eeced60b) - [Money crops, dark](https://git.kayg.org/attachments/ab58053e-8649-4163-9bd3-dba0cf13ad7a) - [Money crops, light](https://git.kayg.org/attachments/fd21baf3-35f0-4b3d-a8d6-951d6e59fe28) - [Money full screens, 1440 px, dark](https://git.kayg.org/attachments/4b9411ad-9582-44a8-8ae6-103b8cb818a2) - [Money full screens, 1440 px, light](https://git.kayg.org/attachments/6a8f098b-3975-40f2-9ff6-b34a77d0728f) - [Money full screens, 390 px, dark](https://git.kayg.org/attachments/dbd5ff12-012b-478c-b1e1-58cee55cd176) - [Money full screens, 390 px, light](https://git.kayg.org/attachments/572f4f1b-dd94-4825-8aec-fdbcbed390fa) - [Money import summary, 820 px, dark](https://git.kayg.org/attachments/a9a9bf30-6ba8-401b-aa3e-b085e1d6d27b) - [Money budget summary, 820 px, dark](https://git.kayg.org/attachments/9ebaa490-b02a-433c-b4e1-f500b47919ce) - [Money card balance, 820 px, dark](https://git.kayg.org/attachments/f1946c4a-7a3a-410d-9734-a0599fbe2e56) - [Money account balance, 820 px, dark](https://git.kayg.org/attachments/a830c9db-65c0-4547-aab7-7531a8e6117d) - [Money import summary, 820 px, light](https://git.kayg.org/attachments/c5f89f81-8958-43b6-acfc-c322324ee473) - [Money budget summary, 820 px, light](https://git.kayg.org/attachments/700f0e83-6466-4180-8cfa-fd79b5f35998) - [Money card balance, 820 px, light](https://git.kayg.org/attachments/bfc2c103-29b0-49df-b2b6-b3db184319b7) - [Money account balance, 820 px, light](https://git.kayg.org/attachments/3310179e-531c-4b1f-959c-df28d048ee4f) ### For the merge round The profile run is deferred because #1179 is visual-only under the current perf-run policy. If a perf follow-up is requested, build the app and server, then run `CALTERNAL_SERVER_BIN="$CARGO_TARGET_DIR/debug/calternal-server" node bench/money-import-462.mjs --rows=50000`. It must report p50/p95 UI render latency, server CPU/RSS for 2,000-row average and 50,000-row worst-case previews, plus the existing four-request burst.
Author
Owner

Starting cards2-1179 on branch job/cards-1179, based at f5fbced3c733370b5b37544d7816732628be2fd0 (current branch head 99ff6002cf9b0f82f4e494cfeda1152e685e7512). I am reviewing the shared MetricCard sizing and the Money import summary grouping/grid from the latest visual review. I will verify the requested production screen at 390, 820 and 1440 px in light and dark before final gates.

Starting cards2-1179 on branch `job/cards-1179`, based at `f5fbced3c733370b5b37544d7816732628be2fd0` (current branch head `99ff6002cf9b0f82f4e494cfeda1152e685e7512`). I am reviewing the shared MetricCard sizing and the Money import summary grouping/grid from the latest visual review. I will verify the requested production screen at 390, 820 and 1440 px in light and dark before final gates.
Author
Owner

Focused Vitest finding: StatRow summary cards > renders the four metrics once each failed because it expected a direct, empty .metric-detail child on every card. MetricCard now groups content under .metric-content and omits the detail node when it is empty, so that test now checks the compact child order and retains the value/accessory/label assertions.

Focused Vitest finding: `StatRow summary cards > renders the four metrics once each` failed because it expected a direct, empty `.metric-detail` child on every card. MetricCard now groups content under `.metric-content` and omits the detail node when it is empty, so that test now checks the compact child order and retains the value/accessory/label assertions.
Author
Owner

Focused-test finding: the all-passing Checks value assertion read both Torph's visible value and its hidden accessibility string from textContent, so the fixture produced 33 instead of visible 3. The assertion now removes the hidden Torph node, matching the existing MetricCard test and preserving the actual visible-value check.

Focused-test finding: the all-passing Checks value assertion read both Torph's visible value and its hidden accessibility string from `textContent`, so the fixture produced `33` instead of visible `3`. The assertion now removes the hidden Torph node, matching the existing MetricCard test and preserving the actual visible-value check.
Author
Owner

Performance-guard finding: bun run check reported an unused StatRow render.bound exception after the shared card content stack changed. I refreshed the exact live StatRow scope hash from 02915bc1… to f907f92d… with the repo's approved pin refresh. contracts/perf/ratchet.json did not change, and perf-lint reports 0 violations.

Performance-guard finding: `bun run check` reported an unused StatRow `render.bound` exception after the shared card content stack changed. I refreshed the exact live StatRow scope hash from `02915bc1…` to `f907f92d…` with the repo's approved pin refresh. `contracts/perf/ratchet.json` did not change, and perf-lint reports 0 violations.
Author
Owner

Finding: The first MetricCard.svelte.test.ts run failed because its new source guard rejected minmax(0, 1fr) auto auto globally. MetricCard now uses those three rows only under .metric-content.has-detail; the ordinary stack has two rows and no minimum height. I updated the guard to assert both variants explicitly. Focused and full web gates are running.

Finding: The first `MetricCard.svelte.test.ts` run failed because its new source guard rejected `minmax(0, 1fr) auto auto` globally. MetricCard now uses those three rows only under `.metric-content.has-detail`; the ordinary stack has two rows and no minimum height. I updated the guard to assert both variants explicitly. Focused and full web gates are running.
Author
Owner

Finding from the real 390px light Money import review screenshot: the transaction card's wrapped breakdown makes its grid row tall, and the sibling Assignments card stretches to the same height. Its value-to-label gap is still large. I am changing the Money metrics grid to keep cards content-sized while retaining the 7.5rem auto-fit columns. The first E2E attempt also timed out waiting for the review dialog at previewCsvImport; I will inspect the run and repeat it after the layout fix.

Finding from the real 390px light Money import review screenshot: the transaction card's wrapped breakdown makes its grid row tall, and the sibling Assignments card stretches to the same height. Its value-to-label gap is still large. I am changing the Money metrics grid to keep cards content-sized while retaining the 7.5rem auto-fit columns. The first E2E attempt also timed out waiting for the review dialog at `previewCsvImport`; I will inspect the run and repeat it after the layout fix.
Author
Owner

The E2E setup's HTTP 507 did not reproduce: a fresh server and User returned HTTP 200 for the same PUT /api/v1/appearance request. No API or storage code changed. I will make one final production screenshot run against the final web build.

The E2E setup's HTTP 507 did not reproduce: a fresh server and User returned HTTP 200 for the same `PUT /api/v1/appearance` request. No API or storage code changed. I will make one final production screenshot run against the final web build.
Author
Owner

The one local run of bench/money-import-462.mjs did not reach its first sample: the Money route did not show its real empty state within 30 seconds. Host load at launch was 32.13 / 33.71 / 42.76 (1 / 5 / 15 minutes). This run produced no usable latency, CPU or RSS result. I will report it as unavailable and will not retry under the one-run rule.

The one local run of `bench/money-import-462.mjs` did not reach its first sample: the Money route did not show its real empty state within 30 seconds. Host load at launch was 32.13 / 33.71 / 42.76 (1 / 5 / 15 minutes). This run produced no usable latency, CPU or RSS result. I will report it as unavailable and will not retry under the one-run rule.
Author
Owner

#1179 complete

Branch: job/cards-1179
Head: e2306061063f1dfa7f419c8d548d6d87f44c47a3

Built

  • Made shared MetricCard content-sized, with intrinsic value/detail/label rows, a compact shared gap, and no empty card height. Added the shared --metric-card-min-width: 7.5rem token so auto-fit grids fill without narrow cards.
  • Changed Money import review from nine separate metrics to six: Accounts, Categories, Assignments, Transactions with split/transfer/cleared detail, Months, and check status with a passed/failed detail. The grid has no orphan at 390, 820 or 1440 px.
  • Kept Analytics metric rows on the shared card and refreshed the Money import profile to assert the six-card design.

Files

  • packages/ui/src/components/MetricCard.svelte
  • packages/ui/src/tokens.css
  • apps/web/src/lib/components/money/MoneyImportMetrics.svelte
  • apps/web/src/lib/components/analytics/widgets/StatRow.svelte
  • apps/web/src/lib/components/MetricCard.svelte.test.ts
  • apps/web/src/lib/components/money/MoneyImportMetrics.svelte.test.ts
  • apps/web/src/lib/components/analytics/widgets/StatRow.svelte.test.ts
  • bench/money-import-462.mjs
  • docs/DESIGN.md
  • contracts/perf/adoption-1058.json
  • contracts/perf/exceptions.json

Visible metric surfaces use shared MetricCard; Card owns the surface. This change adds no interactive controls.

UX gaps closed

  • The value and label now sit in a compact stack. Supporting breakdowns stay within their card and do not stretch neighboring cards.
  • Related counts share one Transactions card and one Checks card. At the reviewed widths, the six cards form 2×3, 6×1 and 3×2 rows.
  • Captured and reviewed real production screenshots in macOS emulation, light and dark, at 390, 820 and 1440 px. The Money issue attachments show the metric grid only, so transaction details are not exposed in this public issue.

Money metric screenshots:

Analytics screenshots:

UX gaps left

  • The full Analytics overlay E2E did not complete: its period-switch route wait timed out after 15 seconds under host load. A focused production-app capture completed the missing desktop-dark view; all six Analytics views are attached, and the shared component tests and full web unit suite passed.

Performance

The view renders six metric cards after the server has produced the import summary: O(6) card work, independent of imported row count. Existing import parsing remains proportional to the input rows.

The one local run of bench/money-import-462.mjs stopped before its first sample because the Money route did not show its real empty state within 30 seconds. Launch load average was 32.13 / 33.71 / 42.76 (1 / 5 / 15 minutes). It produced no valid latency, CPU or RSS numbers. No Money import profile exists in docs/perf/baseline.json; no measurement was added. I did not retry under the one-run rule.

Decisions

  • Use a 7.5rem minimum card width. It fits two columns on phone, three in the desktop sheet, and lets the wide tablet sheet fill all six cards.
  • Keep supporting text between the value and label, as DESIGN §34 specifies. The transactions breakdown may wrap at narrow card widths; the row height follows its content.

Gates

The branch was fetched against and merged with origin/dev; Git reported Already up to date. No Rust source changed, so Rust gates were not run. Gate output:

PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 22359 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Keyboard focus rings use the shared focus tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
svelte-check found 0 errors and 2 warnings in 2 files

Ran 136 tests in 0.053s

OK
 8 pass
 0 fail
 16 expect() calls
Ran 8 tests across 1 file. [553.00ms]

 Test Files  276 passed (276)
      Tests  1913 passed (1913)
   Start at  15:01:56
   Duration  403.35s (transform 29%, environment 26%, import 24%, tests 16%, setup 5%)

     Removed 8633 files, 7.3GiB total

The two Svelte warnings are the existing empty CSS rules in AttachmentDeck.svelte:1055 and AgendaList.svelte:1277.

## #1179 complete Branch: `job/cards-1179` Head: `e2306061063f1dfa7f419c8d548d6d87f44c47a3` ### Built - Made shared `MetricCard` content-sized, with intrinsic value/detail/label rows, a compact shared gap, and no empty card height. Added the shared `--metric-card-min-width: 7.5rem` token so auto-fit grids fill without narrow cards. - Changed Money import review from nine separate metrics to six: Accounts, Categories, Assignments, Transactions with split/transfer/cleared detail, Months, and check status with a passed/failed detail. The grid has no orphan at 390, 820 or 1440 px. - Kept Analytics metric rows on the shared card and refreshed the Money import profile to assert the six-card design. ### Files - `packages/ui/src/components/MetricCard.svelte` - `packages/ui/src/tokens.css` - `apps/web/src/lib/components/money/MoneyImportMetrics.svelte` - `apps/web/src/lib/components/analytics/widgets/StatRow.svelte` - `apps/web/src/lib/components/MetricCard.svelte.test.ts` - `apps/web/src/lib/components/money/MoneyImportMetrics.svelte.test.ts` - `apps/web/src/lib/components/analytics/widgets/StatRow.svelte.test.ts` - `bench/money-import-462.mjs` - `docs/DESIGN.md` - `contracts/perf/adoption-1058.json` - `contracts/perf/exceptions.json` Visible metric surfaces use shared `MetricCard`; `Card` owns the surface. This change adds no interactive controls. ### UX gaps closed - The value and label now sit in a compact stack. Supporting breakdowns stay within their card and do not stretch neighboring cards. - Related counts share one Transactions card and one Checks card. At the reviewed widths, the six cards form 2×3, 6×1 and 3×2 rows. - Captured and reviewed real production screenshots in macOS emulation, light and dark, at 390, 820 and 1440 px. The Money issue attachments show the metric grid only, so transaction details are not exposed in this public issue. Money metric screenshots: - 390 px: [light](https://git.kayg.org/attachments/3bcb576d-fbe5-42f6-a246-3d6540e587e8) · [dark](https://git.kayg.org/attachments/abc72544-3344-4730-8758-b38b4c8c7f62) - 820 px: [light](https://git.kayg.org/attachments/65ef8ee1-036a-4d73-8396-7597fa13226b) · [dark](https://git.kayg.org/attachments/5d02634a-10d6-4788-92ee-8f32f305a533) - 1440 px: [light](https://git.kayg.org/attachments/f20b8e9c-d172-4873-a948-645326f14e83) · [dark](https://git.kayg.org/attachments/911a9a91-a617-4b8f-9602-f94eea707ecf) Analytics screenshots: - 390 px: [light](https://git.kayg.org/attachments/0cd2abcc-e4be-4199-aaa3-e8ab88acc008) · [dark](https://git.kayg.org/attachments/01f3a9b6-44e1-4efc-a1be-a6205c140135) - 820 px: [light](https://git.kayg.org/attachments/a5f0b92e-40d6-462a-9ece-a15de25d0bab) · [dark](https://git.kayg.org/attachments/1a80c411-2361-42b8-bc51-2d1bd987c5ec) - 1440 px: [light](https://git.kayg.org/attachments/bee58900-0fbc-482c-a73d-761997f5beea) · [dark](https://git.kayg.org/attachments/4a674c3c-a818-4f68-939a-24542a0aef36) ### UX gaps left - The full Analytics overlay E2E did not complete: its period-switch route wait timed out after 15 seconds under host load. A focused production-app capture completed the missing desktop-dark view; all six Analytics views are attached, and the shared component tests and full web unit suite passed. ### Performance The view renders six metric cards after the server has produced the import summary: O(6) card work, independent of imported row count. Existing import parsing remains proportional to the input rows. The one local run of `bench/money-import-462.mjs` stopped before its first sample because the Money route did not show its real empty state within 30 seconds. Launch load average was 32.13 / 33.71 / 42.76 (1 / 5 / 15 minutes). It produced no valid latency, CPU or RSS numbers. No Money import profile exists in `docs/perf/baseline.json`; no measurement was added. I did not retry under the one-run rule. ### Decisions - Use a `7.5rem` minimum card width. It fits two columns on phone, three in the desktop sheet, and lets the wide tablet sheet fill all six cards. - Keep supporting text between the value and label, as DESIGN §34 specifies. The transactions breakdown may wrap at narrow card widths; the row height follows its content. ### Gates The branch was fetched against and merged with `origin/dev`; Git reported `Already up to date.` No Rust source changed, so Rust gates were not run. Gate output: ```text PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 22359 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Keyboard focus rings use the shared focus tokens. UI transitions and animation options use shared motion tokens or documented exceptions. svelte-check found 0 errors and 2 warnings in 2 files Ran 136 tests in 0.053s OK 8 pass 0 fail 16 expect() calls Ran 8 tests across 1 file. [553.00ms] Test Files 276 passed (276) Tests 1913 passed (1913) Start at 15:01:56 Duration 403.35s (transform 29%, environment 26%, import 24%, tests 16%, setup 5%) Removed 8633 files, 7.3GiB total ``` The two Svelte warnings are the existing empty CSS rules in `AttachmentDeck.svelte:1055` and `AgendaList.svelte:1277`.
Author
Owner

Follow-up from the #1179 review: in the shared metric card, a secondary breakdown line ("0 split · 0 transfer · 1 cleared") must sit BELOW the label in the small muted token, on one line with ellipsis or as a separate small row, never between value and label. Also apply the wizard polish: account-type picker placeholder "Choose type" sized to its longest option; "Import budget" uses the shared disabled style while types are missing.

Follow-up from the #1179 review: in the shared metric card, a secondary breakdown line ("0 split · 0 transfer · 1 cleared") must sit BELOW the label in the small muted token, on one line with ellipsis or as a separate small row, never between value and label. Also apply the wizard polish: account-type picker placeholder "Choose type" sized to its longest option; "Import budget" uses the shared disabled style while types are missing.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#1179
No description provided.