Skip to content

fix(ladder): timeline editor 409 reload, focus restore and canvas tab stop - #688

Merged
cristim merged 2 commits into
mainfrom
fix/667-ladder-timeline-editor
Oct 9, 2026
Merged

cristim merged 2 commits into
mainfrom
fix/667-ladder-timeline-editor

Conversation

@cristim

@cristim cristim commented Oct 9, 2026

Copy link
Copy Markdown
Member

Summary

Part of #667 (follow-ups from PR #665), the editor and focus items:

  • After a 409 the modal now shows "Reload and discard draft". It closes the modal and reloads the timeline, so a retry uses the current expected_revision. Other errors do not show it; the draft-retained message is unchanged.
  • After a successful save or a reload, focus returns to the edited purchase button ([data-event=<id>]) instead of falling to body.
  • The chart canvas no longer has tabindex="0" (it is role="img"); keyboard access stays on the purchase table.

Not in this PR

Disposing the Chart.js instance and listeners when the user navigates away. Tabs are hidden, not unmounted, and switchTab has no teardown hook for the settings section, so doing it properly means adding one in navigation.ts and re-init on return. That is a separate concern; #667 stays open for it (comment to follow on the issue).

What is verified

Run locally (macOS): Jest src/__tests__/ladder* 30/30, tsc --noEmit and eslint on the changed files exit 0. The three new Jest tests (409 reload and refocus, no reload on a 500, focus after save plus no canvas tabindex) fail on the previous code (stash check) and pass now.

What is not proven

  • Jest uses jsdom and mocked API modules; this is fixture-based, not a real browser against a backend.
  • The Playwright ladder-timeline.spec.ts was not run locally (disk hold, needs a production build); CI's Frontend E2E covers it. The spec's canvas click probe and the "draft is retained" text assertion are unaffected by these changes on reading, not by a run.
  • No deployed-environment check. Independent review at the head SHA is pending.

Refs #667

… stop

- After a 409 the editor now shows a "Reload and discard draft" action
  that closes the modal and reloads the timeline, so the stale
  expected_revision can be replaced; other errors do not offer it.
- After a successful save or a reload, focus returns to the edited
  purchase button instead of falling to the body.
- Remove tabindex from the chart canvas: it has role img, takes focus
  and does nothing; keyboard access is the purchase table.
- Jest: three tests that fail on the previous code (409 reload and
  refocus, no reload on a 500, focus after save and no canvas tabindex).

Refs #667
@cristim cristim added priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline impact/few Limited audience effort/s Hours type/bug Defect triaged Item has been triaged labels Oct 9, 2026
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 3 billable files and costs up to $0.75.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 43 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 83 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 415a6d7c-0999-46fc-b55e-a1f441621f15

📥 Commits

Reviewing files that changed from the base of the PR and between c126553 and 65ca9c2.


📒 Files selected for processing (3)
  • frontend/src/__tests__/ladder-timeline.test.ts
  • frontend/src/ladder-timeline-editor.ts
  • frontend/src/ladder-timeline.ts


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@cristim

cristim commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Independent review (ladder-graph-kimi, Claude Sonnet 5.5; full-diff pass by me, not a model-pinned Opus run) at head b1bdb53.

Verdict: no actionable findings. Approve at this SHA, conditional on CI green (see limits).

Checked:

  • 409 path: ApiError.status is set in api/client.ts for non-OK responses; reload.hidden = !conflict is reassigned on every failure, so a later non-409 error hides the button again. The finally re-enables all buttons (reload included); a successful retry closes the modal. The existing e2e rejectNextSave assertions match the new text ("draft is retained" is kept; Escape still closes; focus returns to event-2). No e2e or Jest assertion depends on the old "Reload the timeline before retrying" text or on canvas focus.
  • Focus after reload: load() destroys the chart and re-renders the table, then focusEvent runs. If the row is filtered out or the load is superseded, focus ends on body; acceptable and not a regression (the old path had the same behavior).
  • Chart disposal left out: safe for this change. load() calls chart?.destroy() before every load and render() destroys before each new Chart, so reload cannot double-render or leak. The remaining gap is only leaving the page, which is unchanged by this PR and tracked on fix(ladder): timeline editor 409 recovery, focus and chart cleanup #667.
  • a11y: the reload button is inside the form footer, in tab order Cancel, Undo, Reload, Save, and isVisible excludes it while hidden, so the shared trap cycles correctly. Removing tabindex from the role="img" canvas leaves keyboard access to the table; it does not contradict rv-ux-usability (M6 is about modals bypassing openModal; this one uses it. M15 is about text alternatives, untouched here).
  • Mutation (Jest, scratch copy of the SHA, existing node_modules): base 12/12 pass. Restore tabindex: 1 fails. Drop the 409 branch: 1 fails. Drop the focus call: 2 fail. Drop closeModal in reload: 1 fails.

Limits: Jest evidence is jsdom (mock-based). I did not run Playwright locally (disk hold, no build); at my last check Frontend E2E, Unit Tests and Lint were still pending. The gate needs CI green on this SHA; any new commit restarts it.

@cristim
cristim merged commit e5ac75d into main Oct 9, 2026
27 checks passed
@cristim
cristim deleted the fix/667-ladder-timeline-editor branch October 16, 2026 19:54
IAmJSD added a commit that referenced this pull request Oct 24, 2026
…ion (#1357)

The Chart.js instance behind the ladder planned-purchase timeline, with
its ResizeObserver and listeners, was only destroyed when the timeline
was re-initialized, so it stayed alive after the user navigated away
from Settings > Purchasing. Visiting the General sub-tab also rebuilt it
inside the hidden Purchasing panel, because loadGlobalSettings runs the
ladder init for both sub-tabs.

navigation.ts now calls disposeLadderTimeline() when switching to any
top-level tab other than Settings and to any settings sub-tab other than
Purchasing. Dispose destroys the chart and drops in-flight loads.
initLadderTimeline skips building while its section is hidden, and the
existing loadGlobalSettings call re-initializes the timeline when the
Purchasing sub-tab is shown again.

The other items in the issue (409 reload, focus restore after save, no
tab stop on the canvas) were shipped in #688.

Refs #667

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit that referenced this pull request Oct 24, 2026
…g loading skeletons busy (#1370)

Low-severity accessibility items from the 2026-10-08 review:

- UX-27: Escape in the RI Exchange column-filter popovers (Active RIs and
  Reshape) closed them and dropped focus to <body>. They now return focus
  to the column's filter button, as the History popover already does.
- UX-31: with the chip-select search input showing, aria-activedescendant
  stayed on the unfocused trigger, so screen readers never announced the
  highlighted option; it now lives on whichever element holds focus (the
  search input, which also gets aria-controls). Picking an option from the
  search input hid the focused element and dropped focus to <body>; focus
  now returns to the chip.
- UX-34: skeleton containers now carry aria-busy="true" while a skeleton
  shows. Success renders replace the children without calling back into
  lib/skeleton.ts, so a MutationObserver clears the flag once no
  placeholder is left; teardownSkeleton clears it directly. The ladder
  canvas tab stop was already removed by #688.
- UX-36: #topbar-filters gets role="group" so its aria-label is exposed.
  The small-target links reported on Admin General could not be reproduced
  on this base and are left out.

Refs #729

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/s Hours impact/few Limited audience priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/bug Defect urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant