Skip to content

fix(frontend): name ladder accounts and guard Auto Approve saves - #1379

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

IAmJSD merged 14 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-742

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

The per-account ladder table listed each configuration by its internal account UUID, Auto Approve saved silently even though later ladder purchases then skip human approval, and a rejected save showed only "Failed to save ladder config. Check inputs and try again." with the server's reason discarded. The config modal was also hard to use: labels wrapped word by word, inputs stretched to the label height, and the title scrolled away on short screens. This PR names the accounts, warns and confirms on Auto Approve, surfaces the server error, and lays the form out as a compact two-column grid.

Closes #742

What changed

  • New frontend/src/ladder-config-table.ts renders the table (46c5153). The column is now "Account" and shows name (external ID) from the registered account list, keeps the UUID as the cell's title tooltip, and falls back to the UUID when the list fails or the account is gone.
  • frontend/src/ladder.ts (46c5153):
    • Loads the configs and listAccountsMinimal() in parallel; an account-list failure is treated as an empty list.
    • An inline .warning-message under Approval Mode shows whenever Auto Approve is selected, including when editing an existing Auto Approve config.
    • A save that newly turns on unattended purchasing (the saved config is enabled + Auto Approve and the stored one was not) first opens confirmDialog: title "Enable Auto Approve?", a red "Enable Auto Approve" button, and Cancel.
    • The error toast is Failed to save ladder config: <API error text>, with the generic text only when there is no message.
    • A thin exported renderConfigTable wrapper stays in ladder.ts.
  • New frontend/src/ladder-config-modal.ts: the modal markup moved out of ladder.ts with no changes (24dea97), leaving ladder.ts at 382 lines.
  • Config modal layout (a01a277), with new frontend/src/styles/ladder-form.css (imported from styles/index.css):
    • Labels sit above standard-height inputs (2.25rem) in an aligned two-column grid that drops to one column under 640px. Rows: Cloud Account | Provider, Approval Mode | Enabled, the Auto Approve warning across the full width, a "Schedule and limits" divider, then Cadence | Lookback Days, Target Coverage | Buffer Fraction, Baseline Percentile | Buffer Utilization Threshold, Max Hourly Commit | Max Actions.
    • The Ramp Schedule textarea spans the full width in monospace, with its help text below.
    • Qualifiers such as "Minimum 10", "Between 1 and 50" and "Leave blank for no cap" are muted hints under the inputs, linked with aria-describedby.
    • The modal is 680px wide. The header stays fixed, the body scrolls inside the modal on short viewports, and Cancel / Save stay pinned at the bottom.
    • The layout commit changes no field IDs or validation.
  • Toast placement (frontend/src/toast.ts, e516dcc, 83568cb). This is an app-wide change, but it only matters when a toast would cover an open dialog's button row. Error toasts stay up for 30s, and at 1280x720 the default bottom-right corner sat on the ladder modal's Save, swallowing the retry click. The new layoutToastStack() measures the stack at its default corner (right 16px, bottom 16px, its real width and height). It changes nothing unless that box overlaps an on-screen button row (.modal-footer, .modal-buttons, .modal-confirm-actions) of the topmost open dialog (.modal-content or .modal-confirm; a confirm counts over a .modal). In that case the stack moves:
    • beside the dialog when the gap to its right is at least 240px (width capped at 400px);
    • otherwise above the whole dialog when there is room;
    • otherwise just above the button row it would cover. On a phone-sized bottom sheet this can sit over the bottom of the sheet's scrolling body; the toast can be dismissed.
    • Toasts in every other position, and dialogs whose buttons are elsewhere on screen (for example a short centred .modal-wide dialog or a confirm), keep the default corner.
    • The layout re-runs while toasts are showing when a dialog opens or closes (a MutationObserver on class/hidden/child changes), on scroll inside a dialog (captured on document), and on resize, coalesced to one pass per frame. The watchers are removed when the last toast goes away.
    • frontend/src/styles/components.css: the toast container reads --toast-bottom and --toast-max-width (defaults 1rem and 400px), which toast.ts overrides through the CSSOM, so no inline style attributes are needed under the CSP.
  • Tests: src/__tests__/ladder.test.ts, src/__tests__/toast.test.ts, tests-e2e/ladder-account-selector.spec.ts and tests-e2e/toast-dialog-placement.spec.ts.

The free-text UUID field the issue describes was already replaced on main by a registered-account dropdown (#868, commit 970de3d0), so the modal's account input needed no further change.

Why this approach

The Auto Approve safeguard follows the existing RI exchange "Fully Automated" pattern (inline warning banner plus a destructive confirm), so the two automation settings behave the same. The confirm fires only on the change into unattended mode, so editing an already auto-approved config or saving a disabled one does not nag; the inline warning always shows.

The table does its own account lookup instead of sharing the modal selector's fetch. The selector deliberately refetches on every open (tests guard against stale responses), so sharing would have meant reworking it; the cost is one extra GET /api/accounts/list on page load.

The error text comes from apiRequest, which already copies the JSON error field into err.message, so no API change is needed.

Moving the modal markup to its own module in a separate, behavior-free commit keeps the layout change reviewable and keeps ladder.ts well under the 500-line rule.

The toast fix lives in toast.ts rather than in the ladder modal because any dialog with its buttons in the bottom-right corner had the same problem. It moves the stack only when the stack would actually cover a dialog's buttons, so other screens see no change.

Screenshots

Before is main (a14dc8f2), after is this branch. Both are headless Chromium at 1440x900 against the repo's mocked e2e API, with mocked AWS accounts "Production" and "Staging". The last pair is at 1280x720.

Before After
config-table before config-table after
auto-approve-warning before auto-approve-warning after
auto-approve-confirm before auto-approve-confirm after
save-error before save-error after
ladder-config-form before ladder-config-form after
ladder-config-form-short before ladder-config-form-short after
  • Config table: main identifies each config only by its internal UUID; this branch shows the registered name and external ID, with the UUID as the hover title.
  • Auto Approve selected: main gives no indication of what it does; this branch shows an amber warning under Approval Mode that purchases skip human approval.
  • Saving an enabled Auto Approve config: main goes straight to "Ladder config saved."; this branch opens "Enable Auto Approve?" with Cancel as the safe default.
  • A 400 from the API: main shows only "Check inputs and try again."; this branch shows the server's validation message.
  • Add Account Config at 1440x900: main wraps labels word by word, stretches inputs to the label height and squeezes the ramp textarea; this branch has labels above standard-height inputs in an aligned two-column grid with muted range hints.
  • The same form at 1280x720, scrolled to the end: on main the title has scrolled away and the ramp textarea is a sliver beside its help; on this branch the body scrolls inside the modal, the title and Cancel / Save stay visible, and the ramp textarea spans the full width.

The screenshots were taken on an earlier head, before main moved to f2ab6731 and before the toast placement change. The changes since then are outside the screenshotted areas except for the "save-error" after shot: on the current head, a toast that would cover Cancel / Save moves beside the modal (or above it on narrow screens), so it no longer sits in the corner shown there.

Verification

Run on this branch's head 903a2d5f, which includes main f2ab6731:

  • npm run lint: 0 errors (141 pre-existing warnings). Typecheck and build pass.
  • Jest: 149 suites, 4122 passed, 1 skipped.
  • New jest tests ("account labels and auto-approve safeguards"): the table shows the name and keeps the ID as the title; it falls back to the ID when the list fails; the warning follows the mode select and shows when editing an Auto Approve config; the confirm is shown, Cancel blocks the save and Confirm saves; no confirm when Auto Approve is already active or the config stays disabled; the server error text appears in the toast; Lookback Days has "Minimum 10" as its accessible description. Three existing selector tests now install their pending account mock after the section renders, since the table also fetches accounts at init.
  • New jest tests in toast.test.ts ("toast stack and open dialogs", with stubbed geometry): the default corner with no dialog; beside the dialog when the corner would cover its buttons; never wider than 400px beside a wide gap; lifted above the button row when there is no room beside or above; lifted above a short bottom sheet rather than onto it; the default corner under a centred short dialog with room below it, and when the stack is too narrow to reach the buttons; only the topmost dialog counts, so a confirm over the ladder modal keeps the toast off the confirm (and a tall confirm sends it beside, never onto, the confirm); the visible button row is used, not the last one in the DOM; a button row scrolled into view under the stack moves it; open/close re-layout; unrendered dialogs ignored; the watchers stop once the last toast is gone.
  • Playwright via e2e-local.sh: 238 passed. New e2e coverage: the table name and title, the warning, confirm Cancel (no PUT) and the 400 error text end to end; at 1440x900, inputs under 44px tall, paired fields sharing a row with aligned left edges, the ramp textarea spanning both columns with its hint below; at 1280x720, the body scrolls while the title and Save stay on screen; at 390px, a single column. For the toast:
    • "Save stays clickable while a save-error toast is showing" at 1280x720 and 390x844: the toast's box does not overlap Save, and a second click on Save sends a second PUT with the toast still up.
    • The cleared Buffer Fraction test checks that the error toast does not cover Save, then saves without dismissing it.
    • At 1280x720, an error toast over the short, centred account overrides dialog (.modal-wide) does not overlap the dialog.
    • At 1100x720, a toast that is up when the Auto Approve confirm opens over the ladder modal does not overlap the confirm or its warning text.
  • Secrets scan clean (planted key flagged); scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.

Notes for review

  • Warning copy: "Auto Approve skips human approval. Ladder purchases for this account will run without an approval email, and commitments are long-term and usually cannot be cancelled once bought." The confirm adds "Check the target coverage and per-run caps before continuing." The planner currently says "Ladder purchases are not executed yet", so the wording may want "once the ladder engine runs purchases".
  • Having the confirm at all, rather than only the inline warning, is a design call; the issue allowed either.
  • The column header changed from "Account ID" to "Account".
  • The server error only goes to the toast, not inline in the modal.
  • fix(frontend): dialogs bypassing openModal lack focus handling, Escape and alert roles #731 (fix(frontend): route class-toggled dialogs through openModal #1372) is on main: the ladder modal opens and closes through openModal / closeModal, and the Auto Approve confirmDialog stacks over it with its own key handler.
  • Toast placement now changes app-wide, but only when a toast would cover an open dialog's button row. On a phone-sized bottom sheet the lifted toast can sit over the bottom of the sheet's scrolling body (for example over a field named in the error); the body still scrolls and the toast can be dismissed.

Overlaps

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 3 commits October 23, 2026 23:59
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 LeanerCloud#742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 LeanerCloud#742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 LeanerCloud#742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged urgency/this-quarter Within the quarter impact/few Limited audience effort/m Days type/bug Defect labels Oct 24, 2026
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • 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 5 commits October 24, 2026 17:18
Conflicts:
- frontend/src/ladder.ts: import block kept both (this branch's
  ladder-config-table, ladder-config-modal and listAccountsMinimal imports
  plus main's readLadderNumbers from LeanerCloud#1326). renderConfigTable kept this
  branch's thin wrapper over renderLadderConfigTable; main's only change to
  the old inline table, the target_coverage guard (LeanerCloud#1326, Refs LeanerCloud#134:
  render N/A instead of throwing on a missing value), is re-applied in
  frontend/src/ladder-config-table.ts, where the table now lives. Main's
  readLadderNumbers validation in saveLadderConfig merged cleanly and runs
  before this branch's Auto Approve confirm and server-error toast.
- frontend/tests-e2e/ladder-account-selector.spec.ts: kept both. This
  branch's two tests (account names / Auto Approve confirm / server error
  text; compact layout) followed by main's cleared Buffer Fraction test.

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

Main's cleared-Buffer-Fraction test (LeanerCloud#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 LeanerCloud#742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- frontend/src/ladder.ts: import block only. This branch imports
  confirmDialog (Auto Approve confirm); main (LeanerCloud#731 via LeanerCloud#1372) imports
  openModal/closeModal to route the ladder config modal through the
  shared focus-trap helper. Kept both. Main's openModal(modal) /
  closeModal(modal) calls merged cleanly into this branch's
  openLadderConfigModal / closeLadderModal. The confirmDialog opened
  over the ladder modal is appended to body with its own capture-phase
  key handler, so its Escape does not reach the ladder modal's trap,
  and it restores focus to Save on settle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 LeanerCloud#742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
css-custom-properties.test.ts (LeanerCloud#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 LeanerCloud#742

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): REQUEST_CHANGES 802ddfef569e77848604988e322fa03f9efa80d2

Blocking:

  • frontend/src/toast.ts:104-114: whenever the gap beside the dialog is under 240px, the stack is lifted above the dialog's highest button row. The code never checks whether the toast at its default bottom-right corner would have touched that row. So for any dialog whose buttons sit mid-screen, the toast moves from empty space onto the dialog's content. Scenario 1: at 1280x720, a .modal-wide dialog (800px wide, leaving a 208px gap), such as a short purchase, account-overrides or RI-exchange modal, is vertically centred with .modal-buttons near y≈480. Before, an error toast sat at the bottom-right (y≈624-704) and covered nothing. Now it moves to bottom≈248px, on top of the modal's lower-right content and fields. Scenario 2 (nested): at viewport widths 1000-1223 the ladder modal leaves less than 240px. When the Auto Approve confirmDialog opens over it while a validation toast is still up, Math.min(footerTops) picks the confirm's mid-screen action row. The toast jumps onto the right side of the confirm's body: the warning text the user must read before a destructive action. A lone confirmDialog at about 768-1000px wide does the same. Fix: lift only when the default toast box (right 16px, bottom 16px, up to 400px wide) actually overlaps the button row's rect; otherwise keep the defaults.
    Non-blocking:
  • frontend/src/toast.ts:103: --toast-max-width is set to the whole gap with no 400px cap. At 1920 wide with a 480px confirm open, long toasts grow to about 688px and then shrink back when the dialog closes. Use min(side, 400).
  • frontend/src/toast.ts:136-143: the layout only re-runs on class/hidden/childList mutations and window resize, never on scroll. .modal-buttons scrolls inside .modal-content (overflow-y: auto). If the buttons are below the fold when the toast appears, nothing is lifted, and scrolling down to Save brings back the original toast-over-Save bug in exactly the scroll-heavy case. Scrolling back up leaves the toast lifted over fields. The CSS comment "never covers Save" (components.css:353) overclaims.
  • frontend/src/toast.ts:107: .pop() takes the last button row in DOM order, not the last visible one. In the profile modal (auth.ts: the Save Changes row, then the MFA section's own .modal-buttons), a narrow viewport lifts relative to the MFA row and ignores Save Changes. A hidden trailing row (null rect) means no lift at all.
  • At 390x844 the lifted toast (up to ~358px wide) sits over the bottom of the sheet's scroll body. The new e2e only asserts that Save is clear, not that the field named in the error (Max Actions) is still reachable. This is acceptable because the body scrolls and the toast can be dismissed, but it is a trade-off, not a fix.
  • The MutationObserver watches class/hidden on the whole <body> subtree for the full 30s a toast is up. It is coalesced to one layout per frame, but it runs layout on every unrelated class toggle. Teardown only happens through dismiss(), and nothing else removes toasts today, so there is no actual leak.
  • CSP is fine: el.style.setProperty through the CSSOM is CSP-safe and copies the existing lib/table-scroll.ts setVar pattern. The 802ddfe defaults satisfy css-custom-properties.test.ts.
    Attacked: Remerge-diffed a6b0b38. The import union is right. Main's fix(frontend): sign-in, ladder save and registrations review fixes #1326 target_coverage N/A guard was carried into ladder-config-table.ts:39, and readLadderNumbers validation still runs before the Auto Approve confirm. All three e2e tests were kept. 62cbf97 is an import-only union, and main's openModal/closeModal (fix(frontend): route class-toggled dialogs through openModal #1372) calls are intact in the open and close paths. Compared main's old inline modal markup with ladder-config-modal.ts: same IDs and min/max/step, labels moved into aria-describedby hints, and the title, label and data-* attributes are escaped in the table. Main f2ab673 merges cleanly and doesn't touch these files. Worked the toast.ts geometry through modals.css, .modal-wide, #plan-modal, .modal-confirm, the ≤480px bottom sheet and the ladder dialog at 390/768/1024/1280/1920 widths. Checked open/close mechanics (the .hidden class, confirm backdrop append/remove): toasts raised before or after a dialog opens re-lay out, and the defaults come back after it closes. Ran jest (toast, ladder, css-custom-properties) in a scratch worktree: 198/198. The unit tests stub rects and cover only a full-height dialog, never a centred short dialog or a stacked confirm.

IAmJSD and others added 3 commits October 24, 2026 20:46
Bring in f2ab673 (bulk approval confirm totals, LeanerCloud#1362) before fixing
the toast placement review finding. It merges cleanly; none of its files
overlap this branch.

Refs LeanerCloud#742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'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 LeanerCloud#742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 LeanerCloud#742

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

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

On the blocking re-review finding above: fixed in 83568cbd (code and unit tests) and 903a2d5f (e2e). Main is merged at c78c9725.

  • When the stack moves: layoutToastStack() measures the stack at its default corner, and moves it only when that box overlaps an on-screen button row of the topmost dialog. So when a confirm sits over the ladder modal, only the confirm counts. Rows are picked by visible rect, not DOM order.
  • Where it goes: beside the dialog, with width capped at min(gap, 400px). Failing that, above the whole dialog when there's room. Failing that, just above the covered row.
  • What no longer moves: short centered .modal-wide dialogs and mid-screen confirms now keep the default corner.
  • One remaining case: a confirm that fills the screen, such as a phone bottom sheet. Any position covers something there, so the stack lifts above the buttons to keep them clickable.
  • Non-blocking items:
    • Layout re-runs on scroll inside a dialog, rAF-coalesced.
    • The watchers are torn down with the last toast.
    • The CSS comment now states what the code guarantees, with no "never covers Save" claim.
  • Tests:
    • New jest cases cover the centered short dialog, a stack too narrow to reach the buttons, a confirm over the ladder modal, a tall confirm, the 400px cap, a bottom sheet, picking a visible row over a hidden trailing one, scroll, and teardown.
    • New e2e tests cover the 1280x720 account-overrides dialog and a 1100x720 Auto Approve confirm over the ladder modal.
    • On 802ddfef, 9 of 25 toast jest tests and both new e2e specs fail. All pass now.
  • Checks: jest 4122 passed, Playwright 238 passed, and lint, typecheck, build and git-secrets all pass.

IAmJSD and others added 3 commits October 24, 2026 22:17
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>
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>
Conflicts:
- frontend/src/ladder.ts: main's LeanerCloud#1386 (LeanerCloud#751 icon set) imported iconSvg
  and swapped the ladder modal's close &times; for iconSvg('x') in the
  inline modal markup, which this branch moved to ladder-config-modal.ts.
  Kept this branch's side (confirmDialog import, renderLadderConfigModal()
  call) and reapplied main's change in ladder-config-modal.ts: it now
  imports iconSvg and renders the close button as ${iconSvg('x')}.
  ladder.ts no longer needs the iconSvg import.

frontend/src/toast.ts auto-merged: main's SVG kind/close icons
(createIcon) sit alongside this branch's layoutToastStack placement.

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 d7b31a24f83613a3d4c9fb297c22d8bf412a1700

Blocking:

  • none (the last review's blocker is fixed: toast.ts:140-142 returns early unless the default 16px corner box actually intersects an on-screen button row of the topmost dialog, so short centred .modal-wide dialogs and mid-screen confirms keep the corner)
    Non-blocking:
  • frontend/src/toast.ts:310 vs open fix(frontend): low-severity copy, toast, confirm and stale-state fixes #728 (head a006d14): fix(frontend): low-severity copy, toast, confirm and stale-state fixes #728 now also edits showToast's tail (hover/focus timer pause, startTimer() in place of the setTimeout block) and conflicts here, not just in ladder.test.ts. Whoever lands second must keep layoutToastStack() and startWatching() after the append, together with fix(frontend): low-severity copy, toast, confirm and stale-state fixes #728's startTimer().
  • frontend/src/ladder.ts:72 + ladder-timeline.ts:140: on page load, /api/accounts/list is fetched for the table and then again, one after the other, by the timeline. The timeline's fetch predates this branch, and the account list could be passed through. Minor.
  • The ${name} (${external_id}) label is now built three times in the ladder area: ladder-config-table.ts:23, ladder-account-selector.ts:24/36 and ladder-timeline.ts setAccounts. A shared helper would keep them in step.
  • toast.ts final fallback (step-up loop): with tall stacked rows or a tall stack, bottom can push the stack's top above the viewport. No clamp; unlikely in practice.
    Attacked: Re-derived layoutToastStack geometry: only the topmost dialog is used (rank, then DOM order); row boxes are clipped to the visible viewport; side width is min(gap,400); then above the dialog, then the step-up. Checked watcher hygiene: style changes are not observed, so there is no feedback loop; scroll is captured and removed with a matching capture flag; teardown happens on the last dismiss. Merge d7b31a2 vs both parents: ladder.ts keeps the branch side; main chore(frontend): replace mixed Unicode glyphs with one SVG icon set #1386's iconSvg('x') close button is reapplied in ladder-config-modal.ts (no × left); toast.ts carries main's createIcon kind/close icons plus layoutToastStack. 24efa94: fix(frontend): card layout and UTC date inputs for ladder purchases #1387's timeline move and chore(frontend): add typography foundation for text, headings, controls #1378 tokens auto-merged. 0931776: font-weight tokens. The modal markup matches main's (IDs, min/max/step), and the table escapes all interpolations. In a scratch worktree, jest toast/ladder*/typography/css*/icons/confirmDialog passed 485/485 across 15 suites.

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at d7b31a24 merged onto current main (6cc5e355; merge-tree 40e660e7; 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 22a093f5, main plus 5 PRs) passes npm ci, lint, typecheck, build, jest (1 skipped, 4374 passed, 4375 total), and the full Playwright suite (259 passed).
  • CI is green on d7b31a24 (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:59
@IAmJSD
IAmJSD merged commit e59d143 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 d7b31a24.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/few Limited audience priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/bug Defect urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(frontend): ladder config asks for a raw account UUID and hides auto-approve impact

1 participant