Repository navigation
Commit 488b3ed
fix(studio): object pickers offer only the objects the edited package may reference (#28329)
domain:ui#3 · os-tesla · session_01L9qibvSmNNn2cV2emnvGXn
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoWiWdGBWp9rocznTPtPPZM9)_
Fixes #28240
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:Studio
的对象选择器(流程触发对象、流程节点对象、查找字段的关联对象、新建数据集的对象)现在只能选当前软件包可用的对象:自己的、已声明依赖包的、系统对象。范围外的对象放在最后一个默认折叠的分组里,灰显不可选,并说明原因,附「声明依赖」入口,直接打开软件包编辑框;保存后不用刷新即可选。
- 为什么改:发布闸门本来就拒绝范围外的对象,但选择器什么都列,作者在对象上把流程做完,保存后才看到拒绝。
- 风险与回滚:依赖遍历从服务端搬进 `@objectstack/lint` 的
`resolvePackageClosure`,服务端改为调用它,接纳范围不变(服务端原有闭包测试未改、全绿)。软件包列表读不到时不做任何收窄,由服务端判定,与闸门的失败方向一致。回滚即
revert 本 PR。
-
先看:`ui/packages/app-shell/src/views/metadata-admin/inspectors/objectPickerClosure.ts`、`ObjectPicker.tsx`
的分组与禁用逻辑、`packages/metadata-protocol/src/protocol-write-package-scope.ts`。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/inspectors/ObjectPicker.closure-28240.test.tsx`,预期
6/6 通过。
- 需要你做的:无,席位落地。
## What changes
- **`@objectstack/lint`**: new export `resolvePackageClosure(packageId,
getPackage)`, on both the root and the `./runtime` entry. It is the
dependency walk `resolveWritePackageScope` ran inline, moved verbatim:
same guards (no id, the `sys_metadata` sentinel, an unknown package
answer `undefined`), same DFS, a declared-but-unresolvable dependency
still in the closure, own id skipped. A throwing `getPackage`
propagates; the host decides.
- **`@objectstack/metadata-protocol`**: `resolveWritePackageScope` keeps
its two host guards (registry without `getPackage`, the silent `catch`)
and calls the shared walk. What the gate admits is unchanged.
- **Studio object picker** (`ObjectPicker.tsx`, shared by the flow New
dialog's record presets, every flow `object` reference, a lookup's
related object and the new-dataset dialog): when a package is being
edited, it reads `GET /api/v1/packages` (the request `packages-io.ts`
already shares) and lazily imports `@objectstack/lint`, then calls
`resolvePackageClosure` and `narrowObjectsToPackageClosure` over the
served object list. Neither rule is restated in ui.
- Objects the closure does not admit are listed last, in a group that
starts collapsed ("Show objects outside this package's dependencies
(N)"), each `aria-disabled` with "From PACKAGE". A search reaches them
without opening the group.
- The group's first row carries the reason and, when the package is
writable, **Declare a dependency**, which opens the package's edit
dialog (`PackageFormDialog` mode `edit`). The picker re-reads the
closure on `objectui:packages-changed`, so a declared dependency's
objects become pickable without a reload.
- Choosing a disabled row commits nothing. Free-text entry is unchanged
(Enter or blur keeps what was typed; the server judges it).
- No package in scope, the package list unreadable, or the package not
listed: nothing is narrowed (the gate's own failure direction).
- **i18n**: six `engine.inspector.objectPicker.*` keys, one contiguous
block in each studio pack (en, zh).
- **Changeset**: `.changeset/28240-studio-object-picker-closure.md`
(patch: app-shell, i18n, lint, metadata-protocol).
Presentation choice (card item 1, "absent or disabled"): **disabled with
the reason and the declare door**, as the seat ruled. A missing object
reads as "the platform has no such object"; a disabled one tells the
author what to do.
## Tests
- `packages/lint/src/runtime-gate.resolve-package-closure.test.ts`
(new): the guards, own-only closure, transitive walk through a cycle, an
unresolvable declared dependency kept, a throwing registry propagating,
and that the narrow `./runtime` entry names the function.
- `ui/.../inspectors/ObjectPicker.closure-28240.test.tsx` (new), with
the real lint functions and only the two reads doubled:
- **the card repro as a pin**: a package with no dependencies is not
offered `showcase_task`; searching finds it disabled, with the reason,
and clicking it commits nothing;
- **CONTROL**: with `com.example.showcase` declared, `showcase_task` is
offered and commits;
- a dependency declared after mount makes the object pickable once the
package list changes;
- platform objects stay pickable with no dependency;
- the declare row opens the package edit dialog;
- an unreadable package list narrows nothing.
- `ObjectPicker.test.tsx`: a module-scope `fetch` double for `GET
/api/v1/packages` (empty list, so nothing narrows and its expectations
are unchanged). The picker now reads that list on mount, and the
network-escape guard reds a real socket.
<details><summary>Readings</summary>
```
# lint + server closure tests (server behaviour unchanged; existing server tests untouched)
packages/lint: runtime-gate.resolve-package-closure, runtime-gate.package-closure, rule-id-barrel-exports, authoring-rule-wiring
Test Files 4 passed (4) · Tests 75 passed (75)
packages/metadata-protocol: protocol.package-closure-gate, protocol-publish-drafts-closure, protocol.draft-publish-verdict,
protocol.runtime-authoring-gate, sys-metadata-repository.package-writability, protocol.packageless-save-address
Test Files 6 passed (6) · Tests 382 passed (382)
# picker
ObjectPicker.closure-28240.test.tsx + ObjectPicker.test.tsx :: Test Files 2 passed · Tests 16 passed
# broad sweep (every test under views/metadata-admin/, views/studio-design/, console/ai/)
pnpm --dir ui exec vitest run --maxWorkers=3 packages/app-shell/src/views/metadata-admin/ packages/app-shell/src/views/studio-design/ packages/app-shell/src/console/ai/
Test Files 1010 passed (1010) · Tests 9500 passed | 1 skipped (9501)
# ablation (source; the test imports ObjectPicker relatively, no dist in the path)
node scripts/ablation-replace.mjs --anchor "admitted && !admitted.has(e.name)\n ? 'outside'" --replacement "false\n ? 'outside'"
mutation landed: anchor 1 -> 0, blob df4f2d4 -> 92c0e63c
ObjectPicker.closure-28240.test.tsx :: Tests 4 failed | 2 passed (repro, re-read, platform, declare red; CONTROL and unreadable stay green, as they must)
restore: blob after restore df4f2d4 = HEAD, git diff HEAD empty
# type-check
pnpm --filter @objectstack/lint run typecheck · @objectstack/metadata-protocol typecheck · @object-ui/app-shell type-check · @object-ui/i18n type-check :: exit 0
```
</details>
## Verification
Head `3c5e468c` (branch merged with `origin/main`, full rebuild `pnpm
turbo run build --filter='!@objectstack/docs'` 115/115).
- Derived gates: `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives **64** families; each was run with
`</dev/null` and every one exits **0**. Reconciliation `--ran`: `64
derived, 64 run, 0 NOT-MEASURED, 0 UNRUN`.
- `node scripts/check-sdui-manifest.mjs` is among the 64 (exit 0). `ui
check:new-line-citations`: 0 new. `pnpm --dir ui exec vitest run
scripts/` (pre-merge head): 169 files, 4901 tests passed.
- Rerun on this head: picker 2 files / 16 tests, lint 4 files / 75
tests, metadata-protocol closure tests 6 files / 382 tests, type-checks
(lint, metadata-protocol, i18n, app-shell) exit 0.
<details><summary>Live measure (card item 2, and the picker)</summary>
Own stack: showcase backend `objectstack dev --seed-admin --fresh -p
41873`; the Console production build (`vite build`) served by `vite
preview --port 51874` proxied to it. (The Vite **dev** server cannot
load `@objectstack/lint` in the browser, see Acceptance notes, so the
picker was measured on the production bundle.)
Item 2, the dependencies editor is reachable from Studio, no wiring
needed:
- **New package** (Studio landing, *New package (writable base)*):
`package-form-dependencies` visible before *Advanced* = `false`, after
expanding *Advanced* = `true`. It sits in the collapsed Advanced
section, which is likely why the walkthrough missed it.
- **Package settings** (package chip, *Package info & settings*,
*Edit*): `package-form-dependencies` visible = `true`, one row,
`com.example.showcase` (declared in the step below).
Item 1, the card's repro on `com.default.acme-custom` (no dependencies),
Automations, New, *When a record is created*, Object:
- pickable before search: `sys_inbox_message`, `sys_file`,
`sys_upload_session` (platform objects); toggle "Show objects outside
this package's dependencies (24)";
- typing `task`: `showcase_task` `aria-disabled="true"`, "From
com.example.showcase", reason row "Acme Custom can use only its own
objects, …"; clicking it left the input at the typed `task` (nothing
committed);
- *Declare a dependency* opened the package edit dialog; adding
`com.example.showcase` / `*` and saving closed it; the stored manifest
reads `{"com.example.showcase":"*"}`, and the same picker then offered
`showcase_task` enabled.
</details>
## Acceptance notes
- **Vite dev Console cannot import `@objectstack/lint` in the browser**
(measured on this branch: `Module "module" has been externalized for
browser compatibility. Cannot access "module.createRequire" in client
code`). The lint graph imports `node:module` in six rule files. In dev
the picker therefore narrows nothing and the server judges, the
documented failure direction. The production bundle loads it (measured
above). The existing in-browser lint callers
(`preview/capabilityLint.ts`, `preview/securityPostureLint.ts`,
`metadata-admin/celAuthoring.ts`) take the same dynamic import, so they
degrade the same way in dev. Not changed here; Refs #28333.
- **Report data source**: a new report binds a dataset, not an object,
so its picker is the dataset list and is not narrowed here. A new
dataset's object picker is narrowed.
- **Free text stays free**: a name typed and committed with Enter is
still saved as typed (flow references may hold expressions), and the
server refuses an out-of-closure name at publish as before.
- Card item 3 "the server refusal no longer reachable through the
picker" holds for every pick from the list; the free-text path above is
the remaining way to reach it, by design.
- File surface: `ObjectPicker.tsx` and the new `objectPickerClosure.ts`
live in `views/metadata-admin/inspectors/` (the shared picker every
Studio object picker uses), and `packages-io.ts` gains a manifest
reader. `StudioDesignSurface.tsx` is not touched. `packages/lint` and
`protocol-write-package-scope.ts` per the seat's claim amendment
6538025413.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L9qibvSmNNn2cV2emnvGXn
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibvSmNNn2cV2emnvGXn)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 64c5668 commit 488b3ed
13 files changed
Lines changed: 644 additions & 44 deletions
File tree
- .changeset
- packages
- lint/src
- metadata-protocol/src
- ui/packages
- app-shell/src/views
- metadata-admin/inspectors
- studio-design
- i18n/src/namespaces/studio
| 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 | |
|---|---|---|---|
| |||
1112 | 1112 | | |
1113 | 1113 | | |
1114 | 1114 | | |
1115 | | - | |
| 1115 | + | |
1116 | 1116 | | |
1117 | 1117 | | |
1118 | 1118 | | |
1119 | 1119 | | |
1120 | 1120 | | |
1121 | 1121 | | |
| 1122 | + | |
1122 | 1123 | | |
1123 | 1124 | | |
1124 | 1125 | | |
| |||
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
464 | 464 | | |
465 | 465 | | |
466 | 466 | | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
467 | 511 | | |
468 | 512 | | |
469 | 513 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | | - | |
| 38 | + | |
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| |||
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
| 67 | + | |
67 | 68 | | |
68 | 69 | | |
69 | 70 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
12 | | - | |
13 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
27 | | - | |
28 | | - | |
29 | | - | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
| 33 | + | |
32 | 34 | | |
33 | 35 | | |
34 | 36 | | |
| |||
42 | 44 | | |
43 | 45 | | |
44 | 46 | | |
45 | | - | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
46 | 56 | | |
47 | 57 | | |
48 | 58 | | |
49 | 59 | | |
50 | | - | |
51 | 60 | | |
52 | 61 | | |
53 | 62 | | |
54 | 63 | | |
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 | | - | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
82 | 67 | | |
83 | 68 | | |
84 | 69 | | |
| |||
0 commit comments