docs(ui): App 页补 ## Areas 一节 —— 含服务端/客户端闸门不对称 (#4880) - #5890
Merged
Conversation
…#4880) `content/docs/ui/apps.mdx` never mentioned `areas[]` — an authorable key with permission-adjacent semantics at zero doc coverage, while three changes landed on it in the 17.0.0 window. An author reading only this page could not learn that areas exist, let alone that the gate they reach for first is the one the server does not enforce. New `## Areas` section: - when an area beats a top-level `group` (a group keeps siblings on screen; an area REPLACES the sidebar, so it fits contexts a user switches between); - the area property table — the whole strict key set, with `description` marked as the annotation no surface renders today; - how `areas[].navigation` relates to the top-level tree: areas take precedence and the top-level tree is the fallback when no area is visible; declaration order IS display order (nothing sorts areas); the switcher appears only above one visible area; area visibility is DERIVED from the items inside, never authored; - the server/client gate asymmetry as the section's safety point: item-level `requiredPermissions` / `requiresService` are stripped server-side in BOTH trees since #4722 and re-checked in the shell, while `visible` (CEL) and `requiresObject` stay client-only at every level — so anything that must never reach the browser goes in `requiredPermissions`, never in `visible`; - the 17.0.0 retirements (`order` #4667, `visible` / `requiredPermissions` #4651) as parse-time rejections, with the fail-open history that motivated them and the note that they do not read back onto item-level gating; - a Complete-Example-grade sample marked `{/* os:check */}` so the gate type-checks it against the built spec (206 prose examples, was 205), showing both gate kinds side by side and carrying none of the three retired keys. Wording follows the corrected `AREA_REQUIRED_PERMISSIONS_RETIRED` / `AREA_VISIBLE_RETIRED` prescriptions and the `areas.navigation` note in `packages/spec/liveness/app.json`; nothing under `packages/**` is touched. Closes #4880 Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
marked this pull request as ready for review
August 6, 2026 11:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4880
content/docs/ui/apps.mdx此前一次都没有提到areas[]—— 一个真实可作者化、且带权限相邻语义的键,文档覆盖为零;而 17.0.0 窗口里有三处改动正好落在它上面。只读这一页的作者不但不知道 areas 存在,更不知道他第一反应会去写的那个闸门(visible)恰恰是服务端不强制的那个。新增
## Areas一节group把同级都留在屏幕上,area 则替换整棵侧边栏 —— 所以 area 适合「用户在其间切换的上下文」,不适合「需要并排浏览的分组」。简单应用只用navigation。id/label/icon/description/navigation就是全集(NavigationAreaSchema是 strict)。description明确标注为「今天没有任何界面渲染它」的作者侧注解,避免作者误期待。areas[].navigation与顶层树的关系:areas 优先,顶层navigation是「该用户看不到任何 area 时」的兜底;声明顺序即显示顺序(侧边栏与AppSchemaRenderer都按作者写的顺序迭代,没有任何地方排序 areas);切换器只在可见 area 多于一个时出现;area 的可见性是派生的、不可作者化 —— 内部一项都不剩的 area 会从切换器消失,也不会被自动选中。requiredPermissions/requiresService自 filterAppForUser 只走 app 顶层 navigation —— areas[] 里的 nav 项权限过滤仅客户端生效(#4651 移除假闸门后剩下的真缺口) #4722 起在两棵树都由filterAppForUser服务端剥离、再在 shell 复检;visible(CEL)与requiresObject在任何层级依然只在客户端求值。结论一句话写进 Callout:必须永不到达浏览器的东西写requiredPermissions,不要写visible。同时写清收紧后的响应形状:被 gate 滤空的 area 整个剥离(与groupcollapse 同构),而作者写空的 area 原样透传。areas[].order(清空剩余 6 条 authorWarn 死键 —— book ×2 / job.id / translation.validationMessages / app.homePageId / app.areas[].order(ADR-0049,v17 限时) #4667)、areas[].visible/areas[].requiredPermissions(app.areas[] 的 visible / requiredPermissions 是 fail-open 的访问闸门 —— 服务端从不走 areas(ADR-0049,v17 限时) #4651)现在是 parse 期拒收而非静默忽略,一个残留键会让整次保存失败;附上它们当初是 fail-open 闸门的历史(干净 parse + 存下的值 + 对每个用户照常下发),以及一句边界:退役不回读到项级 —— area 内部的项级闸门完整可用,且自 filterAppForUser 只走 app 顶层 navigation —— areas[] 里的 nav 项权限过滤仅客户端生效(#4651 移除假闸门后剩下的真缺口) #4722 起是服务端强制的。样例中不出现这三个键。requiredPermissions: ['service.finance']服务端强制 vsvisible仅客户端去噪),并带{/* os:check */}标记,让门禁按真实构建产物 type-check 它。措辞蓝本
按 issue 的「谁先做谁定调」约定,配对单 #4749(PR #5336)与相邻单 #5337 均已 closed-completed,本节措辞对齐它们落地后的口径:
packages/spec/src/ui/app.zod.ts的AREA_REQUIRED_PERMISSIONS_RETIRED/AREA_VISIBLE_RETIRED处方正文,以及packages/spec/liveness/app.json的areas.navigationnote。实测核对(逐条对 origin/main 验过,非照抄 issue)
packages/rest/src/rest-server.tsfilterAppForUser:filterAreas对每个areas[].navigation复用同一个filterNavfilterAreas:kids.length === 0时continue;anav.length === 0时out.push(a)visible/requiresObject仅客户端AppSidebar与AppSchemaRenderer均为areas.filter(...),无 sortactiveArea?.navigation || activeApp?.navigation || []hasVisibleNavigationItems(area.navigation, ...)(objectui#3311 / #3319)一处据实测修正了 issue 的隐含表述:issue 只说 areas「按作者顺序迭代」,实测还有一条它没提、但对作者更要紧的事实 —— area 的可见性是从内部项派生的(全被 gate 掉就整个消失且不会被选中)。这正是 #4651 退役 area 级
visible后仍能保住「整个 area 消失」这一 UX 的机制,已写入本节。检查
tsx packages/spec/scripts/check-skill-examples.ts→206 prose examples type-check against @objectstack/spec(新样例登记为content/docs/ui/apps.mdx:339,205 → 206)check-doc-authoring.mjs(含--self-test)→362 files cleandocs-audit/affected-docs.mjs --self-test+check-audit-scope.mjs(含--self-test)→ 全绿,178 hand-written doc(s)in synccheck-nul-bytes.mjs→ OK;另做超出门禁的自扫grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'零命中check-role-word.mjs→ OK@mdx-js/mdx+remark-gfm实编译该页通过(门禁的lychee设了include_fragments = false,所以页内锚点不受 CI 保护 —— 因此把被引用的小节标题改成无标点的### Retired area-level keys,锚点不再依赖17.0.0/ 破折号的 slug 化行为)边界
docs-only:仅改
content/docs/ui/apps.mdx(+197 行)。⛔ 未动packages/**源码(spec 处方与其 pin 只读不改),⛔ 未动content/docs/releases/**。不发布任何包,故无 changeset,PR 自带skip-changeset标签。Generated by Claude Code