feat: extract json-renderer core - #30
Merged
Merged
Conversation
Extract the framework-agnostic JSON UI document core into the new json-renderer package: document envelope + node tree generic over the node vocabulary, validation primitives and JSON Schema export, composition (fragments, slots, per-node overrides, merge), React-free binding/scope resolution, and an explicit renderer adapter contract. blocks-schema now specializes that core with the Constructive vocabulary and blocks-renderer is its first adapter (reactAdapter). Public APIs of both packages are unchanged.
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Review complete. No blocking issues — approved ✅; 1 nitpick below. 🧹 Nitpicks (1) — 🟢 1 low
The change introduces a
Reviewed commit: e623d25 |
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.
Summary
Extracts the framework-agnostic JSON UI document core out of
blocks-schema/blocks-rendererinto a new packagepackages/json-renderer(json-renderer@0.0.1, publishable but not published here — Lerna bump publishes it, and it must publish beforeblocks-schema). Per https://github.com/constructive-io/constructive-planning/issues/1822, the core owns the document format; Constructive's vocabulary and React stay in the layers above.json-rendererdepends only onzod. No React, no shadcn, no@constructive-io/*, and no Constructive block list.What moved (implementation moved, not copied — the old bodies are deleted):
node.ts—DocumentNode<TType, TProps>,walkNodes,findNodeByKey,collectNodes,mapNodes,collectNodeTypes,createNodeblocks-schema/nodeenvelope.ts—DocumentEnvelope<TNode, TKind, TVersion>,createEnvelope,isDocumentEnvelope,EnvelopeKindblocks-schema/envelopeconstraints.ts—NodeConstraints,validateValueblocks-schema/validationfields.ts—collectFieldNames/DefaultValues/FieldConstraints(node, predicate)blocks-schema/node(hardcodedisWidgetNode)compose.ts—composeEnvelope,composeNodeTree,mergeNodeTrees,mergeEnvelopesblocks-schema/composebindings.ts—readPath,resolveBinding,resolveNodeProps,composeScopeblocks-renderer/bindingsregistry.ts—composeRegistry,resolveHandler,registeredTypes,missingTypesblocks-renderer/registryzod.ts/json-schema.ts—createNodeSchema,createDocumentSchema,parseEnvelope,toJsonSchemablocks-schema/zod,blocks-schema/json-schemaadapter.ts— the adapter contract (new)Genericity is by parameter, not by fixed list: the vocabulary is
TType extends string, field-ness is a predicate you pass in, and even the composition keywords are configurable, so a host that spells indirectioninclude/fromcomposes the same way:The adapter contract (
json-renderer/adapter) states what a renderer must provide, soblocks-rendereris one adapter rather than the renderer:resolveNode()returns{ status: 'resolved', handler } | { status: 'unknown' }andUnknownNodePolicy = 'fallback' | 'omit' | 'throw'names the choice instead of burying it in a component.blocks-renderernow exportsreactAdapter(THandler = BlockComponent,TOutput = ReactNode, unknown →UnknownBlock); the React components remain the ergonomic entry point, the adapter object states the contract they satisfy. A test injson-rendererimplements a string-output adapter with no framework at all, proving the contract isn't React-shaped.Backwards compatibility. No renames, no removals: every
blocks-schemaandblocks-rendererexport keeps its name and signature and now delegates instead of duplicating, e.g.WIDGET_NODE_TYPES/CONTAINER_NODE_TYPES/BLOCK_NODE_TYPES, the node predicates,uiNodeSchema& friends,composeDocument,parseDocument,toDocumentJsonSchema,BlockRenderer,DocumentRenderer,composeRegistry,resolveBlock,useBlockField— all unchanged forjson-schema-to-blocks,meta-to-blocks, andflow-to-blocks(their tests pass untouched). Additions only:blocks-schemagainsblocks-schema/corere-exporting the generic types plusmergeDocuments/mergeNodes;blocks-renderergainsreactAdapter,composeScope,missingTypes.API-shape decisions worth reviewing:
createEnvelope(kind, page, options)takes the kind as a value ({ documentType, formatVersion }) so a specialization pins its discriminator once —blocks-schemaexports it asUI_DOCUMENT_KINDandcreateDocumentis a thin wrapper.RenderContextis split:RenderContextBaseholds what the core needs (document,registry,scope,unknownNodePolicy), andRenderContext<THandler, TDocument, TAction>addsonAction. That's what letsRendererContextValueextend it withUIActionand the form'svalues/errorswithout the core knowing about either.props.nameconvention, because "which nodes are fields" is vocabulary, not format.blocks-schema's build script changed frommakage buildtomakage clean && makage build-ts && makage assets(same formblocks-rendereralready uses) becausemakage build'scheck-publishstep rejects theworkspace:^dependency onjson-rendererin the source manifest.pnpm pack:checkstill proves the packed manifests contain noworkspace:protocol.Wiring: root
build:packages(beforeblocks-schema),scripts/pack-local.ts,scripts/check-packed-packages.ts(packed tarball installs into the isolated consumer, asserts dist-relative entry points, no survivingworkspace:ranges, aLICENSE, and resolvesjson-renderer+json-renderer/compose+json-renderer/adapterwhile exercisingcreateEnvelope/composeEnvelope), rootREADME.md,docs/RELEASING.md. No exports map, no generated registry output touched, no automated publishing.Verified locally:
pnpm --filter json-renderer build,pnpm build,pnpm lint:types,pnpm test(33 new core tests),pnpm check,pnpm pack:check.Link to Devin session: https://app.devin.ai/sessions/027937d092794c92a31c6ee49c513f59
Requested by: @pyramation