Skip to content

redesign(ui): rebuild the plan-reminder form as a grouped-row panel - #2002

Merged
jackwener merged 1 commit into
mainfrom
redesign/plan-reminders-astryx
Aug 3, 2026
Merged

redesign(ui): rebuild the plan-reminder form as a grouped-row panel#2002
jackwener merged 1 commit into
mainfrom
redesign/plan-reminders-astryx

Conversation

@jackwener

Copy link
Copy Markdown
Member

Rebuilds the 新建定时任务 create/edit dialog per the review feedback ("特别丑,完全重做"). Layout inspiration from the reference scheduled-task panels; expression entirely in Maka's own idiom on Astryx.

Before → After

Before: nine fields packed into paired two-column FormLayouts under an eyebrow heading — dense, boxy, unlike any other Maka surface.

After:

  • A quiet kicker (新建提醒 / 编辑提醒) with the template menu + close beside it.
  • One display element: a large borderless title input — a raw <input> wrapped in Astryx Field, so the hidden label and detached error status stay fully accessible.
  • The bare description textarea (no label chrome), then two labeled groups of label-left / control-right hairline rows — the settings open-row idiom scoped to the dialog:
    • 频率: 重复 / 时间 / Cron(条件), with the run-at presets as a quiet ghost row under 时间
    • 投递: 方式 / 平台(条件) / Chat ID(条件)
  • The title's required-error no longer scolds an untouched empty form — it appears after first input (submit was already gated).
  • Copy gains groupSchedule/groupDelivery + short row labels (时间 / 方式) so group and row labels stop repeating; the unused eyebrow style is retired.

All form state, validation, and the submit single-flight / close-guard invariants are unchanged — presentation only.

Verification

build ✅ · typecheck ✅ · check-dead-css ✅ · check-a11y/copy/console ✅ · Storybook smoke 71 renders × 3 viewports ✅ · rendered screenshot of the open dialog verified from the built Storybook (posted in the work thread).

Follow-up (next PR): the list page (plan-reminder-panel.tsx) — big title + search + segmented state filter + quiet rows, per the same references.

The create/edit dialog packed nine fields into paired two-column
FormLayouts under an eyebrow heading — dense, boxy, and unlike any
other Maka surface. Rebuilt on the reference scheduled-task panel
shape, expressed in Maka's own idiom:

- a quiet kicker (新建提醒 / 编辑提醒) with the template menu and close
  beside it, then ONE display element: a large borderless title input
  (raw <input> wrapped in Astryx Field so the hidden label and
  detached error status stay accessible), then the bare description
  textarea;
- the schedule and delivery fields become two labeled groups of
  label-left / control-right hairline rows (重复 / 时间 / Cron, then
  方式 / 平台 / Chat ID) — the settings open-row idiom scoped to the
  dialog — with the run-at presets as a quiet ghost row under 时间;
- the title's "required" error no longer scolds an untouched empty
  form: it appears after first input (the submit button was already
  the gate);
- copy gains groupSchedule/groupDelivery + short row labels (时间 /
  方式) so group and row labels stop repeating each other; the unused
  eyebrow style is retired.

All form state, validation, and the submit single-flight/close-guard
invariants are unchanged — presentation only.

Verified: build, typecheck, check-dead-css, check-a11y/copy/console,
product Storybook smoke (71 renders x 3 viewports), plus a rendered
screenshot of the open dialog from the built Storybook.
@jackwener
jackwener merged commit 72027d2 into main Aug 3, 2026
Astro-Han added a commit that referenced this pull request Aug 3, 2026
#2002 rebuilt the dialog's title row as a bare <input autoFocus> inside a
Field, replacing a TextInput with `hasAutoFocus`. Astryx's Dialog picks its
initial focus target by querying `[data-autofocus]` after showModal(), and
its own source documents why React's autoFocus cannot work here: it calls
.focus() during commit, while the dialog is still invisible, so the focus
silently fails. TextInput emits `data-autofocus`; the bare input did not,
so opening 编辑提醒 left the caret on the close button instead of the title.

Move the field onto the same seam. Caught by the E2E that asserts the title
box is focused when the edit dialog opens.
Astro-Han added a commit that referenced this pull request Aug 3, 2026
…utofocus, harbor-cell deadline race) (#2001)

* test(e2e): sample Mermaid toolbar geometry atomically

`toolbar.top - figure.top` is structurally the figure's 1px top border:
the Toolbar is the figure's first flex child, so zoom cannot move it. The
assertion still read 2-3px of change in CI because it derived that offset
from two independent boundingBox() round-trips, and the transcript is a
bottom-pinned scroller that re-pins on every ResizeObserver update — the
zoom-induced viewport reflow lands between the two samples and the scroll
drift shows up as a phantom offset.

Read both the toolbar offset and the viewport height inside one evaluate,
so the pair is sampled in a single frame, and poll for the steady state
instead of the frame right after the click: the zoomed layout settles over
a rAF, a ResizeObserver pass, and the scroller's re-pin. The 1px tolerance
is unchanged; only the sampling is fixed.

Closes #2000

* fix(ui): restore autofocus on the plan-reminder title field

#2002 rebuilt the dialog's title row as a bare <input autoFocus> inside a
Field, replacing a TextInput with `hasAutoFocus`. Astryx's Dialog picks its
initial focus target by querying `[data-autofocus]` after showModal(), and
its own source documents why React's autoFocus cannot work here: it calls
.focus() during commit, while the dialog is still invisible, so the focus
silently fails. TextInput emits `data-autofocus`; the bare input did not,
so opening 编辑提醒 left the caret on the close button instead of the title.

Move the field onto the same seam. Caught by the E2E that asserts the title
box is focused when the edit dialog opens.

* test(headless): stop racing the deadline against harbor-cell setup

The force-stop test asserts that the deadline stops an *active* isolated
tool, but established that precondition by giving the cell 1000ms of wall
clock to finish setup, create the session, and drive the first send before
the timer fired. Lose that race on a loaded runner and the run is still
cancelled by `benchmark.deadline` — settledByDeadline stays true — while
the backend is never stopped, so `stopModes` is empty. That is the CI
failure: actual [] vs expected ['immediate'].

Reproduced by forcing the ordering with a 1ms deadline, which yields the
same signature. Budget the setup at 3000ms instead of 1000ms and assert
the precondition directly, so losing the race reports 'the isolated tool
never started within the budget' rather than a bare deepEqual mismatch.
jackwener added a commit that referenced this pull request Aug 3, 2026
Adversarial review of #2002/#2004 (correctness + design lenses, every
finding verified before fixing).

Confirmed regressions, fixed:
- Autofocus was broken: raw `autoFocus` fires at React commit, before
  showModal() makes the dialog visible, so focus silently landed on
  the first button. The title input now wears `data-autofocus`, the
  hook Astryx Dialog actually queries after opening (this is also
  what the plan-reminders e2e asserts).
- The raw title input carried none of the a11y wiring TextInput used
  to provide — aria-required / aria-invalid / aria-describedby now
  point at Field's generated status id, and Field gets isRequired
  back, so the validation message is announced again.
- The `::backdrop` override derived its color from --foreground,
  which inverts in dark mode (a whitening veil over a dark UI). It
  derives from --background now — light wash over light, dark scrim
  over dark — and the sheet gains a hairline inline-start border so
  dark mode separates by ring before shadow.
- The form's own `overflow: auto` was a no-op inside LayoutContent's
  `overflow: clip`; tall variants (cron + bot delivery) clipped the
  Chat ID row and Create with no way to scroll. LayoutContent is the
  single scroll region now (isScrollable), between the pinned
  header and footer Layout slots.

Design-review completions:
- 时间 row: hand-typed `YYYY-MM-DDTHH:mm` TextInput → Astryx
  DateTimeInput (compact, 24h, 5-min stepping, full-row width); the
  sheet widens 440 → 480 so the date+time pair fits its min-content
  with proper insets.
- The inset group cards gain a real fill (--muted) — a border on the
  same surface color read as a bare outlined rectangle, not an inset.
- The borderless title gets a :focus-visible ring (it was the one
  control with no focus indicator); its placeholder moves to the
  platform text-secondary token (the hand mix was below 4.5:1).
- Group labels step up to the label type role so the kicker/label
  ladder reads as two tiers; presets keep role="group"; the sheet
  slides in from the right (Astryx's center scale-pop detached a
  100dvh element from three window edges mid-animation).
- Dead copy keys pruned (eyebrow, cancel, field.delivery,
  templatesAriaLabel); two classNames that landed on the menu popover
  instead of the trigger removed.

Verified: build, typecheck, check-dead-css, check-a11y/copy/console,
plan-reminder unit tests (9/9), Storybook smoke (74 renders x 3
viewports), rendered screenshot of the open sheet re-checked.
jackwener added a commit that referenced this pull request Aug 3, 2026
* fix(ui): deep-review round over the plan-reminder sheet

Adversarial review of #2002/#2004 (correctness + design lenses, every
finding verified before fixing).

Confirmed regressions, fixed:
- Autofocus was broken: raw `autoFocus` fires at React commit, before
  showModal() makes the dialog visible, so focus silently landed on
  the first button. The title input now wears `data-autofocus`, the
  hook Astryx Dialog actually queries after opening (this is also
  what the plan-reminders e2e asserts).
- The raw title input carried none of the a11y wiring TextInput used
  to provide — aria-required / aria-invalid / aria-describedby now
  point at Field's generated status id, and Field gets isRequired
  back, so the validation message is announced again.
- The `::backdrop` override derived its color from --foreground,
  which inverts in dark mode (a whitening veil over a dark UI). It
  derives from --background now — light wash over light, dark scrim
  over dark — and the sheet gains a hairline inline-start border so
  dark mode separates by ring before shadow.
- The form's own `overflow: auto` was a no-op inside LayoutContent's
  `overflow: clip`; tall variants (cron + bot delivery) clipped the
  Chat ID row and Create with no way to scroll. LayoutContent is the
  single scroll region now (isScrollable), between the pinned
  header and footer Layout slots.

Design-review completions:
- 时间 row: hand-typed `YYYY-MM-DDTHH:mm` TextInput → Astryx
  DateTimeInput (compact, 24h, 5-min stepping, full-row width); the
  sheet widens 440 → 480 so the date+time pair fits its min-content
  with proper insets.
- The inset group cards gain a real fill (--muted) — a border on the
  same surface color read as a bare outlined rectangle, not an inset.
- The borderless title gets a :focus-visible ring (it was the one
  control with no focus indicator); its placeholder moves to the
  platform text-secondary token (the hand mix was below 4.5:1).
- Group labels step up to the label type role so the kicker/label
  ladder reads as two tiers; presets keep role="group"; the sheet
  slides in from the right (Astryx's center scale-pop detached a
  100dvh element from three window edges mid-animation).
- Dead copy keys pruned (eyebrow, cancel, field.delivery,
  templatesAriaLabel); two classNames that landed on the menu popover
  instead of the trigger removed.

Verified: build, typecheck, check-dead-css, check-a11y/copy/console,
plan-reminder unit tests (9/9), Storybook smoke (74 renders x 3
viewports), rendered screenshot of the open sheet re-checked.

* redesign(ui): plan-reminder form becomes a true split-view panel

WAWQAQ round 3: the sheet was pinned to the WINDOW edge while the page
content is width-clamped, leaving a dead band between the list and the
form — the reference has none: its create panel sits flush against the
list pane, one divider between, non-modal.

- The form drops the <dialog> overlay entirely. It is an in-page
  <aside> now: when open, .maka-plan-panel becomes a two-column grid
  (list keeps flowing in column one; the panel takes
  minmax(380px, 460px) flush right) with a single hairline seam.
  Non-modal role="dialog" + aria-labelledby keeps the accessible name
  the e2e locates; Escape closes through the same guarded path via an
  onKeyDown at the panel root; the title autofocuses from a mount
  effect (the component still remounts per form session).
- The aside is position: sticky with its own pinned header/footer and
  the form as the single scroll region between them, so the list can
  scroll independently — matching the reference's split behavior.
- The sheet-era CSS (window-pinned dialog, custom ::backdrop) is
  retired; the slide-in animation survives on the aside.

Verified: build, typecheck, check-dead-css, check-a11y/copy/console,
plan-reminder unit tests 9/9, Storybook smoke (74 renders x 3
viewports), rendered screenshot re-checked against the reference.

* fix(ui): plan form panel hands focus back to its opener on close

The Astryx Dialog used to capture the focused element at open and
restore it on close; the split-view aside dropped that contract, so
Escape from the edit panel left focus on <body> — caught by the e2e
that expects the row's menu trigger to regain focus. The open effect
now captures the opener (the panel has already re-focused the
triggering row control by then) and its cleanup hands focus back when
the panel closes. Verified locally: both plan-reminders e2e tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant