Repository navigation
feat(app-shell): Setup shows a time-triggered flow disabled by deployment policy with the platform's reason, not as a binding failure (objectui#9217) - #11271
Conversation
…m and muted (objectui#9217) Setup > Packaged automation read only `name` and `enabled` from `GET /automation/_status`, so a time-triggered flow the deployment's scheduled-work switch left unarmed showed as a plain "On", identical to a flow whose trigger failed to bind and to one that runs. `packagedFlows.ts` now carries `FlowRuntimeState.reason` (spec 17.5.0) when the row sends a non-empty string, and leaves the key absent otherwise. `PackagedAutomationPage` renders that sentence verbatim under the flow name, in muted text. It is not an alert and has no destructive class, and the text is never parsed or compared. Rows without a reason, including those from older backends, render exactly as before. The page header's ADR-0126 section 7.4 note records the exception. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: PR #11271 on card #9217. Net diff against Check-runs on the head (the gate verdicts): 43 latest runs, all completed at judgment — 40 ① Derived judgmentsThe binding shape is the seat's answer B (
② Semver level
Clause-②: no — the PR body carries it, matching claim ③ Boundary flagsDev flags from
Carried note — not folded, not a finding against this head: the Studio Automations rail's Implemented-by: VERDICT: PASS Generated by Claude Code |
…, mirror two chatbot keys, file eighteen runtime slots by name (objectui#6152, round 4) (objectstack-ai#11285) Refs objectstack-ai#6152 Clause-②: yes (narrowing). The two mirrors widen `@object-ui/types/zod` and the strict authoring face to keys the TypeScript face already invited and the renderers read (`requestBody`, `floatingConfig`). The four retirements (`label.content`, `report.chartConfig`, `report.reportType`, `detail-view.autoDiscoverRelated`) narrow both faces at once, under the startup no-gradualism rule. It is priced in `.changeset/6152-unmirrored-round4.md` as `minor`, with the break stated. Round 4 of objectui#6152, dispatched under claim `5919877490` (the `domain:devx` seat 2, session `session_01TdiauJaVCHuj45EzZGUxHh`, on the maintainer's direct order). The rulings are the seat review `5914490915`: Q1 B, Q2 A, Q3 B. ⛔ This is a draft from a dispatched dev: do not mark it ready and do not enable auto-merge. The card stays open, because `ObjectGridSchema`, the spec-derived pairs and three `DataTableSchema` keys (below) are not this round's. ## What changed 1. **Q1 B: `LabelSchema.content` is retired.** It is `?: never` on `form.ts` and a `retirementTombstone` on `form.zod.ts` that names the kept spellings `text` and `label`. The `label` renderer drops its third read limb (`schema.text || schema.label`). Re-measured: 0 documents author it (JSON, Markdown JSON fences and typed literals); its only author was a test in `form-renderers.test.tsx`, which now pins the refusal. Which of `text` / `label` is canonical is not decided here. 2. **Q2 A: `DetailViewSchema` `activities`, `comments`, `history` and `recordNavigation` go to `RuntimeOnlyDeclared` by name.** Each has a row with its reason on `RuntimeOnlyNamedAllowList`, in round 3's shape. They carry host-fetched runtime data, and `recordNavigation.onNavigate` is a required function. 3. **Q3 B, under objectui#7654 ruling B: `displayMode` keeps its TypeScript-only refusal.** It gets no zod arm, because stored designer documents carry `displayMode: 'floating'` (pinned by `chatbot-display-mode-retired.test.ts`). The ledger entries no longer call the mirror half "owed"; the key stays listed by decision. Separately, after a reader census for each: - `ChatbotSchema.requestBody` is mirrored through the shared pick. All three chatbot registrations read it (`body: schema.requestBody`). - `ChatbotFloatingSchema.floatingConfig` is mirrored through a module-private restatement of `FloatingChatbotConfig`. That restatement judges the six members `FloatingChatbot` destructures, and carries the `triggerIcon` tombstone's runtime half that the objectui#7654 tripwire asked for. 4. **The three zero-read candidates are retired on both faces.** They are `ReportComponentSchema.chartConfig`, `ReportComponentSchema.reportType` and `DetailViewSchema.autoDiscoverRelated`, each `?: never` plus a `retirementTombstone`. `reportType`'s one in-code producer, `specReportToPresentation` in `spec-report.ts`, no longer writes it, and `LegacyReportPresentationLike` no longer declares it. 5. **`DataTableSchema`: 14 of 17 keys go to `RuntimeOnlyDeclared` by name.** They are produced only in code, by `ObjectGrid`'s in-code `data-table` node or `RelatedList`'s, and authored by no document. Three keys fit none of the classes, so the round stopped at them (see "Stopped" below). 6. **The pagination corpus fix, carried from round 3.** `components-basic-pagination/with-item-count.json` no longer authors `pageSize` / `totalItems`, which neither face declares and nothing reads. The plaintext interface block on `content/docs/components/basic/pagination.mdx` stops listing them, and its section is retitled from "With Page Size" to "On a Later Page". 7. **Changesets.** One new: `.changeset/6152-unmirrored-round4.md` (`@object-ui/types` minor, `@object-ui/components` minor, with the break stated). Four dated, append-only corrections were added, each 2 lines with frontmatter unchanged: `6639-objectgrid-title-mirrored.md` (the stale "97 + 1 + 23" sentence), `7654-floating-chatbot-trigger-icon-tombstone.md`, `7655-chatbot-registration-authoring-faces.md` and `9447-detail-view-handler-key-refusal.md`. Every other pending changeset was re-read after the merges, including the 8 that landed since the base, and none is made false by this round. ## Ledger totals, before and after These are read by the file's own pins (`objectui#7279` derives every figure) and re-counted with the TypeScript parser on the final head. | ledger | before (`origin/main` `e420df3`) | after (`2cc4ab9`) | | --- | --- | --- | | `UnmirroredDeclared` | 9 entries / 44 keys: 3 / 14 spec-derived, 6 / 30 local | **6 / 20**: 3 / 14 spec-derived, 3 / 6 local | | `RuntimeOnlyDeclared` | 4 / 9 | **4 / 27** | | `RuntimeOnlyNamedAllowList` | 1 entry / 2 rows | 3 / 20 | | cross-ledger | 2 of the 4 are in `UnmirroredDeclared`; union 11 = 9 + 2 | **1 of the 4**; union **9** = 6 + 3 | | `KnownDrift` | 49 / 86 | 49 / 86, unchanged. This round does not edit its total line; PR objectstack-ai#11269's 87 to 86 edit is kept as merged. | | `WiderThanDeclared`, `MirroredUndeclared` | 3 / 3, 2 / 2 | unchanged | The 24 keys that left `UnmirroredDeclared` moved by three routes, which must not be added up as one repair: 2 were mirrored, 4 were retired, and 18 were reclassified by name. The reclassification is one move seen from both ledgers: it is not new debt, and it mirrors nothing. ## Per-key table | pair · key | measured (type-checker read census over 5548 sources; authored census over tracked JSON, Markdown JSON fences and `type` literals) | route | | --- | --- | --- | | `label` · `content` | 1 read (the third limb in `label.tsx`), 0 documents (1 test) | retired on both faces; the read is dropped | | `report` · `chartConfig` | 0 typed reads. The untyped `widget?.chartConfig` hits are plugin-dashboard's `DatasetWidget` (`widget: any`, a dashboard widget), which plugin-report never imports. 0 documents | retired on both faces | | `report` · `reportType` | 0 reads; one in-code producer (`specReportToPresentation`); 0 documents | retired on both faces; the producer write is removed | | `detail-view` · `autoDiscoverRelated` | 0 reads (typed or untyped), 0 documents | retired on both faces | | `chatbot` · `requestBody` | 3 reads (all three chatbot registrations) | mirrored (shared pick) | | `chatbot-floating` · `floatingConfig` | 1 read (`floatingConfig={schema.floatingConfig}`); 6 of 7 `FloatingChatbotConfig` members read, and `triggerIcon` 0 | mirrored, with a `FloatingChatbotConfig` restatement | | `chatbot`, `chatbot-floating` · `displayMode` | 0 reads; stored documents carry it | unchanged by ruling (TypeScript-only refusal) | | `detail-view` · `activities` (7 reads), `comments` (4), `history` (7) | host-fetched rows; 0 documents | `RuntimeOnlyDeclared` by name | | `detail-view` · `recordNavigation` | 11 reads; its value carries a required function | `RuntimeOnlyDeclared` by name | | `data-table` · `disableInnerScroll`, `manualPagination`, `manualSearch`, `manualSorting`, `page`, `rowActionDefs`, `rowClassName`, `rowCount`, `rowStyle`, `search`, `selectionResetKey`, `showAddRow`, `showSelectionCount`, `sort` | read by `data-table`; produced only in code (`ObjectGrid`, `RelatedList`); 0 docs and 0 catalog documents | `RuntimeOnlyDeclared` by name; the reasons are on the allow-list | | `data-table` · `editable`, `singleClickEdit` | read; produced in code by `ObjectGrid`; 0 documents, but `data-table.mdx` lists both and teaches `editable: true` | **stopped**, ruling needed | | `data-table` · `selectionStyle` | read; 0 producers and 0 documents (0 hits in objectstack too) | **stopped**, ruling needed | ## Runtime probe for the three zero-read retirements AGENTS.md requires more than a source census, because leftover keys reach a renderer as spread props. So the probe used the real `SchemaRenderer` and the real registry, and varied only the key. It was a one-off: the files were deleted afterwards and `git diff HEAD` is empty. - `report`: the DOM is identical with and without `chartConfig` plus `reportType` (797 = 797 characters, byte-equal). Lit control: changing `title` changes the DOM (805). - `detail-view`: the DOM is identical with and without `autoDiscoverRelated: true`, after normalising React's `useId` tokens. The only raw difference was `radix-_r_5_` against `radix-_r_d_`. The data-source and `fetch` call log is identical: `getObjectSchema(account)` and two `security/explain` fetches. Lit control: changing `title` changes the DOM (5943 against 5951). ## Ablations The predictions were written at `4c272dc` before any run, and every leg was restored with the blob equal to `HEAD` and `git diff HEAD` empty. - **A1:** plant a `LabelSchema` literal carrying `content` into `label.tsx`. Components `tsc --noEmit` exits 2 with exactly one TS2322 there (`'string' is not assignable to type 'undefined'`), as predicted. This red direction also shows the consumer read the rebuilt `.d.ts`. - **A2:** restore the four `zod/*.zod.ts` files to base `f61dab1`. `unmirrored-declared-round4-6152.test.ts` reads `27 failed | 6 passed (33)`, exactly as predicted, and `tsc -p tsconfig.test.json` exits 2 with its only error in the ledger reconciliation of `zod-mirror-parity.test.ts`. - **A3:** rename the allow-list row `rowCount` to `rowCountRenamed`. `tsc -p tsconfig.test.json` exits 2 with exactly two TS2344, at the two allow-list pins, as predicted. ## Verification on the final head `2cc4ab9` This round resumed after two container restarts, so every run below was redone on `2cc4ab9`. That head carries `origin/main` `e420df3`, merged twice (never rebased): once through PR objectstack-ai#11275, and before that through PRs objectstack-ai#11267, objectstack-ai#11268, objectstack-ai#11269, objectstack-ai#11271, objectstack-ai#11273 and objectstack-ai#11274. Each run went through the shared verify lock with at most 2 workers. - `@object-ui/types`: `type-check` (the `--noEmit`, `tsconfig.examples.json` and `tsconfig.test.json` legs) exits 0; `vitest run packages/types/` gives 299 files and 7498 tests passed; `build` exits 0 (138 emitted files verified). - Consumers, from the type-checker census: `type-check` exits 0 with 0 TS errors for `components`, `plugin-report`, `plugin-detail` and `plugin-chatbot`. `vitest run packages/plugin-report/ packages/plugin-chatbot/` plus `form-renderers.test.tsx` gives 78 files, 827 passed and 7 skipped. All 228 `plugin-detail` test files, run in two halves, give 227 files passed plus 1 skipped, and 2264 tests passed plus 8 skipped. - Zod-face consumers: `vitest run examples/schema-catalog/ packages/cli/` plus the 17 app-shell files that import `@object-ui/types/zod` or sit in `metadata-admin/previews/__tests__` give 77 files and 2741 tests passed. - Gates, all exit 0: `check:changeset-claims`, `check:pending-changeset-literals`, `check-changeset-presence` (16 source files of 2 released packages, 1 changeset), `check-changeset-no-major`, `check-changeset-fixed`, `check-changeset-overwrite` (report-only; its 4 findings are the 4 dated appends above), `check:control-bytes` (9717 files), `check:doc-fences`, `check:doc-snippets` (697 of 697 blocks judged, 0 failed), `check:doc-examples`, `check:doc-types`, `check:doc-example-ids`, `check:doc-example-readers`, `check:skill-examples`, `check:handler-key-reads`, `check:component-surface-parity` (report-only), `check:designer-field-key-parity`, `check:element-data-source-declaration`, `check:new-line-citations` (0 new), `check:prompt-keys`, `check:readme-exports`, `check:spec-symbols`, `check:test-path-roots`, `check:unreferenced-sources` and `check-type-check-coverage`. `doc-snippets` and `doc-examples` ran after the scoped build their own `--build-filter` names (35 turbo tasks, all successful). - Lint, narrowed: `eslint --no-inline-config --format json` over the 16 lintable changed files gives 0 errors. All 16 were judged and 0 were ignored, a population read from eslint's own output. The 39 warnings are pre-existing `no-explicit-any`, and the diff adds no explicit `any`. `eslint.config.js` declares no `parserOptions.project` or `projectService`, and no custom rule in `eslint-rules/` reads the disk, so this diff cannot move any untouched file's verdict. The repo-wide `pnpm lint` is CI's. - Governed surface: `check-governed-queue-guard.mjs --test` over the 23 paths reports NOT GOVERNED. - NOT MEASURED: - `check:sdui-registration-pins`: it needs a console build, and this diff changes no registration. - The full `pnpm test` farm and repo-wide lint: CI's. - `plugin-grid`: its code and tests are untouched. The 14 reclassified keys moved between two test ledgers and no declaration changed. ## Stopped (ruling needed; reported in the card report with options) - `DataTableSchema.editable` and `singleClickEdit`. `data-table` reads both and `ObjectGrid` sets both in code, while no document authors them. But `content/docs/components/complex/data-table.mdx` lists both in its interface block and teaches `editable: true`. Filing them runtime-only would contradict that page. Mirroring them would rest on no authored document, and, from the source, an authored `editable: true` stages cell edits locally and persists nothing without the `onRowSave` / `onCellChange` function slots. - `DataTableSchema.selectionStyle`. `data-table` reads it (`'always'` or `'hover'`), and nothing authors or produces it in either repository. - Noted, not narrowed: `ChatbotSchema.floatingConfig` has 0 reads on the `chatbot` node (only `chatbot-floating` reads it), which makes it an ADR-0049 narrowing candidate. The `chatbot` half of `floating-chatbot-trigger-icon-retired.test.ts` pins its absence as a tripwire. ## Parallel discipline PR objectstack-ai#11269 and PR objectstack-ai#11273 edited `zod-mirror-parity.test.ts` in disjoint hunks: the `KnownDrift` total line, the Kanban entry and one `EXCLUSIONS` row. Both merges were clean. This round's ledger edits stay in the `UnmirroredDeclared` / `RuntimeOnlyDeclared` / `RuntimeOnlyNamedAllowList` blocks, their prose totals and its own pairs' entries. Every prose total was reconciled to the measured count above. The diff does not touch `zod/objectql.zod.ts`, `zod/index.zod.ts`, `zod/nested-component-walk.ts`, `strict-authoring-face.ts`, `zod/README.md`, `field-types.ts`, the `DetailViewField` member, or `data-table.tsx` (read only). ## Acceptance notes - 23 files, under the ~25-file size guard, so no pair was cut. - Every pending changeset this round made false carries a dated append. The ones it leaves true were re-read: `7654-chatbot-display-mode-tombstone.md`, `6150-…`, `7804-…`, `6175-…`, `7722-…`, `7947-…` and `8072-…`. Session: `https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh` --- _Generated by [Claude Code](https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #9217
Clause-②: no
Setup › Packaged automation now shows the platform's reason for a packaged flow the engine has not armed. The sentence appears verbatim, in muted text, under the flow name. This is option B from the card's decision: the seat's answer is on the card, comment 5919298624.
Why
The page read only
nameandenabledfromGET /api/v1/automation/_status. A time-triggered flow left unarmed by the deployment's scheduled-work switch (OS_AUTOMATION_SCHEDULED_WORK_ENABLEDunset, the default in every posture) showed as a plain "On", the same as a flow that runs. A flow whose trigger failed to bind looked exactly the same.@objectstack/spec17.5.0 now carriesFlowRuntimeState.reason, and its contract says "Consumers RENDER it; ⛔ do not parse it".What changed
packagedFlows.ts:FlowRuntimeStateRowdeclaresreason?: unknown.joinPackagedFlowscopies it onto the row only when it is a non-empty string. Otherwise the key is absent, the same way the engine omits it.PackagedAutomationPage.tsx: a row with areasonrenders it under the flow name as a muted paragraph (text-muted-foreground). There is norole=alertand no destructive class, and the text is never parsed or compared. A row without one renders exactly as before. The header's ADR-0126 §7.4 note gains one sentence recording the exception.SCHEDULED_WORK_DISABLED_REASONin@objectstack/types17.5.0 (checked with a node equality against the installed package).@object-ui/app-shellminor.Pins
packagedFlows.test.ts, describe "the unbound reason (objectui#9217)": a string reason passes through unchanged; the key is absent on rows that send none (older backend included); non-string or empty values (42, true, null, an object, an array, the empty string) are ignored.PackagedAutomationPage.test.tsx, describe "the platform reason for an unbound flow": a policy row shows the platform sentence verbatim, muted, and not as an alert, with no "binding failed" wording added (acceptance ① and ③); a binding-failure row shows its own sentence the same way (acceptance ②); a row without a reason keeps exactly its old flow-cell text (bound, disabled and bare older-backend rows); a non-string reason is ignored.Evidence (HEAD
d1a24b8fb, every run after the final commit, heavy runs underos-verify-lock)pnpm exec vitest run --reporter=verbose packages/app-shell/src/views/setup/gaveTest Files 5 passed (5)andTests 65 passed (65).ablation-replace.mjs, WRAP mode). The reason paragraph was replaced withnull(anchor 1 to 0, blob3c9713a98d6ato6d25c6141bfd). The page suite went toTests 2 failed | 12 passed (14). The two red tests are exactly the policy row and the binding-failure row; "row without a reason" and "non-string reason" stayed green. Restore was proven: the blob equals HEAD3c9713a98d6aandgit diff HEADis empty. The subject is imported from source, not fromdist, so no dist preflight applies.turbo run build --filter='@object-ui/app-shell^...' --concurrency=2: 28/28 tasks successful. Thenpnpm --filter @object-ui/app-shell type-check, which runstsc --noEmit && tsc -p tsconfig.test.json, exited 0 with 0error TS. Both projects include the changed files;tsconfig.test.jsonlists both test files.pnpm exec eslinton the 4 changed source/test files: 4 files linted, 0 errors, 0 warnings (counts from--format json).node scripts/check-changeset-presence.mjs,node scripts/check-control-bytes.mjs,pnpm check:new-line-citations(0 new),pnpm changeset:check,pnpm check:pending-changeset-literalsandpnpm check:changeset-claimsall exited 0.node scripts/markdown-test-inputs.mjs --auditwas run (exit 0), and so waspnpm exec vitest run scripts/__tests__/:Test Files 177 passed | 2 skipped (179),Tests 5371 passed | 2 skipped (5373).t()call moved) and the fullpnpm test/pnpm lintfarm, which is left to CI.Acceptance notes
FlowStatusDotstill titles every enabled unbound flow "Enabled · no trigger (run manually)", including a policy-unbound schedule flow. Its file,StudioDesignSurface.tsx, is held by objectui#11232. The seat has recorded the finding and will file it as a sub-issue of objectui#9217 once that file is free.Generated by Claude Code