Filed unassigned by the framework-side dev on objectstack-ai/objectstack#8134, per that card's maintainer ruling (comment 5307570335, 2026-08-16), cross-repo clause: "if the injection needs an objectui-side change, that half is a new card in objectui with Blocked-by: back here."
Blocked-by:objectstack-ai/objectstack#8134 — and note the real dependency runs the other way too: framework#8134 cannot land until this card does. The framework half is inert (worse: build-breaking) without the hook this card asks for. See "Why the framework half cannot land first" below.
What framework#8134 needs
The framework's scripts/build-console.sh already injects this tree's@objectstack/client into the console build, so a framework release never ships a console bundled against a stale published client. It works because objectui honours OBJECTSTACK_CLIENT_DIST in apps/console/vite.config.ts.
The same class of skew exists for @objectstack/spec, and is currently silent: any authorable key added to packages/spec after the last spec publish is accepted and round-tripped by the server, while the Studio designer rejects it as an unrecognized key — with no gate anywhere asking whether the vendored spec carries the surface the framework now declares. framework#8134 is the card to close that class, and the ruled mechanism is an OBJECTSTACK_SPEC_DIST injection mirroring the client one.
Measured on objectui origin/main @ 1ef236e18 (2026-08-16)
| probe | result |
|---|
OBJECTSTACK_SPEC_DIST anywhere in objectui | 0 occurrences, repo-wide |
OBJECTSTACK_SPEC_DIST at the pinned SHA the console builds from (665661ab0932) | 0 occurrences |
any other spec-override env var (grep OBJECTSTACK_*SPEC* / *DIST*) | none |
OBJECTSTACK_CLIENT_DIST hook | present — apps/console/vite.config.ts:161, :167 |
The client hook does not transfer as-is — this is the substantive part
The client override is a prefix alias to a package directory:
constclientDistOverride=process.env.OBJECTSTACK_CLIENT_DIST;// :161workspaceAliases['@objectstack/client']=resolved;// :167
That is safe for the client because @objectstack/client's exports map has exactly 1 entry (.) and nothing imports a subpath. @objectstack/spec is a different shape:
@objectstack/spec's exports map has 18 entries (., ./ui, ./data, ./kernel, ./api, ./system, ./contracts, ./automation, ./security, ./shared, ./integration, ./cloud, ./ai, ./studio, ./identity, ./qa, ./openapi.json, ./package.json).- objectui imports 17 distinct subpaths across 29 packages — including 350 uses of
@objectstack/spec/ui and 126 of @objectstack/spec/data. - The exports map redirects each subpath into
dist/: ./ui resolves to ./dist/ui/index.mjs, not ./ui.
So a literal copy of the client line — aliasing @objectstack/spec to the package directory — rewrites @objectstack/spec/ui to SPEC_PKG/ui, a path that does not exist on disk, breaking all 350 of those imports. A Vite string alias is prefix replacement; it does not consult the target package's exports map.
A working hook therefore needs subpath-aware resolution, roughly: map the bare specifier to SPEC_PKG/dist/index.mjs, and map the @objectstack/spec/NAME form to SPEC_PKG/dist/NAME/index.mjs, with ./openapi.json and ./package.json handled as the two non-directory exceptions. Shape is yours to choose — the measurement above is the constraint, not a prescription.
Other objectui-side surfaces the change touches
Flagging these as measured observations, not a design:
server.fs.allow — the client hook pushes the override's parent dirs (:168-:169) because the framework tree lives outside the workspace root and Vite's default fs.allow would 403 it. A spec hook needs the same.optimizeDeps.include (:236-:241) currently lists @objectstack/spec plus /data, /system, /ui. Pre-bundling interacts with an aliased out-of-workspace dep; worth a look.manualChunks (:269) routes the vendor-objectstack chunk by testing for node_modules/@objectstack/ in the path. An aliased spec resolves outsidenode_modules, so it falls out of that chunk. Note the injected client already has this property today, so this may well be acceptable — calling it out so it is a decision rather than a surprise.turbo.json:14 declares env: ["NODE_ENV", "VITE_BASE_PATH", "OBJECTSTACK_CLIENT_DIST"]. Turbo v2 strict env mode strips undeclared vars. The framework script sidesteps this for the console app itself by invoking the console's build script directly rather than through turbo, but the deps build does go through turbo — so adding the var here is likely still wanted.
Why the framework half cannot land first
scripts/build-console.sh:130-:131 guards the client injection with a hook-presence check that hard-fails when the pinned objectui checkout lacks the hook:
if ! grep -q "OBJECTSTACK_CLIENT_DIST" "${BUILD_ROOT}/apps/console/vite.config.ts"; then
echo "✗ objectui@... has no OBJECTSTACK_CLIENT_DIST hook ..."
exit 1
Mirroring that guard for spec — which framework#8134 should, since an unguarded injection would fail silently and re-create exactly the silent-skew class the card exists to kill — means the framework half breaks every console build until an objectui SHA carrying the spec hook is pinned. So the order is: this card lands, then objectui's .objectui-sha pin moves in the framework, then framework#8134's script change lands.
Acceptance
apps/console/vite.config.ts honours OBJECTSTACK_SPEC_DIST, resolving both the bare specifier and every subpath in the 18-entry exports map.- Inert when the var is unset — production and CI builds resolve
@objectstack/spec from the lockfile exactly as today. - The four surfaces above are each either handled or explicitly judged not to need handling.
Not in scope
⛔ This is not the GlobalFilterSchema.object instance from framework#7804 — that is delivered by framework#8893 via a lockfile refresh plus pin bump, and needs nothing from this card. This card is the build-mechanism half only.
Generated by Claude Code
Filed unassigned by the framework-side dev on objectstack-ai/objectstack#8134, per that card's maintainer ruling (comment
5307570335, 2026-08-16), cross-repo clause: "if the injection needs an objectui-side change, that half is a new card in objectui withBlocked-by:back here."Blocked-by:objectstack-ai/objectstack#8134 — and note the real dependency runs the other way too: framework#8134 cannot land until this card does. The framework half is inert (worse: build-breaking) without the hook this card asks for. See "Why the framework half cannot land first" below.What framework#8134 needs
The framework's
scripts/build-console.shalready injects this tree's@objectstack/clientinto the console build, so a framework release never ships a console bundled against a stale published client. It works because objectui honoursOBJECTSTACK_CLIENT_DISTinapps/console/vite.config.ts.The same class of skew exists for
@objectstack/spec, and is currently silent: any authorable key added topackages/specafter the last spec publish is accepted and round-tripped by the server, while the Studio designer rejects it as an unrecognized key — with no gate anywhere asking whether the vendored spec carries the surface the framework now declares. framework#8134 is the card to close that class, and the ruled mechanism is anOBJECTSTACK_SPEC_DISTinjection mirroring the client one.Measured on objectui
origin/main@1ef236e18(2026-08-16)OBJECTSTACK_SPEC_DISTanywhere in objectuiOBJECTSTACK_SPEC_DISTat the pinned SHA the console builds from (665661ab0932)OBJECTSTACK_*SPEC*/*DIST*)OBJECTSTACK_CLIENT_DISThookapps/console/vite.config.ts:161,:167The client hook does not transfer as-is — this is the substantive part
The client override is a prefix alias to a package directory:
That is safe for the client because
@objectstack/client's exports map has exactly 1 entry (.) and nothing imports a subpath.@objectstack/specis a different shape:@objectstack/spec's exports map has 18 entries (.,./ui,./data,./kernel,./api,./system,./contracts,./automation,./security,./shared,./integration,./cloud,./ai,./studio,./identity,./qa,./openapi.json,./package.json).@objectstack/spec/uiand 126 of@objectstack/spec/data.dist/:./uiresolves to./dist/ui/index.mjs, not./ui.So a literal copy of the client line — aliasing
@objectstack/specto the package directory — rewrites@objectstack/spec/uitoSPEC_PKG/ui, a path that does not exist on disk, breaking all 350 of those imports. A Vite string alias is prefix replacement; it does not consult the target package's exports map.A working hook therefore needs subpath-aware resolution, roughly: map the bare specifier to
SPEC_PKG/dist/index.mjs, and map the@objectstack/spec/NAMEform toSPEC_PKG/dist/NAME/index.mjs, with./openapi.jsonand./package.jsonhandled as the two non-directory exceptions. Shape is yours to choose — the measurement above is the constraint, not a prescription.Other objectui-side surfaces the change touches
Flagging these as measured observations, not a design:
server.fs.allow— the client hook pushes the override's parent dirs (:168-:169) because the framework tree lives outside the workspace root and Vite's defaultfs.allowwould 403 it. A spec hook needs the same.optimizeDeps.include(:236-:241) currently lists@objectstack/specplus/data,/system,/ui. Pre-bundling interacts with an aliased out-of-workspace dep; worth a look.manualChunks(:269) routes thevendor-objectstackchunk by testing fornode_modules/@objectstack/in the path. An aliased spec resolves outsidenode_modules, so it falls out of that chunk. Note the injected client already has this property today, so this may well be acceptable — calling it out so it is a decision rather than a surprise.turbo.json:14declaresenv: ["NODE_ENV", "VITE_BASE_PATH", "OBJECTSTACK_CLIENT_DIST"]. Turbo v2 strict env mode strips undeclared vars. The framework script sidesteps this for the console app itself by invoking the console's build script directly rather than through turbo, but the deps build does go through turbo — so adding the var here is likely still wanted.Why the framework half cannot land first
scripts/build-console.sh:130-:131guards the client injection with a hook-presence check that hard-fails when the pinned objectui checkout lacks the hook:Mirroring that guard for spec — which framework#8134 should, since an unguarded injection would fail silently and re-create exactly the silent-skew class the card exists to kill — means the framework half breaks every console build until an objectui SHA carrying the spec hook is pinned. So the order is: this card lands, then objectui's
.objectui-shapin moves in the framework, then framework#8134's script change lands.Acceptance
apps/console/vite.config.tshonoursOBJECTSTACK_SPEC_DIST, resolving both the bare specifier and every subpath in the 18-entry exports map.@objectstack/specfrom the lockfile exactly as today.Not in scope
⛔ This is not the
GlobalFilterSchema.objectinstance from framework#7804 — that is delivered by framework#8893 via a lockfile refresh plus pin bump, and needs nothing from this card. This card is the build-mechanism half only.Generated by Claude Code