feat(app-shell,console): Studio reaches package-less flows at /studio/~org/automations (objectui#11553) - #11565
Conversation
…io/~org/automations (objectui#11553) A clone of a packaged flow is correctly package-less (ADR-0126 §7.1), and every Studio route and rail was keyed by a package, so the clone reached no Studio surface. Studio gains one reserved scope, served by the same StudioDesignSurface with no package under it: - studioScope.ts: the reserved `~org` segment (outside every package-id alphabet), the path builder, and the package-less predicates read off the spec's `_packageId` (served items) and the draft header's `packageId`. - packageSurfaces.ts: loadPackageLessSurfaces, the unscoped list and drafts narrowed to items bound to no package, through the shared merge. - AutomationsPillar: `packageId: null` lists package-less flows and saves package-less drafts; no "New" there. A deep link naming a flow the rail does not hold no longer opens the first flow instead; from a package's pillar a package-less flow is found and opened in the package-less scope. - StudioDesignSurface: the package-less scope is writable, offers only the Automations pillar, no Create app and no package copilot; its header counts and its Publish ships package-less flow drafts by reference. - PackageSwitcher and BuilderLanding: an entry for the scope, shown whether or not a writable package exists. - DraftChangesPanel: an `include` predicate so the scope reviews what it ships. - console studioRoutes: the bare `/studio/~org` leg lands on its pillar. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
- studioScope: the reserved segment fails the spec's and this app's package-id rules; the package-less predicates and the path round-trip. - loadPackageLessSurfaces: unscoped reads narrowed to items and drafts bound to no package. - StudioDesignSurface on the real route shape: the clone is listed and opens editable, its edit saves a package-less draft, the deep link opens the named flow (also through a package's pillar), a link naming a flow nobody holds opens no other, the read-only package stays read-only, and the scope's header counts, reviews and publishes only package-less flow drafts. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
…objectui#11553) Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
… package-less scope (objectui#11553) Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
… (objectui#11553) Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
… by a state write (objectui#11553) A read-only answer from the package the surface showed before stays in state across the switch; the scope now reads as writable whatever it holds. Pinned by the switcher path from a read-only package. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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: Written 2026-10-03T16:45Z by a subagent of the Check-runs on the head, read 2026-10-03T16:43Z: 43 check-runs, 40 ① Derived judgmentsAccept set and public surface, each named:
The ruling's three pins are each carried by a named test in ② Semver level
③ Boundary flagsThe dev report's
Implemented-by: VERDICT: PASS |
|
Generated by Claude Code |
Fixes #11553
Clause-②: yes
Implements the ruling triage recorded on objectstack-ai/objectstack#21332 (comment
5946433961): the Studio arm, not the package arm. The clone stays package-less (ADR-0126 §7.1). Nothing changes on the objectstack side. Studio gains a surface for package-less, org-owned flows.Dev run:
session_01FjqrwXPfSMkSfkKYDSRkN2(mode:subagentof the PM seat), branchclaude/issue-11553-studio-packageless-flows, cut fromorigin/maind0c0c7fe9.What failed, measured on
origin/mainbefore the changestudioRoutesdeclared only/studio,/studio/:packageIdand/studio/:packageId/:tab, so every pillar was keyed by a package.loadPackageSurfaces(client, 'flow', packageId), which readslist('flow', { packageId })andlistDrafts({ packageId, type }). The registry's package filter keeps only items whose_packageIdequals that package, so a flow with no package is in no rail.BuilderLanding) listed packages only. On the stock showcase that means "No writable packages yet" plus the read-only showcase package.setCurrent((c) => c ?? deepLinked ?? items[0] ?? null). A?surface=flow:NAMElink naming a flow the rail did not hold therefore opened the first flow, and the URL mirror rewrote?surface=to that flow. That is the "redirects to another flow" from the QA run.The change
One reserved scope,
/studio/~org/automations, served by the sameStudioDesignSurfacewith no package under it. There is no second builder.studioScope.ts(new) holds:~org;studioOrgScopePath;_packageIdon served items (fromMetadataProtectionFields, whichFlowSchemaspreads) andpackageIdon_draftsheaders. The DB-authoredsys_metadatapseudo-package also reads as "no package", the same readingstudioPackageIdinutils/appRoute.tsgives it.~is outside the spec'sMANIFEST_ID_PATTERNand outside this app'sPACKAGE_ID_REandsanitizePackageId, so the segment cannot equal a package id. A test pins this against both rules.packageSurfaces.ts: newloadPackageLessSurfaces. It does an unscopedlist('flow')andlistDrafts({ type }), keeps only rows and headers bound to no package, and merges them through the existing published-wins merge.AutomationsPillar:packageId: string | null. Withnullit lists package-less flows, and drafts save with nopackage=. The scope has no "New" flow (authoring stays package-first).?surface=flow:link that names a flow the rail does not hold no longer opens the first flow. From a package's pillar, a named package-less flow is found and opened in/studio/~org/automations?surface=flow:NAME, once per mount with replace navigation. A flow found nowhere is reported on the canvas ("The link names flow “NAME”, which is not here.").StudioDesignSurface, in the package-less scope:readOnlyispackageId !== null && pkgWritable === false, so a read-only answer left over from the package shown before the switch cannot lock the scope. The server write gate stays the authority.publishDraft.POST /packages/:id/publish-draftscannot reach a draft bound to no package; this is the same door Home's publish-all uses for package-less drafts. It is not all-or-nothing, and failures are named per draft.PackageSwitcherandBuilderLandingeach get an entry, "Organization flows". On the home it sits under its own "Not in a package" heading, shown whether or not any writable package exists.DraftChangesPanelgets an optionalincludepredicate. The scope passes its own filter, so the sheet reviews exactly what that scope's Publish ships. Package hosts pass nothing, and their behaviour is unchanged.studioRoutesget the bare/studio/~orgleg, which lands onautomations. The pillar URL itself is served by the generic:packageId/:tabroute.metadata-admin/i18n.ts(en and zh), added besideengine.studio.landing.installedHeading. No key is moved or reordered.packages/app-shell/README.mdandcontent/docs/guide/console.md. Changeset:.changeset/11553-studio-package-less-flows.md(@object-ui/app-shellminor,@object-ui/consolepatch).Landing versus the claim's file surface. Everything sits in the claimed surface except three files in the same package, each needed by it:
packages/app-shell/src/preview/DraftChangesPanel.tsx: theincludepredicate, so the sheet does not review drafts the scope's Publish never ships;packages/app-shell/src/index.ts: exports the segment, pillar and path builder for the console route;The ruling's pins
StudioDesignSurface.packageLessFlows-11553.test.tsx: "lists the clone and none of the packaged flows"; "opens it editable, and an edit saves a draft bound to no package"StudioDesignSurface.automationsReadOnly-11124.test.tsxstays green, unchangedFurther pins:
studioScope.test.ts,packageSurfaces.test.ts(loadPackageLessSurfaces),BuilderLanding.orgScope-11553.test.tsx(the entry on the measured shape, with no writable package) andStudioRoute.test.tsx(the~orgleg, behind the same entry gate).Verification (final head
4831c5a)pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' buildexited 0 (29 packages). The plugin dists the console needs were built for its type-check.pnpm --filter @object-ui/app-shell type-check: exit 0. This covers bothtsc --noEmitandtsconfig.test.json.pnpm --filter @object-ui/console type-check: exit 0, after rebuilding app-shell.pnpm exec vitest run packages/app-shell/src/views/studio-design/: 88 files, 521 tests passed.pnpm exec vitest run packages/app-shell/src/preview/ apps/console/src/components/: 50 files, 562 tests passed.eslinton the 13 changed TS/TSX files: exit 0, 0 errors. Warnings per file equal the based0c0c7fe9count for each pre-existing file (StudioDesignSurface 17, DraftChangesPanel 2, StudioRoute 1, BuilderLanding 0).check:i18n-keys,check:i18n-drift,check:i18n-dead-keys,check:i18n-designer-paritycheck:control-bytes,check:new-line-citations,check:changeset-claims,check:pending-changeset-literalscheck:spec-symbols,check:readme-exports,check:unreferenced-sources,check:test-path-rootscheck:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shapecheck:metadata-write-doors,check:handler-key-reads,check:self-import,check:phantom-deps,check:esm-specifierscheck:doc-fences,check:doc-typescheck-changeset-presence,check-changeset-no-major,check-changeset-overwrite,check-changeset-fixed,check-doc-linksAblations. Each mutation was made on committed code through
ablation-replace.mjsin WRAP mode. Each anchor hit once, the blob changed, and the restore was proven with blob equal to HEAD and an emptygit diff HEAD. The tests import./StudioDesignSurfacefrom source, so no build was needed.?? items[0]includedroppedreadOnlyput back topkgWritable === false(at4831c5a)Lint was narrowed to the changed files. The repo-wide
pnpm lintand the full test shards are left to CI. No browser check was run.Acceptance notes
studioOrgScopePath({ type: 'flow', name })could produce a direct link, but none is added here (that file is outside this card). Noted, not filed; carrier: none.cur ?? deepLinked ?? firstLeaf) and the Data rail (c ?? deepLinked ?? items[0]) still open their first item when a?surface=link names one they do not hold. That is the same mechanism this card removed for flows, but it was not measured through a public entry here. Noted, not filed; carrier: none.StudioDesignSurfacewith its ownaiSlot(the cloud edition) and reads:packageIditself would see~orgas a package id in this scope. Not measured: there is no such host in this repository.Generated by Claude Code