Skip to content

v4.4.0

Choose a tag to compare

@58bits 58bits released this 18 Jul 18:30

Highlights

  • @byline/core — Byline now has a single shared field-path grammar (packages/core/src/paths/). Several subsystems addressed fields by dotted path and did not all mean the same thing by it; four independently hand-rolled tree walks had drifted apart, and two of them carried bugs the others didn't. The grammar names the distinction that explains every difference: instance paths address a value in one item of one document (content[1].gallery[0].alt — item selectors required, no block type, because the addressed item carries its own _type), while declaration paths address a field declaration in the schema (content.photoBlock.gallery.alt — no selectors, block type required, since two blocks may declare the same field name). Eliding the selectors from a storage path yields exactly the declaration path: they are one grammar with two serialisations. New public exports are parseDeclarationPath / parseInstancePath, formatDeclarationPath / formatInstancePath, toDeclarationSegments, resolveDeclarationPath (schema-aware, with a blocks: 'qualified' | 'forbidden' policy), and walkFieldDeclarations (the canonical schema walk, with an onBlock callback for validation that must observe every block including ones declaring no fields). Parsing is deliberately schema-unaware — a block type and a field name are both bare identifiers, so only resolution against a field set can tell them apart. The upload hook registry, admin fields{} validation, and boot-validation error messages all now route through it; storage field_path (persisted) and patch paths (wire format) stay frozen and are documented rather than changed.

  • monorepo — a new architecture reference, Path Grammar, is the authoritative account of both notations: the segment AST, every notation's shape in one table, what is frozen and why, and the one deliberate narrowing (admin fields{} keys bar block traversal entirely, because fields inside a block take their overrides from the blockType-keyed blockAdmin registry). The "Schema paths vs instance paths" section of the fields doc is now the short author-facing account and links out to it. Two contract tests pin every notation against a fixture that declares the same field name in two block types — the collision an unqualified path cannot resolve — so a new notation, or a change to an existing one, fails loudly.

Bug Fixes

  • @byline/adminupload fields declared inside a block now work, where previously two independent defects stacked to make them unusable. First, the upload widget never rendered its drop zone at all: collectionPath was prop-drilled down through the field tree, and BlocksField had never forwarded it, so a file / image field inside any block fell back to a read-only placeholder with no error. (ArrayField and GroupField were given the same fix in 4.2.x; blocks were missed because no consumer had an upload inside a block to notice.) Second, even once rendering, upload.context resolved against the wrong block: the executor matched the first block declaring a field of the next path segment's name, so with two blocks each declaring an intermediate array — legal, since the boot-time uniqueness constraint covers only upload-capable leaf names — the declared context values were silently dropped and never reached server-side beforeStore / afterStore hooks. Resolution now reads the addressed item's _type from form state and descends into that block; where form data cannot disambiguate, it accepts only a unique match rather than guessing.

  • @byline/coreboot-validation error messages now name the block type. A field inside a block was reported as content.alt, which is ambiguous the moment two blocks in the same field declare alt — the message could not say which declaration was at fault. Paths are now block-qualified (content.photoBlock.gallery.alt).

  • @byline/corean unresolvable search config field name is now rejected at boot instead of silently skipped. buildSearchDocument resolves search.{body,facets,filters} names among the collection's top-level fields and skipped anything it could not resolve, so a typo, a dotted path, a facets entry that was not a relation, or a filters entry naming a container produced a collection that indexed less than its author intended — with no signal at boot and none at index time. See Breaking Changes for the consumer-side effect.

  • @byline/corea malformed patch path is now rejected rather than applied at a truncated path. The previous parser was lenient: "a[" yielded a single a segment, and the patch was then written to a — a path the client never addressed. parsePatchPath now delegates to the shared grammar's parseInstancePath (the two agree on every well-formed path) and returns no segments for a malformed one, so the patch surfaces as an error instead of a silent mis-write.

Chores

  • @byline/db-postgres — the package's node unit-test tier was declared in its Vitest config but had no files and a test script that only echoed a message. It now runs, contributing pure flattener tests that need no Postgres to the root pnpm test.

  • @byline/cli — removed four obsolete document example fixtures. They encoded a storage path notation that never existed (a sequential per-field counter after the block type, content.1.photoBlock.0.display), traced back to an early analysis document and copied forward — and byline init was placing them in user projects as reference material for what storage rows look like. The same error was corrected in CLAUDE.md and the architecture index; the detailed storage reference had always been correct.

Breaking Changes

  • @byline/corea collection whose search config names a field that does not resolve will now fail to boot. Previously it started and quietly under-indexed. If your search.{body,facets,filters} entries are correct, nothing changes. If one is a typo, a dotted path, a non-relation in facets, or a container in filters, boot fails with a message naming the collection, the option, and the offending entry. Action: correct the name. To index content nested inside a group / array / blocks field, name the top-level container — it is walked recursively and every nested text leaf is flattened into the body. Addressing one specific nested declaration is not supported and is rejected explicitly rather than accepted and ignored.

  • @byline/adminthe collectionPath prop was removed from FieldRenderer, ArrayField, GroupField, and BlocksField (all exported from @byline/admin/react). It is constant for the whole form, so threading it meant every nesting-capable container had to remember to forward it, and one that forgot silently rendered upload fields read-only — a defect that recurred twice. It now lives on the form-context store, set once by FormProvider, and FileField / ImageField read it via useFormContext(). Action: none if you use the admin shell. If you render any of those components directly, drop the prop and ensure FormProvider receives collectionPath.

  • @byline/corea malformed patch path is now rejected (see Bug Fixes). The admin client builds patch paths structurally and is unaffected; this only reaches a client constructing paths by hand.

All other @byline/* packages bumped to 4.4.0 in lockstep with no behavioural changes this cycle.