TypeScript API stability
This document declares which @fuaran-ui/* surfaces are stable, what counts as a breaking change in each, and the semver rules that govern the npm packages shipped from this repo. It is the contract that downstream consumers can rely on when pinning a @fuaran-ui/* version. It mirrors the shape of the F# language tier's STABILITY.md; where the two tiers describe the same wire format, the fuaran-specification spec + conformance corpus are the shared authority.
Scope
| Package | Licence | Version status |
|---|---|---|
@fuaran-ui/schema | Apache 2.0 | pre-1.0 |
@fuaran-ui/ui | Apache 2.0 | pre-1.0 |
@fuaran-ui/ops | Apache 2.0 | pre-1.0 |
@fuaran-ui/renderer | Apache 2.0 | pre-1.0 |
@fuaran-ui/renderer-server | Apache 2.0 | pre-1.0 |
@fuaran-ui/op-stream | Apache 2.0 | pre-1.0 |
@fuaran-ui/layout-observer | Apache 2.0 | pre-1.0 |
@fuaran-ui/style-observer | Apache 2.0 | pre-1.0 |
@fuaran-ui/theme-manifest | Apache 2.0 | pre-1.0 |
@fuaran-ui/ai-tools | Apache 2.0 | pre-1.0 |
@fuaran-ui/conformance | Apache 2.0 | pre-1.0 |
@fuaran-ui/validator | Apache 2.0 | pre-1.0 |
@fuaran-ui/client | Apache 2.0 | pre-1.0 |
The first published line is 0.1.0 across the package set, with no prerelease suffix. The core authoring surface (schema / ui / ops / renderer) was validated against a real authoring workload (the samples/demo app) before the version was cut; the follow-up packages hold the same version for coherence, with per-surface maturity annotated in the sections below. The core packages bump in lockstep for peer-dependency coherence.
All packages are Apache 2.0 from the first published version.
Pre-1.0 caveat
Until @fuaran-ui/schema and @fuaran-ui/ui ship 1.0.0, every minor version may break. The semver rules below take effect from 1.0.0 onward. Pre-1.0 they are aspirational – they describe what kind of change is intended to be a major-vs-minor bump, but the packages do not yet promise to honour them at the package-version layer. Consumers pinning a pre-1.0 version should pin the exact version (0.1.2, not ^0.1.0) and plan on a per-bump audit.
Semver
For each package, from 1.0.0 onward:
- Major (
X.0.0) – any change to a stable surface that requires consumer source-code edits to compile or behave equivalently. - Minor (
x.Y.0) – backward-compatible feature addition. Existing consumer code keeps compiling. - Patch (
x.x.Z) – backward-compatible bug fix. No surface change.
Adding a NodeKind tagged-union case (or any new optional spec field) is minor, not major – existing consumers' exhaustive switch statements keep compiling (TypeScript flags an unhandled case only under a default: never exhaustiveness pattern, which is the correct signal, not a break).
Stable surfaces
@fuaran-ui/schema
The wire-format-mirroring portion of @fuaran-ui/schema is stable, governed by the wire-format forward-coupling rule (below). This covers:
- The
Node,NodeKind,LayoutKind,DisplayKind,InputKind,VisKindtagged unions and every spec type (MetricSpec,DashboardSpec,TabsSpec, …) inpackages/schema/src/types.ts– the TypeScript shape of the §4b record contract. - The
Binding,Action,TextSource,CellFormat,CellValue,ColumnWidthtagged unions and the bare-string enums (Orientation,ToneVariant,BadgeVariant, …). - The branded primitives
NodeId,FragmentId,ApiEndpoint,IconSourceand their constructors. - The
defaults.*field set per spec (packages/schema/src/defaults.ts). - The bounded-primitive kit (
packages/schema/src/bounded.ts) –nonEmptyString,boundedString,boundedInt,fraction, andBoundedConstructionError. - The
Result<T, E>type (packages/schema/src/result.ts).
The discriminant strings (kind: 'Layout', kind: 'Dashboard', kind: 'Static', …) are part of the stable surface: they map to the wire $type discriminators that Phase 76's codec serialises, so renaming one is a wire-format breaking change.
@fuaran-ui/ui
The smart-constructor signatures (fuaran.dashboard, fuaran.metric, binding.*, action.*, format.*, column.*, node.*, formFieldKind.*, filterKind.*) are stable as of Phase 78, having been validated against the samples/demo authoring workload. The one ergonomic change that window surfaced: every node id option (and the positional id of fuaran.markdown / fuaran.markdownSpec / fuaran.skeleton) now accepts a bare string as well as a pre-branded NodeId – a backward-compatible widening that mirrors the F# Fuaran.X surface, whose ids are plain string literals. The option-object shapes are otherwise held stable from 0.1.0.
The preEmitValidate defect-code surface (EMPTY_NODE_ID, DUPLICATE_NODE_ID, EMPTY_CUSTOM_KIND_IDENTIFIER, TAB_HEADER_COUNT_MISMATCH, TAB_TAG_COUNT_MISMATCH, TAB_ACTIVE_TAG_WITHOUT_TAGS) matches the F# PreEmitValidate defect identities and is held stable so a cross-implementation eval suite scores uniformly; changing a code string is a breaking change.
@fuaran-ui/ops
The encoder + decoder are wire-conformant and stable, governed by the wire-format forward-coupling rule (below). Their stability contract is byte-equality against the workspace wire-format-fixtures/ corpus, not merely API non-breakage:
encodeNode/encodeOp(packages/ops/src/encode.ts) – deterministic canonical-JSON output, byte-identical to the F#CanonicalJsonencoder for every fixture.decodeNode/decodeOp(packages/ops/src/decode.ts) –Result<…, DecodeError>semantics; the sixDecodeErrorCodes (INVALID_JSON,MISSING_FIELD,WRONG_TYPE,UNKNOWN_DU_CASE,WRONG_NODE_KIND,EMPTY_NODE_ID) and the$-rooted error paths match the F# decoder byte-for-byte.- The
TreeOptagged union (packages/ops/src/treeOp.ts) and its discriminant strings – they map to the wire$typediscriminators, so renaming one is a wire-format breaking change.
The apply engine (apply, ApplyResult, ApplyError, OpApplyTelemetryRecord – packages/ops/src/apply.ts) remains alpha: the Phase 78 samples/demo app exercises the encoder + decoder (the wire round-trip) but not the op-apply path. The op-stream package (Phase 79) is now the first real op-applying consumer – its applyTo / applyAndPersist fold ops through apply and assert replay reconstructs the same tree direct apply produces – but the ApplyResult / telemetry shapes stay alpha until a stateful sample app drives the apply path through a full authoring session. The ApplyErrorCode identities mirror the F# ApplyErrorCode DU.
The hand-rolled parse surface and the coerce UpdateProp helpers are internal-leaning: parse is stable in behaviour (it backs the decoder), but its JsonAst shape and the coerce.* set are not part of the consumer-facing contract and may change in a patch release.
@fuaran-ui/renderer
The <FuaranRenderer> prop shape and the emitted class-name + ARIA vocabulary are stable, subject to the wire-format forward-coupling rule – renderer dispatch must accept any tree the wire spec admits, and the per-NodeKind / LayoutKind / DisplayKind class names are byte-for-byte parity with the F# reference renderer (fuaran-dotnet/src/Fuaran.UI.Renderer/Render.fs). This covers:
- The
<FuaranRenderer tree dispatch sources runtime theme>prop record (packages/renderer/src/Renderer.tsx). - The class-name contract (
fuaran-kind-*,fuaran-layout-*,fuaran-tone-*,fuaran-button-*,fuaran-badge-*,fuaran-motion-*, …) and the ARIA attribute set (packages/renderer/src/classNames.ts+ the per-family renderers underpackages/renderer/src/render/) – renaming a class is a major-version event, because the packaged reference CSS and any consumer CSS key off them. - The packaged reference CSS (
@fuaran-ui/renderer/css, the./cssexport) – declared stable + versioned alongside the package; a breaking class-name change is a major-version event. It is sync-packaged from the F# tier's reference CSS (a maintainers' byte-copy sync discipline). - The
@fuaran-ui/renderer/sanitizeseam (sanitizeUrl,sanitizeUrlOrBlank,sanitizeExtraAttributes,sanitizeMarkdownHtml) – the render-time injection-safety contract mirroringfuaran-dotnet/SANITIZATION.md(Phase 56). The behaviour (which schemes/keys/elements are blocked) is stable; exact diagnostic strings are not.
The custom-renderer registry (createCustomRendererRegistry, registerCustomRenderer, CustomRendererRegistry, FuaranRuntime) and the typed Theme + themeToCss bridge are stable as of Phase 78, validated by the samples/demo app (it registers a Custom React component through the registry + FuaranRuntime, and applies a sample theme via the theme prop). React 19+ is a peer dependency.
The in-page introspection surface (window.__fuaran, buildDebugGlobal, registerDebugGlobal) remains explicitly DEBUG-only and unstable, excluded from semver. It is undefined unless the host sets debug.
Additive surface — extension affordances (Phase 735)
Three additions, all additive; no existing entry point changed shape, so a consumer on the previous version keeps compiling and behaving identically.
- Change subscription.
__fuaran.subscribe(cb)(returning an unsubscribe handle) and__fuaran.treeRevision(), over the exported page-wide hub (pageChangeHub,createChangeHub,ChangeHub/TreeChange/ChangeCause). The revision token is opaque — compare for equality; parsing or ordering it is not a supported use, and its format is not part of any contract. - Gated apply, widened.
__fuaran.applynow accepts aTreeOpas a structured object as well as the original JSON string; the string form is unchanged, including the bytes handed to an op-stream sink. TheApplyEnvelopegains optional fields (treeRevisiononapplied,decodeErrorondecodeFailed,codeonrejected) — additions to a union member, not a change to one.FuaranDebugGlobalgainscanApplyandgetBindingState(the tagged resolution envelope;getBindingValue's bareResolutionis unchanged).<FuaranRenderer>gains an optionalvalidateprop. - DevTools relay page peer.
<FuaranRenderer relay>pluscreateRelayPeer/installRelayPeer/acceptsRelayMessage/parseRelayProfileand theRelay*types. Off by default: without the prop no listener is installed, and a peer built with no options is not opted in.
The relay's stability contract is the relay@1.0 profile, not this package's semver: the wire shapes are pinned by the contract's own fixture family (wire-format-fixtures/devtools-relay/, run in packages/renderer/test/relayCorpus.test.tsx), and they version independently of the wire profile core@1.0. Adding a request type, capability, optional payload field, or refusal class is a minor relay bump; removing or renaming any of them is a major one. A relay change is therefore governed the way a wire-format change is — by the specification and its corpus — and a change to the profile id is a breaking change to every peer, regardless of what this package's version does.
@fuaran-ui/renderer-server
The renderToHtml body-fragment output is stable, subject to the same class-name + ARIA forward-coupling rule as @fuaran-ui/renderer – the server renderer is a pure-string twin of the F# Fuaran.UI.Renderer.Server that emits the same fuaran-* class vocabulary the React client renderer does, with no React and no DOM. This covers:
- The
renderToHtml(tree, { sources })entry point + its body-fragment contract (the host owns the document shell + the<link>to the packaged@fuaran-ui/renderer/css). - The emitted class-name +
data-fuaran-node-idvocabulary, parity-locked two ways (test/parity.test.tsx): the class set + node-id set equal the React client renderer'srenderToStaticMarkupoutput for every corpus fixture, and every class is in the F# reference renderer's vocabulary. A drift in either direction is a build failure. This is what makes a server-rendered fragment safe to hand to the client renderer'shydrateentry points. - The server semantics: interactivity renders inert (no event handlers);
Linkis a real sanitised<a href>;Staticbindings resolve and the rest fall back;Chart/Maprender a deterministic placeholder;Customrenders the inert labelled placeholder.
@fuaran-ui/schema + @fuaran-ui/renderer are peer dependencies (the latter only for its React-free /sanitize subpath + the packaged reference CSS); react / react-dom are not runtime dependencies. The HTML-escaping floor (escapeText / escapeAttr) and the binding / class-name helpers are re-exported for hosts.
@fuaran-ui/op-stream
The OpRecord wire shape and the hash-chain semantics are stable, governed by the wire-format forward-coupling rule (below). This covers:
- The
OpRecord<TMsg>,OpResultEnvelope, andCheckpoint<TMsg>shapes (packages/op-stream/src/types.ts) – the TypeScript port of the F#Fuaran.UI.OpStream.Abstractionsrecord contract. - The hash-chain algorithm (
computeHash,sha256Hex,verifyChain,genesisPreviousHash–packages/op-stream/src/hashChain.ts):hash[n] = SHA-256(previousHash ++ encodeOp(op) ++ String(sequence) ++ String(timestampUnixSeconds)), withgenesisPreviousHash= sixty-four0characters. The stability contract is bit-equality with the F#HashChain.computeHashover the same op sequence (verified bytest/parity.test.tsagainst the corpus op fixtures), not merely API non-breakage – changing the payload formula, the genesis constant, or the digest is a wire-level breaking change. - The
IOpStreamSink<TMsg>/IOpStreamCheckpointSink<TMsg>interfaces and theVerificationError/ReplayErrordiscriminated unions.
The in-memory sink (InMemorySink, createInMemorySink) is stable on ship – the contract is the interface, and the Map-backed implementation has no external surface beyond it. The applyAndPersist / replayStream / PersistContext wrappers track the @fuaran-ui/ops apply engine's alpha status (they fold ops through it), so their shapes stay alpha until the apply engine promotes; the persistence + hash-chain semantics they implement are stable. The F# Fuaran.UI.OpStream.Sqlite SQL-backed sink is intentionally not ported (an IndexedDB-backed sink is a candidate follow-up); a future persistent TS sink would be an additive package, not a break here.
@fuaran-ui/layout-observer
The LayoutFlag DU shape and the flag JSON encode are stable, governed by the wire-format forward-coupling rule (below) – the flag set is a load-bearing AI/dev-tooling input shared with the F# tier. This covers:
- The
LayoutFlagtagged union and its discriminant strings (OverflowHorizontal,OverflowVertical,ZeroDimension,SqueezedToMin,ChildClippedByAncestor,AspectRatioWildlyOff–packages/layout-observer/src/flags.ts), matching the F#LayoutFlagcases. Additive-only: a new flag case is a minor bump; redefining an existing case is a wire-level breaking change. - The
encodeFlag/encodeObservationJSON output – byte-identical to F#LayoutFlag.encode/LayoutObservation.encodefor the same value (the{"kind":…}tagged-object form + 2-decimal invariant floats). The stability contract is byte-equality, not merely API non-breakage. - The
LayoutObservationshape and theILayoutObserverinterface (observe/observeTree/subscribe/register/unregister).
The observers (BrowserLayoutObserver, InMemoryLayoutObserver) and the useFuaranLayoutObserver React hook are stable in interface but alpha in detection algorithm – the per-flag geometric thresholds (the 0.5px collapse floor, the default 3× aspect threshold, the debounce policy) may be tuned in a minor release as browsers' ResizeObserver / computed-geometry behaviour is observed in the wild. react is an optional peer dependency (only the hook needs it); the observer + flag core are React-free.
@fuaran-ui/style-observer
The StyleFlag DU shape, the Rgba / StyleObservation shapes, and their JSON encode are stable, governed by the wire-format forward-coupling rule (below) – the flag set is a load-bearing AI/dev-tooling input shared with the F# tier. This covers:
- The
StyleFlagtagged union and its discriminant strings (ContrastBelowAA,InvisibleText,AccentIndistinct,TokenResolutionFailed,OffPaletteColour,UsageBudgetExceeded,ContrastBelowDeclaredFloor–packages/style-observer/src/flags.ts), matching the F#StyleFlagcases. Additive-only: a new flag case is a minor bump; redefining an existing case is a wire-level breaking change. - The
encodeStyleFlag/encodeStyleObservation/encodeRgbaJSON output – byte-identical to F#StyleFlag.encode/StyleObservation.encode/Rgba.encodefor the same value (the{"kind":…}tagged-object form + 2-decimal invariant floats;emittedToneisnullwhen absent). The stability contract is byte-equality, not merely API non-breakage. - The
Rgba/FontRole/StyleObservationshapes and theIStyleObserverinterface (observe/observeTree/subscribe/register/unregister).
The observer derives the manifest-free flag tier (ContrastBelowAA / InvisibleText / AccentIndistinct) from resolved colours + WCAG contrast unconditionally, and the four manifest-aware flags (TokenResolutionFailed / OffPaletteColour / UsageBudgetExceeded / ContrastBelowDeclaredFloor) when an observer is constructed with a @fuaran-ui/theme-manifest (graceful degradation – without a manifest only the manifest-free tier fires). perNodeFlags + verifyUsageBudgets are exported for direct use (the latter joins with @fuaran-ui/layout-observer areas for the tree-level 60-30-10 check).
The observers (BrowserStyleObserver, InMemoryStyleObserver) and the useFuaranStyleObserver React hook are stable in interface but alpha in detection algorithm – the WCAG thresholds are tunable via StyleObserverOptions, so the per-flag derivation may evolve in a minor release. react is an optional peer dependency (only the hook needs it); the observer + flag core are React-free.
@fuaran-ui/theme-manifest
The contract shapes are stable, governed by the same forward-coupling discipline as the rest of the tier. This covers:
- The
ThemeManifest,ManifestMeta,ManifestToken,ManifestRole,RoleBinding,Invariant,InvariantKind,MotionBudgetshapes (packages/theme-manifest/src/manifest.ts), matching the F#Fuaran.UI.ThemeManifestcontract. - The
decodeManifest/manifestFromJsonbehaviour – the DTCG group-tree walk + the Fuaran wrapper shape, the role / invariant parse, and the$extensions.fuaran.rolemining. A vanilla DTCG file decodes to tokens with empty roles/invariants. - The
invariantvocabulary is additive-only: a newInvariantKindis a minor bump; redefining one breaks every manifest authored against it.
The projectors (projectFromFuaranToneVars / projectFromCssCustomProperties / projectFromDtcg / merge) are stable in behaviour; their exact role-inference heuristics may be tuned in a minor release. Manifest JSON encode and the F# ThemeBridge (typed-Theme projector) are not yet ported (follow-up). @fuaran-ui/schema is the only peer dependency.
@fuaran-ui/ai-tools
The introspection surface stays alpha – the F# Fuaran.UI.AiTools tier is shaped around an orchestrator's specific needs, and the TS port may grow as adopters surface different ones. Two sub-surfaces are nonetheless pinned to the F# tier, because a cross-implementation eval suite scores against them:
kindNameoutput – the wire-discriminator string per node, matching F#Introspect.kindName(including the Layout grid →GridLayout/ Visualisation grid →Griddistinction).- The binding-slot expression forms (
bindingExpression–$static/$queries.<name>/$filters.<name>/$selection.<nodeId>/$state.<key>/$computed/$i18n.<key>/$local/$format) and the per-kind binding-slot table (extractBindingSlots), matching F#BindingProbe.identify+extractBindings.
The envelope shapes (NodeIntrospection, TreeIntrospection) and the getNodeState / findNodes / inspectTree / FuaranIntrospectionProvider surface may grow additively (e.g. a future props block or live binding-value resolution). @fuaran-ui/schema is a peer dependency; react is an optional peer (only the context provider + hook need it).
@fuaran-ui/conformance
The third-party certification kit (CONFORMANCE.md). Two sub-surfaces are stable from first ship, because external certification claims depend on them:
- The
ConformanceAdapterseam (packages/conformance/src/adapter.ts) – thedecodeNode/encodeNode/decodeOp/encodeOphook signatures and theAdapterDecodeResult/AdapterDecodeErrorshapes. All hooks are optional by contract; adding a hook (e.g. activating the reservedapplyOpwhen apply fixtures land) is minor, changing an existing hook's signature is a breaking change. - The report semantics – the
LegIdset, the mandatory/optional tiering, the three-verdict model (conformant/partially-conformant/non-conformant), and the corpus-naming fields (manifestVersion+digest). A published certification must stay interpretable: removing or re-meaning a leg, or changing how the verdict is computed from leg outcomes, is a breaking change; adding a new leg for a new corpus fixture class is minor.
The bundled corpus snapshot versions with the package and is byte-synced from the authoritative workspace corpus (guarded by the package's own test suite); certification is per corpus version, so a kit release that ships an advanced corpus is by nature a re-certification event, not a breakage. The human-readable formatReport text layout and the CLI flag surface are not stable (the structured ConformanceReport JSON is the machine contract).
@fuaran-ui/validator
The build-time TypeScript-source validator (a TS-compiler-API walker over the @fuaran-ui/ui surface). One sub-surface is stable from first ship, because a cross-implementation eval suite scores against it:
- The defect codes + severities – the
FUARAN###identities and theirerror/warningseverity match the F#Fuaran.UI.Validatortier byte-for-byte for the ported rule subset (001/002/010/020/046/047/048/049/050/060/061/063/064/900). Re-meaning a code or flipping its severity is a breaking change; adding a newly-ported rule (e.g. activating a currently-out-of-scope F#-tier code) is minor. The §4d AI-recovery JSON shape (code/severity/file/line/column/message/available_fields/suggestion) and the manifest wire shape (shared with the F# tier) are likewise stable.
The rule coverage (which F#-tier codes are ported) is explicitly a growing, not pinned surface – the README "Coverage vs the F# tier" section enumerates the out-of-scope codes; porting more of them is additive. The CLI flag surface, the human-readable plain output layout, and the RunResult envelope may grow additively. The walker's syntactic-only boundary (no type resolution, canonical namespace identifiers only) is a documented capability limit, not a stability promise.
@fuaran-ui/client
A small, typed client over the Fuaran generation endpoint – a paid, stateless, bring-your-own-key (BYOK) HTTPS surface that takes a prompt (+ an optional current tree) and returns a new canonical wire-format tree. The endpoint URL + the paid access token are the commercial gate; this package is a thin, OSS-safe HTTPS + types layer over it. Its contract authority is the generation endpoint's own published surface contract: the request/response types here mirror that surface field-for-field and are kept in lockstep – a field added at the surface is added here in the same change, and wire.ts pins how each maps onto the HTTP envelope. So the client's stability contract is faithful mirroring of the surface version it is built against, the same posture the codec packages take toward the shared wire-format-fixtures/ corpus. This covers:
- The
TurnResultthree-case discriminated union (packages/client/src/contract.ts), discriminated onkind(produced/accessDenied/turnFailed) – the HTTP status selects the case (seewire.ts). Re-meaning a case, or changing which status maps to it, is a breaking change. AppliedOp(opId+opJson) –opIdis a stable dedup discriminant carried by equality;opJsonis canonical wire JSON, decoded with@fuaran-ui/opsdecodeOp.RecoverableError(stage+code+message) –codeis a stable discriminant a caller's retry/recovery loop pattern-matches, andTurnStage(access-token/provider/parse/apply) is a stable enum. These mirror the surface's applied-op + recoverable-error records; changing a discriminant string is a breaking change, adding a case is minor.SURFACE_VERSION+isSurfaceVersionCompatible– the surface-version echo + the shared-major compatibility check. A produced result echoes the live surface version (Produced.version); a differing major signals a breaking surface revision the client predates. The compatibility semantics (major-equality) are stable; the version string moves with the surface.- The
FuaranClient(generate+FuaranClientConfig) andFuaranSessionturn-loop helper (holds the tree so the next prompt is a repair diff) – the public API shape is stable as of Phase 215. The@fuaran-ui/client/rendersubpath (decode + mount glue, pulling in React +@fuaran-ui/renderer) and the low-leveltoWireBody/parseTurnResponsewire mapping (for advanced hosts driving their own transport) are exported; the core entry is dependency-light so it runs in a server-proxy (Node) context.
The BYOK provider key and the paid access token are memory-only, never bundled or logged – that discipline is a security contract, not a stability surface. The internal request-body helpers beyond toWireBody / parseTurnResponse are not part of the consumer contract and may change in a patch release.
Wire format
The canonical JSON wire format is specified language-neutrally in fuaran-dotnet/docs/WIRE_FORMAT.md, with the workspace wire-format-fixtures/ corpus as the executable conformance suite. @fuaran-ui/schema is the TypeScript shape the codec is built on; @fuaran-ui/ops (Phase 76) ships the encoder/decoder verified against the corpus byte-for-byte (all 84 fixtures: 56 round-trips byte-identical to the F# encoder – 46 Node + 10 TreeOp – + 28 reject cases surfacing the same DecodeErrorCode at the same path).
The wire format is stable. Breaking changes (major-version events): changing a discriminant string, removing or retyping an emitted field, changing a sentinel string, or changing a DecodeError code. Non-breaking (additive): a new tagged-union case or a new optional field omitted when absent.
Forward-coupling rule (load-bearing). Per WIRE_FORMAT.md §11, adding a new NodeKind / Spec / TreeOp / Binding / Action case in any future phase MUST, in the same commit, update the F# encoder + decoder + the wire-format-fixtures/ corpus and bump the @fuaran-ui/schema shape + the @fuaran-ui/ui smart-ctor (when applicable) + the TS encoder/decoder (Phase 76 onward). The TypeScript and F# implementations move in lockstep against the shared spec + corpus.
Unstable surfaces
The following are explicitly not covered by semver and may change in any patch release without notice:
- Anything whose name is prefixed
__(double underscore) – including the phantom brand fields on branded primitives (__brand,__min,__max,__validated). - The
try*non-throwing bounded variants' exact error-message strings (the failure is stable; the message text is not). - The
NOT_PROVIDED_SENTINELconstant value. - Test-only surface in any
test/directory.
Versioning policy
Per-release semver bump. Pre-1.0: plain 0.x.y versions, no prerelease suffix (a release bumps the patch, or the minor when the surface grows). The publish workflow (.github/workflows/publish.yml) is triggered by a vX.Y.Z tag push and runs pnpm -r publish --access public; @fuaran-ui/ui declares @fuaran-ui/schema as a peer dependency, so the two ship together.
Re-confirmation gate before public exposure
Before this repo flips public (public GitHub repository, published-to-npm package, or marketing surface), the licensing posture declared in LICENSE must be re-confirmed by Diametrical Ltd.
See also
LICENSECONTRIBUTING.mdCLAUDE.mdfuaran-dotnet/STABILITY.md– the F# language-tier counterpart.fuaran-dotnet/docs/WIRE_FORMAT.md– the shared wire-format authority.