Skip to content

fix(app-shell): previews 只读 spec 声明的键(#3275,合并 #3281) - #3285

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3275-previews-drop-retired-keys
Aug 3, 2026
Merged

fix(app-shell): previews 只读 spec 声明的键(#3275,合并 #3281)#3285
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3275-previews-drop-retired-keys

Conversation

@xuyushun441-sys

@xuyushun441-sys xuyushun441-sys commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #3275
Fixes #3281

预览与样例此前是「一起错」才看起来正常。objectui#3266 把样例改对之后,画廊反而少画了东西 —— 因为这些预览读的键 spec 现在明确拒收。本 PR 是那件事的渲染器另一半,做法照抄 objectui#3236 / PR #3258ToolPreview 的同型修复。

预览渲染一个保存时会被拒的键,等于告诉作者「这样写是对的」,直到发布才被打脸 —— 对 AI 生成的元数据,这正是过期键藏身并扩散的地方。所以下面每一处读取都是删掉,而不是留个兜底(AGENTS.md #0.1)。

浏览器实测 before → after

方法与 #3266 相同:apps/console 预览画廊 + ?only=< type > 隔离单个设计器,Playwright headless 抓取。after 一次,把改动 git stash 后再抓一次 before,所以下表是量出来的,不是推出来的。

设计器 BEFORE(改动 stash 掉) AFTER(本 PR)
app Landing: / Home: home → Home(id + 它选中的条目)
app Dashboard 徽标 = item Dashboard 徽标 = dashboard#3266 记录的那条
app Docs 徽标 = link Docs 徽标 = url(spec 词汇)
app 每项目标行 crm_welcome / account / sales_order / sales_overview / https://docs.example.com ✅ 路径行回来了
agent TOOLS / No direct tools (skills can provide them) 空态 TOOLS 区块整体消失,改为一行说明:tools 来自已挂载的 skills
skill TRIGGER CONDITIONS (1)COND | sales_order FIELD / OPERATOR / VALUEobjectName / eq / sales_order
datasource 没有 CAPABILITIES 区块 CAPABILITIES: readOnly, queryAggregations#3266 记录的那条
validation CONDITION: amount > 0 同上(无变化)

validation 这行无差异是符合预期的:当前样例带的是合法的 condition,而 ?? expression 回退对它是死代码 —— 这恰恰是单子里说的「预览照样画得出来,于是没人发现 expression 是假的」。样例侧属 apps/console(#3276 的 scope fence),本 PR 不动;该分支的行为由单测覆盖。

内容与旧输出不同是正常的 —— 它现在来自 spec 合法的键;判据是合规草稿渲染出有内容的区块,而不是空区块

改了什么

删掉的退役键读取(都是 retiredKey() 墓碑,按名字被拒):agent.tools(objectstack#3894,agent 能碰到的 tool 就是它 skills 声明的那些,ADR-0064)、agent.knowledge(objectstack#3896,从不 scope 检索)、skill.triggerPhrases(objectstack#3896,phrases 从未与用户消息匹配过)。

AppPreview / AppNavCanvas —— AppSchema.navigation 是按 type 的判别联合且每支 .strict()。两处都无视了判别键:kind 靠 it.object / it.dashboard 猜,路由靠 it.path ?? it.href ?? it.route ?? it.url,landing 靠 landingRoute ?? landing ?? defaultRoute ?? '/'。这些一个都不是键(landing 已被 objectstack#4001 移除),所以读法是完全反的。现在:type 即徽标;目标读各分支自己的键;路由交给 resolveHref —— shell 自己的 nav → URL 映射(NavigationRenderer 写明它是唯一真源,useNavPins / SearchResultsPage 已在共用),所以预览里的链接就是运行时会走的链接。homePageId 按它本来的样子渲染成 nav item 的 id,并解析出它选中的条目,绝不当路径画

DatasourcePreview —— capabilitiesDatasourceCapabilities 布尔标志对象,而预览判的是 Array.isArray,正好只对 schema 拒收的旧数组形态亮灯。改为列出置 true 的标志。同时删掉 driver ?? d.type(schema 的提示就是 typedriver)与 isDefault ?? default 的 default 芯片(路由在 stack 层的 datasourceMapping 声明,不在 datasource 上)。

SkillPreview —— trigger-conditions 表原本在 cond.type 栏下读 cond.expression ?? cond.value,于是合规条件只显示出 value 一列。改为三列 field / operator / value,直接对应 SkillTriggerConditionSchema;缺必填格时明说 missing,而不是留个看起来「正常」的空格。

ValidationPreview(#3281,PM 中途并入)—— 画了 9 个规则类型,而联合只有 6 个。unique / async / customvalidation.zod.ts 同一段话裁掉(规则必须是对单条记录的确定性、同步、无副作用谓词),三者现在各自指向真正负责的那一层(唯一性用 unique 索引 —— SELECT-then-INSERT 天生有 TOCTOU 竞态;async 属表单层;custom 属生命周期钩子)。随之删掉两个别名回退:condition ?? expressionpattern ?? regex —— expressionpattern 从来不是任何分支的键。另有两支本来就读错了:conditional 读的是 condition 而非它的 when(合规规则显示「No expression set」,现在还会渲染嵌套的 then / otherwise),json_schema 压根没有分支(合规规则显示「Unknown rule type」)。

一个刻意没有删的读取

validation.object 保留。单子建议「确认没有别的调用方会传 object-scoped 草稿之后删掉」—— 确认结果是:anchors.ts 注册了独立的 validation 资源,并以 anchorByField('object') 匹配,所以独立校验规则确实带这个键。联合没有的键,不等于都是残留。

验证

  • pnpm exec vitest run packages/app-shell/264 files / 2272 passed, 1 skipped, 0 failed
  • pnpm --filter @object-ui/app-shell type-check → 干净通过(tsc --noEmit + typetests)
  • eslint 改动的 11 个文件 → 0 errors;warning 18 条,而 origin/main 上同样 6 个源文件的基线是 19 条 —— 没有新增 lint 债
  • 每个改动的预览按 PR fix(app-shell): ToolPreview 不再渲染已退役的 ToolSchema 标记徽标 (#3236) #3258ToolPreview.test.tsx 模板补了断言:喂合规草稿断言区块渲染出来,喂带退役键的陈旧草稿断言不渲染(agent 那条更强:合规草稿与仅多带 tools/knowledge 的草稿 DOM 完全相同)
  • 起的 vite dev server 已按记录的 PID 停掉,端口已释放

⚠️ 给维护者的一个上游提示(本 PR 没有据此改动)

本 PR 按当前 pin 住的 @objectstack/spec@17.0.0-rc.1 实现 —— 在那个已发布版本里 homePageId 是合法的 z.string().optional(),且 objectui 自己的 AppContent.resolveLandingRoute() 确实在读它

但 objectstack main(尚未发布)已用 #4667 / ADR-0049 把 app.homePageId 也退役了,墓碑文案写的是「no shell ever read it」—— 这与 AppContent.tsx:876 的实际读取相矛盾。等 objectui 升到 spec 17.0.0 正式版时,要动的不只是这个预览,还有 AppContent 的落地路由解析。已在 out_of_scope_findings 记录,未在本 PR 处理(超出 scope fence,且需要维护者对「谁说了算」拍板)。

顺带记录(未在本 PR 修)

AgentPreview 侧栏读的 d.permissions 是合法键;d.access 在 spec 里是 array,预览未渲染它 —— 不是缺陷,只是没覆盖。

🤖 Generated with Claude Code

https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa

The previews and the console samples were wrong TOGETHER, so the gallery
looked healthy. objectui#3266 corrected the samples and the gallery
immediately rendered LESS: agent TOOLS/KNOWLEDGE, skill TRIGGER PHRASES,
app's per-item row and `Dashboard` badge, datasource CAPABILITIES all
vanished or degraded. Those blocks were only ever lit up by metadata that
cannot be saved. Renderer half of that finding; same fix as PR #3258.

Deleted (retiredKey tombstones, rejected by name): `agent.tools`
(#3894), `agent.knowledge` (#3896), `skill.triggerPhrases` (#3896).

AppPreview/AppNavCanvas: read the `type` discriminator and each branch's
own target key; route via `resolveHref`, the shell's own nav -> URL
mapping. `homePageId` renders as the nav item id it is, resolved to the
entry it selects, never as a path.

DatasourcePreview: `capabilities` rendered as the boolean-flag OBJECT it
is (`Array.isArray` lit up only for the form the schema refuses); dropped
`driver ?? d.type` and the `isDefault ?? default` pill.

SkillPreview: trigger conditions as three columns field/operator/value,
replacing `cond.expression ?? cond.value` under a `cond.type` gutter.

ValidationPreview (#3281): removed the `unique`/`async`/`custom` branches
(each redirects to the layer that owns the job), the `condition ??
expression` and `pattern ?? regex` aliases for keys that never existed,
fixed `conditional` to read `when`, and added the missing `json_schema`
branch. `validation.object` stays — anchors.ts matches standalone rules by
it.

Verified in the preview gallery before/after per designer.

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

vercel Bot commented Aug 3, 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)
objectui Ignored Ignored Aug 3, 2026 10:49am

Request Review

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-BF4YgjEI.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.47KB 3.09KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 476.42KB 104.61KB
core (index.js) 2.25KB 0.80KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 136.23KB 34.75KB
fields (index.js) 223.53KB 54.78KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 26.14KB 6.07KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 37.96KB 10.54KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 60.54KB 17.13KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 112.01KB 28.86KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 230.56KB 56.80KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 111.49KB 26.95KB
plugin-gantt (index.js) 162.25KB 39.55KB
plugin-grid (index.js) 185.08KB 49.04KB
plugin-kanban (index.js) 47.89KB 13.18KB
plugin-list (index.js) 104.94KB 25.32KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.55KB 10.59KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.34KB 2.82KB
plugin-view (index.js) 83.67KB 20.43KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.46KB 1.21KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 10:57
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 8ff3ad7 Aug 3, 2026
17 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-3275-previews-drop-retired-keys branch August 3, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment