Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

plugin-form: the redirect delayMs timer outlives the component — a submitter who leaves during the delay is yanked back by a full-page navigation #5033

Closed

Description

@yinlianghui

Found while implementing objectui#4989 (the contract half of the same two call sites). Filed unassigned, NOT fixed there — #4989's card scopes itself to five named defects and this is not one of them.

What

ObjectForm and WizardForm arm the declared submitBehavior: { kind: 'redirect' } delay with a bare setTimeout inside the submit handler:

  • packages/plugin-form/src/ObjectForm.tsx (redirect arm of the submitBehavior switch)
  • packages/plugin-form/src/WizardForm.tsx (same arm)
setTimeout(() => window.location.assign(verdict.url), behavior.delayMs ?? 0);

Nothing ties that timer to the component. It is not stored, not cleared on unmount, and not owned by an effect. So for the whole of delayMs there is a pending full-page navigation that survives the form being taken off screen.

Consequence

A submitter who acts during the delay gets pulled away from wherever they went. The reachable shapes, in rising order of how ordinary they are:

  • the form is a modal/drawer variant (ModalForm, DrawerForm both wrap these), the user dismisses it after the confirmation and starts doing something else — then the timer fires and the browser leaves the page.
  • the host is an SPA and the user clicks any in-app link during the delay; the timer's window.location.assign overrides that navigation with a full page load.
  • the host unmounts the form for its own reasons (route change, tab switch, data refresh that re-keys a subtree).

delayMs exists precisely so the confirmation is readable before the redirect, so a non-trivial value is the intended authoring, not an edge case. The longer the declared delay, the wider the window.

Prior art in this repo — the same defect, already fixed on the console side

apps/console's FormPage had this and PR #4992 moved the wait into an effect keyed on the pending destination, with a clearTimeout cleanup. Its changeset states the reason in one line: "the wait now lives in an effect tied to the component, so a submitter who navigates away during the delay is no longer yanked back by a timer that outlived the page."

So the fix shape is already decided and demonstrated in this repo; this is the same move for the published renderer's two call sites. It should not change delayMs semantics at all — the pause stays a pause, an unset value still means "go now".

Reachability, honestly

Needs a form authoring a redirect behaviour WITH a delayMs, and an unmount or navigation inside that window. Nobody loses data — the write has already succeeded before the timer is armed — so the harm is a surprising navigation, not corruption. Cheap to fix and cheap to pin (arm the delay, unmount, assert nothing was assigned).

Not to be confused with

objectui#4989, which is about WHAT the url means (relative-only, interpolation, escaping, and refusing an out-of-contract value loudly). That card's PR leaves the timer exactly as it found it. This issue is only about the timer's lifetime.

Activity

  1. self-assigned this
    on Aug 17, 2026
  2. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    ContributorAuthor

    认领(objectui 分片 PM 直辖,批次 22)—— session session_01GTRjn8xBqp75dk7kFupVRt,分支 claude/issue-5033-redirect-timer-lifetime。os-dev 席位实施,PM 只读验收。

    范围钉子:只修定时器生命周期 —— ObjectForm.tsx / WizardForm.tsx 两处 redirect 臂的 setTimeout 移入组件所有的 effect(照 PR #4992 在 console FormPage 已验证的形状:effect 键在待定目的地上,cleanup clearTimeout)。delayMs 语义不变(停顿仍是停顿,未设仍是立即走);⛔ 不动 #4989/PR #5032 已落地的 checkSubmitRedirectUrl 契约裁定流。钉子测试:armed delay → unmount → 断言未发生任何 assign。


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpm:queue

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions