Repository navigation
@objectstack/spec/shared costs a consumer 60.1 KB gzipped to import one string fold — the /meta spelling contract has no fine-grained export #10096
Description
Activity
Triage (session
session_012XVwvSDo9v5TFLgc8hFfLC): entered the decision inbox —needs-user-decision·domain:spec· type Feature (widens the published export surface of@objectstack/spec, mechanical boundary test ⇒ manual floor). Consumer side is objectui#5359, nowBlocked-by:this card — one ruling disposes of both.<!-- os-decision-facets -->- 项目长远合理性:细粒度导出(拼写契约独立成小入口)符合「契约单源、消费者不许 fork」的既有裁决 —— derivation guard 已经在机械上禁止消费者抄表,那么给被指定的唯一入口一个不带 registry 闭包的形态是这条裁决的自然补全;不做则每个未来消费者都付同样的 60KB。
- 实际业务拉动:已测 —— console 今天为一个 string fold 付 +60.1KB gzip(≈ service-storage:
IStorageService.list(prefix)means two different things on the two shipped adapters (local: one level, directories as files; S3: recursive, silently capped at 1000) #5266 整卡优化成果的三分之二),且落在预算门看不见的 vendor chunk 上;消费侧三条逃生路全部实测关闭。 - 防 AI 犯错:load-time 断言
assertMetaUrlSpellingsAgree()若随瘦身移出加载路径,必须换成 build-time 强制 —— 静默丢断言比 60KB 更贵;新入口须保持 derivation-guard 可识别,否则等于重新打开抄表之门。 - 创业阶段不扩散:这不是投机能力面 —— 消费者、成本、守卫三者都已存在;做的是一个入口,不是一个机制。反对面:每个新增发布入口都是永久维护义务,若维护者认为 60KB 可接受,零成本选项是记录接受。
- 推荐:A = 增加细粒度导出(map + fold + refusal helper,断言改 build-time 强制,形状/命名归 spec 席),B = 明示接受字节成本(objectui#5359 转本地搬移杠杆)。荐 A。
- 置信缺口:本分析看不见 spec 包构建管线对新增 subpath 的边际复杂度(每个 subpath 自带 self-contained bundle 的现状是三个成本机制之一)—— 若加一个入口显著加重 publish 管线,B 的相对价格会变。
Generated by Claude Code
Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session
session_01PjAP6vbcsg2yMtvySPv1Qo)Ruled: fine-grained export approved, as one half of a two-card landing with #10031 — and a standing principle is minted with it. The maintainer challenged the premise first — verbatim (untranslated): 「spec 包是协议,适合写这么大的函数吗?」 — and, on the analysis that the functions are tiny while the bundled zod schema graph is the weight, ruled: 「同意 把「浏览器可达的 spec 导出面必须 schema-free」写成常设原则一并落卡」.
The standing principle (⛔ binding for future spec export design, not just this card)
浏览器可达的 spec 导出面必须 schema-free。 A
@objectstack/specexport surface that browser/client consumers reach must carry vocabulary — maps, folds, enums, pure predicates — without linking the zod schema/validation machinery. The schema graph is the server/publish side's dependency, never the price of spelling a URL segment or reading a posture predicate.What is ruled for this card (with #10031, one family — fold-or-serial is the spec seat's call, five-gate test reads as foldable: same package, same defect shape, both ruled)
- A schema-free fine-grained export for the
/metaspelling contract (map + fold + refusal helper), staying derivation-compatible socheck-spec-symbol-derivation.mjskeeps accepting it as the sanctioned import. sideEffectsdeclaration (and/or/*#__PURE__*/annotations) per spec: importing one function from a@objectstack/specsubpath pulls its whole module graph — measured +237 KB minified, nothing tree-shakes #10031 — the dev proves schema-module evaluation purity by measurement, not assumption.assertMetaUrlSpellingsAgree()moves to a build-time check — ⛔ dropping the assertion to save bytes is forbidden; the agreement it pins must keep an enforcement home.- The implementing PR writes the standing principle into the spec package's own docs where export surfaces are defined, so the next export addition meets it as a stated rule; mechanizing it as a gate (browser-reachable entry ⇒ no schema module in its graph) is a welcome follow-up card, not a requirement of this landing.
Tier:
claude-fable-5(spec surface; the new export is public-surface growth —Clause-②: yes). Consuming-side objectui#5359 stays blocked on this and gets strictly better afterwards.State transition in the same stroke:
needs-user-decision→pm:queue.
Generated by Claude Code
- A schema-free fine-grained export for the
- added a commit that references this issue
on Aug 25, 2026 - added a commit that references this issue
on Sep 1, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed at destination by the
repo:objectuiexecution seat (round 18, sessionsession_01RV6yuVCxymHYE16PL9vQkE) after accepting objectui PR5361. Filed naked and unassigned — the fix widens@objectstack/spec's published export surface, so grading and routing belong to the triage seat, andpackages/specbelongs to thedomain:specseat. ⛔ Not claiming, ⛔ not grading.What a consumer measured
objectui's Console needed exactly one thing from this package:
canonicalMetaUrlType, the fold that turns a stored metadata type into the singular spelling a/meta/:typeroute requires (objectstack#7894, #8424). One function, over a 35-entry string map.Importing it cost, measured with esbuild (
--bundle --minify --format=esm --platform=browser) against a graph that already carried@objectstack/spec/uiand@objectstack/spec/kernel:spec/ui+spec/kernelspec/sharedFor scale in the consuming repo: objectui#5266 was a whole dedicated optimisation card whose entire product was moving
@objectstack/lintoff that same eager chunk, worth −89.0 KiB gzip. One string fold hands back two thirds of it.Why it costs that much — three mechanisms, all in this package
dist/ui/index.mjsinlines its dependencies rather than importing a shared chunk, so/sharedre-ships registry and schema modules/uiand/kernelalready carry. A consumer holding two entries pays for the overlap twice.sideEffects.shared/index.mjsrunsassertMetaUrlSpellingsAgree()at module load, which pinsMETA_URL_TO_SINGULARagainstDEFAULT_METADATA_TYPE_REGISTRY— a genuine integrity check, and also a hard load-time dependency on the registry for anyone who wanted one function.The consumer had no cheaper option — all three alternatives were measured closed
This is not a case of a consumer importing lazily or carelessly. Each escape route was tried and is shut:
scripts/check-spec-symbol-derivation.mjs: a spec-named symbol must be derived from@objectstack/spec, and the guard's own header explains that a faithful copy is precisely the fork it exists to prevent. Correct guard, correctly applied.exportsmap:canonicalMetaUrlTypeandMETA_URL_TO_SINGULARare exported by./sharedand by nothing else. All 15 code entries grep 0/0 in bothindex.d.tsandindex.mjs, zeros counter-probed on those same files with symbols objectui imports from them today (expandViewContainer,PageSchema,deriveNamespaceFromPackageId,composeStacks— all non-zero), then confirmed by resolving each entry at runtime and filtering its real export list: root 125 exports and/ui218 exports carry nothing fold-shaped;/shared's 71 carry the whole family./kerneland root merely inlinePLURAL_TO_SINGULAR(7 and 10 occurrences) without exporting it — and it is the wrong map anyway, since its keys aredefineStack()collection properties and it lacksfield,seed,external_catalogandtranslation./kernel's exportedDEFAULT_METADATA_TYPE_REGISTRYwould mean rebuildingrestPluralOfMetaType, which objectstack#8424 deliberately keeps module-internal.⛔ No deep-path import past the
exportsmap was attempted, and none should be — that is a different defect, not a fix.What would resolve it
A fine-grained export for the
/metaspelling contract alone — the map, the fold, and the refusal helper, without the registry closure — so that a consumer who needs to spell a URL segment correctly does not link the schema registry to do it.Shape and naming are the
domain:specseat's call; ⛔ this card deliberately does not prescribe them. Two constraints worth carrying into that decision:check-spec-symbol-derivation.mjskeeps accepting it as the sanctioned import — the guard is what stops consumers forking the table, and it should keep doing so.assertMetaUrlSpellingsAgree()exists for a reason. If it moves off the load path of the narrow entry, the agreement it pins needs somewhere else to be enforced — a build-time check rather than a module-load one. Dropping the assertion to save bytes would trade a measured cost for a silent one, which is the worse deal.This removes the cost for every consumer rather than relocating it for one. The consuming-side alternatives (relocating the bytes off objectui's eager chunk via its own vite config) are tracked as objectui#5359 and are strictly worse: they move the weight rather than deleting it, and they buy a chunk-load failure mode for the panel.
Cross-references
/metaruling@objectstack/spec/shared— should the three predicate helpers stay internal? #8424 — where the/metaspelling contract and its module-internal helper were established