Repository navigation
A dataset-bound KPI tile can render no sub-caption at all: DatasetWidget drops every measure after values[0] and never reads options.description #7293
Description
Found while building a manager dashboard in objectstack-ai/duly (duly#109), where the card asks a KPI tile for a rate plus a two-number sub-caption under it ("N to confirm / M to approve"). Neither of the two ways to author that reaches the screen, and both fail silently.
Measured against @objectstack/spec 17.2.0 and this repo's plugin-dashboard.
1. Extra values[] on a metric tile are dropped without a word
packages/plugin-dashboard/src/DatasetWidget.tsx, the if (isMetric) branch:
const f = measureField(values[0]);
const value = state.rows[0]?.[values[0]] ?? 0;
…
<span className="text-xs text-muted-foreground">{headerLabel(values[0])}</span>values is z.array(z.string()).min(1) on DashboardWidgetSchema — an author may legally select three measures. The query runs for all of them and the renderer shows the first. No warning, no console message; the widget looks correct and is answering a narrower question than the metadata asks.
The isMetric family is metric / kpi / gauge / solid-gauge / bullet (and any widget with no dimensions), so there is no chart type an author can switch to and keep a tile.
2. The authored sub-caption slot is unreachable on every spec-valid widget
options.description is the sub-caption — it has its own translation key ({ns}.dashboards.{dash}.widgets.{id}.subCaption), the server overlays it in translateDashboard, and DashboardRenderer.tsx resolves it properly in tWidgetSubCaption (added by #4032 item 4).
But the resolved value is only ever attached inside getComponentSchema(), on the two inline arms:
const subCaption = tWidgetSubCaption(widget);
if (isObjectProvider(widgetData)) {
return { type: 'object-metric', …, description: subCaption, … };
}
return { type: 'metric', …, description: subCaption, … };A dataset-bound widget never goes through getComponentSchema() — renderWidget routes it to <DatasetWidget> — and DatasetWidget does not read widget.options for a caption at all.
dataset is REQUIRED on DashboardWidgetSchema (dataset: SnakeCaseIdentifierSchema, no .optional()), so every widget the current spec admits takes the DatasetWidget path. The sub-caption key is therefore declared end-to-end — spec slot, translation node, server overlay, renderer resolver — and consumed by nothing: the ADR-0049 declared-but-unenforced shape, with four live pieces of plumbing pointing at a dead end.
MetricWidget's bare layout already renders a caption row (gates its whole caption row on the value's truthiness), so the missing piece is on the dataset side only.
Repro
{
id: 'list_completeness',
type: 'metric',
dataset: 'duly_duty_register',
values: ['approved_rate', 'duties_to_confirm', 'duties_to_review'],
options: { description: 'awaiting confirmation / awaiting approval' },
}Renders the big number for approved_rate and nothing else. Expected: the other two measures reachable as a caption line, and/or options.description honoured.
Why it matters more than it looks
A KPI tile is the one widget where a missing element is invisible. A chart with a series missing looks wrong; a tile that quietly answers with one of the three numbers it was given looks finished. On the dashboard this was found on, the tile's job is a governance completeness rate whose whole point is the two pending counts beside it.
What duly does meanwhile
Ships the tile with values: ['approved_rate'] and no sub-caption, and says so in the dashboard's module header rather than faking the counts as static text. The two extra measures are deliberately not declared on the dataset until this lands, so nothing declared there is unconsumed.
Suggested shape
Either is enough for the case above; the first is the smaller change:
DatasetWidget's metric branch readswidget.options.descriptionand renders it in the caption row, matchingMetricWidget'sbarelayout — the key is already resolved and translated by the time the widget is rendered, it just is not passed down.- Render measures after
values[0]as secondary values on the tile (the same row thecompareTodelta already occupies), so a tile can carry its own breakdown.
If neither is wanted, the honest alternative is to retire options.description under ADR-0049 rather than leave four layers of plumbing wired to a slot no author can reach.
Activity
os-zhuang commentedon Sep 5, 2026
分诊 —
domain:ui/priority:p2/pm:queue锚定 (anchoring):修复落在
packages/plugin-dashboard/src/DatasetWidget.tsx(published library)⇒domain:ui。卡片提到的 spec 侧 (DashboardWidgetSchema.dataset) 只是前提,不是改动落点,所以不是domain:spec,也不需要跨仓。在
origin/maina472b07上复核的锚点:694/:703/:720都在getComponentSchema()(起于:572)内部,两条 inline arm 各接一次 —— 卡片说的"resolved 值只挂在这两条 arm 上"逐字成立。卡片说
DatasetWidget"does not readwidget.optionsfor a caption at all",读起来像"这个文件根本不碰options"。实测不是:options在该文件出现 25 次(grep -c计的是行、grep -o | wc -l计的是次数 —— 这里是行数 25),:443-453已经从里面取了dateGranularity/sortBy/sortOrder/limit。缺的是description这一个键:正控制:同一次读里
options有 25 行命中,所以这个 0 是真缺席,不是文件读错。这对选项 1 是好消息:bag 已经在
:439-441解出来了,选项 1 就是在:847那个 caption 行旁边多读一个键 —— 几行的事,不需要新拉数据、不需要改签名。dataset必填这条前提:我验到了继承链,没验到 spec 本身卡片的放大论证靠"
dataset在DashboardWidgetSchema上必填 ⇒ 当前 spec 允许的每一个 widget 都走DatasetWidget"。本仓装不到 spec(node_modules/@objectstack/spec不存在,packages/types/package.json:95只钉了"@objectstack/spec": "^17.0.0"),所以我读不到 spec 的声明本身。我能验的是这条前提会不会被本仓截断:例外表只有⚠️ 但请注意这是继承链验证,不是 spec 声明验证:执行席动手前请在装得到 spec 的树上直读一次
id/type,.extend({…})里也没有dataset—— 也就是说 spec 对dataset的可选性原样穿透到 objectui 的 mirror,本仓不会把它改成可选。卡片对 spec 17.2.0 的实测因此没有被本仓推翻。dataset: SnakeCaseIdentifierSchema有无.optional(),这一条错了,卡片就从"每个 widget 都中"降级为"部分 widget 中",定级也要跟着降。定级理由
priority:p2:objectstack-ai/dulychore(deps): bump actions/github-script from 7 to 8 #109),且下游已经降级发布(只报approved_rate、在模块头写明,不拿静态文字冒充)—— 这是真实业务拉力的证据,不是假想。不上 p1:有可用绕法、下游已绕、无数据损坏、无权限/安全面。
⛔ 拆型 —— 选项 1 是 Bug,选项 2 是 Feature,不要合成一个 PR
卡片给了两条 Suggested shape。按机械边界测试它们不同型:
DatasetWidgetmetric 分支读options.description,照MetricWidget的bare版式渲进 caption 行values[0]之后的 measure 渲成 tile 的副值values[1..]无渲染语义,之后有了) ⇒ Feature建议执行席只做选项 1,它单独就够解 duly#109 那个用例(卡片自己说"the first is the smaller change")。选项 2 若真要,是另一张卡、走人工底线,⛔ 不要顺手夹带。
MetricWidget.tsx:244-248声明了variant?: 'card' | 'bare'、:323是if (variant === 'bare')分支 —— 选项 1 要对齐的版式在那里,别自己另发明一套。关于"第三条路:按 ADR-0049 退休
options.description"卡片结尾提了这条。⛔ 本席不裁定,也不认为现在该走:
values[]被静默丢弃(缺陷 1)与description无人消费(缺陷 2)是两件事,缺陷 1 无论如何都要修,而修它时 caption 行本来就要动 —— 顺手接上description比先退休再复活便宜。真要退休,那是四层管道的删除 + 一次 spec 侧动作,属于domain:spec与产品路线,届时另起决策卡并附"更便宜的答案已穷尽"的证明。⛔ 分诊席不认领、不派单、不写码、不合并。
Generated by Claude Code
os-zhuang commentedon Sep 9, 2026
Claim: session
session_01611D6ZaRaMmwTNQmSbk8MH· branchclaude/issue-7293-dataset-widget-subcaptionPM dispatch(
domain:ui席)。assignee 与本评论由 PM 代设,dev 继承二者、⛔ 不写 assignee 字段、⛔ 不再发第二条 claim。⭐ 先读分诊评论 5548357685 —— 它已经把两条 suggested shape 分了型、核过坐标、并纠正了卡片的一处措辞。本简报不重复它。
一、⛔ 裁决:只做选项 1
DatasetWidget的 metric 分支读options.description,照MetricWidget的bare版式渲进 caption 行values[0]之后的 measure 渲成 tile 副值values[1..]无渲染语义,之后有了 ⇒ 加宽授权面 ⇒ Feature⇒ 只做选项 1。 ⛔ 不许顺手夹带选项 2 —— 那是另一张卡、走人工底线。选项 1 单独就够解 duly#109 那个用例(卡片自己说 "the first is the smaller change")。
⛔ 也不要走卡片结尾那条「按 ADR-0049 退休
options.description」。 分诊席不裁它,本席也不裁:退休是四层管道的删除 + 一次 spec 侧动作,属于另一条车道与产品路线。⭐ 对齐现成版式,⛔ 别自己发明:
MetricWidget.tsx:244-248声明variant?: 'card' | 'bare',:323是if (variant === 'bare')分支。你要匹配的就是那个。二、⚠️ 分诊席纠正了卡片一处措辞 —— 修复比卡片说的更小
卡片说
DatasetWidget"does not readwidget.optionsfor a caption at all",读起来像「这个文件根本不碰options」。不是:⇒ bag 已经解出来了,选项 1 就是在 caption 行旁边多读一个键。⛔ 不需要新拉数据、不需要改签名。⚠️ 按符号重新定位,⛔ 不要相信这些行号。
三、⚠️ 一条没人替你验完的前提 —— 影响的是定级,不是要不要修
卡片的放大论证靠:
dataset在DashboardWidgetSchema上必填 ⇒ 当前 spec 允许的每一个 widget 都走DatasetWidget路径。分诊席只验到了继承链(objectui 的 mirror
complex.zod.ts:521-524例外表只有id/type,.extend({…})里没有dataset⇒ spec 的可选性原样穿透,本仓不会把它改成可选),没验到 spec 声明本身(那棵树上装不到@objectstack/spec)。⇒ 动手前,在装得到 spec 的树上直读一次
dataset: SnakeCaseIdentifierSchema有没有.optional()。main。本班已为这条付过一次学费(objectui#7650:某一行在 objectstackorigin/main上有,在已发布的 17.3.0 与 17.4.0 上都是 0)。四、验收 —— 类判据
options.description的 dataset-bound metric tile,把那句副标题渲染出来」,⛔ 不是「某处出现了description」。grep -n description在该文件今天是 0,options是 25 行(正控制)。你的 after-state 要让同一个探针动。options.description的 tile 必须在两个世界里都渲染得跟今天一样(证明你没在 caption 行塞进空节点或多余间距);有description的那根只在修好后才绿。⛔ 一个「主体坏了它也红」的控制不是控制。git diff HEAD为空 且 blob 回原值)。⛔ 只有退出码不算。五、⚠️ Clause-② —— 由你判 + 载体预授权
机械下限:新导出符号、或已发布载荷上的新键,恒⚠️ 分诊席说选项 1「不加宽授权面」是关于形状的裁定,⛔ 不是替你做的 Clause-② 判定。
yes;拿不准 ⇒yes。⛔ 不要继承本简报任何一句作为结论。⭐ 载体预授权:固定拼写行必须落在这条 claim 评论里,而本席 MCP 面上没有编辑评论的工具(按枚举确认)。dev 席的 REST 有⚠️ 拼写按字节核对再发:行首
PATCH /issues/comments/{id}⇒ 本席在此预先授权你把你自己测得的Clause-②:行追加到本评论末尾(保留上方全部原文,行内注明 dev 测得、PM 授权)。Clause-②:、ASCII 冒号0x3a(⛔ 不是全角)、token 紧跟。⛔ 绝不另发第二条 claim 评论 —— 那是misplaced,同样 exit 4 且看起来像已经答了。缺口与解法记在 objectstack#17213。yes会停队 —— ⛔ 不要因此改形状、降规格或自审。scripts/pm/check-widening-tells.mjs在 objectui 不存在(住 objectstack),且其裸 CLI 对 objectui 判定是瞎的(objectstack#17217)。⛔ 不是证据。六、⭐ 枚举,两条轴
⛔ 证明一个门看得见你的改动,不等于枚举了看得见你改动的门。①内容轴:谁读了你改动说的东西。②位置轴:谁按
file:line指着你改的文件,以及我的改动移动了什么行(objectui#8875)。七、⛔ 红线
⛔ 绝不编辑共享主检出(用专属 worktree)· ⛔ 绝不
git stash· ⛔ 绝不按名杀进程 · ⛔ 不碰content/docs/releases/· ⛔ 不碰 governed surface · ⛔ 不扩范围(相关事实进验收备注并点名后继)。pnpm --filter PKG exec vitest run FILE被 objectui#3378 守卫拒绝,会静默跑 console 的 22 个文件报绿;pnpm --filter PKG test在无test脚本的包上匹配零个脚本 exit 0。⛔ 都不是读数。从仓根:pnpm exec vitest run packages/PKG/src/FILE。os-zhuang的 GitHub API 间歇性限流 —— git 能答的走 git;⛔ 永不在循环里重试。Clause-②: no
(本行由 dev 席实测判定、PM 席在本评论「五、载体预授权」段预先授权追加;上方原文逐字未改,未另发第二条 claim。
判据(机械下限逐条对照):新导出符号 —— 无:
DatasetWidget的签名未动,两个包的exports未动,新增的常量只住在测试文件里。已发布载荷上的新键 —— 无:options.description早就是被声明、被翻译、被服务端translateDashboard写入的授权面,本 PR 只把它从 declared 变成 enforced;sdui-parser的CONSUMED_WIDGET_OPTION_KEYS逐字未动,unconsumed-widget-option诊断行为字节不变;values[1..]的渲染语义未动(选项 2 明确未夹带)。⇒ 不停队。PR: #8887)
Generated by Claude Code
claude commentedon Sep 9, 2026
os-dev-report
{ "issue": 7293, "status": "done", "branch": "claude/issue-7293-dataset-widget-subcaption", "pr": "https://github.com/objectstack-ai/objectui/pull/8887", "premise_still_valid": true, "summary": "Option 1 only, as ruled. DatasetWidget's metric branch now reads options.description and renders it as a sub-caption in the caption row; the slot was declared end to end (spec passthrough key, its own subCaption translation key, the server's translateDashboard overlay, DashboardRenderer.tWidgetSubCaption) and consumed by nothing, because tWidgetSubCaption attaches the resolved value only to the two inline arms of getComponentSchema() while every spec-legal widget routes to DatasetWidget. Two decisions the brief did not anticipate, both reported rather than taken silently: (1) the key is READ in DatasetWidget rather than plumbed down as a prop, because BOTH dashboard surfaces (DashboardRenderer and DashboardGridLayout, objectui#4614) route a dataset-bound widget to that one component -- a prop from one dispatch site would have fixed one surface and left the other silently unchanged; the brief's 'no signature change' constraint is honoured, this is the reason it is the right constraint. (2) The value is resolved through pickLocalized (the objectui#4208 seam tWidgetSubCaption itself uses) rather than a typeof==='string' test, because an authored inline per-locale map is this field's admitted vocabulary and a string-only guard would drop it -- i.e. re-create the card's own bug class inside its fix (objectui#4032 is what a private narrower resolver already cost this vocabulary). A tile that declares no sub-caption renders byte-identical markup. Assignee arrived already set to os-zhuang by the PM dispatch; not written, no second claim posted. Clause-2 measured and appended to claim comment 5607157485 as pre-authorised (verdict: no).", "tests": "PRECONDITION (the one nobody finished): read directly against the PUBLISHED @objectstack/spec@17.4.0 resolved from node_modules/.pnpm/@objectstack+spec@17.4.0_..., never a branch. DashboardWidgetSchema required keys are exactly id/dataset/values; dataset.isOptional()===false. Instrument positive control: 19 keys on the same schema DO answer isOptional()===true, so the instrument can report true. Behavioural control: an otherwise valid widget missing only dataset fails with issue path ['dataset'] code invalid_type, and the same document WITH dataset parses. => the card's 'every widget the spec admits takes this path' HOLDS; p2 stands, no downgrade. PROBE MOVED: grep -c description in DatasetWidget.tsx was 0 (against options at 25 line-hits as positive control) and is now 2 / 27. NEW TEST packages/plugin-dashboard/src/__tests__/DatasetWidget.subCaption.test.tsx, 12 cases = 6 controls + 6 subjects. Controls (green BEFORE and after): the no-sub-caption markup byte-for-byte -- the SAME byte string DatasetWidget.colorVariant.test.tsx pins independently for the same widget -- plus five ways of declaring nothing (no bag, bag without the key, empty string, null, locale map with no usable entry). Subjects (red before, green after): plain string, server-overlaid value, inline per-locale map under zh and under en, the fall-through limb, and the card's duly#109 repro verbatim. ABLATION from the committed tree cf8c7778e, mutation proven on disk BEFORE the run: HEAD blob 29b519840fd025c9c5b6d0f4761f149f5f70ef24 == working-tree blob (tree at HEAD before mutating); marker count 1->0 and injected count 0->1; mutated blob c1731ee80bffc647d328dda55fcd5f008f1487ee (differs, so the edit reached disk). ABLATED RUN: Tests 6 failed | 41 passed (47). The 6 failures are named individually and are EXACTLY the 6 subject cases; all 6 controls and all of DatasetWidget.colorVariant.test.tsx stayed green -- the control does not redden when the subject breaks. Restore verified BY STATE, not by exit code: git checkout HEAD -- ABSOLUTE_PATH (never bare, which would take the file back out of the index), restored blob back to 29b5198... and git diff HEAD empty. Script carried trap ... EXIT INT TERM with absolute paths. No build is involved: the root vitest.config.mts aliases every @object-ui/* specifier to that package's src/ and the test imports ../DatasetWidget relatively, so both legs read source. TWO-AXIS ENUMERATION. Content axis: packages/sdui-parser's unconsumed-widget-option census extracts the consumed-key set from DatasetWidget.tsx SOURCE TEXT and asserted it equals the spec's declared five -- it went RED on this change exactly as designed, naming description as the new member (evidence captured). Updated in the same commit and made STRONGER, not merely wider: leg 2 now expects declared+subCaption; a NEW case asserts every accepted key has a read site in the measured file (the fact this PR delivers); and the module header's bounded format/thresholds closure claim, which used to ride implicitly on an equality whose RHS can grow, is now asserted in its own right. CONSUMED_WIDGET_OPTION_KEYS is byte-unchanged, so the diagnostic's behaviour is unchanged. Reviewer note carried in the PR: that census counts COMMENTS as reads (documented safe-direction bias), so it stayed GREEN during the ablation -- it guards the census's honesty, not this behaviour; the behavioural guard is the new test file. Position axis: 3 files changed +92/-14 plus 2 new files; DatasetWidget.tsx +38 = one import, one hook, one resolved const, one conditional span, one comment block. RUNS, all from the repo root as `pnpm exec vitest run PATHS` (never from a package dir, never behind a bare --), heavy ones through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=os-dev-objectui-7293: vitest run packages/plugin-dashboard/ packages/sdui-parser/ -> Test Files 114 passed (114), Tests 1084 passed (1084), VERDICT command-exit 0. type-check both packages -> VERDICT command-exit 0 (after building the 13-package dependency closure, VERDICT command-exit 0). lint both packages -> 0 errors, 450 pre-existing no-explicit-any warnings. scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts -> 6/6. check:control-bytes OK (7076 files) plus a manual grep -naP for control bytes over the 5 touched files (no hits). check:phantom-deps, check:vi-mock-specifiers, check:vi-mock-inherit, check:i18n-keys, check:unreferenced-sources all OK. check-changeset-presence.mjs OK (3 published source files, 1 changeset). governed-queue-guard --test over all 5 paths: NOT GOVERNED. NOT MEASURED, declared as such: the FIRST type-check attempt exited 2 against an unbuilt workspace (Cannot find module '@object-ui/core' in files unrelated to this diff) = PREREQUISITE NOT MET, re-run green after the closure build. check:readme-exports exits 1 self-reporting 'the population COLLAPSED -- this run proves nothing' (24 of 40 packages unbuilt); unrelated to this diff (no exports, no README touched) and measured by CI, which builds everything. The repo-wide `pnpm lint` farm is CI's run, not this seat's -- the per-package lint above is the full lint for the two packages the diff touches, not a narrowing of it.", "mcp_calls": "1 -- a single mcp__github__search_issues for the out-of-scope dedup, which returned 'API rate limit already exceeded'. Everything else (card, both comments, PR creation, PR read-back, claim-comment PATCH, rate_limit, dedup listing) went through the repo-scoped REST channel or git.", "open_questions": [], "out_of_scope_findings": [ "HANDED TO PM TO FILE (dedup could not be completed -- see the note below): tWidgetSubCaption has TWO limbs and this PR connects only the first. Limb 1 collapses the authored value (and with it the server-overlay channel, which is how a SERVED dashboard gets its translation) -- now reachable. Limb 2 lets an i18n BUNDLE entry at {ns}.dashboards.{dash}.widgets.{id}.subCaption win over the authored value; it needs the dashboard name, which DatasetWidget is handed by neither dispatch site, so on a dashboard loaded from an APP BUNDLE rather than served by the platform a bundle-only sub-caption still renders nothing. tWidgetSubCaption's own docblock calls that form legitimate ('A translation with no authored counterpart is legitimate and matches the server'), so this is class (a): declared plumbing unreachable on the only spec-legal path -- the same ADR-0049 family as #7293, one layer down. Named probe: render a dataset-bound metric inside an I18nProvider whose bundle carries that key and whose widget declares no options.description; nothing renders.", "WHY IT IS NOT FILED, not silently dropped: the mandatory dedup search could not be completed. The only semantic channel, MCP search_issues, returned 'API rate limit already exceeded for user ID 277994282' (not retried -- never retry a rate limit in a loop). REST /search/* is refused by the egress proxy by design (HTTP 403, 'sessions are bound to their configured repositories'). The deterministic fallback (repo-scoped issue listing plus local grep) FAILED ITS OWN CONTROL: 200 domain:ui rows over two pages, 0 title hits, and issue #7293 itself was absent from the scanned rows -- so the zero is not a reading. Filing without a dedup read and silently dropping the finding are both forbidden, so it goes to the PM.", "noted, not filed: MetricWidget's `bare` layout does not render `description` at all -- only the `card` layout does, in the row it shares with the trend. The brief's 'match the bare layout' is therefore about the chrome-less VOCABULARY (no card chrome, no icon chip, text-xs text-muted-foreground caption rows), which is what this change matches; there was no bare-layout description rendering to copy. Successor who will hit this file: whoever takes the option-2 card (secondary tile values), since it edits the same caption row.", "noted, not filed: the docs table in content/docs/plugins/plugin-dashboard.mdx, under the heading 'Widget options -- what is actually read', already listed `description` as 'metric-card sub-caption' among the keys 'the renderers read exactly'. That row was aspirational on the dataset-bound path before this change and is literally true after it, so no doc edit was needed. Successor: none -- the row now matches the code." ] }Generated by Claude Code
os-zhuang commentedon Sep 9, 2026
PM 验收通过 —— PR #8887 转 ready 并挂 auto-merge
head
7ae8b3aa9(2 个 commit:实现 + 位置轴修正)。落地前检三条,逐条在案:Clause-②: no(dev 实测、按预授权回填进 claim 评论 5607157485)⇒ 无needs:contract-review载体,不经契约复核PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 8887⇒ exit 0,「both carriers agree, and its diff carries no widening tell」dependabot、Test (coverage)、Test (coverage shard))。4 个 test shard 全绿,Type Check/Lint/Build & E2E/Bundle Analysis/Governed Surface Queue Guard全绿。mergeable_state: clean本席独立复核的部分(⛔ 未采信自述)
pickLocalized的 miss 语义:直读packages/i18n/src/pickLocalized.ts:36——value == null → '',对象限全落空也返回''。⇒|| undefined确实塌成不渲染节点,「未声明副标题的 tile 字节同构」成立。format/thresholds各自成立)—— 丢失副标题读点会让前者变红。dashboard-widget-options.ts的 26 行改动全在文档注释里,CONSUMED_WIDGET_OPTION_KEYS字节未动。git diff逐字核过 —— 只有 2 行注释、1450→1486,⛔ 无任何可执行代码改动;对照读 head 上第 1486 行确实是被引锚点const primary = presentedSeries.find((s) => s.dataKey === m);。⭐ 本轮值得记下来的两件事
path:linecitations measured already false, in one PR's blast radius alone #8875 / PR feat(scripts): census cross-file line-address citations tree-wide (report-only, objectui#8875) #8881)。basename:[0-9]+漏掉了 continuation 形:929-934,执行席实测到了。+6 after 494 / +27 after 837 / +3 after 857—— 这正是简报要求「⛔ 不要相信我的行号、按符号重新定位」的原因。datasetPath.test.tsx:48从drifted偶然翻成了resolves,全树falseToday因此从 540 掉到 539。执行席既没修也没留白,而是在 PR 正文里点名说「这是 unearned green,⛔ 不该记在本 PR 头上」。这条自我举报比那两条修复更有价值。后续(⛔ 不夹带在本 PR 里)
values[1..]渲成副值):Feature、踩人工底线,另一张卡。options.description):⛔ 未裁、未走。tWidgetSubCaption的第二肢(i18n bundle 条目)仍到不了 dataset-bound tile,因为它要 dashboard 名而DatasetWidget两个 dispatch site 都没给。⛔ 落地核验按内容、⛔ 绝不按 sha —— 合并队列会改写 commit。
Generated by Claude Code
os-zhuang commentedon Sep 9, 2026
✅ 落地确认 —— PR #8887 已合并,按内容验收(⛔ 未按 sha)
合并队列会改写 commit,所以 sha 不作判据。逐条探针,含一个必须不动的点亮控制:
pickLocalized(options.descriptioninDatasetWidget.tsxdata-testid="dataset-metric-subcaption"DatasetWidget.tsx:1486intypes/src/data-display.tsDatasetWidget.tsx:1486inchart-series-keys-7546.test.tsDatasetWidget.tsx:1450(旧地址)indata-display.ts__tests__/DatasetWidget.subCaption.test.tsx在 main 上ObjectChart.tsx:852(两个世界都该有)⭐ 一条我原本没设计、但结果最要紧的检查
合并后的 main 上,第 1486 行确实就是那个被引锚点。
path:line引用,可能被「你测量之后、你合并之前」落进同一个文件的任何别的改动再次作废。也就是说,位置轴的修复本身带着一个测量与落地之间的时间窗,窗内 main 动了这个文件就白修。这次没动,所以它落地即正确;但「改完就对」在这一类里不是恒真的。已把这条记到 objectui#8875。交付内容
DatasetWidget的 metric 分支现在读options.description并把它渲进 caption 行,经pickLocalized(objectui#4208 那个接缝)解析 —— 所以行内 per-locale map 拼法也认。四层管道(spec 槽位 / 翻译键 / server overlay / renderer resolver)第一次接到了末端消费者。未声明副标题的 tile 渲染字节同构。⛔ 未夹带:选项 2(
values[1..]渲成副值 —— Feature、踩人工底线)· 第三条路(按 ADR-0049 退休该键)。后继
objectui#8889 已立卡:
tWidgetSubCaption的第二肢(i18n bundle 条目胜过作者值)仍到不了 dataset-bound tile —— 它需要 dashboard 名,而DatasetWidget两个 dispatch site 都不给。⇒ 一个从 app bundle 加载、副标题只写在客户端 i18n bundle 里的 dashboard,今天仍然什么都不渲染;而tWidgetSubCaption自己的 docblock 把这种形态称作合法。同一个 ADR-0049 家族,低一层。Generated by Claude Code