Skip to content

chore(frontend): add typography foundation for text, headings, controls - #1378

Merged
IAmJSD merged 9 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-741
Oct 24, 2026
Merged

IAmJSD merged 9 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-741

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

The stylesheet had no body line-height (text used the browser's cramped ~1.2 default), form controls did not inherit the page font (inputs in one form rendered at 13.33px, 14.4px and 14px), and headings without a page-specific rule got browser default sizes. This PR adds a body line-height, a token-based heading scale and one font size for form controls, and swaps literal sizes and weights that exactly equal a design token for that token.

Refs #741
Partial: off-scale literal font sizes (0.8rem, 0.85rem, 0.9rem, 0.95rem, ...) are not snapped onto the token scale, because that visibly changes dozens of components and needs a design call.

What changed

  • frontend/src/styles/base.css: body { line-height: var(--cudly-lh-normal) } (1.5); button, input, select, textarea { font: inherit; font-size: var(--cudly-fs-sm); line-height: var(--cudly-lh-tight) }; a zero-specificity :where(h1..h6) scale on the tokens (h1 --cudly-fs-2xl, h2 xl, h3 lg, h4 md, h5/h6 sm) with tight line-height and bold weight (3e46a9a).
  • Literal font sizes equal to a --cudly-fs-* value (0.75/0.875/1/1.75rem) and all font weights (bold, 700, 600, 500) now use the matching token, about 60 lines across 6 CSS files (components, forms, modals, plans, settings, tables). Pure substitution: no computed value changes (751ea48).
  • The .modal-content input rule and the bare select rule use --cudly-fs-sm (14px) instead of 14.4px, so every control in a form matches the buttons (73385ea).
  • Two literals that main added and that equal a token are tokenized the same way: .account-delete-pending-total font weight 600 and .last-updated-stamp font size 0.75rem (25c2918). No computed value changes.
  • .form-help (<small>) is now display: block, placed after main's .form-group [aria-invalid="true"] rule. As an inline element its wrapped lines sat 24px apart under the new line-height and its margin-top never applied (b772276).
  • Tests: frontend/src/__tests__/typography.test.ts (jest, reads the stylesheets) and frontend/tests-e2e/typography.spec.ts (Playwright, checks computed fonts in Chromium, which jsdom cannot cascade).

Why this approach

The heading scale uses :where() so the ~20 existing contextual heading rules keep winning; only headings that had no rule change size. The scale tracks the browser defaults it replaces, and heading weight stays bold so headings that set only a size do not change weight. Form controls keep a tight line-height so button and input heights barely change (otherwise every button would grow about 4px). Only exact-equivalent literal-to-token swaps are included; snapping off-scale sizes would be a visible design change and would collide with most other open CSS branches.

Screenshots

Screenshots were taken before this branch was merged with main; before = main at a14dc8f. After is this branch at that time. Both are headless Chromium at 1440x900 against the repo's mocked e2e API.

Before After
apikey-modal before apikey-modal after
users-page before users-page after
  • Create API Key modal: on main the name input (14.4px) is larger than the password and date inputs (13.33px system font), and the date input uses a different face. On this branch all inputs use the page font at 14px like the buttons, and help text has block spacing.
  • Admin > Users: the same page with the 1.5 body line-height instead of ~1.2. Rows and paragraphs are less cramped; this is the main site-wide change to check.

Verification

Run on this branch's head f5ba4e5d, merged with main 6122fa24:

  • npm run lint: 0 errors. Typecheck and build pass.
  • Jest: 4116 passed, 1 skipped, 0 failed, including the new typography.test.ts (20 tests over 13 stylesheets).
  • Playwright (full e2e suite): 233 passed, 0 failed, including the 2 new typography.spec.ts tests. The input/button size check fails on main.
  • Secrets scan clean.
  • Merged with main at 6122fa2; conflicts in components.css, modals.css, forms.css, tables.css and tabs.css resolved, see the merge commits.

Notes for review

  • Site-wide visible change: the 1.5 body line-height makes every page a bit taller, including table rows, wrapped table headers such as "Last Login", and paragraphs. All layout e2e specs still pass.
  • Smaller changes: headings without a contextual rule change size slightly (h1 32px to 28px, h2 24px to 22px, h5/h6 up to 14px); bare inputs go from 13.33px to 14px; selects and modal text inputs go from 14.4px to 14px; date and number inputs use the page font instead of the system UI font.
  • Not done (design call): collapsing the ~20 remaining off-scale literal sizes onto the token scale, and unifying heading weights to 600.
  • Pre-existing bug seen but not addressed: in the Create API Key modal each permission checkbox sits centered on its own line above its label, because .modal-content label is display: block and the checkbox gets width: 100% from the modal input rules. That belongs to a forms/modal issue.
  • Commits 751ea48 and 25c2918 are mechanical substitutions and are easy to regenerate after a rebase.
  • The typography.test.ts guard against literals that duplicate a token only matches font-size: <literal>; and font-weight: <literal>;. It does not catch !important, the font: shorthand or px equivalents.
  • .form-help keeps its off-scale 0.8rem size.

Overlaps

This touches base.css, components.css, forms.css, modals.css, plans.css, settings.css and tables.css. Open CSS branches that edit the same files: #737 (status badges; conflicts textually) and #743 (modal/card unification; merges cleanly with this branch today).

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

IAmJSD and others added 4 commits October 23, 2026 22:10
…ale, control font)

The body had no line-height, so text used the browser's ~1.2 default.
Form controls did not inherit the page font, so .form-group inputs
rendered at 13.33px system text next to 14px buttons. Headings had no
base scale: every heading without a contextual rule got the browser's
em-based default.

- body: line-height var(--cudly-lh-normal) (1.5).
- button, input, select, textarea: font: inherit, size pinned to
  --cudly-fs-sm (the button size) and line-height --cudly-lh-tight, so
  control heights stay where they were. Element selectors keep the
  existing `select`/`button`/class rules in charge.
- :where(h1..h6): a 5-step scale on the --cudly-fs-* tokens (2xl, xl,
  lg, md, sm), tight line-height, bold weight. Zero specificity, so the
  ~20 contextual heading rules still win.

Refs LeanerCloud#741

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t equals one

Literal font sizes that are exactly a --cudly-fs-* value (0.75rem,
0.875rem, 1rem, 1.75rem) and every numeric or keyword font weight (bold,
700, 600, 500) now use the matching token, so a later change to the
scale reaches them. Pure substitution: every computed value is
unchanged. Off-scale sizes (0.8rem, 0.85rem, 0.9rem, ...) are left as
they are, since snapping them to the scale is a visible design change.

Refs LeanerCloud#741

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h tests

Inside the Create API Key modal the text input got 14.4px from the
.modal-content rule while the password and date inputs fell back to the
browser's 13.33px, next to 14px buttons. The modal input rule and the
bare `select` rule now use --cudly-fs-sm, so every control in a form
matches the buttons (a 0.4px change for those two rules).

Tests: a jest suite reads the stylesheets for the body line-height, the
control font rule, the heading scale and the absence of token-duplicate
literals; a Playwright spec checks the computed input/button font and
body line-height in Chromium, which jsdom cannot cascade.

Refs LeanerCloud#741

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.form-help is used on <small> elements, which are inline by default.
With the new 1.5 body line-height their wrapped lines sat on the 16px
parent line box and ended up 24px apart, and their margin-top never
applied. As a block they get their own 0.8rem line box and the intended
gap below the input.

Refs LeanerCloud#741

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added priority/p2 Backlog-worthy severity/medium Moderate harm type/chore Maintenance / non-user-visible triaged Item has been triaged urgency/this-quarter Within the quarter impact/all-users Affects every user effort/m Days labels Oct 24, 2026
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 10 billable files and costs up to $2.50.

Or wait 59 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 19534fc and 9ffff64.

Files selected for processing: 10
  • frontend/src/__tests__/typography.test.ts
  • frontend/src/styles/base.css
  • frontend/src/styles/charts.css
  • frontend/src/styles/components.css
  • frontend/src/styles/forms.css
  • frontend/src/styles/modals.css
  • frontend/src/styles/plans.css
  • frontend/src/styles/settings.css
  • frontend/src/styles/tables.css
  • frontend/tests-e2e/typography.spec.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

IAmJSD and others added 3 commits October 24, 2026 17:30
Conflicts:
- frontend/src/styles/components.css: button/.btn base rules took main's
  tokenized transitions (LeanerCloud#1366) with this branch's --cudly-fs-sm;
  .approval-details-table thead th took main's --cudly-surface-muted
  background (LeanerCloud#1382 replaced the undefined --cudly-surface-2) with this
  branch's --cudly-fw-semibold.
- frontend/src/styles/modals.css: .archera-signup-btn and
  .archera-offer-signup keep this branch's --cudly-fs-md /
  --cudly-fw-medium and main's tokenized transitions.
- frontend/src/styles/forms.css: main removed the duplicated ramp-option /
  #custom-ramp-config block (LeanerCloud#1366); took the removal. The surviving copy in
  plans.css already uses --cudly-fw-semibold for .ramp-label.
- frontend/src/styles/tables.css: th took main's header style (LeanerCloud#1366),
  which already uses --cudly-fw-semibold; took main's removal of the
  .planned-purchases-table th override this branch had tokenized.
- frontend/src/styles/tabs.css: took main's removal of the dead
  .tabs / .tab-btn rules (LeanerCloud#1366) that this branch had tokenized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… merge

Main added .account-delete-pending-total { font-weight: 600 } (LeanerCloud#1373) and
.last-updated-stamp { font-size: 0.75rem } (LeanerCloud#1314). Both equal a design
token exactly, so the typography test that pins 'no literal that
duplicates a token' failed after merging main. They now use
--cudly-fw-semibold and --cudly-fs-xs; computed values are unchanged.

Refs LeanerCloud#741

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- frontend/src/styles/forms.css: both sides inserted text before
  .form-help. Kept main's .form-group [aria-invalid="true"] border rule
  (LeanerCloud#1368) and then this branch's comment and display: block on
  .form-help; the two rules are independent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE f5ba4e5d277b577c9281024f83ecb3e9431f4342

Blocking:

IAmJSD and others added 2 commits October 24, 2026 21:00
…n added

Main's LeanerCloud#1387 added .ladder-timeline-controls label (font-weight 500)
and .ladder-timeline-stat (font-weight 600) to charts.css. Swap them for
--cudly-fw-medium and --cudly-fw-semibold; computed values are unchanged.

Refs LeanerCloud#741

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE 9ffff64c814dc80e0b0961a88e8ac1d2384809c2

Blocking:

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at 9ffff64c merged onto current main (2a3110e8; merge-tree ff56058f; Linux, Node, headless Chromium):

  • This branch's own checks passed at this head: lint, typecheck, build, the full jest suite, the full Playwright e2e suite, and the git-secrets checks (see Verification in the PR body).
  • The combined tree of every UI PR in this merge wave (tree ab77aa6e, main plus 5 PRs) passes npm ci, lint, typecheck, build, jest (1 skipped, 4248 passed, 4249 total), and the full Playwright suite (247 passed).
  • CI is green on 9ffff64c (CI Success, Run pre-commit hooks).
  • The independent review above approves this exact head.
  • Nothing was applied to any cloud.

@IAmJSD
IAmJSD marked this pull request as ready for review October 24, 2026 22:06
@IAmJSD
IAmJSD merged commit bb564dd into LeanerCloud:main Oct 24, 2026
29 checks passed
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
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 9ffff64c.

IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Brings in LeanerCloud#1365, LeanerCloud#1381, LeanerCloud#1378 and LeanerCloud#1389. No conflicts; index.html
auto-merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Conflicts:
- frontend/src/styles/components.css: main (LeanerCloud#1378 typography tokens)
  only swapped literal font weights for --cudly-fw-* tokens inside the
  .status-badge and .badge-admin/-user/-readonly rules, which this branch
  removes; kept the removal. In .badge kept this branch's
  line-height: var(--cudly-lh-tight) and white-space: nowrap together with
  main's tokenized font weight and radius.
- frontend/src/styles/tables.css: main only tokenized the font weight of
  the planned-purchases Bootstrap badge palette, which this branch
  replaces with the shared .badge tones; kept this branch's
  .planned-purchase-row.status-paused dimming rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
No textual conflicts. frontend/src/ladder.ts and
frontend/src/styles/components.css auto-merged: main's LeanerCloud#1387 moved the
#ladder-purchase-timeline container out of the laddering fieldset and
LeanerCloud#1378 swapped literal font sizes/weights in components.css for the
--cudly-fs-*/--cudly-fw-* tokens; this branch's ladder table/modal split
and toast stack variables sit in other hunks and are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Main's typography foundation (LeanerCloud#1378) forbids literal font weights that
duplicate a --cudly-fw-* token; the ladder config form's labels and
section divider still used 500 and 600.

Refs LeanerCloud#742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit that referenced this pull request Oct 24, 2026
* fix(frontend): name ladder accounts and guard Auto Approve saves

The ladder config table listed each configuration by its internal
account UUID, Auto Approve could be saved without any hint that later
ladder purchases skip human approval, and a rejected save replaced the
server's validation message with a generic failure.

The table now loads the registered account list alongside the configs
and shows "name (external ID)", keeping the UUID as the cell title and
falling back to it when the list is unavailable. Selecting Auto Approve
shows an inline warning, and a save that newly turns on unattended
purchasing (enabled + Auto Approve) asks for confirmation first, the
same pattern the RI exchange Fully Automated mode uses. Save failures
now show the API's error text. The table renderer moves to
ladder-config-table.ts to keep ladder.ts under 500 lines.

Refs #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(frontend): move ladder config modal markup to its own module

ladder.ts sits at 494 lines, just under the 500-line rule, and the config
modal's form layout needs reworking (review feedback: huge inputs and
word-wrapped labels). Moving the static markup out first keeps that
change reviewable and leaves ladder.ts room. No markup changes.

Refs #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(frontend): compact two-column layout for the ladder config modal

Review feedback: the Add/Edit Account Config form laid each label and
input side by side, so long labels wrapped word by word, every input
stretched to the wrapped label's height, input edges were ragged, the
Ramp Schedule textarea was squeezed next to its help text, and on short
viewports the title scrolled away with the form.

Labels now sit above standard-height inputs, and the short fields pair
up in an aligned two-column grid (one column under 640px). Range and
unit qualifiers such as "Minimum 10" or "Leave blank for no cap" move to
muted hints under the inputs, linked with aria-describedby. The ramp
textarea spans the full width with its help below. The header stays
fixed while the body scrolls, with Cancel / Save pinned to the bottom.
Field IDs, validation and save behaviour are unchanged.

Refs #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(frontend): dismiss the validation toast before re-saving in the ladder e2e test

Main's cleared-Buffer-Fraction test (#1326) clicks Save again while the
validation toast is up. With this branch's compact modal the Save button
is pinned to the bottom-right, under the toast stack at the default
1280x720 viewport, so the click was intercepted. Dismiss the toast first,
as ladder-config.spec.ts already does between saves.

Refs #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(frontend): keep the toast stack off an open dialog's Save button

At 1280x720 the compact ladder config modal puts Save in its
bottom-right corner, directly under the bottom-right toast stack. A
validation or server-error toast stays up for 30 seconds and swallowed
clicks on Save until it was dismissed.

While a dialog box (.modal-content or .modal-confirm) is on screen,
toast.ts now lays the stack out around it: beside the dialog, narrowed
to the gap on its right, when that gap is at least 240px wide (268px at
1280x720 for the ladder modal), otherwise lifted above the dialog's
button row (.modal-footer, .modal-buttons, .modal-confirm-actions). The
positions are CSS custom properties set through the CSSOM, so no style
attributes are added (CSP). A MutationObserver and a resize listener
re-run the layout while toasts are showing, so a toast that was already
up when a dialog opens moves too; nothing is watched while the stack is
empty. This applies to every dialog, not only the ladder modal.

The ladder e2e test no longer dismisses the validation toast before
saving again; it asserts the toast does not cover Save. A new e2e test
at 1280x720 and 390x844 shows a server-error toast and clicks Save
again with it still up. Without this change those three e2e checks
fail with the toast's box over Save's.

Refs #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(frontend): declare the toast stack's position properties

css-custom-properties.test.ts (#745) requires every var() a stylesheet
reads to be defined. Declare --toast-bottom and --toast-max-width on
.toast-container with the old 1rem / 400px values and read them without
fallbacks; toast.ts's inline override still wins while a dialog is open,
and removing it restores the defaults.

Refs #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(frontend): move the toast stack only when it would cover a dialog's buttons

The stack was lifted above an open dialog's button row whenever the gap
beside the dialog was under 240px, without checking that the toast's
corner touched that row. A short centred .modal-wide dialog at 1280x720,
or a confirm stacked over the ladder modal, had the toast moved from empty
space onto the dialog's content, including a destructive confirm's warning.

layoutToastStack() now measures the stack at its default corner and moves
it only when that box overlaps an on-screen button row of the topmost
dialog (a confirm over a .modal; rows picked by visible rect, not DOM
order). It then goes beside the dialog (width capped at 400px), above the
whole dialog when there is room, or just above the covered row. Layout
also re-runs on scroll inside a dialog, and the watchers are torn down once
the last toast is gone. The CSS comment no longer claims more than that.

Refs #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(frontend): check toasts stay off short and stacked dialogs

Browser coverage for the toast placement fix: at 1280x720 an error toast
raised over the short, centred account overrides dialog (.modal-wide)
stays in its corner instead of moving onto the dialog, and at 1100x720 a
toast that is up when the Auto Approve confirm opens over the ladder modal
stays off the confirm and its warning text. Both fail on the previous
layout code.

Refs #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(frontend): use the font-weight tokens in the ladder form styles

Main's typography foundation (#1378) forbids literal font weights that
duplicate a --cudly-fw-* token; the ladder config form's labels and
section divider still used 500 and 600.

Refs #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
…tatus

Main's typography foundation (LeanerCloud#1378) forbids literal font weights in
modals.css that duplicate a token. The fan-out status line used a
literal 600; use var(--cudly-fw-semibold) instead, same weight.

Refs LeanerCloud#757

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit that referenced this pull request Oct 25, 2026
…#1390)

* fix(frontend): keep the bulk purchase modal open after a partial failure and retry only failed buckets

When some buckets of a multi-bucket ("fan-out") purchase failed, the modal
closed and the selection was cleared before the outcome was classified, and
the toast truncated the reasons (3) and labels (5). Users had to find and
reselect the failed rows in a large table, at the risk of resubmitting the
buckets that had already gone through.

On any bucket failure the modal now stays open:
- each bucket section shows "Submitted: awaiting approval [from X]" /
  "Submitted: joined existing request ..." or "Failed: <full reason>";
- submitted buckets are dimmed and their payment selects are locked;
- a banner (role=alert) states how many were submitted and failed, that
  submitted buckets will not be sent again, and that closing the dialog
  does not cancel them;
- the fan-out state is narrowed to the failed buckets, so the submit
  button becomes "Retry N failed" and resubmits only those. A failed
  bucket's payment can still be changed before retrying.
The sticky failure toast is skipped in this mode (the modal has the full
detail, and the toast covered the Retry button), and the Archera offer
waits until the purchase modal closes. A fully successful retry closes
the modal as before.

Buckets get a stable id (copies from getFanOutBuckets() lose identity)
and sections carry data-bucket-id so results map back to their section.
Callers without ids keep the previous close-and-toast behavior.

Refs #757

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(frontend): cover partial bulk purchase failure and retry

Jest: the modal stays open with per-bucket status, submitted buckets are
locked, the button reads "Retry N failed", a retry posts only the failed
buckets and closes the modal (and opens the Archera offer) once all
succeed, a second partial failure keeps earlier successes counted, an
all-failed run also stays open, buckets without ids keep the old
behavior, and failure reasons cannot inject markup.
Playwright: three providers, Azure fails once, the dialog shows the
failure, Retry 1 failed posts only the Azure bucket and closes.

Refs #757

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(frontend): match the bulk-approval confirm label in the fan-out retry e2e

Main's #1362 renamed the fan-out confirm button from "Send all for
approval" to "Send for Approval ($X upfront on approval)", naming the
combined upfront of the buckets being sent. The partial-failure spec
still clicked the old label and timed out. It now matches the new
label with the amount as a pattern, since the first send and the retry
carry different totals.

Refs #757

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(frontend): use the semibold weight token for the fan-out bucket status

Main's typography foundation (#1378) forbids literal font weights in
modals.css that duplicate a token. The fan-out status line used a
literal 600; use var(--cudly-fw-semibold) instead, same weight.

Refs #757

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(frontend): only enter fan-out retry mode while the submitting modal is open

showFanOutRetry only checked that #purchase-details still held fan-out
sections. Closing the modal (Escape or Cancel) while the POSTs were in
flight leaves those sections in the hidden modal, so a failed bucket put
its banner there and suppressed the failure toast: the user got no
notice at all. Bucket ids were also per-index and reused on every open,
so results settling after the user opened a new bulk modal marked its
sections submitted and narrowed its buckets to the old failed ids.

Bucket ids now carry the purchase-modal open token, and retry mode is
entered only when #purchase-modal is visible and still holds the
submitted bucket ids. Otherwise the toasts fire as on main. If a newer
purchase modal is open, it is not closed or cleared. The #1503
payment-adjustment toast also uses the default timeout in retry mode
instead of staying on top of the Retry button.

Refs #757

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

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

Labels

effort/m Days impact/all-users Affects every user priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant