Let a consumer of the shared name reach the schema prose - #41
Merged
Conversation
Hover on a field in an editor showed its structural facts and no sentence saying what the field is for. The sentence exists, ships, and is already resolved by describeObjectType: what a consumer could not do is obtain the pool. idfkit re-exports httpSource but not nodeSource or readBundleFileSync, which live behind @idfkit/schemas/node, a package a consumer of the shared name did not install. idfkit/node exposes schemas() and schemaFor() and nothing for prose. So the only route was to depend on a package the consumer did not declare, hardcode the file name docs.json, and cast the unknown that read returns. Both the name and the layout are implementation details, and both would have broken in silence. SchemaBundle gains loadProse() and prose(), the same pair as load(version) and loaded(version), with the same in-flight sharing. ON THE BUNDLE, and not as a free-standing loader, which is the one decision here worth arguing. The indices are only meaningful against manifests built in the same run: build.mjs writes docs.json and the manifests together, and SlimType.m and SlimField.n point into that exact array. A pool paired with manifests from another build resolves every sentence to a real sentence belonging to a different field, and nothing fails. Putting the loader on the bundle that carries the indices makes that pairing hard to reach by accident, where a module-level singleton beside httpSource would make it easy: the browser consumer is precisely the one that supplies its own source. It also answers both runtimes at once. envelop reaches schemas through httpSource today and gets the pool the same way, with nothing node-specific in the path. THE SYNCHRONOUS ACCESSOR IS THE POINT. Every reader of prose is synchronous. describeObjectType takes a pool rather than fetching one, and every answer in @idfkit/language is a pure function, which is what lets an editor server answer a cursor without holding a thread. A consumer calls loadProse once when a document arrives and reads prose() on the request path. With only a promise it would either await inside a path it deliberately keeps synchronous, or build a second cache beside the one SchemaBundle already has. ProsePool moves from @idfkit/core to @idfkit/schemas because schemas cannot import from core and the pool is a schemas artifact. Core re-exports it, so the name a consumer imports is unchanged and the register entry still resolves. Nothing about the parse path moves. The pool stays its own file, loaded by its own call, and check:bundle-purity still reports a minimal read-and-write bundle at 18.3 KB carrying no schema data. A caller who never asks for prose still pays nothing, and describeObjectType with no pool still returns structural facts with the prose undefined rather than text derived from a field name. MEASURED. Over ten common types in 26.1.0: with the pool, 10 of 10 carry a memo and 52 of 103 fields carry a note; without it, none do. The remaining 51 have no note in the schema itself. The install grows 5,657 bytes, from 1,795,857 to 1,801,514 against a budget of 1,835,008. That is 98.2 percent with 32.7 KB of headroom, down from 38.2 KB. The pool itself was already installed; this is the loader. The budget is not moved.
The level carrying the register entries for SchemaBundle.loadProse and SchemaBundle.prose, the two names this branch adds. conformance-2026.8 does not move: no corpus case changes.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by idfkit-lsp, and envelop hits the same gap. Hover on a field shows its structural facts and no sentence saying what the field is for.
The sentence that belongs under that heading is the schema's own note. It exists, it ships, and
describeObjectTypealready resolves it. What a consumer could not do is obtain the pool.The gap, confirmed rather than taken on report
idfkitre-exportshttpSourcebut notnodeSourceorreadBundleFileSync; those live behind@idfkit/schemas/node, a package a consumer of the shared name did not install.idfkit/nodeis@idfkit/core/node, which exposesschemas()andschemaFor()and nothing for prose.readreturnsunknownanddocs.jsonis a bundle-layout detail, so the only route was to hardcode a name and cast. Both would break silently if the layout moved.What this adds
SchemaBundlegainsloadProse()andprose()— the same pair asload(version)/loaded(version), with the same in-flight sharing.On the bundle, not a free-standing loader
This is the one decision worth arguing, and it departs from the shape the request sketched.
The indices are only meaningful against manifests built in the same run.
build.mjswritesdocs.jsonand the manifests together, andSlimType.m/SlimField.npoint into that exact array. A pool paired with manifests from another build resolves every sentence to a real sentence belonging to a different field, and nothing fails.A module-level
prosesingleton inidfkit/nodewould make that pairing easy to reach: the consumer most likely to supply its ownBundleSourceis the browser one, which is envelop. Putting the loader on the bundle that carries the indices makes it hard to reach by accident.It also answers both runtimes at once, which the request asked for separately. envelop reaches schemas through
httpSourcetoday and gets the pool the same way, with nothing node-specific in the path.The synchronous accessor
Kept, and for the reason the request gave. Every reader of prose is synchronous:
describeObjectTypetakes a pool rather than fetching one, and every answer in@idfkit/languageis a pure function, which is what lets an editor server answer a cursor without holding a thread. Load once when a document arrives, read on the request path.What does not change
ProsePoolmoves from@idfkit/coreto@idfkit/schemas, because schemas cannot import from core and the pool is a schemas artifact. Core re-exports it, so the name a consumer imports is unchanged and the register entry still resolves.Nothing about the parse path moves.
check:bundle-puritystill reports a minimal read-and-write bundle at 18.3 KB with no schema data.describeObjectTypewith no pool still returns structural facts withprose: undefined, never text derived from a field name (FR-022).Measured
Over ten common types in 26.1.0: with the pool, 10/10 carry a memo and 52/103 fields carry a note; without it, none do. The other 51 have no note in the schema itself.
Install grows 5,657 bytes, 1,795,857 to 1,801,514 against a budget of 1,835,008: 98.2 percent, 32.7 KB headroom, down from 38.2 KB. The pool was already installed; this is the loader. The budget is not moved.
Governance
idfkit-conformance#3 registers
SchemaBundle.loadProseandSchemaBundle.proseas divergent, on the same footing asSchemaBundle.loadagainstget_schema.It does not block this PR. The naming gate's coverage reaches top-level exports, and a method on an exported class sits outside that reach —
check-naming-register.mjssays so in its own header.check:namingpasses here at 136 of 136 either way. Unlike feature 005, no tag is a prerequisite.Gates
typecheck,test(703, up from 699),format:check,check:naming,check:parity,check:facade,check:bundle-purity,check:install-size,check:no-indexall green locally.check:publicationstill refuses on the pre-existing FR-044 conformance-level question.