Skip to content

[finding] ForgotPasswordForm's {email} marker match is substring-based — a stray legacy {{email}} in a caller-supplied label produces stray braces around the address, not a graceful fallback #4716

Description

@yinlianghui

Measured while implementing objectui#4135 (converging auth.forgotPassword.successDescription's downstream-filled hole on single braces {email}) — not in that PR's scope, and observation-class: no shipped code path in this repo produces the input this describes.

The pattern

ForgotPasswordForm.tsx's marker match (as of objectui#4135) is:

const successMsg = l.successDescription.includes('{email}')
  ? l.successDescription.replace('{email}', email)
  : `${l.successDescription} ${email}`;

labels.successDescription is a public prop (ForgotPasswordFormLabels), so nothing stops an external consumer of @object-ui/auth from passing a string that still spells the hole the pre-#4135 way, {{email}} (double braces) — e.g. a caller who copied the old convention, or a translation that hasn't been migrated.

The finding

{email} is a literal substring of {{email}} — "{{email}}".includes("{email}") is true (positions 1–7 of {{email}} spell {email} exactly). So a label containing the retired {{email}} spelling does not fall to the (correct, if imperfect) append branch; it partial-matches and .replace() only swaps the inner {email} for the address, leaving the surrounding braces behind:

const label = 'We have sent a link to {{email}}. Check your inbox.';
label.includes('{email}') ? label.replace('{email}', 'user@example.com') : `${label} user@example.com`;
// => "We have sent a link to {user@example.com}. Check your inbox."

The address renders wrapped in stray literal braces — malformed, but not the double-append shape objectui#4135 was about, and not a crash.

Why the direction changed, not just moved

The relationship is asymmetric, so this is a new failure direction introduced by #4135's fix, not a preexisting one moved sideways:

Why it's dormant rather than broken

Every shipped label for this key — all ten locale packs, ForgotPasswordForm's own built-in default, and both console call sites' inline defaultValues — was migrated to {email} in the same PR (objectui#4135), so nothing in this repository produces the input above today. The only way to hit this is an external consumer's own labels prop still carrying the retired spelling.

Options (no recommendation attached — a component-behavior decision, not a gate finding's to make)

  • A. Leave it. The public prop already assumes the caller knows the current marker convention; a stale caller gets a cosmetic (not data-loss) rendering bug, self-correcting the moment they update their copy.
  • B. Tighten the match — e.g. a regex with a boundary check (/(?<!\{)\{email\}(?!\})/) or an explicit {{email}} guard that always prefers the append branch over a partial replace — so a legacy caller degrades to append (correct-if-plain) instead of stray braces.
  • C. Document it — a one-line comment on the marker check noting the substring relationship, so a future edit to either spelling doesn't reintroduce this blind.

Related: objectui#4135 (introduced the current spelling and this asymmetry).

Activity

  1. yinlianghui commented on Aug 15, 2026

    @yinlianghui
    CollaboratorAuthor

    Findings triage (first grade, PM session session_01RnQd8iMMUwFQ… — correction: session_01RnQd8iMMUwXQEV1crFmQiQ): **HOLD — finding` stands as filed.**

    Premise accepted from the filing dev's measurement on PR #4714 (the direction flip is real: {email} IS a substring of {{email}}, so a legacy-spelled caller label now partial-matches where the old check could not). Held rather than queued because the input can only come from an external consumer's own labels prop — no shipped code path in this repo produces it, all ten packs and both call sites are converged post-#4714, and the failure is cosmetic (stray literal braces), not data loss. Restart conditions: ① any real consumer report of the stray-braces rendering; ② the next PR touching ForgotPasswordForm's label contract inherits the tighten-or-document choice; ③ a second downstream-filled hole joining the convention (would justify a shared marker-matching helper that settles this class).


    Generated by Claude Code

  2. yinlianghui commented on Aug 15, 2026

    @yinlianghui
    CollaboratorAuthor

    Grading — HOLD resolved to a recorded disposition: Option A (leave it), under delegated adjudication (repo:objectui PM seat, session session_01RnQd8iMMUwXQEV1crFmQiQ; maintainer veto window open).

    Reasoning, since the card deliberately attached no recommendation:

    • The input is external-only and dormant — every shipped label, built-in default, and inline defaultValue was migrated in objectui#4135; nothing in this repo can produce the legacy spelling. The blast radius is one cosmetic artifact (braces around the address) in an external caller's copy, self-correcting the moment they update it.
    • Option B (boundary-check so {{email}} falls to append) renders worse, not better: the stale caller would see the raw {{email}} braces retained in the sentence plus the appended address — two artifacts where today's partial match produces one.
    • The B′ variant (also replace legacy {{email}} wholesale) would render correctly but quietly re-blesses the retired double-brace spelling on a public surface, cutting against the repo-wide single-brace convention ruling. Not worth the precedent for a defect no one has hit.

    Card stays open as finding — the direction-change analysis is the valuable record. Re-grade triggers: a real external consumer reporting the artifact, or any future PR touching this component's marker logic (whoever touches it next folds the boundary check in at zero marginal cost, and this card is the spec for it).


    Generated by Claude Code

  3. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    State normalization (triage seat): finding → pm:on-hold. The 2026-08-15 delegated adjudication by the repo:objectui seat (Option A — leave it; comment 5304520655, maintainer veto window was open) is a completed grading with named re-grade triggers, so the honest label state is a hold, not an ungraded finding. The ruling is unchanged; this card remains the spec for the boundary check whoever touches the marker logic next.

    Restart-when: an external consumer reports the stray-brace rendering, OR git log origin/main --since=2026-08-16 -- packages/auth/src/**/ForgotPasswordForm* shows a commit touching the marker logic (fold the boundary check in then).


    Generated by Claude Code

  4. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 23, 2026
  5. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    Contributor

    Triage: domain:ui (lane only — pm:on-hold untouched). Landing: ForgotPasswordForm's {email} marker substitution — a published component's own source.

    Routed via the maintainer direct-dispatch channel, this session, verbatim: 「然后 批 4–5」. PM session session_0124Qg8rLvpXnQDwCmpKUmaJ. objectui three-stream split (maintainer 2026-08-21); not a Routine triage fire — the triage seat may re-grade.


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    关闭 not_planned —— 维护者同意:处理方式早已定为"保持现状",卡片只是留作记录

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T12:44Z。objectui 清理第八批(#8587 的挂起卡一次性清点):本席读完了卡面和评论(4 条,全部读到),并在本地 main(62597c5)上重新测量,把建议报给维护者。维护者答:「第⑧批 同意」。

    本卡说的是什么

    ForgotPasswordForm 用 includes('{email}') 找标签里的占位符。如果调用方传入的标签还用旧写法 {{email}},这个匹配也会命中,结果地址显示成 {user@example.com}。这只是显示问题,而且只影响外部调用方:仓库里所有自带的标签已经在 #4135 迁移到新写法。

    本席实测(本地 main 62597c5)

    • 占位符匹配逻辑没有变,在 ForgotPasswordForm.tsx:286-288。
    • 08-15 的 5304520655 已经做过裁定(委托裁定,留有维护者否决期):选方案 A,保持现状。方案 B 显示效果更差;方案 B′ 等于重新认可已经退役的旧写法。卡片之所以还开着,只是"留作记录"。
    • 重启条件 5331061601(外部使用方反馈,或有提交改动这段匹配逻辑)没有发生。

    为什么关

    处理方式已经定了,就是不改。关闭后记录仍然可以搜到,开着只是占队列。

    什么时候重开

    有外部使用方反馈地址显示成带花括号的样子;或者有提交改动 ForgotPasswordForm 的占位符匹配逻辑时,顺手重新评估。

    标签:摘 pm:on-hold;以 not_planned 关闭。


    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

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions