Repository navigation
Commit 3482f3d
fix(console): open the record page scoped to the current app (PageSchema.apps) (#29631)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbS1gR5cGSSfRihXeXjmJC3p)_
Fixes #29592
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:控制台打开记录页时,先看当前应用:某张记录页在 `apps` 里列了这个应用,就在该应用里打开它;其它地方(含
Setup)照旧打开对象唯一的默认记录页。记录抽屉同样生效。`page.apps` 的活性登记从 planned 改为 live,描述去掉
PLANNED 标记。
- **为什么改**:#28979 裁决 A 的 ui 半。规格半(PR #29437)已合入,但控制台没读 `apps`,所以 Duly
这类应用在自己的路由里打开用户记录,仍然看到平台的 `sys_user_detail`。
- **风险与回滚**:没有任何页面写 `apps` 时行为不变(测试有对照组,现有 291
个相关测试文件全绿)。路由段先解析成应用再比对应用名,不拿包 id 去比。回滚即 revert 本 PR。
-
**先看**:`ui/packages/react/src/hooks/usePageAssignment.ts`(选页顺序:应用页、默认页、声明顺序)、`ui/packages/app-shell/src/views/RecordDetailView.tsx`(路由段解析为应用名)、`ui/packages/app-shell/src/console/appObjectScope.ts`(`declaredRecordPage`
与渲染选同一张页)。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/RecordDetailView.appScopedPage-29592.test.tsx`,应见
6 个用例全绿:Duly 应用里打开 `duly_member`,Setup 里仍是 `sys_user_detail`。
- **需要你做的**:无,席位落地。
## What changes
Ruling A on #28979 lets a record page name the apps whose routes open it
(`PageSchema.apps`); the spec half landed in #29437. This is the console
half.
- **`usePageAssignment` (`@object-ui/react`)** now reads its existing
`app` option, the owning app's NAME. Order: the first record page of the
object for which `defaultRecordPageObject(page, app)` answers the
object, then the `isDefault` page, then declaration order. The option's
type is unchanged; it was declared "reserved for future use" and read by
nothing, and its only caller never passed it. No new parameter, so
`Clause-②: no` (seat ruling).
- **`RecordDetailView`** resolves the route's `:appName` segment with
`matchAppBySegment` over the full metadata app list and passes
`app.name`, never the raw segment (contract-review flag ③.2 on #29437:
the segment is `app._packageId ?? app.name`). The record drawer, mounted
under the host list route, reads the same segment, so a record opened in
the drawer inside an app gets that app's page too. A package that ships
two apps resolves its package-id segment to the first app, so the second
app's scoped page opens under its name segment only, as the
`appAddressSegment` docblock states.
- **`appObjectScope.ts` `declaredRecordPage`** (the record route's
object scope, objectui#13046) asks the same order with the app
`AppContent` already resolved, so the route reads the objects of the
page it renders. Only this picker was touched; the packaged-lookup and
package-less arms are untouched.
- **Spec (the one edit the card owes, as amended):** `page.apps`
liveness row `planned` → `live`, citing the three console read points;
the describe drops `[PLANNED — not yet rendered]` and the closing "Until
the console reads it" sentence, and the TSDoc names the renderer. Forced
by that: the describe pin in `stack-app-record-page.test.ts` now pins
the marker's absence; `liveness/state-counts/page.md` and
`content/docs/references/ui/page.mdx` are regenerated. The `page.apps`
omit row in `metadata-form-zod-reconciliation.test.ts` stays, with a
rewritten reason: live, but not offered in the Studio page form because
no registered widget spells a list of app names (seat ruling; whether
the form offer becomes a follow-up card is the seat's call at ACCEPT).
With no `apps` on any page, every branch falls through to the previous
line.
## Tests
-
`ui/packages/react/src/hooks/__tests__/usePageAssignment.appScope-29592.test.tsx`
(10): the scoped page opens in its app whatever the served order; Setup
and no-app open the default; a page listing several apps;
declaration-order fallthrough; another object's scoped page ignored;
scoped slotted page hands back slots; `pageName` still wins; CONTROL
with no `apps`.
-
`ui/packages/app-shell/src/views/RecordDetailView.appScopedPage-29592.test.tsx`
(6): the ruling's pin (Duly under `com.duly` and under `duly` opens
`duly_member`; `/apps/com.objectstack.setup/...` opens
`sys_user_detail`), the two-apps-one-package corner, the drawer, and the
CONTROL over five segments.
-
`ui/packages/app-shell/src/console/__tests__/appObjectScope.appScopedRecordPage-29592.test.tsx`
(3): the scope reads the scoped page's objects in Duly and the default's
in Setup; the scope picker and `usePageAssignment` agree across apps and
orders; CONTROL.
- Existing assignment tests unchanged and green:
`usePageAssignment.isDefault-12195`, `.typeDiscriminator-9674`,
`.declarationOrder-7298`, `.isRecordPageOf-12715`.
<details><summary>Ablations (each mutated through
scripts/ablation-replace.mjs, restored to HEAD and verified)</summary>
- A, compare the raw segment (`matchAppBySegment(metadataApps,
appName)?.name` → the segment): record-view suite 3 failed / 3 passed
(package-id segment, two-apps corner, drawer red; the name segment stays
green, as expected).
- B, the hook ignores the app (`if (opts.app && false)`): 8 failed / 11
passed across the three new files.
- C, the scope picker ignores the app: 2 failed / 1 passed in the
agreement file.
</details>
<details><summary>Sweep</summary>
Every ui test file naming `RecordDetailView`, `appObjectScope`,
`usePageAssignment` or `readAppObjectScope` (291 files, `git grep -l` at
8fead84), three locked batches at 2 workers: 98/98 files 788 tests,
98/98 files 654 tests, 95/95 files 847 tests. Spec:
`src/stack-app-record-page.test.ts` +
`src/system/metadata-form-zod-reconciliation.test.ts` 103/103.
`check:liveness` and `check:generated` green after regeneration.
</details>
## Acceptance notes
- Live measurement (app-crm `--fresh` backend, Vite console on this
branch's source, 1440×900): a record page `crm_member` for `sys_user`
with `apps: ['crm_app']` was saved through `PUT
/api/v1/meta/page/crm_member` (200). Under
`/apps/com.example.crm/sys_user/record/ID` it opens `crm_member`;
`/apps/crm_app/...` canonicalises to the package-id segment and opens it
too; `/apps/com.objectstack.setup/sys_user/record/ID` opens
`sys_user_detail`. Before leg (the three ui files restored from
`origin/main` in the same running console): the CRM route opened
`sys_user_detail`. Screenshots are in the project files under
`issue-29592/` (`before-crm-app-user-record.png`,
`after-crm-app-user-record.png`, `after-setup-users-record.png`); the
seat links them.
- The record drawer is included by seat ruling; its host is
`RecordDetailView` mounted with `embedded` under the list route
(`/apps/:appName/:objectName`).
- The Studio page form does not offer `apps` (no app-name widget); see
the reconciliation ledger row.
## Verification
Local, `dispatch-gates --commands --repo objectstack-ai/objectstack`
derived 107 families on this diff: all 107 run at `0275f773`, every one
exit 0, `--ran` reconciliation `107 run, 0 NOT-MEASURED, 0 UNRUN` (the
ui `vitest run scripts/` suite and `check:type-check-debt` under the
verify lock: 170 files / 4972 tests green; debt exit 0). `dispatch-gates
--tier`: no path-derived mandate (`page.zod.ts` is a Clause-② suspect
hint only; the change there is describe text).
After merging `main` (`5b257756`, which brought #29552's
`appObjectScope.ts` arm in cleanly): spec rebuilt, `check:generated` and
`check:liveness` exit 0; the 16 ui test files naming `appObjectScope` /
`readAppObjectScope` / `usePageAssignment` plus the record-view file and
#29552's new test, 18 files / 113 tests green; the two spec files
103/103; ratchets rerun on the new head: `check:type-check-debt`,
`check-changeset-no-major`, `check-empty-changeset`, `check:nul-bytes`
exit 0. Type-check (`@object-ui/react`, `@object-ui/app-shell`,
`@objectstack/spec`) exit 0 and eslint 0 errors on the touched ui files,
both at `0275f773`. CI check-runs: see this PR.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jTtndxBXWfjH1SiQ6w)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d280265 commit 3482f3d
13 files changed
Lines changed: 517 additions & 34 deletions
File tree
- .changeset
- content/docs/references/ui
- packages/spec
- liveness
- state-counts
- src
- system
- ui
- ui/packages
- app-shell/src
- console
- __tests__
- views
- react/src/hooks
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
227 | 227 | | |
228 | 228 | | |
229 | 229 | | |
230 | | - | |
| 230 | + | |
231 | 231 | | |
232 | 232 | | |
233 | 233 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
135 | | - | |
| 135 | + | |
136 | 136 | | |
137 | | - | |
138 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
139 | 140 | | |
140 | 141 | | |
141 | 142 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
102 | 102 | | |
103 | | - | |
| 103 | + | |
| 104 | + | |
104 | 105 | | |
105 | 106 | | |
106 | 107 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
354 | 354 | | |
355 | 355 | | |
356 | 356 | | |
357 | | - | |
358 | | - | |
359 | | - | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
360 | 361 | | |
361 | 362 | | |
362 | 363 | | |
363 | 364 | | |
364 | 365 | | |
365 | | - | |
| 366 | + | |
366 | 367 | | |
367 | 368 | | |
368 | 369 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2072 | 2072 | | |
2073 | 2073 | | |
2074 | 2074 | | |
2075 | | - | |
2076 | | - | |
2077 | | - | |
| 2075 | + | |
| 2076 | + | |
| 2077 | + | |
| 2078 | + | |
2078 | 2079 | | |
2079 | | - | |
2080 | 2080 | | |
2081 | | - | |
| 2081 | + | |
2082 | 2082 | | |
2083 | 2083 | | |
2084 | 2084 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
0 commit comments