djs/tokenizer: parseDjsMinusState drops its case '-' — js/tokenizer always merges two adjacent - characters into one '--' token, so minus-state can never see another - — and metadataAfterTag drops an indexOf miss check its one call site can never trigger #1553
types/bit_vec: tryListToVec/tryU8ListToVec reuse the shared balanced fold, at the same cost as the accumulator they replace #1548
common/monoid: fold reduces as a balanced tree instead of a left fold — bigint.product drops from O(n²) to O(n log n), string.concat pays a little for the one combinator, number.sum may round differently. New foldAbsorbing stops at the first element reaching an absorbing value #1548
types/rtti/ts: the runtime printer supports recursive schemas — it converts through fjs/types/rtti/data (new dataToTs), so a recursive schema prints as type <name> = <expression> definitions plus an entry expression instead of overflowing the stack; output is the data form's canonical form, a struct key admitting undefined prints optional (mirroring Ts<>), and a rule name that cannot name a type alias (reserved word — strict-mode ones included, predefined type name, non-identifier) gets a generated identifier #1547.
BREAKING CHANGES:types/ts: a Printer.struct field (StructField) takes an optional third element marking the key optional ("key"?: type). Callers and contextually-typed implementations are unaffected; an external Printer implementation annotating the parameter as readonly (readonly [string, string])[] must widen it to readonly StructField[]#1547.
types/sorted_list: the two merge tail policies are now named keepTail and dropTail instead of sharing one shadowed name; internal only #1546
Every module-level @import tag lives in its module's leading JSDoc block (125 files swept), so emitted declaration headers list their type imports in one place #1545
djs/serializer: stringify's constSerialize drops a defensive throw for a refs lookup that can never miss — consts only ever holds values getConstants already found an entry for #1544
types/uint8array: toVec attempts the conversion instead of precomputing a byte-count bound; behavior and error message unchanged #1543
media/json/schema: toJsonSchema supports recursive schemas — it converts through fjs/types/rtti/data (new dataToJsonSchema) and emits named recursion as $defs/$ref; output is canonical, so anyOf members and object keys follow the data form's normalized order, and a non-empty tuple prefix emits minItems#1542.
types/rtti/data: rule and property lookups are own-property only, so a name shadowing an Object.prototype member (toString, …) is a missing definition for validate/subset and an ordinary extra key when validating values; subset and toData's coverage collapse now terminate on unions mixing rest-based and property-based object recursion #1542.
basen/base64: decode drops an overflow check in its padded branch that could never trigger — head's length is always a multiple of 6, so the largest value stringToVec can return without overflowing already lands at or under maxLength once the trimmed last chunk is added back #1541
text/utf16: u16 now rejects non-integers, so a fractional word is reported invalid (0xFFFFFFFF) instead of being misclassified by the surrogate/BMP range checks — which only partition the integers in 0x0000–0xFFFF. That closes the only path into utf16ByteToCodePointOp's trailing fallback arm, which is removed along with its isHighSurrogate recheck #1540
RTTI: new fjs/types/rtti/data module — a function-free, serializable, canonical data form for schemas with toData, cmp, equal, subset, and a data-driven validate#1539.
types/object: new structurallySame, plus assertStructurallySame in fjs/asserts — structural comparison for proofs #1538
text/code_point: new eofFlush factory builds the end-of-input step decoder takes. The UTF-8 and UTF-16 decoders derive their eof ops from it instead of each writing the flush out, so "leftover state becomes exactly one error unit" is stated once #1537
media/json/parser: endArray/endObject no longer branch on state.top and tokenToValue drops its defensive default arm — the parser's state machine already guarantees these invariants, so the dead branches are gone instead of tested #1536
fjs/cas, fjs/cas/evo and fjs/emergent_testing compose through the raw step / mapStep / historyStep combinators instead of the fluent Eff wrapper, which keeps its module but now has no consumers. Behavior is unchanged #1534
media/type: the magic-byte signatures are declared once. detect now folds its bytes through the same eliminator the streaming detector uses, instead of matching a second, hand-mirrored copy of the same table, so a Vec of sub-byte length reads the way detectStream already read it #1533
BREAKING CHANGES:fjs/bnf/ll1's matcher runs as an explicit-stack machine over a cursor into the shared input, so long or deeply nested input no longer overflows the JS call stack and matching is no longer quadratic. The MatchRule type described the recursive matcher and is removed; match results are unchanged #1531
BREAKING CHANGES: the last documented public typedefs authored in .mjs move to sibling types.ts files — ParseContext/djsResult to fjs/djs/transpiler/types.ts and Step to fjs/protocol/mcp/stdio/types.ts — so their documentation survives declaration emit; importers must use the types.ts specifier for these types #1530
types/bigfloat: decToBin no longer returns a 54-bit mantissa when rounding carries out of the top bit; the result is always the 53-bit IEEE-754 significand #1527
Emitted declarations keep their documentation headers: 24 modules lost @module to declaration emit and 4 never had one; all 127 module declarations now carry it, and every types.ts / proof.* prose header now leads its emitted declaration #1526
fjs/media/json/schema's emitted declaration is exact: an explicit @type naming the recursive schema by typeof replaces the @type {const} cast that collapsed not to /*elided*/ any and five sibling fields to any#1526
fjs/djs/parser's proof covers endObject's defensive non-object-top guard directly, the same way it already covers pushKey and endArray's equivalent unreachable branches #1525
fjs/text/ascii owns the hex-digit codec: hexDigitValue, hexDigitCodePoint, and the a-f / A-F ranges. The JSON serializer and both tokenizers use it instead of rederiving the offsets; the DJS tokenizer no longer decodes a non-hex code point to a garbage digit #1522
BREAKING CHANGES: the npm package ships no .js files: the empty types.js stubs and compiled test files are gone. Import types only with fully erased import type, and use fjs/emergent_testing/all.test.mjs as the external-runner entry #1520
The fjs/emergent_testing/scenarios fixtures, which never ran in CI, are deleted; fjs/emergent_testing/scenarios.md records how to recreate them. types.ts is now the only authored TypeScript #1520
fjs/types/result exports okThen, the pure Result bind — mapOk's monad sibling and the pure twin of fjs/effects' okStep. Its two error types are unioned, so a chain that widens its error needs no rewrapping #1519
BREAKING CHANGES: BNF EOF is the semantic symbol -1, not 2^24 - 1: fullRange is 0 .. 2^24 - 2, and the parser backends synthesize one logical EOF after the physical input. Serialized ranges that ended at the old EOF must be regenerated #1516
fjs/protocol/json_rpcdispatch looks up handlers by own property. An Object.prototype method name arriving as method no longer throws or emits a malformed response; it answers -32601 like any other unknown method #1514