Repository navigation
fix(frontend): one card and dialog surface, kpi-tile on Opportunities - #1380
Merged
Merged
Conversation
Home built its four summary cards as .card.kpi-tile with DOM calls, while Opportunities wrote plain .card innerHTML, so the same kind of KPI had a different title weight, value size, padding and border on the two pages. New lib/kpi-tile.ts (buildKpiTile / renderKpiTiles) builds the tile markup once with textContent. Home's renderDashboardSummary now uses it (same markup as before, sparkline slot included) and so does the Opportunities summary (data-kpi count / savings / upfront / payback, no detail line or sparkline). The card/value/savings classes stay, so existing selectors and tests keep working. Refs LeanerCloud#743 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cards: .card was a borderless 0 2px 4px shadow on white, .kpi-tile and .group-card used a hairline border, .plan-card and .upcoming-card a token shadow with no border. .card is now the single surface (1px --cudly-border, --cudly-shadow-sm, --cudly-r-md, --cudly-surface). .kpi-tile inherits it instead of redefining it, and .plan-card, .group-card and .upcoming-card match it. .stat-card stays a borderless muted inset tile because it sits inside a card. Dialogs: .modal, .modal-overlay, confirmDialog and the Archera offer each had their own scrim (0.5 vs 0.45 black), radius (8px) and shadow, plus loose z-index literals. New base.css tokens: --cudly-scrim, --cudly-shadow-dialog and a named z-index ladder (modal 1000, raised 1050, page overlay 1100, confirm 2000, toast 2100). The values are unchanged, so the stacking order stays the same and a confirm still opens above any modal. Every dialog panel now uses --cudly-r-lg (12px, matching the existing mobile bottom-sheet corners) and the dialog shadow. Refs LeanerCloud#743 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…face Compares computed styles in Chromium. The first Home and Opportunities summary tiles must match on border, radius, shadow, padding, background and title/value type. The New Plan .modal and the Delete-plan confirmDialog must match on scrim color, radius (12px) and shadow. Refs LeanerCloud#743 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Conflicts: - frontend/src/dashboard.ts: import block. Kept this branch's buildKpiTile import and main's formatTrendTooltipTitle (LeanerCloud#1323) on the combined chart-utils import. - frontend/src/recommendations.ts: import block. Kept this branch's renderKpiTiles import and main's captureFocus/restoreFocus import. The shared-surface CSS (base, components, modals, plans, settings) merged without conflicts; main's CSS changes in those files (motion tokens, contrast, spacing, code-example block) touch other properties and keep the branch's scrim, dialog shadow and z-index tokens intact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No textual conflicts. Checked main's changes next to this branch's: LeanerCloud#1344 moved the YTD sparkline into loadYtdSparkline(), which looks up .kpi-tile-spark[data-spark-key="ytd"]; lib/kpi-tile.ts still renders that slot for the YTD tile. CSS added by LeanerCloud#1336/LeanerCloud#1332/LeanerCloud#1388 (sticky section nav z-index 5, table scroll shadows z-index 9, sort buttons) sits below the dialog z-index ladder and does not touch card or dialog surfaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - frontend/src/dashboard.ts (imports): kept this branch's lib/kpi-tile import and took main's widened chart-utils import (formatSparseTrendMessage, MIN_TREND_POINTS from LeanerCloud#1334). - frontend/src/dashboard.ts (renderDashboardSummary): this branch replaced the hand-built tile loop with buildKpiTile; main (LeanerCloud#1376, LeanerCloud#739) added a guard to that loop so an absent NO_VALUE figure is not styled as a saving. Kept buildKpiTile and moved main's guard into it (lib/kpi-tile.ts: valueSavings && value !== NO_VALUE), so Home keeps main's behaviour. Because Opportunities uses the same builder, its savings tile showing NO_VALUE is now neutral too, matching LeanerCloud#739's intent. Added a kpi-tile.test.ts case for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - frontend/src/dashboard.ts: import block only. Kept this branch's buildKpiTile import (lib/kpi-tile) and main's upcoming-scope import (LeanerCloud#1365) side by side. No body conflicts; main's onboarding, upcoming scope and chart a11y changes merged cleanly around the kpi-tile loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE Blocking:
|
Collaborator
Author
|
Local verification at
|
IAmJSD
marked this pull request as ready for review
October 24, 2026 22:57
Collaborator
Author
|
Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Opportunities summary cards were hand-built
.cardinnerHTML while Home used the.kpi-tilecomponent, so the same kind of KPI looked different on the two pages. Card styles disagreed on border and shadow, and each of the four dialog systems (.modal,.modal-overlay,confirmDialog, the Archera offer) had its own scrim, radius, shadow and z-index literal. This PR reuses the kpi-tile on Opportunities and puts every card and dialog panel on one shared surface through tokens.Refs #743
Partial: the shared
.dialogcomponent with a header row and close button, and merging the four JS modal systems into one implementation, are not done (they need edits to about 20 dialogs and their close wiring).What changed
9b7ca8e4: newfrontend/src/lib/kpi-tile.ts(buildKpiTile/renderKpiTiles,textContentonly).dashboard.tsrenderDashboardSummaryuses it, replacing a 23-line hand-built loop with identical markup (sparkline slot included).recommendations.tsrenderRecommendationsSummaryuses it instead of innerHTML (data-kpicount / savings / upfront / payback). Thecard/value/savingsclasses are kept, so existing selectors and tests still work.03fe2ef6(CSS):styles/base.cssadds--cudly-scrim,--cudly-shadow-dialogand a named z-index ladder (--cudly-z-modal1000,-modal-raised1050,-page-overlay1100,-confirm2000,-toast2100). The values are unchanged, so the stacking order is the same and a confirm still opens above any modal..cardis now 1px--cudly-border+--cudly-shadow-sm+--cudly-r-md+--cudly-surface..kpi-tileinherits that instead of redefining it, and.plan-card,.group-cardand.upcoming-cardmatch it (.upcoming-cardkeeps its 4px blue left accent)..stat-cardstays a borderless muted inset tile..modal,.modal-overlay,.modal-confirm-backdropand.archera-offer-backdropuse the scrim;.modal-content,.modal-confirmand.archera-offer-paneluse--cudly-r-lg(12px, matching the existing mobile bottom-sheet corners) and the dialog shadow. All dialog and toast z-indexes use the tokens.e1fa861e:tests-e2e/surfaces.spec.ts. Plus new jest fileskpi-tile.test.ts,recommendations-kpi-tiles.test.tsandsurface-css.test.ts.Why this approach
This is the subset that fixes what users see without rewriting dialog markup: one tile component, one card surface and one dialog surface, all through tokens. The z-index values keep their order on a named ladder rather than being collapsed, since a confirm must still open above a modal. The dialog shadow is a new
--cudly-shadow-dialograther than the suggested--cudly-shadow-lg, becauseshadow-lg(0.08 alpha) disappears over a 50% scrim. The scrim is a slate tint (15, 23, 42) matching the existing shadow tokens instead of pure black..stat-cardkeeps its borderless fill because it sits inside a card, and a border there would draw a box inside a box. The tile builder lives inlib/sorecommendations.ts(5,700 lines) anddashboard.tsshare one implementation.Screenshots
Before is main (
a14dc8f2), after is this branch. Both are headless Chromium at 1440x900 against the repo's mocked e2e API.Verification
Run on this branch's head
e1fa861eon top of maina14dc8f2:--randomize(33). New:kpi-tile.test.ts(5: full tile, optional parts, empty detail, text-not-markup, render order),recommendations-kpi-tiles.test.ts(2: the Opportunities summary is four.kpi-tilecards with the expected titles anddata-kpi; only savings is green),surface-css.test.ts(26: tokens defined, z-ladder order, every scrim/dialog panel/z-index/card surface uses the shared tokens).surfaces.spec.ts(2: computed styles of the first Home and Opportunities tiles match on border, radius, shadow, padding, background and title/value type; the New Plan modal and the Delete-plan confirm share scrim, 12px radius and shadow).scripts/test-git-secrets-allowlist.sh: 137 pass.Notes for review
.cardin the app now has a 1px hairline border and a lighter shadow (the old one was0 2px 4pxat 0.1 black). Worth looking over the Admin, Plans and Purchases cards.--cudly-shadow-dialogis a new token; the issue suggested reusingshadow-lg..dialogcomponent with a shared header row and close button. OnlyconfirmDialoghas an X today; adding one to every.modalmeans editing about 20 dialogs inindex.htmland TS, plus close wiring. (b) Merging the four JS modal systems (modal.tsopenModal,confirmDialog.ts, the Archera offer, the auth/plans.modal-overlayvariants) into one implementation.Overlaps
#recommendations-summaryand#summary(grid columns inlayout.css/tables.css) on different lines; together they give balanced 2x2 kpi tiles.renderRecommendationsSummary's innerHTML template (adds a "Filtered: N of M" detail<p>), which conflicts with the switch torenderKpiTiles. Resolve by passing that text as the savings tile'sdetail.lib/kpi-tile.ts(an absent value should not be green). Its conditionvalueSavings && value !== NO_VALUEneeds to move intobuildKpiTileor into the callers' specs..plan-card/.upcoming-card/.kpi-tileandmodals.css; adjacent, should merge cleanly. chore(frontend): add typography foundation (line-height, heading scale, font inherit) #741 changes font-size/weight lines inmodals.cssandcomponents.css; no shared lines. fix(frontend): dialogs bypassing openModal lack focus handling, Escape and alert roles #731 routes more dialogs throughopenModal, which is complementary.Those PRs will be opened alongside this one and merged in sequence, with conflicts resolved on the later branch.
Note for the UI owner
This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.
🤖 Generated with Claude Code