Skip to content

Commit c618e89

Browse files
[maintainer] refactor(app-shell): move Studio's flow rail helpers, pillar kit and Data pillar helpers out of StudioDesignSurface.tsx (slice 4, final: file under 10,000 lines) (#30027)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBCE4YXYCrA6G1yrthEDVvNJ)_ Fixes #29988 ## 维护者速读 - 改了什么:把 Studio 三组辅助代码原样搬进同目录三个新文件——自动化栏的流程辅助(347 行)进 `AutomationsFlowRail.tsx`,各支柱共用的工具集(335 行)进 `StudioPillarKit.tsx`,数据支柱的辅助(181 行)进 `DataPillarHelpers.tsx`;主文件 10,757 → **9,928 行**,低于 1 万,本卡完成。 - 为什么改:#29988 按裁定 #24586 A 只搬不改,把这个文件拆到 1 万行以下;维护者问 1,000 行一片是否值得,席位决定最后一片合并接缝表第 4–6 行一次做完。第 7–10 行(整个支柱组件)不搬,留给以后的卡。 - 风险与回滚:无行为变化,八段搬走的代码逐字节一致,只多了 import 和 `export`;原来从 `StudioDesignSurface` 导出的五个名字在原处重新导出,所有引用照常工作;对外导出摘要两边一致(只是类型的声明文件变了);回滚即 revert 本 PR。 - 先看:三个新文件的 import 段;`StudioDesignSurface.tsx` 的 import 段和三行重新导出;`studio-authoring.json` 四处出处改动与新加的 history 行。 - 两分钟验证:在本分支跑下方「Verification」里的 `cmp` 命令组,预期全部无输出(逐字节相同)。 - 需要你做的:无,席位落地。 Clause-②: no Metadata-protocol: none ## Seam table Carried forward from #30014. Base lines below are at `origin/main` `9d1a465f0` (this PR's base, after slice 3), where `ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx` is 10,757 lines. "Left" is the file's line count after that row and every row above it, measured at this PR's commits. | # | Seam | Base lines | Size | Reads from the rest of the module · hooks it calls | Imported by | Status | Left | |---|---|---|---|---|---|---|---| | 1 | Nav item inspector (`StudioNavItemInspector`, nav target helpers, `StudioPicker`) | — | 978 | — | — | **landed in #30002** | 11,884 | | 2 | `PackageSwitcher` with `PACKAGE_LIST_TOAST_ID` and `STUDIO_LANDING_PATH` | — | 663 | — | — | **landed in #30008** | 11,205 | | 3 | Interfaces nav tree rail (`NavTree` … `KIND_ICON` / `navIcon`) | — | 442 | — | — | **landed in #30014** | 10,757 | | **4** | **Flow rail helpers: `FlowRuntimeState` … `readFlowCopiedArrival`, incl. `FlowStatusDot`, glyph tables, `pairIdOf`** → `AutomationsFlowRail.tsx` | 7614–7960 | **347** | nothing · no hooks | `AutomationsPillar`; `AccessPillar` (`pairIdOf`); 2 tests import `FlowStatusDot` (re-exported) | **this PR** (commit 1) | 10,424 | | **6** | **Data pillar helpers: the OWD option keys, `nextFieldName` … `STUDIO_SYSTEM_FIELD_NAMES`, `formFieldNames` … `formPreviewSource`** → `DataPillarHelpers.tsx` | 443–460, 5135–5202, 5281–5375 | **181** | nothing · no hooks | `DataPillar` only | **this PR** (commit 2); `renderStudioGridList` (5204–5279, 76 lines) stays, see What changes | 10,253 | | **5** | **Pillar kit: `useHeldEditReport`, `confirmDropUnsavedWork`, `findTakenIdentifier`, `CreateTakenNotice`, `StudioHeldNotice`, the "Show me" focus helpers with their section comment, `announceDraftSaved`, `StudioReadOnlyLink`, `StudioBaseCustomization`** → `StudioPillarKit.tsx` | 504–643, 663–751, 764–843, 2233–2258 | **335** | nothing · `useStudioTabRowFold` | all four pillars and `StudioDesignSurfaceBody`; `StudioBundleItemEditor.tsx`, `StudioMetadataArea.tsx` and 6 tests import its types or components through `./StudioDesignSurface` (re-exported) | **this PR** (commit 3) | **9,928** (under 10,000) | | 7 | `AccessPillar` with its arrival reader and `StudioNameLabel` | 2201–2225, 9814–10755 | 967 | rows 4, 5 · 11 hooks | `StudioDesignSurfaceBody`; 7 test files | not moved; available to a later card | — | | 8 | `AutomationsPillar` | 7962–9812 | 1,851 | rows 4, 5 (and `StudioPicker`, row 1) · 21 hooks | `StudioDesignSurfaceBody`; 23+ test files | not moved; available to a later card | — | | 9 | `DataPillar` | 5379–7611 | 2,233 | rows 2, 5, 6 (and `StudioPicker`) · 17 hooks | `StudioDesignSurfaceBody`; 38+ test files | not moved; available to a later card (holds three `clickableNonControlCensus-13461` rows keyed by this file) | — | | 10 | `InterfacesPillar` with `interfacesSaveBody`, `leafKeyOf`, `customizeItemKey` | 484–502, 2230–2231, 2260–5133 | 2,895 | rows 1, 3, 5 · 23 hooks | `StudioDesignSurfaceBody`; 29+ test files | not moved; available to a later card (holds one census row) | — | | 11 | `StudioDesignSurface` / `StudioDesignSurfaceBody`, `PILLARS`, `METADATA_AREA_TAB`, `PILLAR_FOR_SURFACE_TYPE`, publish-refusal and backfill helpers | the rest | — | — | `index.ts`, the console's lazy Studio route, 51 test files | stays: this is the file | — | Rows 4–6 now live outside the file, so a later card can move rows 7–10 without an import cycle back into it. ## What changes One commit per seam row, then one for the readers and the checklist. - **Row 4 → `ui/packages/app-shell/src/views/studio-design/AutomationsFlowRail.tsx` (new)**: base lines 7614–7960, byte for byte, under a copyright line, a module comment and the imports the block reads. `export` is added on the 18 names the rest of the file reads (`FlowRuntimeState`, `FlowRailState`, `flowRailState`, `flowRailStatus`, `flowDotClass`, `FLOW_GLYPH_ICON`, `FLOW_GLYPH_LABEL_KEY`, `NO_OBJECT_GROUP_KEY`, `readFlowRegistrations`, `readFlowDefinitions`, `readObjectLabels`, `newFlowTriggerFields`, `withStartTrigger`, `NO_OBJECT_ORDER`, `FlowCopiedStrip`, `FLOW_COPIED_ARRIVAL_STATE`, `pairIdOf`, `readFlowCopiedArrival`). `FlowStatusDot` and `NewFlowTrigger` were already exported, keep their `export`, and are re-exported from `StudioDesignSurface.tsx`. The one-line section comment above the block at 7613 (`/** Automations pillar — flows: … */`) heads `AutomationsPillar` and stays. - **Row 6 → `DataPillarHelpers.tsx` (new)**: base lines 443–460, 5135–5202 and 5281–5375, byte for byte; `export` on the 13 names `DataPillar` reads. **`renderStudioGridList` (5204–5279) stays in `StudioDesignSurface.tsx`.** It carries `// eslint-disable-next-line react-refresh/only-export-components`, and in a module that exports no component that line is an unused directive, which this repo's eslint reports as an error (measured on the first cut). Moving it would need that line edited or dropped, which is not a move, and the file is under 10,000 lines without it. - **Row 5 → `StudioPillarKit.tsx` (new)**: base lines 504–643, 663–751 (the "Show me" section comment at 663–671 and its helpers), 764–843 and 2233–2258, byte for byte; `export` on the 7 names the rest of the file reads (`useHeldEditReport`, `confirmDropUnsavedWork`, `findTakenIdentifier`, `CreateTakenNotice`, `useShowInputFocus`, `StudioSavedDraft`, `announceDraftSaved`). `StudioHeldNotice`, `StudioReadOnlyLink` and `StudioBaseCustomization` were already exported and are re-exported from `StudioDesignSurface.tsx`. - **`StudioDesignSurface.tsx`**: the eight blocks are gone. It imports the names from the three modules beside the slice 3 import, re-exports the five names it exported before in two lines with a comment each (where their first block was), and drops the import bindings only the moved code used (`describeFlowRunStatus`, `FlowRunStatusView`, `FlowRuntimeRow`, `fieldsForNodeType`, `localizeFlowFields`, `FlowConfigField`, `Zap`, `Clock`, `MousePointerClick`, `CornerDownRight`, `FlowTriggerGlyph`, `customizationPackageId`, `SpecFlowRuntimeState`, `romanizeHan`, `toFieldNameLoose`, `PackageCustomizationsState`, `useStudioTabRowFold`). - **Importers through `./StudioDesignSurface` are unchanged**, by the re-exports: `FlowStatusDot.test.tsx`, `FlowStatusDot.registration-12349.test.tsx`, `StudioReadOnlyLink.overlay-27356.test.tsx`, the five tests that import `type StudioBaseCustomization`, and the two source files `StudioBundleItemEditor.tsx` and `StudioMetadataArea.tsx` (type-only imports, so no runtime cycle). `StudioDesignSurface.gridRefresh.test.tsx` imports `renderStudioGridList`, which did not move. - **By-path readers** (ruling 6597555122 item 3): `homeRecoveryRedirectsFollowDeclaration-7373.test.ts`'s Studio site `alsoReads` and `spec-symbol-ledger-app-shell-7265.test.ts`'s `SITES` each gain the three modules. The ledger's `FlowRuntimeState` site (`type FlowRuntimeState = Partial<SpecFlowRuntimeState>`) now lives in `AutomationsFlowRail.tsx`. - **`docs/qa/platform-checklist/areas/studio-authoring.json`**, source re-points only, each with a revision bump and a history row ("No clause changed"): - `held-edit-leave-guard` r5 → r6: `confirmDropUnsavedWork` and `useHeldEditReport` now cited in `StudioPillarKit.tsx`; the rest stays on `StudioDesignSurface.tsx`. - `automations-rail-and-new-flow` r2 → r3: `FLOW_GLYPH_ICON` / `FLOW_GLYPH_LABEL_KEY`, `flowDotClass`, `FlowStatusDot` now cited in `AutomationsFlowRail.tsx`; the rail legend and the New automation presets stay. - `flow-status-matches-runtime` r2 → r3: `FlowStatusDot` and `flowRailStatus` now cited in `AutomationsFlowRail.tsx`. - `access-permission-set-lifecycle` r7 → r8: `findTakenIdentifier` and `CreateTakenNotice` now cited in `StudioPillarKit.tsx`; the Access pillar's use of them stays. - `.changeset/29988-studio-pillar-helpers-modules.md`: `@object-ui/app-shell` patch, "internal: move-only". ## Tests No behaviour changes, so no new behaviour test. The existing suites run unchanged; results are in Verification. ## Acceptance notes - **The bar is met**: `StudioDesignSurface.tsx` is 9,928 lines at this head (`wc -l`), so this PR says `Fixes #29988`. - **No fallback trigger hit**: no import cycle (the three modules import nothing from `StudioDesignSurface.tsx`; the two source files that import a moved type through it do so type-only), no census row to re-key (`clickableNonControlCensus-13461`'s rows for this file are `DataPillar` and `StudioDesignSurfaceBody` handlers, which stay; the moved clicks are `<button>`s), and 1,941 changed lines in all, under 3,000. - **Readers re-grepped.** `git grep` of `StudioDesignSurface.tsx` / `studio-design/StudioDesignSurface` over `scripts/**`, `ui/scripts/**` and every ui test file finds the same two whole-text readers as before; both gained the three modules. The directory walkers (`noRetiredSetupLink-28578`, `noSetupMetadataLink-28578`, `CreateItemDialog.oneLabel-11787`) and `clickableNonControlCensus-13461` walk the tree themselves. `openPathObjectReaders-12811` classifies readers of `useMetadata().objects`; none of the moved code reads it. - **Checklist rows re-read.** Every `StudioDesignSurface.tsx` citation under `docs/qa/` was read against the moved names: the four above named moved helpers, and the rest cite pillar logic that stays (`railPairId`, `ledgerRow`, `openCreateFlow`, `openCreateSet`, `heldEdit`, `unsavedPillarWork`, each still in the file). - **Comments that name the file for moved text, left as they are** (outside the file surface): `spec-symbol-parity.test.ts` says "`StudioDesignSurface` declares `type FlowRuntimeState = …`" (now in `AutomationsFlowRail.tsx`; the test pins the types, not the file). - **Serial check.** No open PR touched `StudioDesignSurface.tsx`, the three new modules, either extended reader or `studio-authoring.json` when the seat claimed (round 4 Claim) or right before this push (see Verification). The branch was restarted from `origin/main` after #30014 merged and is rebased on `63b86b38`, which touches only `packages/rest/` and its changeset. ## Verification <details><summary>Move-only proof: byte-compare of all eight blocks and the moved-lines diff</summary> ``` B=ui/packages/app-shell/src/views/studio-design base() { git show 9d1a465:$B/StudioDesignSurface.tsx | sed -n "$1p"; } UA() { sed -E 's/^export (async function|function|const|type|interface) (FlowRuntimeState|FlowRailState|flowRailState|flowRailStatus|flowDotClass|FLOW_GLYPH_ICON|FLOW_GLYPH_LABEL_KEY|NO_OBJECT_GROUP_KEY|readFlowRegistrations|readFlowDefinitions|readObjectLabels|newFlowTriggerFields|withStartTrigger|NO_OBJECT_ORDER|FlowCopiedStrip|FLOW_COPIED_ARRIVAL_STATE|pairIdOf|readFlowCopiedArrival)\b/\1 \2/'; } cmp <(base 7614,7960) <(sed -n 38,384p $B/AutomationsFlowRail.tsx | UA) US() { sed -E 's/^export (async function|function|const|type|interface) (useHeldEditReport|confirmDropUnsavedWork|findTakenIdentifier|CreateTakenNotice|useShowInputFocus|StudioSavedDraft|announceDraftSaved)\b/\1 \2/'; } cmp <(base 504,643) <(sed -n 25,164p $B/StudioPillarKit.tsx | US) cmp <(base 663,751) <(sed -n 166,254p $B/StudioPillarKit.tsx | US) cmp <(base 764,843) <(sed -n 256,335p $B/StudioPillarKit.tsx | US) cmp <(base 2233,2258) <(sed -n 337,362p $B/StudioPillarKit.tsx | US) UD() { sed -E 's/^export (async function|function|const|type|interface) (OWD_OPTION_LABEL_KEY|OWD_OPTION_DESC_KEY|addedFieldName|addedExtensionFieldName|extensionFieldsOf|STUDIO_SYSTEM_FIELD_NAMES|formFieldNames|fieldCelStamp|CelPanelTab|isCelPanelTab|panelItemOf|panelItemCelStamp|formPreviewSource)\b/\1 \2/'; } cmp <(base 443,460) <(sed -n 16,33p $B/DataPillarHelpers.tsx | UD) cmp <(base 5135,5202) <(sed -n 35,102p $B/DataPillarHelpers.tsx | UD) cmp <(base 5281,5375) <(sed -n 104,198p $B/DataPillarHelpers.tsx | UD) ``` All eight silent (identical). Per block (base range → module range, sha256 of the base text): - `AutomationsFlowRail.tsx`: - base 7614-7960 (347 lines) → module 38-384: IDENTICAL sha256 35d1751a7b947f16 - `StudioPillarKit.tsx`: - base 504-643 (140 lines) → module 25-164: IDENTICAL sha256 228dce9ca39416f0 - base 663-751 (89 lines) → module 166-254: IDENTICAL sha256 c3000f03708e8df6 - base 764-843 (80 lines) → module 256-335: IDENTICAL sha256 7c9f296df5ef288d - base 2233-2258 (26 lines) → module 337-362: IDENTICAL sha256 8f83e8151f8048dd - `DataPillarHelpers.tsx`: - base 443-460 (18 lines) → module 16-33: IDENTICAL sha256 e37505603b438c90 - base 5135-5202 (68 lines) → module 35-102: IDENTICAL sha256 72733d7c8975ec8b - base 5281-5375 (95 lines) → module 104-198: IDENTICAL sha256 963dcd6c0b037958 `git diff --color=always --color-moved=zebra 9d1a465 HEAD -- ui/packages/app-shell/src/views/studio-design/`: 1,678 lines are coloured as moved. Apart from import lines and the three module headers, every line NOT coloured as moved is below: the 38 declarations that gained `export` (each once without, once with), the two re-export lines with their comments, and the two blank separators of cut blocks. ``` +export type FlowRuntimeState = Partial<SpecFlowRuntimeState>; +export type FlowRailState = FlowRuntimeRow & { unboundCause?: string }; +export function flowRailState(s: FlowRuntimeState): FlowRailState { +export function flowRailStatus( +export function flowDotClass(status: FlowRunStatus): string { +export const FLOW_GLYPH_ICON: Record<FlowTriggerGlyph, LucideIcon> = { +export const FLOW_GLYPH_LABEL_KEY: Record<FlowTriggerGlyph, string> = { +export const NO_OBJECT_GROUP_KEY = '__no_object__'; +export async function readFlowRegistrations( +export async function readFlowDefinitions(client: MetadataClient, packageId: string | null): Promise<Map<string, unknown>> { +export async function readObjectLabels(client: MetadataClient, packageId: string | null, locale: string): Promise<Map<string, string>> { +export function newFlowTriggerFields(locale: string): { trigger?: FlowConfigField; object?: FlowConfigField } { +export function withStartTrigger( +export const NO_OBJECT_ORDER: readonly string[] = []; +export type FlowCopiedStrip = +export const FLOW_COPIED_ARRIVAL_STATE = 'flowNewSimilar'; +export function pairIdOf(base: string): string | null { +export function readFlowCopiedArrival(state: unknown, packageId: string | null): FlowCopiedStrip | null { +export const OWD_OPTION_LABEL_KEY: Record<OwdCreateModel, string> = { +export const OWD_OPTION_DESC_KEY: Record<OwdCreateModel, string> = { +export function addedFieldName(label: string, existing: string[]): string { +export function addedExtensionFieldName(label: string, existing: string[]): string { +export function extensionFieldsOf(body: ObjectExtensionBody): Record<string, unknown> { +export const STUDIO_SYSTEM_FIELD_NAMES = new Set<string>([ +export function formFieldNames(fields: unknown): string[] { +export function fieldCelStamp(draft: Record<string, unknown>, field: string): string | null { +export type CelPanelTab = 'rules' | 'actions'; +export const isCelPanelTab = (v: string): v is CelPanelTab => v === 'rules' || v === 'actions'; +export function panelItemOf(draft: Record<string, unknown>, tab: CelPanelTab, item: string): Record<string, unknown> | undefined { +export function panelItemCelStamp(draft: Record<string, unknown>, tab: CelPanelTab, item: string): string | null { +export function formPreviewSource( -const OWD_OPTION_LABEL_KEY: Record<OwdCreateModel, string> = { -const OWD_OPTION_DESC_KEY: Record<OwdCreateModel, string> = { -function useHeldEditReport(report: ((clause: string | null) => void) | undefined, clause: string | null): void { -function confirmDropUnsavedWork(unsaved: boolean, locale: string): boolean { -async function findTakenIdentifier( -function CreateTakenNotice({ +// objectstack#29988 — the pillar kit moved to its own module; re-exported for its pins. +export { StudioHeldNotice, StudioReadOnlyLink, type StudioBaseCustomization }; -function useShowInputFocus(rootRef: React.RefObject<HTMLElement | null>): (input: string | undefined) => void { -interface StudioSavedDraft { -function announceDraftSaved(saved: StudioSavedDraft, label: string, locale: string): void { - -function addedFieldName(label: string, existing: string[]): string { -function addedExtensionFieldName(label: string, existing: string[]): string { -function extensionFieldsOf(body: ObjectExtensionBody): Record<string, unknown> { -const STUDIO_SYSTEM_FIELD_NAMES = new Set<string>([ -function formFieldNames(fields: unknown): string[] { -function fieldCelStamp(draft: Record<string, unknown>, field: string): string | null { -type CelPanelTab = 'rules' | 'actions'; -const isCelPanelTab = (v: string): v is CelPanelTab => v === 'rules' || v === 'actions'; -function panelItemOf(draft: Record<string, unknown>, tab: CelPanelTab, item: string): Record<string, unknown> | undefined { -function panelItemCelStamp(draft: Record<string, unknown>, tab: CelPanelTab, item: string): string | null { -function formPreviewSource( - -type FlowRuntimeState = Partial<SpecFlowRuntimeState>; -type FlowRailState = FlowRuntimeRow & { unboundCause?: string }; -function flowRailState(s: FlowRuntimeState): FlowRailState { -function flowRailStatus( -function flowDotClass(status: FlowRunStatus): string { -const FLOW_GLYPH_ICON: Record<FlowTriggerGlyph, LucideIcon> = { -const FLOW_GLYPH_LABEL_KEY: Record<FlowTriggerGlyph, string> = { -const NO_OBJECT_GROUP_KEY = '__no_object__'; -async function readFlowRegistrations( -async function readFlowDefinitions(client: MetadataClient, packageId: string | null): Promise<Map<string, unknown>> { -async function readObjectLabels(client: MetadataClient, packageId: string | null, locale: string): Promise<Map<string, string>> { -function newFlowTriggerFields(locale: string): { trigger?: FlowConfigField; object?: FlowConfigField } { -function withStartTrigger( -const NO_OBJECT_ORDER: readonly string[] = []; -type FlowCopiedStrip = -const FLOW_COPIED_ARRIVAL_STATE = 'flowNewSimilar'; -function pairIdOf(base: string): string | null { -function readFlowCopiedArrival(state: unknown, packageId: string | null): FlowCopiedStrip | null { +// objectstack#29988 — the Automations rail's flow helpers moved to their own module; re-exported for their pins. +export { FlowStatusDot, type NewFlowTrigger }; +export function useHeldEditReport(report: ((clause: string | null) => void) | undefined, clause: string | null): void { +export function confirmDropUnsavedWork(unsaved: boolean, locale: string): boolean { +export async function findTakenIdentifier( +export function CreateTakenNotice({ +export function useShowInputFocus(rootRef: React.RefObject<HTMLElement | null>): (input: string | undefined) => void { +export interface StudioSavedDraft { +export function announceDraftSaved(saved: StudioSavedDraft, label: string, locale: string): void { ``` </details> <details><summary>Declaration files (ruling 6348891160's bar)</summary> Base declarations emitted from the base source into a scratch `outDir` under the lock, tree restored (`git diff HEAD` empty). Of the 702 base `.d.ts` files, 699 are byte-identical at head. The three that differ: - `chrome/ErrorBoundary.d.ts` and `console/ai/outboundAgentText.d.ts`: the members of one union print in another order (`render()`'s return union; `Record<"en" | "zh", string>` against `Record<"zh" | "en", string>`), program-order type creation; with union members sorted (the ruling's normalisation) each pair is identical. - `views/studio-design/StudioDesignSurface.d.ts` (base sha256 `d0f9665b1e9ed6fc…`): the moved declarations now come from the three new `.d.ts` files, and the five names it exported before are re-exported (`export { FlowStatusDot, type NewFlowTrigger }`, `export { StudioHeldNotice, StudioReadOnlyLink, type StudioBaseCustomization }`). Every declaration chunk of the base file appears verbatim in the head file or in one of `AutomationsFlowRail.d.ts`, `StudioPillarKit.d.ts`, `DataPillarHelpers.d.ts`; the one chunk that does not is the file's header comment with its import block, whose import lines are what changed. The three new `.d.ts` / `.js` pairs are reachable from no entry in the package's `exports` map (`"."` only). Declared-surface digest of `dist/index.d.ts` plus `StudioDesignSurface.d.ts`: 245 exports on both sides. Read with the declaring file of a type masked (the printer qualifies `StudioBaseCustomization`, `StudioSavedDraft`, `FlowRailState` and `NewFlowTrigger` by the file that declares them, which is now the new module), the two dumps are identical (`6e1f795576824163…`); unmasked, they differ in exactly those 7 qualified names. </details> <details><summary>Build, type-check, lint and tests (head ec616a8)</summary> All under scripts/pm/os-verify-lock.sh, one locked chain at 16500092 (ui/, docs/qa/ and the changeset are identical at head ec616a8, checked with git diff --quiet): VERDICT command-exit 0. - npx turbo run build --filter='@object-ui/app-shell...' --concurrency=2: exit 0. - pnpm --dir ui --filter @object-ui/app-shell type-check (tsc --noEmit, then tsconfig.test.json): exit 0. - pnpm --dir ui run type-check:scripts: exit 0. - npx eslint on the four source files and the 7373 test, from packages/app-shell: exit 0 (warnings only: react-refresh/only-export-components on the new modules' function exports, as on the file before). The first cut, which also moved `renderStudioGridList`, failed here with one error (an unused eslint-disable directive in DataPillarHelpers.tsx); that is why it stays. - pnpm --dir ui exec vitest run --maxWorkers=3 packages/app-shell/src/views/studio-design/ plus the 7373 test, PermissionMatrixEditor.saveSurface-12725, builtinDesigners.lateArrival-11939, observableRegistries-11939, PackagesPage.confirmFlows-11784, clickableNonControlCensus-13461, spec-symbol-parity and spec-symbol-ledger-app-shell-7265: exit 0, Test Files 330 passed (330), Tests 2490 passed (2490). - Narrowed, and declared: the full @object-ui/app-shell suite does not finish locally on the shared box; the run covers every test in the moved file's directory and every test outside it that imports the file, reads it by path or names a moved helper. CI's Test UI runs the rest. - node scripts/check-platform-checklist.mjs: exit 0; with --changed-from origin/main: exit 0. </details> <details><summary>Ablation of the two extended readers (one-time, through scripts/ablation-replace.mjs; tree restored, git diff HEAD empty)</summary> Each new module gets two legs per reader: a planted needle in the module (expected red), then the same needle with the module taken out of the reader's list (expected green, i.e. blind). | Leg | Mutation | Expected | Observed | |---|---|---|---| | B0 | none: both readers as in this PR | green | 7373 exit 0, 15 passed; 7265 exit 0, 25 passed | | AutomationsFlowRail.tsx · 7373 | a `'/home'` literal planted in AutomationsFlowRail.tsx, alsoReads as in this PR | red on the Studio site | exit 1: "does not hard-code the launcher path" fails for the Studio site; 1 failed, 14 passed | | AutomationsFlowRail.tsx · 7373b | same mutation, AutomationsFlowRail.tsx removed from the Studio site's alsoReads | green, i.e. blind | exit 0: 15 passed | | AutomationsFlowRail.tsx · 7265 | a local `interface ObjectLike` planted in AutomationsFlowRail.tsx, SITES as in this PR | red | exit 1: "rule 1 finds no spec-named local declaration in …AutomationsFlowRail.tsx" fails; 1 failed, 24 passed | | AutomationsFlowRail.tsx · 7265b | same mutation, the AutomationsFlowRail.tsx SITES row removed | green, i.e. blind | exit 0: 24 passed | | StudioPillarKit.tsx · 7373 | a `'/home'` literal planted in StudioPillarKit.tsx, alsoReads as in this PR | red on the Studio site | exit 1: "does not hard-code the launcher path" fails for the Studio site; 1 failed, 14 passed | | StudioPillarKit.tsx · 7373b | same mutation, StudioPillarKit.tsx removed from the Studio site's alsoReads | green, i.e. blind | exit 0: 15 passed | | StudioPillarKit.tsx · 7265 | a local `interface ObjectLike` planted in StudioPillarKit.tsx, SITES as in this PR | red | exit 1: "rule 1 finds no spec-named local declaration in …StudioPillarKit.tsx" fails; 1 failed, 24 passed | | StudioPillarKit.tsx · 7265b | same mutation, the StudioPillarKit.tsx SITES row removed | green, i.e. blind | exit 0: 24 passed | | DataPillarHelpers.tsx · 7373 | a `'/home'` literal planted in DataPillarHelpers.tsx, alsoReads as in this PR | red on the Studio site | exit 1: "does not hard-code the launcher path" fails for the Studio site; 1 failed, 14 passed | | DataPillarHelpers.tsx · 7373b | same mutation, DataPillarHelpers.tsx removed from the Studio site's alsoReads | green, i.e. blind | exit 0: 15 passed | | DataPillarHelpers.tsx · 7265 | a local `interface ObjectLike` planted in DataPillarHelpers.tsx, SITES as in this PR | red | exit 1: "rule 1 finds no spec-named local declaration in …DataPillarHelpers.tsx" fails; 1 failed, 24 passed | | DataPillarHelpers.tsx · 7265b | same mutation, the DataPillarHelpers.tsx SITES row removed | green, i.e. blind | exit 0: 24 passed | Each mutation's landing was proven by the tool (anchor count 1 to 0, blob hashes) and by a grep count of the changed text; the tree was restored after every module (git diff HEAD empty). </details> <details><summary>Derived gates: node scripts/pm/dispatch-gates.mjs --commands on head ec616a8 (42 commands)</summary> ``` node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-adr-links.mjs :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-empty-changeset.mjs --self-test :: exit 0 node scripts/check-sdui-manifest.mjs :: exit 0 node scripts/check-sdui-manifest.mjs --self-test :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 node scripts/release-pending-publish.mjs --self-test :: exit 0 pnpm --dir ui exec vitest run scripts/ :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:dispatcher-error-vocabulary :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:platform-checklist :: exit 0 pnpm check:published-files :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:registry-log-declared :: exit 0 pnpm check:rest-log-spy-declared :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 ``` node scripts/pm/dispatch-gates.mjs --ran: "42 derived famil(ies) accounted for — 42 run, 0 NOT-MEASURED". </details> <details><summary>Serial check before push</summary> The 19 open PRs at 2026-11-04T08:05Z, each PR's changed files read: the ten branch PRs (#30021, #30015, #30012, #30010, #30004, #29998, #29993, #29984, #29968, #29963) from their fetched heads against `origin/main`, three-dot, re-fetched for the two updated since the first pass; #29979, #29866 and the seven `pm:mailbox` PRs through the PR file list, none updated since it was read (every mailbox PR lists no file). None touches StudioDesignSurface.tsx, AutomationsFlowRail.tsx, StudioPillarKit.tsx, DataPillarHelpers.tsx, homeRecoveryRedirectsFollowDeclaration-7373.test.ts, spec-symbol-ledger-app-shell-7265.test.ts or studio-authoring.json. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01WGufzedfGwVcebpN5t3ETE)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzedfGwVcebpN5t3ETE --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 140d8ce commit c618e89

8 files changed

Lines changed: 1048 additions & 893 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
internal: move-only — Studio's flow rail helpers, pillar kit and Data pillar helpers move out of `StudioDesignSurface.tsx` into three modules, taking it under 10,000 lines (#29988, slice 4)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
No behaviour change. Three groups of helpers now live in their own modules under `views/studio-design/`. `AutomationsFlowRail.tsx` holds the Automations rail's flow status dot, its glyph tables, the flow reads and the new-flow trigger helpers. `StudioPillarKit.tsx` holds what every pillar shares: the held-edit notice, the unsaved-work and taken-identifier checks, the "Show me" focus helpers, the draft-saved announcement and the read-only link. `DataPillarHelpers.tsx` holds the Data pillar's field-name, CEL-stamp and form-preview helpers. The moved code is byte-identical apart from its imports and `export`s. `StudioDesignSurface.tsx` imports it from there and re-exports the five names it exported before, so every import of them keeps working. The package's public exports are unchanged.

‎docs/qa/platform-checklist/areas/studio-authoring.json‎

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,7 +2118,7 @@
21182118
"title": "Studio asks before an unsent edit is left behind: a held (incomplete) or dirty edit in any pillar survives a pillar switch, a rail switch, the package switcher, Home and a reload — or is discarded only after the author confirms",
21192119
"since": "v18",
21202120
"status": "active",
2121-
"revision": 5,
2121+
"revision": 6,
21222122
"priority": "P1",
21232123
"surface": "browser",
21242124
"personas": [
@@ -2177,7 +2177,7 @@
21772177
"stale-console-bundle"
21782178
],
21792179
"source": [
2180-
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (unsavedPillarWork — pillarDirty or a held edit — feeding confirmLeavePillar, the beforeunload effect and useRegisterUnsavedEdits; confirmDropUnsavedWork, which the Data, Automations and Interfaces rails also ask before opening another item; onDirtyChange from the Access pillar and the Interfaces navigation editor, onHeldEditChange / useHeldEditReport from the Data, Automations and Interfaces leaf editors)",
2180+
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (unsavedPillarWork — pillarDirty or a held edit — feeding confirmLeavePillar, the beforeunload effect and useRegisterUnsavedEdits; onDirtyChange from the Access pillar and the Interfaces navigation editor, onHeldEditChange from the Data, Automations and Interfaces leaf editors) + ui/packages/app-shell/src/views/studio-design/StudioPillarKit.tsx (confirmDropUnsavedWork, which the Data, Automations and Interfaces rails also ask before opening another item; useHeldEditReport, through which those leaf editors report a held edit)",
21812181
"ui/packages/i18n/src/namespaces/studio/en.ts (engine.edit.unsavedLeaveConfirm; engine.studio.held.needsOptions / needsTarget / needsInput / widgetNeedsInput / line)",
21822182
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.pillarNavGuard.test.tsx (jsdom pin of the dirty half on the Access stack — corroboration only, it scores no browser clause here)",
21832183
"#26705 (family card: held edits discarded silently on departure in Data, Automations and Interfaces; fixed by PR #26839 and PR #26927 — jsdom pins StudioDesignSurface.heldDataLeave-26705 / heldFlowLeave-26705 / heldWidgetLeave-26705 .test.tsx, corroboration only)",
@@ -2213,6 +2213,12 @@
22132213
"date": "2026-11-01",
22142214
"change": "QA run #28656 (W3-RT) finding: steps[7] says the item draft read GET /api/v1/meta/TYPE/NAME?state=draft&package=PKG answers 404 NO_DRAFT when no draft exists (metadata-protocol protocol.ts noPendingDraftError; its wire answer is pinned in packages/rest/src/meta-item-absent-404.test.ts), so a 404 before and after Leave is the 'unchanged' reading acceptance[3] asks for, as #28656 read it live at 6e11a929. No clause changed. Re-verified against source at base 1c5b1773ec. Wave anchor #27377, wave-3 accuracy slice S10 (#28701); finding parked in anchor comment 6557904764",
22152215
"ref": "#28656"
2216+
},
2217+
{
2218+
"revision": 6,
2219+
"date": "2026-11-04",
2220+
"change": "source[0] re-pointed: confirmDropUnsavedWork and useHeldEditReport moved unchanged from ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx to StudioPillarKit.tsx beside it (#29988 slice 4, a move-only split); the rest of the citation stays on StudioDesignSurface.tsx. Re-read at base 9d1a465f0. No clause changed",
2221+
"ref": "#29988"
22162222
}
22172223
]
22182224
},
@@ -2305,7 +2311,7 @@
23052311
"title": "The Automations rail groups flows by the object they watch, searches by label and name, says when nothing matches, and explains its glyphs and dots in a legend; New automation's presets refuse until the watched object is chosen, and a new flow starts Off",
23062312
"since": "v18",
23072313
"status": "active",
2308-
"revision": 2,
2314+
"revision": 3,
23092315
"priority": "P2",
23102316
"surface": "mixed",
23112317
"personas": [
@@ -2365,7 +2371,7 @@
23652371
"ref": "ui/packages/app-shell/src/views/studio-design/AutomationsPillar.railSearch-11794.test.tsx (label / name / no-match search) + StudioDesignSurface.flowPresets-11861.test.tsx + StudioDesignSurface.flowStatusTruth-11779.test.tsx ('\"New\" saves status: obsolete … Off by default'): jsdom evidence for acceptance[2] and [3]; the live rail, legend and draft read stay the run's"
23662372
},
23672373
"source": [
2368-
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the rail legend auto-rail-legend, FLOW_GLYPH_ICON / FLOW_GLYPH_LABEL_KEY, flowDotClass, FlowStatusDot; the New automation presets; objectui#12454, #11794, #11861, #11779)",
2374+
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the rail legend auto-rail-legend; the New automation presets; objectui#12454, #11794, #11861, #11779) + ui/packages/app-shell/src/views/studio-design/AutomationsFlowRail.tsx (FLOW_GLYPH_ICON / FLOW_GLYPH_LABEL_KEY, flowDotClass, FlowStatusDot)",
23692375
"QA run #26715 (Automations card E1 / E2 pass; the rail search, legend and preset refusal were listed as uncovered)"
23702376
],
23712377
"history": [
@@ -2380,6 +2386,12 @@
23802386
"date": "2026-10-31",
23812387
"change": "fixtures.requires[1] says what makes a schedule flow. The rail's clock glyph comes from resolveFlowTriggerKind (packages/spec/src/automation/flow-trigger-kind.ts), which answers schedule only for a Start node carrying config.schedule or a flow of type schedule. New automation's Advanced → Schedule writes only config.triggerType, so QA run #27438 saw such a flow listed as Autolaunched until a cron or interval was set on its Start node. Accuracy finding of QA run #27438. No clause changed. Wave anchor #27377, wave-3 accuracy slice S2; read at base a0f865491d",
23822388
"ref": "#27438"
2389+
},
2390+
{
2391+
"revision": 3,
2392+
"date": "2026-11-04",
2393+
"change": "source[0] re-pointed: FLOW_GLYPH_ICON / FLOW_GLYPH_LABEL_KEY, flowDotClass and FlowStatusDot moved unchanged from ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx to AutomationsFlowRail.tsx beside it (#29988 slice 4, a move-only split); the rail legend and the New automation presets stay. Re-read at base 9d1a465f0. No clause changed",
2394+
"ref": "#29988"
23832395
}
23842396
]
23852397
},
@@ -2567,7 +2579,7 @@
25672579
"title": "The flow state Studio shows is the state the deployment runs: the rail dot and the header read what GET /api/v1/automation/_status reports — On, Off, Not running here with the platform's reason — and an enabled record-triggered flow actually runs",
25682580
"since": "v18",
25692581
"status": "active",
2570-
"revision": 2,
2582+
"revision": 3,
25712583
"priority": "P1",
25722584
"surface": "mixed",
25732585
"personas": [
@@ -2628,7 +2640,7 @@
26282640
"ref": "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.flowStatusTruth-11779.test.tsx + FlowStatusDot.test.tsx + StudioDesignSurface.railUnboundReason-11281.test.tsx: jsdom pins of the reading against a _status double — evidence for acceptance[0] and [1] at the component layer; the live pairing and the runs stay the run's"
26292641
},
26302642
"source": [
2631-
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (FlowStatusDot and flowRailStatus: green On, grey Not running here titled with the reason, amber Refused, hollow Off; objectui#11779, #11281, #12454)",
2643+
"ui/packages/app-shell/src/views/studio-design/AutomationsFlowRail.tsx (FlowStatusDot and flowRailStatus: green On, grey Not running here titled with the reason, amber Refused, hollow Off; objectui#11779, #11281, #12454)",
26322644
"packages/services/service-automation/src/engine.ts (getFlowRuntimeStates — the rows GET /automation/_status passes through)",
26332645
"RUNNER.md environment facts (package-authored scheduled work is OFF unless OS_AUTOMATION_SCHEDULED_WORK_ENABLED=1)",
26342646
"cross-ref: the API kill switch ({enabled} at the automation door) is automation.flow-toggle-kill-switch; scheduled-work policy itself is the RUNNER.md fact, not a defect"
@@ -2645,6 +2657,12 @@
26452657
"date": "2026-11-01",
26462658
"change": "negative[1] moved to source[3]: it was a scope note naming the items that own the neighbouring surfaces, which no run can decide, so it could only score skipped. Its text is unchanged except the SCOPE: label, which now reads cross-ref:, the spelling the sibling source rows use. Ruling 6545938831 item ① on #28345 (scope notes leave negative[], which keeps only decidable entries; the #27651 precedent). No clause changed. Wave anchor #27377",
26472659
"ref": "#28345"
2660+
},
2661+
{
2662+
"revision": 3,
2663+
"date": "2026-11-04",
2664+
"change": "source[0] re-pointed: FlowStatusDot and flowRailStatus moved unchanged from ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx to AutomationsFlowRail.tsx beside it (#29988 slice 4, a move-only split). Re-read at base 9d1a465f0. No clause changed",
2665+
"ref": "#29988"
26482666
}
26492667
]
26502668
},
@@ -3720,7 +3738,7 @@
37203738
"title": "Permission sets are created, found and locked honestly in Studio's Access pillar: the New dialog derives the identifier and refuses one already taken — published or draft — the rail searches and shows its empty states, and on a read-only package New lands in its .custom pair while the packaged set offers no edit and the server refuses the forged write, and New similar on a packaged set copies it under a new name into that pair",
37213739
"since": "v18",
37223740
"status": "active",
3723-
"revision": 7,
3741+
"revision": 8,
37243742
"priority": "P2",
37253743
"surface": "mixed",
37263744
"personas": [
@@ -3793,7 +3811,7 @@
37933811
"ref": "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.accessNewSimilar-28573.test.tsx (jsdom, the real Access pillar against a mocked server: acceptance[5]'s console half — New similar posts exactly { name, label }, opens the copy where the answer says it landed, shows a 409 in the server's words, and draws no override gesture on a base set) + packages/runtime/src/domains/permission-set-clone.test.ts (the clone door through the HttpDispatcher in-process over a test security catalog: acceptance[5]'s server half — a packaged source with no target is copied whole into <X>.custom through the ensure door). Neither drives a booted deployment, so they score acceptance[5] partial at most (RUNNER rule 6) and its live leg is the run's. Acceptance[0]–[4] have no pin: they are the run's live leg. The live spec of #28579 slice 3 (ui/e2e/live/studio-customize-journey.spec.ts) does not open the Access pillar"
37943812
},
37953813
"source": [
3796-
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the Access pillar: the New set dialog, the taken-identifier check findTakenIdentifier and CreateTakenNotice before the create's If-None-Match: *, discardDraft, the rail search and empty states, openCreateSet offered on a read-only base when newInPair — ADR-0146 D3, #28572) + ui/packages/app-shell/src/views/studio-design/packages-io.ts#ensureCustomizationPackage (the ensure door the New save calls first on a read-only base)",
3814+
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the Access pillar: the New set dialog, the taken-identifier check before the create's If-None-Match: *, discardDraft, the rail search and empty states, openCreateSet offered on a read-only base when newInPair — ADR-0146 D3, #28572) + ui/packages/app-shell/src/views/studio-design/StudioPillarKit.tsx (findTakenIdentifier and CreateTakenNotice, the check and the notice the pillar draws) + ui/packages/app-shell/src/views/studio-design/packages-io.ts#ensureCustomizationPackage (the ensure door the New save calls first on a read-only base)",
37973815
"ui/packages/app-shell/src/views/metadata-admin/permission-set-clone.ts#clonePermissionSet (POST /api/v1/security/permission-sets/:name/clone with { name, label }; where the copy lands is read off the answer) + packages/runtime/src/domains/permission-set-clone.ts#clonePermissionSetDefinition (the whole copy, saved in the source package's pair) — #28573 (N-20b) landed by PR #28669; the door's pair placement #28563 (N-20a)",
37983816
"ui/packages/i18n/src/namespaces/studio/en.ts (engine.studio.access.created / createTaken / createTakenElsewhere / emptyMain / search / bannerTitleReadOnly)",
37993817
"access-security.readonly-package-locks-studio (the sibling read-only surfaces — cross-referenced)",
@@ -3841,6 +3859,12 @@
38413859
"date": "2026-11-02",
38423860
"change": "an automated block is added (#28579 slice 3): New similar's two pins, StudioDesignSurface.accessNewSimilar-28573.test.tsx (jsdom) and permission-set-clone.test.ts (the door in-process), scoped to acceptance[5] and its layers; acceptance[0]–[4] keep no pin, and the slice 3 live spec does not open the Access pillar. kind unit. No clause or step changed",
38433861
"ref": "#28579"
3862+
},
3863+
{
3864+
"revision": 8,
3865+
"date": "2026-11-04",
3866+
"change": "source[0] re-pointed: findTakenIdentifier and CreateTakenNotice moved unchanged from ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx to StudioPillarKit.tsx beside it (#29988 slice 4, a move-only split); the Access pillar's use of them stays. Re-read at base 9d1a465f0. No clause changed",
3867+
"ref": "#29988"
38443868
}
38453869
]
38463870
},

‎ui/packages/app-shell/src/__tests__/homeRecoveryRedirectsFollowDeclaration-7373.test.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ const RECOVERY_SITES: ReadonlyArray<{
140140
'packages/app-shell/src/views/studio-design/StudioNavItemInspector.tsx',
141141
'packages/app-shell/src/views/studio-design/PackageSwitcher.tsx',
142142
'packages/app-shell/src/views/studio-design/InterfacesNavRail.tsx',
143+
'packages/app-shell/src/views/studio-design/AutomationsFlowRail.tsx',
144+
'packages/app-shell/src/views/studio-design/StudioPillarKit.tsx',
145+
'packages/app-shell/src/views/studio-design/DataPillarHelpers.tsx',
143146
],
144147
site: 'Studio: deleted-package eviction + header Home',
145148
expression: /const homePath = useHomePath\(\)/,

0 commit comments

Comments
 (0)