Skip to content

fix(example-showcase): project-detail 的 tab token 改用声明的 value (#5776) - #6598

Merged
qq9340100 merged 1 commit into
mainfrom
claude/issue-5776-tab-key-to-value
Aug 8, 2026
Merged

fix(example-showcase): project-detail 的 tab token 改用声明的 value (#5776)#6598
qq9340100 merged 1 commit into
mainfrom
claude/issue-5776-tab-key-to-value

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #5776

做了什么

examples/app-showcase/src/ui/pages/project-detail.page.tspage:tabs 上,两个 tab 的稳定 ?tab= URL token 写成了 key:。这个拼法两侧都不认识:

  • schema 侧:PageTabsProps.items[](packages/spec/src/ui/component.zod.ts)声明的是 label / icon / visibleWhen / value / count / children —— 没有 key。schema 是 strip 模式,parse 直接把它剥掉。
  • renderer 侧:objectui 的 tabs renderer(packages/components/src/renderers/layout/containers.tsx)只认非空字符串的 it.value,否则按下标派生 token。

所以两个 tab 的 token 一直是下标派生值,深链回不到原来那个 tab,而作者拿到的是成功回执 —— ADR-0078 的教科书形状。

-            key: 'details',
+            value: 'details',
-            key: 'tasks',
+            value: 'tasks',

零 spec 改动、零 objectui 改动:声明与 renderer 早就在 value 上一致(声明由 #5775 / PR #6281 补齐),错的只是这份语料 —— 修在生产者侧,不在消费者侧加别名(Prime Directive #12)。

前提复核(origin/main a36db28b7)

断言 复核结果
页面仍写 key: project-detail.page.ts:43 / :59(行号未漂移)
items[].valuecount 已声明 component.zod.tsPageTabsProps.items[] 里都在,注释直接点名「Declared for #5776
renderer 读 value、不读 key containers.tsx 只有 it.value 一处读;全文件搜不到任何 .key 读取
闸门确实覆盖这个形状(slot 里的组件) page-walk.ts 既走 regions[].components[] 也走 slots.*,本页是 kind: 'slotted'

逆向验证(方向先判后跑:预期 before 红 / after 绿)

#5068 闸门 —— os validate 实跑,showcase 全量语料:

总 warning page:tabs 相关
before(key:) 49 2 条 `key` is not a prop `page:tabs` declares
after(value:) 47 0

两份输出逐行 diff 只有那 2 行的删除,没有任何新增诊断。直接调 validateComponentProps 对 28 个 showcase page 复量,结论一致(4 → 2;剩下 2 条属别的单,见下)。

新增的钉子测试 —— 把 key: 改回去,4 条断言全红,而且红的方式本身就是证据:

× gives both tabs a stable `?tab=` token under `value`
    AssertionError: expected [ undefined, undefined ] to deeply equal [ 'details', 'tasks' ]
× survives `PageTabsProps` parse with the tokens intact — a declared key is CARRIED
    AssertionError: expected [ undefined, undefined ] to deeply equal [ 'details', 'tasks' ]
× leaves no near-miss token spelling on any tab item in the showcase corpus
    AssertionError: page "showcase_project_detail" tab "Details" must carry its token as `value`, not `key`
× uses semantic, distinct tokens — the property the index fallback lacks

第二条的 [ undefined, undefined ] 正是 strip 模式的实证:key 过不了 parse,token 根本到不了消费者。

i18n:预期不动,实测不动。 value 是 URL token 不是 label,而 _tabs 那个翻译槽喂的是对象筛选预设 tab(interfaceConfig.userFilters.tabs),跟 page:tabs 的 items 不是一个载体。pnpm check:i18n-coverageOK (12 config(s), 660 baselined untranslated string(s), none new)

闸门台账

#5068 没有独立的违例基线文件 —— 清单是 warning 期的实跑输出(上表即是),本 PR 把其中 2 条销账,无文件可改。

一处如实记录:packages/lint/src/validate-component-props.ts 的模块头写着「距离 error 升级还差 #5728 与两处页面改写(page:card.visible;#5776 的 tab key)」。那两处现在都已落地(前者 #6513,后者本 PR),这句话已经过期 —— 但把它改对需要跨全部 example 语料重新盘一遍剩余违例,超出本单一行修复的范围,故未在本 PR 内改写,留给 error 升级那一单连同新清单一起写。

测试

  • pnpm --filter @objectstack/example-showcase test15 files / 154 tests 全绿(含新增 4 条)
  • pnpm --filter @objectstack/example-showcase typecheck → 通过
  • pnpm --filter @objectstack/example-showcase validate✓ Validation passed
  • pnpm check:i18n-coverage → OK, none new
  • node scripts/check-nul-bytes.mjs → OK(6162 个文件,无裸控制字节)
  • eslint 两个改动文件 → 无输出

changeset

@objectstack/example-showcaseprivate: true 的示例包,不在 .changeset/config.json 的 fixed 组里,本 PR 不发布任何包 —— 因此打 skip-changeset,不写 changeset(与同类的 #6513 一致)。

顺手发现(未在本 PR 内修)

同一轮闸门实跑里,showcase 还剩 2 条违例:element:buttonaction.params 形状之争已由 #5777 跟踪;另一条 properties.actionName(src/ui/pages/index.ts:82 的 "Create Task" 按钮)此前无单,已另开 #6597 —— renderer 的 handleClick 开头就是 if (!action ...) return;,只写 actionName 的按钮点了没反应,与本单同一形状。


Generated by Claude Code

`page:tabs` 的 `items[]` 上,稳定的 `?tab=` URL token 只有一个拼法:`value`
—— 既是 `PageTabsProps.items[]` 声明的键(#5775 补上),也是 objectui tabs
renderer 唯一读的键(`containers.tsx`:非空字符串 `it.value` 才作数,否则按
下标派生)。这两个 tab 写的是 `key`,两侧都不认识:schema 是 strip 模式,parse
把它剥掉;renderer 回落到 `tab-<i>`。页面看起来正常,深链回不到原来那个 tab,
而作者拿到的是成功回执 —— ADR-0078 的教科书形状。#5068 的
`component-props-unknown-key` 闸门对这一处正好报了 2 条。

只改两行键名,零 spec / 零 objectui 改动:声明与 renderer 早已在 `value` 上一致,
错的是语料。

新增 `test/project-detail-tabs.test.ts` 同时钉形状与语义:token 写在声明的
`value` 下、能过 `PageTabsProps` 的 parse 且不被剥掉(这正是 `key` 与 `value`
的全部差别)、showcase 语料里任何 tab item 都不再出现 `key`/`id`/`name`/
`tabKey`/`slug` 这些近似拼法、token 语义且互不相同(下标派生值不具备的性质)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 8, 2026 6:32am

Request Review

@qq9340100 qq9340100 added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 8, 2026 — with Claude
@qq9340100
qq9340100 marked this pull request as ready for review August 8, 2026 07:22
@qq9340100
qq9340100 added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 6a4ad00 Aug 8, 2026
27 checks passed
@qq9340100
qq9340100 deleted the claude/issue-5776-tab-key-to-value branch August 8, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

showcase 的 project-detail 页在 page:tabs 上授权 items[].key,renderer 读的是 items[].value —— 两个 tab 的稳定 URL token 从未生效

2 participants