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: EmbeddableForm's thank-you redirect timer is unowned too — and its default 3s window fires into "Submit Another Response" #5049

Closed

Description

@yinlianghui

Tripped over while fixing objectui#5033 (the same defect class at the other two call sites). Filed unassigned and NOT fixed there — #5033's card names ObjectForm.tsx and WizardForm.tsx, and this is a third call site with its own config keys, so the PR for #5033 left it exactly as found.

What

packages/plugin-form/src/EmbeddableForm.tsx arms its post-submit redirect with a bare setTimeout inside the submit handler (around line 306):

const rawRedirect = config.thankYouPage?.redirectUrl;
if (rawRedirect) {
  if (isRedirectUrlSafe(rawRedirect, config.allowedRedirectHosts)) {
    const delay = config.thankYouPage?.redirectDelay ?? 3000;
    setTimeout(() => { window.location.href = rawRedirect; }, delay);
  } else { ... }
}

Nothing stores that handle, nothing clears it, no effect owns it. Identical shape to #5033, with two differences that make it worse rather than milder:

  1. The delay defaults to 3000ms, and the thank-you panel it renders says so out loud — Redirecting in {{seconds}} seconds… (line ~345). A multi-second pending full-page navigation is this surface's NORMAL state, not an edge authoring.
  2. It does not even need an unmount. With config.allowMultiple, the thank-you panel offers a Submit Another Response button whose handleReset (line 327) only does setSubmitted(false); setError(null) — the pending navigation keeps ticking. So the component invites the submitter back into a fresh form and then, about three seconds later, throws the whole page away while they are typing the next response. The unmount shapes from 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 (an embed removed by the host page, a route change, a re-keyed subtree) apply on top of that.

Reachability

Live, published surface, not dormant: EmbeddableForm is exported from packages/plugin-form/src/index.tsx (line 53) and registered in the ComponentRegistry as embeddable-form (line 211), so authored metadata reaches it. It needs thankYouPage.redirectUrl to be set; redirectDelay may be left unset, which is the 3s default rather than a zero timer. Nobody loses data — the write has already succeeded before the timer is armed — so the harm is a surprising navigation (and, in the allowMultiple shape, losing what the submitter had just re-typed).

Fix shape, if this is queued

The same move #5033 took, which apps/console's FormPage took before it (PR #4992): hold the accepted destination in state, arm the wait in an effect keyed on it, clearTimeout in the cleanup. Two things this call site needs that #5033's did not:

Cheap to pin the same way #5033 was: arm the delay, unmount (or press Submit Another Response), let the clock run past the delay, assert nothing navigated — paired with a mounted case asserting the destination is still reached when the delay ends, so the pin cannot pass by having deleted the redirect.

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-5049-embeddable-redirect-timer。os-dev 席位实施,PM 只读验收。

    同类前两处已由 PR #4992(console FormPage)与 PR #5055(#5033,ObjectForm/WizardForm,已验收入队)落地,形状已双验证:待定目的地(连同其 delay)入 state,effect 键其上,cleanup clearTimeout。本单两个专属钉子:

    1. PM 代裁(留否决窗):handleReset(Submit Another Response)取消待定 redirect —— 依据即卡面论证:按钮承诺的是「再填一份」,与三秒后丢弃整页不可共存;取消是按钮语义的机械执行,不是新口味。维护者可否决。
    2. ⛔ 不把 thankYouPage.redirectUrl 的 host-allow-list 契约(isRedirectUrlSafe / allowedRedirectHosts)并进 plugin-form: ObjectForm/WizardForm consume submitBehavior.url against the pre-ruling shape — out-of-contract values silently dropped, same-origin absolutes followed, mount-blind #4989/PR fix(plugin-form): consume submitBehavior.url as objectstack#7496 ruled it (#4989) #5032 的 relative-only 裁定 —— 两契约是否统一是独立问题,本单只修定时器生命周期,一行不碰 URL 判定。

    钉子测试按卡面:armed → unmount 与 armed → 按钮重置两条负向、mounted 到点导航一条正向(防「把功能删了换绿」)。


    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