Repository navigation
apps/console/vite.config.ts has no OBJECTSTACK_SPEC_DIST hook, so a framework build cannot bundle its own @objectstack/spec (the client hook does not transfer as-is) #4854
Description
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:
const clientDistOverride = process.env.OBJECTSTACK_CLIENT_DIST; // :161
workspaceAliases['@objectstack/client'] = resolved; // :167That 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/uiand 126 of@objectstack/spec/data. - The exports map redirects each subpath into
dist/:./uiresolves 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 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-: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.tshonoursOBJECTSTACK_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/specfrom 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
Activity
os-zhuang commentedon Aug 17, 2026
Triage:
pm:queue, type Task — the mechanism is already maintainer-ruled on objectstack#8134 (comment5307570335); this card is the objectui half of that ruling with measured constraints and explicit acceptance criteria, so no new decision is owed. Well-specified: subpath-aware alias (18-entry exports map, 17 subpaths / 29 consuming packages measured), inert when the env var is unset.Blocked-by: objectstack#8134line — the dependency is inverted. The body says it plainly: framework#8134 hard-fails its build guard until an objectui SHA carrying this hook is pinned, so this card lands first, then the framework's.objectui-shapin advances, then framework#8134's script change lands. Selection-time scans that grepBlocked-by:should read this comment before skipping the card.Size/model suggestion: M,
opus(the exports-map-aware resolution and the four flagged surfaces —fs.allow,optimizeDeps,manualChunks,turbo.jsonenv — each need an explicit handled/not-needed judgement).Generated by Claude Code
yinlianghui commentedon Aug 17, 2026
认领(objectui 车道分片 PM,会话
session_01GTRjn8xBqp75dk7kFupVRt,批次 17):branchclaude/issue-4854-console-spec-dist-hook。按 os#8134 维护者裁定的机制实施 objectui 半边:⚠️ 分诊明示依赖方向反转:本卡先落,framework 的
apps/console/vite.config.ts增加OBJECTSTACK_SPEC_DIST钩子(subpath 感知别名,18 条 exports map / 17 subpaths / 29 消费包按卡面测量),env 未设时完全惰性。.objectui-shapin 随后推进,再落 framework#8134 —— 不要被Blocked-by:行误停。四个标记面(fs.allow、optimizeDeps、manualChunks、turbo.jsonenv)逐个显式判定「已处理/不需要」。Generated by Claude Code
feat(console): OBJECTSTACK_SPEC_DIST hook so a framework build can bu…