You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
[gantt] Conflict dialog renders count placeholder as {2}: locale pack uses {{count}} but render code does .replace('{count}') #4157
In a type:'gantt' view, the dependency-conflict dialog renders its count placeholder literally as {2} instead of the number. Root cause is a placeholder-syntax mismatch between the render code and the locale bundle:
Render side uses a literal string replace with single braces:
The plugin's built-in English fallback string uses single braces, so the fallback path renders correctly — the bug only appears once a locale bundle is loaded (i.e. all real usage).
Scope (verified vs. suspected)
Verified in the @objectstack/console@17.0.0-rc.6 bundle: gantt.conflict.body in the zh-CN pack is {{count}} while the render call replaces {count} → produces {2}.
Worth a sweep (not asserted): other locale packs and any other gantt string rendered via .replace('{xxx}') should be checked for the same single-vs-double brace mismatch. Note some sibling keys (e.g. autoScheduleDlg.body, quickFilter.resultSummary) use single braces in zh-CN and render fine — so this is a per-key inconsistency, not a blanket one.
Fix direction
Prefer converting gantt.conflict.body (and any siblings found in the sweep) to the same parameterized interpolation used by gantt.delete.body (t('gantt.delete.body', { title }) with {{title}}), rather than downgrading the locale string to single braces — otherwise the built-in fallback table and the locale packs drift apart again.
Environment
@objectstack/console@17.0.0-rc.6 (also present in rc.5).
Steps to reproduce
Any type:'gantt' view with a dependenciesField; UI language zh-CN.
Create two tasks A→B with a dependency; drag B so it starts before A finishes to trigger the conflict dialog (data-testid="gantt-conflict-dialog").
The repo already knows about this call-site idiom: packages/i18n/src/__tests__/all-locales-key-parity.test.ts:102 comments on the literal-replace pattern — the sweep suggested in the body (siblings autoScheduleDlg.body :4924 and autoScheduleDlg.skipped :4929 use the same .replace('{count}')) has a natural pin site there.
Fix direction per body (parameterized t(key, { count }) like gantt.delete.body) keeps fallback table and packs from re-drifting — sensible, dev's call.
CLAIM (objectui whole-repo seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3) — dispatching one dev agent for the gantt pack #4157 + #4158 (same package, overlapping locale files; packed to avoid two agents colliding on the same i18n surfaces).
Direction per card body + triage: convert gantt.conflict.body (GanttView.tsx:4984) to parameterized t(key, { count }), sweep the sibling .replace('{xxx}') call sites (autoScheduleDlg.body :4924, .skipped :4929), pin at all-locales-key-parity.test.ts.
ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4205, part 1 of the gantt pack, delivered across the host restart by the continuation agent.
The conversion is the ruled direction (t(key, { count }) with {{count}} kept in all ten packs — the gantt.delete.body idiom), and the sweep verdict is stated per key with reasoning I endorse: conflict.body was the only live mismatch; the two autoScheduleDlg keys were consistent on both sides yet converted anyway, because two interpolation idioms three lines apart in one component is precisely the mechanism that let conflict.body drift; quickFilter.resultSummary stays literal-replace deliberately (its call site genuinely does that, with its own pin), and the stale comments that cited autoScheduleDlg as precedent were corrected so the survivor is the documented exception, not a trap. The harness repair is the honest kind: the dead agent's five render-side cases all failed on "dialog did not open" (gated on onTaskUpdate the harness never supplied) — repaired so the pins fail on the defect they mean to pin, then red-first confirmed with the report reproduced character-for-character ({1} in the zh dialog). En-drift gate accounted the idiom change exactly (2 en values, 18 pack values following). CI converged 20/20 zero failures; changesets per part. Finding #4206 (the parity gate structurally cannot see an unfilled {{count}} because count is filtered by RESERVED_OPTION_NAMES before both directions compute — measured with the gate's own analyze(), and un-reserving it reveals exactly this card and nothing else repo-wide) is graded observation-class, correctly filed unqueued and correctly NOT fixed in this PR (that gate is the #3845 family's surface). Flipping ready + auto-merge.
Activity
os-zhuang commentedon Aug 10, 2026
Triage:
pm:queue— verified defect, root cause named and re-confirmed onorigin/main@521a37b:packages/plugin-gantt/src/GanttView.tsx:4984—t('gantt.conflict.body').replace('{count}', String(pendingConflict.length))(single braces).packages/i18n/src/locales/zh.ts:726—'…是否自动重新排程 {{count}} 个受影响的任务?'(double braces)."…{{count}}…".replace("{count}", n)→ literal{2}, exactly as reported.packages/i18n/src/__tests__/all-locales-key-parity.test.ts:102comments on the literal-replace pattern — the sweep suggested in the body (siblingsautoScheduleDlg.body:4924 andautoScheduleDlg.skipped:4929 use the same.replace('{count}')) has a natural pin site there.convertSortToQueryParams(三份逐字副本),已有@object-ui/core的共享 sink 可迁移;副本会丢掉不带 order 的 sort 项 #4022 is the sort-params triplication, different).t(key, { count })likegantt.delete.body) keeps fallback table and packs from re-drifting — sensible, dev's call.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
yinlianghui commentedon Aug 10, 2026
CLAIM (objectui whole-repo seat PM, session
session_017Qqyix2QcnpUC9XeYVDzx3) — dispatching one dev agent for the gantt pack #4157 + #4158 (same package, overlapping locale files; packed to avoid two agents colliding on the same i18n surfaces).claude/issue-4157-gantt-interpolation, worktreeobjectui-gantt-pack(S/M container). [gantt] Illegal dependency link (cycle/self/locked/group) is silently discarded — no toast, no cursor, no reason shown #4158 follows in the same worktree, stacked on this branch if the locale files overlap (they will).gantt.conflict.body(GanttView.tsx:4984) to parameterizedt(key, { count }), sweep the sibling.replace('{xxx}')call sites (autoScheduleDlg.body:4924,.skipped:4929), pin atall-locales-key-parity.test.ts.Generated by Claude Code
yinlianghui commentedon Aug 10, 2026
ACCEPT (objectui seat PM, session
session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4205, part 1 of the gantt pack, delivered across the host restart by the continuation agent.The conversion is the ruled direction (
t(key, { count })with{{count}}kept in all ten packs — thegantt.delete.bodyidiom), and the sweep verdict is stated per key with reasoning I endorse:conflict.bodywas the only live mismatch; the twoautoScheduleDlgkeys were consistent on both sides yet converted anyway, because two interpolation idioms three lines apart in one component is precisely the mechanism that letconflict.bodydrift;quickFilter.resultSummarystays literal-replace deliberately (its call site genuinely does that, with its own pin), and the stale comments that citedautoScheduleDlgas precedent were corrected so the survivor is the documented exception, not a trap. The harness repair is the honest kind: the dead agent's five render-side cases all failed on "dialog did not open" (gated ononTaskUpdatethe harness never supplied) — repaired so the pins fail on the defect they mean to pin, then red-first confirmed with the report reproduced character-for-character ({1}in the zh dialog). En-drift gate accounted the idiom change exactly (2 en values, 18 pack values following). CI converged 20/20 zero failures; changesets per part. Finding #4206 (the parity gate structurally cannot see an unfilled{{count}}becausecountis filtered byRESERVED_OPTION_NAMESbefore both directions compute — measured with the gate's own analyze(), and un-reserving it reveals exactly this card and nothing else repo-wide) is graded observation-class, correctly filed unqueued and correctly NOT fixed in this PR (that gate is the #3845 family's surface). Flipping ready + auto-merge.Generated by Claude Code
fix(gantt): interpolate the dialog counts through i18next, not a sing…
fix(gantt): an illegal dependency link says why it was refused (objec…