Skip to content

v0.17.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 07:04
· 666 commits to main since this release

foldkit-agent 0.7.0; foldkit-bundle 0.6.0; foldkit-cms 0.6.0;
foldkit-cms-drizzle 0.6.0; foldkit-composition 0.2.0; foldkit-crud 0.7.0;
foldkit-durable 0.8.0; foldkit-entity 0.8.0; foldkit-mixins 0.9.0;
foldkit-mixins-form 0.5.0; foldkit-mixins-ui 0.8.0; foldkit-primitives
0.8.0; foldkit-remote 0.14.0; foldkit-remote-server 0.13.0;
foldkit-remote-drizzle 0.11.0; foldkit-ssr 0.5.0; foldkit-surface 0.8.0;
foldkit-sync 0.10.0; foldkit-data-grid and foldkit-mixins-data-grid 0.1.2.
New: foldkit-guard and foldkit-site 0.1.0. Republished to re-pin:
foldkit-agent-a2a, foldkit-agent-mcp and foldkit-agent-native 0.5.2,
foldkit-agent-webmcp 0.6.1, foldkit-builder and foldkit-mixins-builder
0.1.1, foldkit-bundle-surface 0.3.2, foldkit-form 0.4.2, foldkit-mirror
0.5.2, foldkit-mixins-crud 0.6.4, foldkit-mixins-richtext 0.2.3,
foldkit-mixins-surface 0.5.3 and foldkit-richtext-dom 0.3.1, each of which
pins a bumped package. Every other package is unchanged.

Who may do what is declared once, and Remote places rows instead of
refetching them.
foldkit-guard declares guards on the shared domain and
enforces them on every seam: reads compiled into SQL, declared writes and
handlers checked before they land and answered as their writer may see the
row, Sync's cell edits, live updates and agents, with a startup check that
refuses a Source left unguarded. A write is declared as data (Write.update,
insert, delete) and served with no handler. A list whose query body
declares its order has a change placed in it with no request, checked against
the server under verifyPlacement. foldkit-site states a routed site's
topology once. And a review of this release's work fixed the ways a write or a
read could get past its rules, lose another author's edit, or send the server's
SQL to a client.

Upgrading from 0.16

  • journalExchange's refuse is admit (foldkit-sync), which answers as
    authorize does and may be an Effect; opens refuses a connection whole.
    defineDocumentHost takes both as functions of the principal, and its
    settle is (env, journal) => Effect.
  • foldkit-sync/entity's overlay helpers are gone (overlay, held,
    newlyHeld, settled, shows): RemoteEdits from foldkit-sync/remote
    shows the edits through Remote.
  • Remote.visibleItems is Data.visibleItems(remote, connection)
    (foldkit-remote), which draws a list as its reads do.
  • An OrderTerm is tagged Term, and a body's orderBy holds Orderings
    (foldkit-entity): an interpreter resolves Query.orderFor(body, input)
    first. foldkit-remote-drizzle refuses an orderBy beside a body that
    declares its order.
  • Cms.slugTaken and the CmsConflict: / CmsSlugTaken: message prefixes
    are gone
    (foldkit-cms): read a refusal with Data.refusal
    (Cms.refusal.field, Cms.refusal.conflict).
  • CmsServer.make takes a guard set (foldkit-cms-drizzle): policy and
    authors for isAuthor, transitions beside allow, and each content
    entry its guarded entity; published(column, isAuthor) is gone, bind with
    GuardDrizzle.visible. Transaction may fail with a RemoteServerError.
  • LiveHub.deleted may fail and needs the Sources' services
    (foldkit-remote-server), as changed does; it re-reads each subscriber's
    selection rather than deleting for all.
  • returning.row fails with a RemoteServerError (foldkit-remote-drizzle)
    when the database refuses it; run a handler's own statements through
    runStatement.

Added

  • foldkit-remote-server: fields every principal reads alike skip the live
    hub's re-read.
    A Source declares shared (RemoteServer.entity,
    RemoteServer.memory per entity, foldkit-remote-drizzle's source);
    hub.changed(ref, fields, values) sends a shared field's given value to
    each subscriber as it is and re-reads the rest, and the handlers give it a
    declared write's answer, the row as stored (a handler's answer is still
    re-read). A shared field the Source does not serve is refused when it is
    made. foldkit-guard: GuardServer.validate refuses a shared field a Read
    rule could tell readers apart by.

  • One editor for create and edit. foldkit-entity: an Option-typed
    input key maps itself to the field its value fits, as a nullable one does,
    so an input's id may be Schema.OptionFromNullOr(PostId). foldkit-crud's
    editor then saves a blank form with Option.none() and an opened one with
    its id. foldkit-guard: GuardServer.mutation's id may answer an
    Option, and none is a create, checked against the row's Create rule.

  • foldkit-agent: authorize may refuse with a reason. It answers
    { allowed: false, reason } (an AuthorizationDecision) beside a boolean,
    and the reason is the AgentAuthorizationError's message.
    foldkit-guard: may carries explained refusals. Its answer gains
    reasons, the sentence an explain gave each row interaction it refused,
    read with May.reason; GuardAgent.authorize refuses with it, so an
    agent learns why as a client's write would.

  • foldkit-entity, foldkit-remote: a list is fetched, not placed, when
    a row changes what the server's read rule filters on.
    ReadFilter.of(members)
    is Entity metadata naming the members a server filters rows by under a rule
    the client does not run; foldkit-guard's attach declares it from the
    row's Read rules. Remote gathers it per Entity name when the domain is
    defined (registry.filtered), so a query declared over the Entity before
    the rule was attached still sees it, and a row a list does not hold that
    changes one invalidates the list. A row it holds is judged by the body as
    before.

  • foldkit-remote-drizzle: a visible rule may answer with an Effect,
    and is given its own binding.
    (principal, self) => SQL | undefined | Effect<SQL | undefined, RemoteServerError>, so a rule that must ask
    something first (a guard's body) can, and one written before bind names
    columns through self.columns. Every path a table is read by applies it as
    before.

  • foldkit-remote-drizzle: source(binding, { filter }), the
    per-record read plan RemoteServer.entity takes, passed through. A source
    and a query carry their binding's visible, for a startup check.

  • foldkit-agent: withPrincipal(key), the principal's type taken from
    the Context key a server reads it from (G.Principal), so the agent's and
    the server's are one type. withPrincipal<P>() stays.

  • foldkit-crud: an editor locks the keys its row's reader may not set.
    Crud.editor(name, { form, mutation, locks }) loads the members locks
    selects beside the form's, and editor.at(...).locked(model) names the
    form's keys whose member (or relation) locks.locked refuses, for
    foldkit-mixins-form's locked. Nothing is locked for a new one, or
    before the row is loaded. foldkit-guard's May.locks is one.

  • foldkit-mixins-form: keys drawn but not editable. The view input
    locked: ['ownerId'] draws those keys disabled, every control kind (a
    many picker's choices included), for a key the person may not set. A key in
    a nested row is not locked by it, and a Bundle-backed key draws as its
    Bundle does.

  • foldkit-remote, foldkit-remote-server: a refusal of who is asking,
    end to end.
    A mutation Source fails with RemoteServer.forbid(reason?);
    the wire carries it as RemoteForbidden (_tag: 'Forbidden', reason
    only when given), RemoteServer.answer sends it as a 403 with
    { error, forbidden: { reason? } }, and Remote.json reads it back. A 403
    whose body is not Remote's answer, from middleware in front of the server,
    fails a mutation as Forbidden and any other call with the status.
    Data.mutation is Failed with the Forbidden error, and RemoteError
    carries reason.

  • foldkit-remote-server: a principal read from Context on each call, and
    a live hub keyed by person.
    RemoteServer.handlers(server, principal)
    takes a Context key for principal as well as a value, read by every read,
    query, mutation and live subscription, so one handler set (one RPC layer)
    answers every request and authentication provides the key per request; the
    key is added to what the handlers require. RemoteServer.liveHub(sources, { key }) shares a re-read between subscribers whose principals have one
    key, read as the first of them; without one, only the same value shares.

  • foldkit-remote-server, foldkit-remote-drizzle: a write answered as its
    writer may now see the row.
    EntityWriter.read, when a writer has one,
    reads a row as a principal may see it; RemoteServer.write answers through
    it, and a row the write hid from its writer answers in deleted. A Drizzle
    writer over a binding with a visible rule reads through it. A writer may
    fail with RemoteForbidden. A declared write's Source carries writes,
    the Entity and the writer, for a startup check.

  • foldkit-remote-server: EntitySource.visible and
    QuerySource.visible
    , the rule that decides which rows a source returns,
    as it was given one. The server never runs it; a startup check such as
    GuardServer.validate reads it. RemoteServer.memory's sources carry its
    readable.

  • foldkit-remote-server: what a principal sees, per record and per row.
    EntitySource.filter(principal, fields) returns a read plan: the fields to
    read (the allowed ones, and any the decision needs) and apply, one record
    as the principal sees it; a member it drops is settled like one the Source
    did not return. It runs for reads by id and live patches. RemoteServer.memory
    takes filter per entity, as it takes authorize, and readable (given a
    lookup of another row by id), which
    hides a row the principal may not read from reads by id, from every query,
    and from relations (a one ref reads null), as a binding's visible does.

  • foldkit-guard: a guard set declared once, decided on the server, and
    enforced on every seam.
    Guards.declare(name, PrincipalSchema, guard => ({ … }), { key })
    names principal guards (bodies only on the server, through G.implement,
    which must cover each one) and row guards (an Expr over one Entity's
    fields, the principal's fields as principal.* inputs). Policy.decide is
    an Effect of true, false or { allowed: false, reason }; a visitor is
    Option.none(). G.Principal is the server's Context key for who is asking.
    Guards.attach(G, { row, interactions, members, relations, always })
    attaches guards to an Entity per surface and interaction (Write for all
    three writes; one the application names, Interaction.make('Archive'),
    under interactions: { [Archive.key]: … }), as metadata:
    a list is a disjunction, Guards.all a conjunction, always is ANDed
    after every surface, and Guards.on with redact or explain chooses a
    refusal other than the surface's default. A member the Entity lacks, a row
    guard over another Entity, a guard of another set, or a redacted value of
    the wrong type is a type error. Guards.matrix prints every surface and
    Guards.guardFields names what a row interaction's guards read.
    A visitor passes no row guard that reads a principal's fields, and does
    pass one over the row alone (isPublished).
    Policy.readable(entity, principal) decides a principal's read rules into a
    Condition over the row, every principal guard asked once; Condition.holds
    runs it over stored values, and Policy.readMembers decides each member's
    own Read rule with its refusal. foldkit-guard/server enforces them:
    GuardServer.memory(Policy, entities) gives RemoteServer.memory its
    readable and filter, so a refused row is absent from reads, queries and
    relations (an inherited Read decided by the related row's own rules) and a
    refused member is withheld or redacted; GuardServer.filter is a Source's
    filter. foldkit-guard/drizzle's GuardDrizzle.visible(Policy, entities)
    compiles the row's Read rules into each binding's visible, an inherited
    Read as a subquery over the related table's own rule, checked when built
    (the inherited Entity listed and ruled, no loop). A relation's Read rules
    are decided over its parent row, as a member's are, and withhold the
    relation where they refuse; over Drizzle, member and relation Reads are the
    Source's filter (source(binding, { filter })).
    GuardServer.validate(Policy, { entities, domain, server }) checks at
    startup that every guarded Entity's Source, and each query listing it,
    applies the rule and filter those seams built for it from that Policy, and
    throws naming each that does not. RemoteServer.handlers(server, G.Principal) reads who is asking per call, and Policy.keyOf(principal)
    names a principal by every field its rules may read, a visitor included,
    for a live hub's key. Policy.writable(entity, principal, interaction),
    Policy.writeMembers and Policy.linkable decide write rules the same way,
    a body told which interaction it is asked about.
    GuardServer.writer(entity, Policy, writer) checks them on a declared
    write before it lands: an update or a delete reads the row first as its
    writer may see it (a hidden row's update is refused, its delete changes
    nothing), the row's rule must hold over the row and, for an update, over it
    as it would be after; each member set needs its own rule, each relation
    pointed elsewhere its Unlink and Link; a create is checked over what it
    proposes, an unset field matching no guard. A refusal is Forbidden, with
    an explain's sentence. presets ({ ownerId: user => user.id }) sets a
    field from who is asking on every create, before the rules are checked;
    an input that sets one is refused, and a visitor has none to give.
    GuardServer.validate also refuses a declared write of a guarded Entity
    that does not land through the GuardServer.writer built for it.
    GuardServer.mutation(M, { entity, policy, interaction, id, source }, run)
    is a handler's Source with the row's rule for interaction (a named one
    such as Archive.key included) checked over the row id names, read
    through the guarded source, before run is given it; with no id, of
    who is asking alone. Each row of the Entity its answer carries is read
    back through source, one the handler hid from its writer answered as
    deleted. Guards.may adds a derived member, may, which
    GuardServer.filter answers per row as its reader sees it: the row's
    Update, Delete and named interactions, each member's own Update, each
    relation's Link and Unlink. May.can, May.canSet and May.canLink
    read it on the client, a member or relation following the row's Update.
    It is a hint read with the row; the write is checked when it is made.
    May.locks(entity) is foldkit-crud's locks for it.
    foldkit-guard/agent's GuardAgent.authorize and GuardAgent.available
    gate a foldkit-agent capability on the may of a row the Model holds,
    refusing until it is answered.
    foldkit-guard/sync rules foldkit-sync/entity's cell edits:
    GuardSync.document(edits, { readers }) is a document's audience, refused
    where it is declared when an edit could reach a reader its rows hide it
    from; GuardSync.authorize is a journal authorize of the principal's own
    guards, under its lock; GuardSync.admit and GuardSync.opens are an
    exchange's admit and opens, reading each edited row through the
    Entity's guarded Source.

  • foldkit-entity: Expr.in(value, among), whether a value is one of a
    list
    , written out or given as an input. SQL's in in every interpreter:
    a null value is unknown, so is no match against a list holding a null, and
    an empty list matches no row. The reference evaluator runs it (and is
    checked against SQLite's own in), foldkit-remote-drizzle compiles it for
    SQLite and Postgres (an empty list as false, which Postgres needs), and
    the TanStack example runs it. The conformance suite gains eight cases. The
    first slice of guard-DESIGN.md, whose membership rules need it.

  • foldkit-remote: verifyPlacement, a placement checked against the
    server.
    Remote.make({ verifyPlacement: true }) places as usual, fetches
    each list it placed an answer, a read or a live patch in too, and keeps any
    disagreement between the order placed and the server's first page in
    remote.placementMismatches and Data.inspect. Pending edits are not
    checked.

  • foldkit-remote: placeIn, where a row falls in a loaded list. By the
    body's declared order: before an edge, at a segment's end that closes the
    list, Outside what is loaded, or Unknown with why. A list whose body is
    placeable reads the fields its body filters and orders by, for it.
    foldkit-entity exports compareRows, the evaluator's comparison of two
    rows under an order.

  • foldkit-entity: Order.chosen, a sort the input picks, declared in the
    body.
    Order.chosen(input.sort, { title: Post.fields.title }) maps each
    name a { by, direction } | null sort can hold to a field, and a name
    without one does not compile. Query.orderFor(query, input) resolves it to
    fixed terms; the evaluator, foldkit-remote-drizzle's compiler and the
    in-memory server sort by it, and impact judges a list by the field its own
    input chose. The conformance suite has chosen-order cases.

  • foldkit-entity: a text field declares how it orders, and a query says
    whether a client can order as the server does.
    Collation.binary,
    .asciiFold and .locale(name), attached with Collation.of or on a term
    (Order.asc(field, { collation })); an id is binary by default. The
    reference evaluator compares text by code point (it compared UTF-16 code
    units), folds ASCII under asciiFold and refuses a locale;
    foldkit-remote-drizzle compiles each, in the order and the keyset; TanStack
    and LiveStore honour binary and refuse what they cannot say.
    Query.placement(query, input) is Placeable or NotPlaceable with why.
    The conformance suite orders text under binary and asciiFold.

  • Where rows without a value go is said, and agreed. An OrderTerm has
    nulls: 'first' | 'last', last ascending and first descending by default
    (Order.asc(field, { nulls }) to change it). The reference evaluator places
    nulls by it instead of refusing them; foldkit-remote-drizzle compiles it as
    NULLS FIRST/NULLS LAST and pages by it. That fixes paging over a nullable
    column on SQLite, whose ORDER BY put nulls first while the keyset predicate
    assumed last.

  • foldkit-entity: every resolved order is total. Query.orderFor ends
    it on the Entity's id unless the terms already read it, so ties break the
    same way on every interpreter; the evaluator used to leave them in row
    order while SQL broke them by id.

  • foldkit-sync/remote: a replica's cell edits as Remote overlays.
    RemoteEdits.make(Data, Edits).reconcile(model, edits, replica) shows each
    edit its row has not reached, holds one the journal absorbed while the
    cached row is below it, and lifts it once a read reaches it, returning the
    newly held and the replaced. shown lists them for a cell's marks;
    clear lifts them for a reset. foldkit-remote is an optional peer.

  • foldkit-sync: Sync.mount's afterUpdate runs after every live
    update, durable and reinstall included, and never in replay, for local state
    derived from the shared slice.

  • foldkit-remote: Data.overlays(model), the ids overlay shows now, in
    drawing order.

  • foldkit-remote: a broken live stream restarts itself. A transport
    failure or a gap restarts the live entry after Sync's backoff (50 ms,
    doubling to 5 s, ±20% jitter; retryBase and maxRetryDelay in the
    subscriptions options). It resubscribes from its cursor and refetches what
    the stream covers, since no server replays what it missed.
    Data.liveStatus(model, active) is Idle, Live or
    Reconnecting { attempt, error }, for a view.

  • foldkit-remote: Data.persistence keeps the cache across reloads as one
    wiring.
    It restores the snapshot stored under key(model) from a
    KeyValueStore, keeping what the Model already holds, and saves the cache
    under that key after each change (debounced, bounded by maxBytes). It saves
    only once that key's own snapshot is in, so startup never writes the empty
    store over it. Hydrated takes an optional from, the key it came from, and
    RemoteModel.restoredFrom records it.

  • foldkit-remote-drizzle: applyEdits(binding, { live }) publishes what
    the journal applies.
    Each edit it wrote is told to the hub (the member and
    the revision); one the row had passed is not.

  • foldkit-remote-drizzle: a retried guarded write does not conflict with
    itself.
    When a write's guard misses, the writer reads the row: one
    revision past the expected one with exactly these values means it landed,
    and the row answers. A guarded delete of a row already gone succeeds.
    EntityWriter.delete answers 'deleted' | 'absent' | 'moved'.

  • foldkit-crud: an editor over a write that expects a revision follows
    it.
    After its own save is applied, the form takes the revision the save
    moved the row to, so its next save is not a conflict with itself; a revision
    moved by another client reads as the new status Moved, the draft kept.

  • Declared inserts and deletes. Write.insert(input, { id }) writes a row
    under an id the client chose and Write.delete(input, { id, expect? })
    deletes one; Mutation.write takes any kind (it was Mutation.update, and
    RemoteServer.update is now RemoteServer.write). An insert retried finds
    its row and answers it; a delete of a row already gone succeeds; a delete
    from a moved revision is a conflict. EntityWriter gains insert and
    delete, and remote-drizzle's writer implements both.

  • A declared write points a one relation. Write.update takes a key
    mapped with Relation.input to a one relation; Write.bind returns it in
    links as a ref (or none). Remote patches it as the ref key
    (patchOfWrite), and remote-drizzle's writer sets the foreign key.

  • foldkit-remote-drizzle: applyEdits(binding), a journal's apply. One
    committed cell edit written to the member's column with its sequence as the
    revision, never moving a row back. The registry example's hand-written SQL
    and column list go; its ProductTable.apply returns an Effect.

  • foldkit-crud: an editor over a declared write sends only what changed.
    With a Mutation.write mutation, a save names the input keys whose value
    differs, by each key's schema, from what the form was filled with; Remote
    shows them until the server answers. EditorModel gains filledWith, and
    DomainLike.mutate an options argument.

  • foldkit-remote-server, foldkit-remote-drizzle: a declared write needs
    no handler.
    RemoteServer.write(mutation, writer) serves a
    Mutation.write: the input bound to its write, only the keys the client
    named, written through an EntityWriter and answered as the row's patch.
    writer(binding) is one update ... returning on the binding's table; a
    write that expects a revision is guarded by it and moves it on in the same
    statement, so the second of two writes from one revision is refused as a
    conflict. MutationSource.run receives the request's keys.
    foldkit-entity's Write.expected(write, value) is that revision, encoded;
    Write.expect now holds its field as well as its key.

  • foldkit-remote: Mutation.write(name, write), a mutation that is a
    declared Write.
    Data.mutate shows the write bound to the input until
    the server answers, unless given its own optimistic list. The new
    keys option narrows that to the input keys an author changed, and travels
    on MutationRequest.keys. MutationDescriptor gains an optional write.

  • foldkit-entity: Write.update declares what an input writes. Over an
    Entity.input, it names the key holding the row's id (and optionally the
    key holding the revision it was read at) and sets every other key mapped to
    a field. Write.writes is what it may change; Write.bind(write, value, keys?) the row and its new values, encoded as the store holds them.

  • foldkit-crud: the editor shows a server's refusal. A save refused with
    Refusal.field(key, reason) for one of the form's keys marks that key
    invalid with the reason, once per save; one refused with Refusal.conflict
    reads as the new status Conflict. EditableForm gains controls and
    Message.Refused, EditorModel refusedFor, and DomainLike refusal;
    foldkit-remote adds the guards Refusal.isField and Refusal.isConflict.

  • foldkit-remote, foldkit-remote-server: a mutation refuses with data.
    Mutation.make takes Refusal, a codec of what the server may refuse with
    (Refusal.field(key, reason), Refusal.conflict). A Source fails with
    RemoteServer.refuse(mutation, value); the server checks the refusal is
    that mutation's, encodes it, and sends it on RemoteMutationError.refusal
    (a 422 over JSON). Data.refusal(model, requestId, mutation) reads it
    decoded. MutationDescriptor gains a fourth parameter, Refused, and a
    Refusal field; MutationSource a Refusal codec.

  • foldkit-entity: Query.where refuses predicates that expand past
    maxQueryNodes (1,000) nodes.
    A shared node (Expr.eq(n, n)) is counted
    on each path that reaches it, because evaluate and the SQL compiler read a
    predicate as a tree: thirty nested levels were thirty-one objects and a
    billion nodes of work at every row.

  • foldkit-entity: Query.dependencies names each field's role. Beside
    fields, it returns predicate (fields that can change which rows match)
    and order (fields that can change where a row sits), typed
    QueryDependencies. A field that filters and orders is in both.

  • foldkit-primitives: place a popup on a viewport point.
    Placing.placeAtPoint is that point in the offset parent's coordinates.
    placeAtTrigger takes at: a point wins, and none places under the
    trigger. OnContextMenu carries no coordinates, so a right-click records
    its own.

  • foldkit-data-grid: a date column edits in a date field. A schema
    that decodes to a date (Schema.DateFromString) is CellEditor.Date.
    The draft the field opens on is the local calendar day, YYYY-MM-DD.
    toISOString is the UTC day, so a local midnight used to open the field
    on the wrong one.

  • foldkit-mixins-ui: AspectRatio and Icon. Both are a slot and a
    recipe, like Separator. AspectRatio holds content in a square, a 16:9
    frame, or a portrait. Icon hides a decorative glyph and names one that
    means something; Icons.glyph stays the mask for an element that already
    exists.

  • foldkit-primitives: place a popup once under its trigger.
    Placing.placeAt is the offset under the trigger's left edge;
    Placing.placeAtTrigger writes left and --fk-placed-top on insert
    and clears them on release. The panel reads the variable as top, so
    keepWithin can still flip. ListNavigation takes typeahead: false
    so a search field keeps its letters while arrows still move the pointer,
    and an optional commit so Enter activates the current item from that
    same handler.

  • examples/cloudflare: the whole stack in one deployable worker. Reads
    are Remote over D1 through foldkit-remote-drizzle; writes are Sync through a
    Durable Object's D1 journal, whose settle applies each committed operation
    (create, rename, toggle, delete) to the table Remote reads back; live is a
    polling LiveSource that re-reads D1 from inside the subscriber's own
    request. pages/ is a Pages site that serves the list. It forwards
    /remote to that Worker and calls the Worker's Durable Object for /sync
    (a service binding drops the socket's replies). pnpm demo prints the loop
    against real workerd and
    pnpm test asserts it, both on miniflare over real HTTP.

  • foldkit-sync/do: serve one document's exchange from a Durable Object.
    defineDocumentHost(DurableObject, { sync, openJournal, resolvePrincipal })
    defines the single-writer class: each accepted socket exchanges against the
    same journal, opened once on the first upgrade over D1 or object storage.
    Sync.transport.workerSocket wraps an accepted WebSocketPair end anywhere
    else. Proven in-process and, bundled, against workerd with D1.

  • foldkit-remote-drizzle: proven over Cloudflare D1.
    The same reads run through drizzle-orm/d1 with no adapter changes
    (databaseLayer(drizzle(env.DB))); test/d1.test.ts covers refs,
    collections, windowed pages, keyset queries, compiled contains, and
    typed writes against a miniflare binding.

  • foldkit-durable: the journal over Cloudflare D1 (d1: true).
    D1 refuses transactions, PRAGMA user_version, and VACUUM, so the
    journal adapts when the options say so: the schema version lives in a
    durable_meta table (migrations are the same steps, each safe to rerun),
    commits retry on uniqueness conflicts instead of holding a write lock
    (concurrent appends still land gap-free), appendAll commits each
    operation on its own, snapshots replay only the prefix their cursor
    names, a tail a compaction cut short mid-read is refused rather than
    returned short, and vacuum succeeds without doing anything (D1
    auto-vacuums). Route appends through one writer: Journal.make (the
    node:sqlite file entry) refuses d1: true. Proven against a miniflare
    binding, which rejects what production D1 rejects.

  • foldkit-mixins theme: deeper shadows and a strength knob. The shadow
    scale gains a 2xl modal step (the Dialog recipe moved onto it), lg/xl
    diffuse wider instead of ringing, and every layer mixes toward transparent
    by the new shadow-strength knob (100% keeps today's look), so one
    override dims the whole elevation system. The design-system demo exposes
    the knob as Faint/Soft/Full presets in its header.

  • foldkit-remote-server/fetch: Remote over the Fetch API.
    serveFetch({ server, resolvePrincipal, layer }) is a Worker's fetch
    (or any Request/Response runtime): reads, queries, and mutations ride
    one POST each, as Remote.http sends them, and a live open rides one
    POST answered with a text/event-stream of LiveChange frames (one
    data: frame per change, a terminal event: error frame on failure).
    The principal is resolved per request and the Sources' requirements are
    provided per request from the environment
    (layer: env => databaseLayer(drizzle(env.DB))).
    Non-POSTs and other paths are 404; a body that is not JSON, or not a
    request, is a 400 that reaches no handler; a throwing principal
    resolution is a 401; anything else this side breaks is a 500 that says
    nothing of it. Disconnecting interrupts the subscription.

  • foldkit-remote: Remote.httpWithLive.
    Reads, queries, and mutations as Remote.http sends them, live as the
    event stream above, for Remote.clientLayer; abandoning the stream
    aborts the request. Remote.http also takes an optional fetch
    implementation for tests.

  • foldkit-mixins-ui: recipes for every remaining slot contract.
    Recipes.Menu, Listbox, Combobox (one shared popup panel, rows, and
    density), Select (the Input control's size and variant axes),
    RadioGroup (checked circle in a tone), Slider (toned fill and thumb),
    Toast (bottom-right stack, accented entries), FileDrop (dashed zone
    that tints on drag-over), DatePicker (trigger and panel), Popover
    (trigger and sized panel), and Tooltip (dark pill). The popup trio share
    an internal base; the file input hides off the root because a mixin class
    beside the component's own class does not reach the element.

  • foldkit-primitives: viewport placing for floating panels.
    Placing.placeFor(rect, viewport) (pure shift/flip geometry) plus the
    KeepWithin mount and keepWithin behavior, proven by five showcase
    islands. No timers, observers, or node relocation — inert without layout.
    Named apart from dom's KeepInView (scrolls content into view).

  • foldkit-mixins: motion knob on the shared durations.
    knob.motion (default '1') multiplies motion.fast/normal, so a
    still theme is one scoped override and every recipe transition follows;
    prefers-reduced-motion still wins through the reset. The control-size
    knob stays unbuilt: no height scale exists for it to multiply.

  • examples/widgets: Wave A showcase, eight thin compositions, live page.
    Toolbar (RovingTabindex + Collection), Toggle (ToggleState, no Bundle),
    ToggleGroup (single Selection), Accordion (Collection + per-item
    disclosure), NumberField (SpinValue + FieldAssociation), CheckboxGroup
    (multiple Selection over native boxes), Meter (stateless value/max), and
    Command (Collection + inline filter + ListNavigation + single Selection).
    Each with tests beside a mutation spec; one makeElement island per
    widget in src/entry.ts (pnpm dev, vite build clean); a jsdom test
    boots that same entry and answers one interaction per island. 44 tests.
    Notable finds: no Filter.text helper exists in source (the filter stays
    a three-line function of the Model); Disclosure names one pair, so N
    sections derive its attributes per item; embed replaces its container,
    so the test queries the drawn page.

  • Wave B (examples/widgets): alert-dialog, autocomplete, otp-field,
    hover-card, context-menu, menubar, navigation-menu.
    Seven more thin
    compositions: explicit-response and nonModal Overlay policies get their
    first consumers; right-click (OnContextMenu) and hover (OnMouseEnter /
    OnPointerLeave) arrive as triggers; roving cells carry an OTP with a
    Backspace handoff. Pointer-exact positioning stays open. 96 tests.

  • foldkit-primitives: Overlay policy over the existing pieces.
    Overlay.modal / Overlay.nonModal (or an explicit Policy) into
    Overlay.behaviors(...), spread into the view's pipe: DismissLayer
    marking, FocusScope, and the scroll/inert mounts the policy keeps —
    nothing reimplemented. Positioning stays per-widget, presence stays CSS.
    Proved by examples/drawer (modal settings panel: Escape/outside-press
    dismiss through the stack, focus contained and restored).

  • foldkit-mixins-ui: DatePicker adapter over a view fork with a toView
    seam.
    DatePickerView.view keeps upstream's Popover+Calendar composition
    (trigger face via triggerContent, grid via toCalendarView) and seams
    only the popover assembly (pinned @foldkit/ui@0.165.0, attributed):
    state, Messages, update, Commands stay upstream, and a parity battery draws
    both views over the same models (incl. ISO hidden inputs and a full
    day-select through the fork). DatePicker adapts the fork (trigger/
    panel/backdrop slots; the adapter is concrete over the date picker's
    own Message since the bundles mix pre-bound popover groups with plain
    attributes) with a DatePicker A11y pattern and catalog entry.

  • foldkit-mixins-ui: Combobox adapter over a view fork with a toView
    seam.
    ComboboxView.create/Multi.create bind the upstream bundles and
    swap in transcribed markup assembly (pinned @foldkit/ui@0.165.0,
    attributed): single and multi share one transcription, proved by a
    three-view parity battery (incl. typing through the input, disabled
    handlers dropped, and multi staying open on select). Combobox adapts the
    fork (11 slots: wrapper/inputWrapper/input/toggleButton/backdrop/items/
    scroll/item/group/heading/separator) with a Combobox A11y pattern and
    catalog entry.

  • foldkit-mixins-ui: Listbox adapter over a view fork with a toView
    seam.
    ListboxView.create/Multi.create bind the upstream bundles and
    swap in transcribed markup assembly (pinned @foldkit/ui@0.165.0,
    attributed): single and multi share one transcription (their Models are
    structurally identical), proved by a three-view parity battery (upstream
    single/multi plus the forks, incl. selection display, readonly, hidden
    form inputs, and multi staying open on select). Listbox adapts the fork
    with a Listbox A11y pattern and catalog entry.

  • foldkit-mixins-ui: Toast adapter over a view fork with a toView seam.
    ToastView.make binds upstream's Toast.make for the payload and swaps in
    transcribed markup assembly (pinned @foldkit/ui@0.165.0, attributed):
    state, Messages, update, Commands, and Mounts stay upstream, and a parity
    battery draws both views over the same models (empty/entries/classes plus a
    full dismiss lifecycle through the fork). Toast adapts the fork
    (ToastSlots: container/entry; the dismiss control stays inside the
    consumer's entryToView), with a Toast A11y pattern and catalog entry.

  • foldkit-mixins-ui: FileDrop adapter, no fork needed. FileDropSlots
    (root, input) with FileDrop.resolve/FileDrop.toView over
    upstream's own FileDropAttributes seam, plus a FileDrop A11y pattern
    and catalog entry. The root's drag handlers have no Event tokens, but the
    resolver still refuses a second owner by event name (proved by a test).

  • foldkit-mixins-ui: Menu adapter over a view fork with a toView seam.
    @foldkit/ui/menu builds its markup internally, so MenuView transcribes
    its markup assembly from the pinned @foldkit/ui@0.165.0 source
    (attributed in the module) with the missing toView added: state,
    Messages, update, Commands, and Mounts stay upstream, and a parity battery
    draws both views over the same models (closed/open/disabled/grouped/scroll
    plus a pointer-open click-select through the fork). Menu adapts the fork
    (MenuSlots: wrapper/button/backdrop/items/scroll/item/group/heading/
    separator; Menu.resolve/Menu.toView in the established nested-adapter
    shape), with a Menu A11y pattern and catalog entry. If upstream gains a
    seam, the fork goes away.

  • foldkit-site: incremental node building. Site.node(router, case)
    returns a builder (.title(...), .section(...), ..., .node) for nodes
    annotated in steps or annotation bundles shared across nodes. Methods, not
    pipeable fragments: each stage of a pipe is a separate generic call and
    cannot infer the route, while a method already knows its node's. Named
    bundles compose through the builder's .pipe (or free pipe) with no
    annotation. Every
    step freezes, so intermediates are usable nodes too. Site.route's options
    are sugar over the builder — one implementation, proved by behavioral
    parity tests.

  • foldkit-site: Site.sourcesFor, the route-level Sources for one
    destination.
    sourcesFor(site, owner, place, target.route) is the same
    entries as Site.sources but only the target's chain — what a prefetch or
    SSR preparation reads, prepared caller-side with Data.satisfy({ ...model, route: target.route }, active). Unknown tags resolve to no Sources.
    Deliberately no SitePlan object yet: the chain is already Site.chainOf,
    and head/access metadata arrive with their real consumers. Hover or
    programmatic prefetch adoption in an app is the next slice.

  • foldkit-site: the SSR connection, proved by composition, no new API.
    URL → route → target Model → Site.sourcesFor → Data.satisfy → an SSR
    plan over those Sources with Remote's resume part renders the destination
    carrying its data (packages/site/test/ssr.test.ts): the route determines
    the route-level SurfaceSources, preparation fills them, and the envelope
    captures what they resolved. The browser takeover stays proved in
    foldkit-ssr.

  • foldkit-bundle: Bundle.follow, an ask that waits for its owner.
    follow(placed, { pending, release, ready, toMessages }) wraps an update
    result: while a pending ask waits and its owner is ready, the ask goes
    through the owner's own Messages (Commands merged after the result's own)
    and is let go. Otherwise the result passes untouched, pending kept — an
    address can ask something of a child with no Model yet, once it has one.

  • foldkit-site (new, 0.1.0): the deployed route topology as data.
    Site.route(router, case, options?) declares one location from its Foldkit
    Router; Site.mount/Site.make freeze the hierarchy; Site.target builds
    the route value and URL from one declaration; Site.chainOf/nodeOf and
    parentOf/ancestorsOf/depthOf inspect it; title/section annotate
    once with titleOf/sectionOf readers, landing gives each section its
    nav address (Site.landing), and shortcut names the keys that go there;
    Site.historyOf declares push-vs-replace once; and Site.sources lowers
    surfaced nodes to Surface.when values for Remote, SSR, and retention. Site.routing is the
    link-click and URL-change lifecycle as one wiring: internal clicks navigate
    per Site.historyOf, external links load, URL changes set the route field
    (or touch nothing on an echo), and the application's own update guards with
    Routing.reduces. Site.placement states one routed page once — its link
    drives placed.update and placed.view, and Site.routing informs it
    through changed when its route arrives. Per-route Bundle placement stays
    per-application until the next cut.

  • foldkit-composition: SurfaceBlock.families, one family per
    Surface Block.
    SurfaceBlock.families(catalog, { from, document }) is
    every Surface Block on the page as one Surface.each family per Block,
    keyed by Block name with each instance keyed by its node id — for
    Data.wiring, Data.subscriptions, Data.satisfy, and SSR plans, wherever
    per-node requirements, retention, and coverage matter rather than one
    combined read. Fetching follows the Document, draft edits included.

  • foldkit-ssr: plans take SurfaceSources, and coverage inspects each
    instance.
    A Surface.each family is one entry of a plan's surfaces;
    the envelope carries its parent's reads with its instances', and
    SSR.inspect covers the parent once and each instance by key (Member[u1]
    with its via parent), so a gap names the instance. Preparation stays
    caller-composed: Data.satisfy the Model, then render it.

  • foldkit-remote: subscriptions, satisfy, and diagnostics follow
    SurfaceSources.
    The active record taken by Data.subscriptions,
    Data.wiring, and Data.satisfy accepts Surface.each families beside
    Surface.at/Surface.when values. A family is one record entry whose read
    unions its parent's requirements with its instances', so the parent is
    fetched before what it reveals and Data.satisfy reaches the instances in
    a later pass. RemoteUnsatisfied, Data.explain, and Data.why name a
    still-reading instance as Child[key].

  • foldkit-surface: Surface.each, one Surface as many keyed instances.
    Surface.each(child, { from, instances }) instantiates a child Surface per
    item of a parent Surface's Model — a stored page's Blocks, say — where
    Surface.at/Surface.when give zero or one. The family keeps its from
    parent, so consumers can tell the requirements needed to discover
    instances from the requirements of them; it refuses empty and
    __proto__ keys and duplicate identities. SurfaceSource is the shared
    shape (at/when values now resolve their lone instance through it, keyed
    by Surface name), read with Surface.instances(source, model).

Changed

  • Breaking: foldkit-sync: an exchange's refuse is admit, and an
    exchange may be refused whole (opens).
    admit(operation) answers as the journal's
    authorize does (true admits; a refusal, with its reason, is rejected by
    id) and may answer with an Effect, for a rule that reads the rows an edit
    names before the journal's lock. opens, an Effect of a boolean, fails
    every exchange of a connection that may not open the document.
    defineDocumentHost takes both as functions of the principal, which its
    refuse was never given.
    Sync.presence.serve takes opens too: a connection joins the presence
    hub only once it may open the document, and one refused is relayed
    nothing, either way.

  • foldkit-cms-drizzle: the audience boundary is a foldkit-guard guard set.
    CmsServer.make takes policy and authors (principal guards) in place of
    isAuthor, and transitions (principal guards per transition) beside
    allow, which stays for a rule about the entry; the principal is the guard
    set's Option. Each content entry names entity, its Entity with guards
    attached; its source carries GuardServer.filter, and make refuses a
    publishable type whose Read rule does not read its published field, or
    whose binding's visible is not the guard set's (GuardServer.validate).
    published(column, isAuthor) is gone: bind with GuardDrizzle.visible. A
    refusal of who is asking is Forbidden, with its sentence. The casts of an
    untyped principal are gone: one that is not the set's is a defect.

  • foldkit-cms: an entry's createdAt declares binary. It is an
    ISO-8601 instant, so code-point order is time order, and CmsEntries
    orders by it: a saved or created entry is now placed in the worklist
    instead of refetching it. On Postgres the order compiles with
    collate "C".

  • foldkit-remote: placement reads a row's id from its key. Every order
    ends on the id tie-break, and a row whose Selection never stored id was
    unplaceable, so its list was fetched again: the entity example's post list
    was, after every create.

  • examples/entity: WritePost and DeletePost write in one
    transaction.
    The author was inserted before the post, and the comments
    deleted before the post, each on its own, so a second statement that failed
    left the first. They use Drizzle's SQLite transaction, which is synchronous.
    foldkit-remote-drizzle's README says why the adapter wraps no
    transaction: the drivers disagree on what one is.

  • foldkit-remote: an inserted row is drawn where its list's order puts
    it.
    A prepend/append connection change and a live ConnectionInsert
    say a row is in a list, not where; in a list the client can place, a read
    draws it in order, or leaves it out past a Cursor. One it cannot place
    stays at the end its change names. Breaking: Remote.visibleItems is
    replaced by Data.visibleItems(remote, connection), which draws a list as
    its reads do. examples/cloudflare drops orderedPage, its own sort of the
    page.

  • examples/entity, examples/registry: verifyPlacement is on in
    development and tests
    (import.meta.env.DEV). The entity HTTP test checks
    a rename against SQLite's order, and every registry page test asserts no
    placement disagreed with the server's.

  • foldkit-remote: a mutation's answer places a row new to the client, and
    one that moves in from past a Cursor.
    A row joined a list only through
    a filtered field, so a created row never joined a list that filters on
    nothing, and a row past a list's loaded rows whose order now put it among
    them stayed out. EntityChange.Changed gains created.

  • examples/cloudflare: AllTodos declares its order in its body, and
    title declares binary. A create's answer names no list: the client
    places the row by title instead of the server's prepend.

  • examples/entity: AuthorsQuery declares its body too, a name search
    by id, so an author a nested write creates joins the loaded list instead
    of refetching it. With the id fix above, the example's tests invalidate no
    list.

  • examples/entity: PostsQuery declares its body (Query.define, a
    title search and Order.chosen), and title declares binary, in place of
    the server's where and orderBy. A rename moves a post in a list sorted by
    title with no list request; the list was refetched before.

  • foldkit-remote: a pending edit, a read and a live patch place the rows
    a list holds.
    A pending optimistic patch or Data.overlay to an
    ordering or filtered field shows its row where the edit puts it, or out of
    the list, until it settles; the server's pages are untouched. A read or an
    EntityPatched with a new value moves a held row in the pages. Before, the
    row kept its old place until the list was fetched again, so a registry
    price edit stayed out of order even after the server had it. Neither brings
    a row into a list.

  • foldkit-remote: a mutation's answer is placed in the lists it changed,
    not refetched.
    A held row that stops matching leaves a list; a row that
    joins, or a held row whose ordering field changed, goes where the body's
    order puts it among the loaded rows (placeIn), or out of them past a
    Cursor. A list is still invalidated for what cannot be placed: no body or
    no declared order, text with no collation, a row it cannot judge, or a row
    an overlay shows. Impact gains Placed, and is a Data.taggedEnum.

  • examples/registry: its text columns declare binary, as its SQLite
    orders them, so its sorted page is placeable.

  • foldkit-remote-drizzle: a page's cursor holds the order's key values
    where it ended.
    It was the last row's id, which the next page looked up:
    "load more" failed once that row was deleted or stopped matching, and
    continued from the wrong place once it moved. A cursor (k:[...]) is
    validated as client input; one from before keys is still read as an id.
    Relation pages keep their id cursors.

  • foldkit-entity: an OrderTerm is tagged Term, and a body's
    orderBy holds Orderings (Term | Chosen).
    An interpreter walking it
    resolves Query.orderFor(body, input) first.

  • foldkit-remote-drizzle: a body that declares its order refuses an
    orderBy beside it.
    Overriding the body's order was allowed; it let the
    server sort one way while the client read another. A body's order is
    compiled per request now, since a chosen order depends on the input.

  • examples/registry: its sort is in ProductsQuery's body
    (Order.chosen), not a server orderBy function.

  • foldkit-remote: a list whose body declares no order is invalidated by
    any change to a row it holds.
    Its order is the server's (an orderBy
    on the server, say), by fields the client cannot see, so a change it judged
    harmless could have moved the row. It was kept before, and the row showed
    its new value in its old place.

  • examples/cloudflare: a tick of the live poll reads only the rows
    written since the last.
    Triggers log each row a write touches
    (migrations/0003_row_changes.sql, the last 1000 entries); the poll asks
    the log first, re-reads only those rows, and reads the list's window only
    when the stream opens. pollLive takes an options object (interval,
    list, changes, a RowLog). The local stack runs the migration files
    rather than repeating them. Apply the migrations before deploying the
    worker; until then it reads every tick.

  • examples/registry: the rows the grid draws follow the table live. The
    journal's writes are told to a hub (applyEdits(Db.Product, { live })); the
    dev server answers through serveFetch, live streams included, and the
    client uses Remote.httpWithLive. A replacement said both by a read and by
    the slice is listed once, the slice's, which names the author.

  • foldkit-sync/remote: replaced covers an edit still in the slice. A
    read reaching this replica's edit with another value says it, held or not,
    and this replica's landed edit is kept (showing the row's own value) until
    the slice drops it, so a later edit arriving live is seen replacing it.

  • examples/registry: the rows read survive a reload. Data.persistence
    keeps Remote's cache in localStorage, so a reload with the server out of
    reach paints the last rows read, the pending edits over them.
    mountRegistry's resources also provides a KeyValueStore.

  • foldkit-sync/entity: the overlay helpers are gone; foldkit-sync/remote
    shows the edits.
    overlay, held, newlyHeld, settled and shows are
    removed: RemoteEdits does their work over Remote, where every read draws
    it. EditableEntity.make's value also carries the entity it was made over.

  • examples/registry: the edits are Remote overlays. The retired field,
    retiredOf, retiresAny, settledOf and the edits laid over rowsOf are
    gone; RemoteEdits reconciles in afterUpdate, so any read of a product,
    not only the grid's row, shows its edit.

  • foldkit-remote: a gap stays until its stream restarts. An in-order
    event after a gap used to clear it, though the event that arrived ahead was
    dropped and what it said was lost. Now only the restart's GapCleared
    clears it. LiveDependencies gains restarts and failures, and
    RemoteModel gains streams.

  • examples/cloudflare: the stored list is Data.persistence. The
    hand-written save Subscription and the boot-time applyCache are gone; the
    wiring restores each actor's snapshot (snapshot: snapshotFor keeps the
    visible edges) from KeyValueStore.layerStorage(() => localStorage). The
    rows now arrive just after the first frame instead of in it.

  • examples/entity: the post edit is a declared write. EditPost is
    Mutation.write over Write.update, its editor relation included, and the
    server serves it with RemoteServer.write(..., writer(Db.Post)); the Drizzle
    handler goes. The delete stays a handler: it removes the post's comments too.

  • examples/cloudflare: rename and toggle are declared writes. Their
    Drizzle handlers and hand-built optimistic patches are gone:
    Mutation.write and RemoteServer.write(..., writer(Db.Todo)) say the
    same, and the rename editor sends only the title it changed.

  • foldkit-cms, foldkit-cms-drizzle: refusals are data, not prefixes.
    SaveDraft and Publish declare Refusal: Cms.refusal.field(key, reason)
    for a taken address on its key, Cms.refusal.conflict for work saved or
    published since. The editor reads both with Data.refusal. Cms.slugTaken
    is removed, and the CmsConflict: and CmsSlugTaken: message prefixes are
    gone: a message is now only for a person to read. EditorDomain gains
    refusal. RemoteServer.refuse takes an optional message for that person.

  • foldkit-remote: a mutation's answer invalidates the loaded lists it may
    have changed.
    Each patch is judged against every loaded list over its
    Entity by the list's query body: a list stands when its rows and order are
    provably unchanged (a rename, a row that still matches), and is otherwise
    marked stale for its read entry to fetch again, restarting a read of it in
    flight. A list over a Query.make query, with no body to judge by, is
    invalidated by any change to its Entity, so such lists now refetch after a
    mutation of their rows. A live ConnectionInvalidate also restarts a read in
    flight now.

  • foldkit-remote-server: a mutation Source is given the client's
    requestId
    , which the client already sent and which is the same on every
    retry of a request. Record a write under it to recognise a retry; nothing
    deduplicates by it yet. foldkit-cms-drizzle runs the application's own
    mutation, when it publishes, as publish:<entry>:<revision>.

  • foldkit-remote-server: handlers given a live hub publish every
    mutation's answer to it.
    Each answered patch is a change of the fields it
    carries and each deletion a deletion, re-read per subscriber as before. Delete
    a hub.changed call that follows a mutation's write, or subscribers hear the
    change twice; keep one for writes that are not mutations. A failed publish is
    logged and the mutation still answers, since its write has happened.

  • examples/cloudflare page: the Pages site is a Foldkit app. The list is
    Crud.list over the AllTodos query. The first visit says "Loading…". A
    later visit paints the rows stored in the browser at once and asks again.
    Add, rename, toggle, and delete are Remote mutations; Data.mutate applies
    the optimistic patch before the POST returns. Another tab hears the change
    on the live stream. Sync through the Durable Object stays for the demo and
    the tests; both writers use the same todos table.

  • foldkit-sync/do: settle now takes the environment and the journal.
    settle: (env, journal) => Effect is called per exchange with the object's
    own environment and the journal it serves, so applying what committed to a D1
    table needs no closure over a binding captured elsewhere.

  • foldkit-remote-server/fetch: live streams heartbeats. A : ping
    comment opens every live stream and follows every liveHeartbeat
    (default 15 seconds), so an idle stream is neither dropped by a proxy nor
    cancelled by a worker runtime. The heartbeat stops with the events it
    serves, so a stream with nothing to serve still ends.

  • foldkit-mixins: Theme.oklch's families are one shape, and hold up in a
    dark scheme.
    Every family has default, hover, active, subtle,
    outline, on-fill and ink; outline steps away from the page in each
    scheme, and the feedback fills are lighter in a dark one (the error fill
    keeps light text). surface.bedrock, a dialog's backdrop, is the deepest
    surface in a dark scheme too, where it was near-white, and outline.focus
    is the accent itself rather than a darker, more saturated shift of it.

  • foldkit-mixins/defaults: native checkboxes, radios and sliders take
    the accent, selected text is the accent's tint, kbd is a raised key, hr
    one quiet line, and inline code sits on surface.muted.

  • foldkit-mixins-ui: recipes over the palette. A neutral Checkbox or
    Switch that is on is in the page's ink (tones.ink), where the neutral
    surface drew it paler on than off; the neutral tone's fill is a quiet grey
    and an outlined control draws its tone's line, so a neutral outline can be
    seen; a chosen pill tab is the accent's tint and ink; the danger tone hovers
    to error.hover. pnpm --filter foldkit-mixins-ui specimen writes a page
    of every recipe in every variant and state.

Fixed

  • foldkit-durable: concurrent appends over D1 commit. A commit that lost
    a race for its sequence retried at once and at most ten times, so with a
    dozen writers on one document one could lose every round and fail with a
    JournalError; it retries up to 64 times, spaced and jittered.

  • foldkit-guard: GuardServer.writer refuses a writer for another
    Entity
    where they meet, rather than leaving it to validate; the README's
    limits say a write's check and the write are two statements.

  • foldkit-guard: a handler's answer naming a row with no values keeps
    it.
    The read-back asked for no field, which Drizzle's Source answers with
    no row, so the row was answered as deleted; it reads the id too.

  • foldkit-remote: a list an answer names still has its other rows
    judged.
    A list the answer put a row into was skipped whole, so a second
    row of it the same answer moved was neither placed nor fetched; only the
    rows the answer itself placed are taken as the server says now.

  • foldkit-entity: an order over a number a row may lack is placeable.
    Query.placement read NullOr(Number) (and OptionFromNullOr(Number)) as
    text with no collation, so such a list was fetched on every change; where
    nulls go is said on every term, so it is placed now.

  • foldkit-remote-drizzle: asciiFold folds only ASCII on Postgres. It
    ordered by lower(x) COLLATE "C", where lower still follows the column's
    collation and, under a UTF-8 one, folds É to é; the client's evaluator
    and SQLite's NOCASE do not, so a client placed rows where Postgres would
    not. It is lower(x COLLATE "C") now, the cursor's values folded alike, as
    contains already did.

  • foldkit-remote: Data.persistence says what keeps one reader's cache
    from the next.
    A restore keeps what the Model holds, so a change of
    principal without Data.forget in the same update saved one person's rows
    under the next one's key; the README now says forget is required there,
    and that a snapshot is serialized on each change though its write waits.

  • foldkit-cms-drizzle: authors is asked as what the author is doing.
    Every operation asked its guards with interaction Read, so a body that let
    a reviewer read drafts let them save and publish too. An operation now asks
    Update, and reading unpublished work Read.

  • foldkit-sync: a connection whose access went hears and speaks no more.
    Presence asked opens once, at join, so a peer kept relaying presence after
    its access was taken; it is asked again with each frame, and a refused peer
    leaves. serveJournal sent a refused connection a notice of every commit,
    which woke it into a failing exchange and told it when the document
    changed; notices now pass opens too.

  • foldkit-remote: Remote.http reads a middleware's { error } 403 as
    Forbidden.
    A 403 whose body happened to be shaped as an error answer was
    taken for Remote's own, an ordinary mutation error; a 403 is now forbidden
    unless its body says so itself, as Remote's server's does.

  • foldkit-remote: placing a change no longer redraws or refetches what it
    left in place.
    A row whose order field changed but whose place did not
    still gave its list a new identity, so every read of it was drawn again and,
    under verifyPlacement, the list was fetched; it is now kept as it was. Each
    list's held rows were rebuilt for every change of a read or an answer; they
    are read once per list.

  • foldkit-guard: Policy.keyOf tells every claim apart. It keyed a
    principal by JSON.stringify of its rule inputs, so a set claim read as
    {} (an admin and a revoked admin shared a live hub's re-read) and a
    bigint threw. It keys by the principal encoded through Schema.toCodecJson.
    And beside Write, a slot of its own's refusal replaces Write's whichever
    key is written first; it hung on the order.

  • foldkit-cms-drizzle: a statement the database refuses no longer sends
    its SQL.
    Its statements ran as defects, as foldkit-remote-drizzle's
    writes did, so a refusal reached the client with the driver's error; they
    now fail as a RemoteServerError. Transaction's type says it may fail so.
    foldkit-remote-drizzle exports the helper, runStatement(statement, failed), for a handler's own statements, and its README's handler uses it.

  • foldkit-cms-drizzle: archiving an entry on show asks unpublish.
    Archiving takes a published row off show, as unpublishing does, but asked
    only archive's guards: an author transitions kept from unpublishing
    could archive a published post, and unarchive it as unpublished work.

  • foldkit-remote: placement no longer drops a row it cannot judge, and
    compares a number id as a number.
    A row that was the last edge before a
    cursor (or the first after one) was left out when it sorted past its
    page's other rows, though its own old keys were the boundary and might
    still hold it: it now invalidates the list. Placement read every id from its
    key, so a number id ordered as text ('10' before '9'): the stored id is used,
    and the key's only for an id stored as text. verifyPlacement also records
    a row placing took out that the server's page still lists, which comparing
    the shared prefix never could; placed now holds { order, dropped }.

  • foldkit-entity: a declared write binds the keys Entity.input lets
    map themselves.
    An input key that is an Option, or optional, type-checked
    and then threw in Write.bind. A some now writes what it holds, and a
    none or a key left out writes nothing; a write whose id may be absent is
    refused where it is declared.

  • foldkit-crud: an editor's later saves no longer lose another author's
    change.
    After its own save, the editor took any revision that came after
    as its own, so another client's later save was overwritten without a
    conflict; it compared each save with what the form was first filled with,
    so a key it had saved was sent again over another author's change; and
    typing while a save was in flight lost that save's answer, so the next save
    conflicted with itself. The editor now takes each save's answer once, as it
    lands (edited, sent and settledFor in its Model), and Moved
    outranks Saved. A create's form starts blank once it lands, so saving it
    again is not the same create twice.

  • foldkit-remote-drizzle: a keyed cursor over an id column of another
    name.
    A page read each order key back by its column's SQL name, beside an
    id selected as id, so an id column named otherwise (task_id) read as
    nothing and every page fell back to an id cursor. foldkit-remote-server:
    RemoteServer.memory refuses a shared entity the domain does not declare,
    or a field it does not, as RemoteServer.entity and source already did.

  • foldkit-guard: GuardServer.validate refuses what it could not
    check.
    A seam built from an Entity before its guards were attached (a
    GuardDrizzle.visible or GuardServer.filter given the base Entity)
    decided nothing yet passed as the guarded one's, since seams were recorded
    by name: they are now recorded by the Entity's attachment. A live Source of
    its own over a guarded Entity, a served query the domain does not declare,
    and a shared relation whose target is not validated are now refused.

  • foldkit-remote-server: the live hub sends a deletion only to whom a
    row is gone.
    A write that hid a row from its writer answered it as
    deleted, and the hub sent every subscriber that deletion, readers who still
    saw it included; one who lost sight of a row was sent nothing. deleted
    now re-reads each subscriber's selection and changed sends a deletion
    where the re-read finds no row. One subscriber's failed read no longer
    stops the rest. Breaking: LiveHub.deleted may fail and needs the
    Sources' services, as changed does.

  • foldkit-remote-drizzle: a write the database refuses fails as
    Database write failed.
    Writes, applyEdits and returning.row ran
    their statements as defects, which Effect RPC sends whole: the driver's
    error, with the SQL and its parameters, reached the client. They now fail
    as a RemoteServerError, the driver's error logged, as reads already did;
    returning.row's error type says so.

  • foldkit-guard: three write checks that let a write through. An
    update that unlinked a relation whose row is hidden from its writer read the
    link as none and skipped Unlink: the link is now read as stored, through
    a new EntityWriter.linked (foldkit-remote-drizzle's writer has it),
    and a writer without it treats a link it sees as none as one that may be
    there. A create's guard over a field it leaves unset held for isNotNull
    (and died on contains): such a guard now never holds. A write's answer was
    read back without the Entity's member Read rules, so it could carry a field
    its writer may not read: GuardServer.writer and GuardServer.mutation
    now answer through them, with no more fields than the write named.

  • foldkit-remote-drizzle: an insert under an id taken by another row
    fails.
    It answered with whatever row held the id, values its writer may
    not read included, and let the client think its own were stored. A retry,
    whose row holds what it writes, is still answered with that row.

  • foldkit-guard/sync: admit judges an operation's edits to a row
    together.
    Each edit was checked over the row with that edit alone, so
    two that each kept a row in its editor's reach, and together moved it out,
    were admitted. An edit to a member outside the document, or a value its
    member cannot encode, is now refused by the operation's id rather than
    failing the exchange; GuardSync.document refuses a member that is not a
    field.

  • foldkit-remote: a filter or live insert the reference interpreter
    refuses no longer throws in update.
    Data.filtered with such an input
    (a search holding NUL) answers no items and complete: false; a live
    insert into such a connection takes its declared policy.

  • foldkit-remote-drizzle: contains refuses searched text holding NUL
    when that text is an input or a literal
    , as evaluate does. It refused a
    NUL only in the search, and answered [] on SQLite and Postgres for the
    rest. foldkit-entity/conformance now exports refusals, bodies every
    interpreter must refuse, and the reference, Drizzle (SQLite and Postgres)
    and Remote matching suites run them.

  • foldkit-crud: any authored edit after a save returns the editor to
    Editing.
    It used to take only a typed field (Changed), so a nested row
    added or removed, or a Bundle-backed control's edit, left it at Saved or
    SaveFailed. The editor now asks the form (authoredChanged), and a
    Message that changes nothing returns its Model as it was.

  • foldkit-primitives: a popup placed in the same turn stays where it was placed.
    keepWithin and the placement write start together, so the fit runs again
    when the write lands and judges that box. A point that would hang off the
    bottom shifts up (Placing.shiftIntoViewport). A trigger still flips, and
    the panel carries data-fk-placed="above" when it does. Enter in
    ListNavigation commits an enabled current item.

  • examples/widgets: the hover bridge follows a flip. Navigation and the
    hover card overlap the trigger on the side the panel opened. Opening the
    context menu for the row it already shows returns the same model.

  • examples/cloudflare page: a new or renamed row is drawn where the
    title order will keep it. The list sorts by title, then id, which is what
    AllTodos asks D1 for, so a live insert does not move it.

  • examples/cloudflare page: the list is kept in the browser, one snapshot
    per actor. A later visit paints those rows immediately. A failed refetch,
    including offline, leaves them. The snapshot is the server's rows. An unsent
    edit is not kept.

  • examples/cloudflare page: an update no longer refetches the list. The
    one-second refresh, and the refresh after a successful write, flashed the
    rows. The page subscribes to the live stream. A field change is a patch. A
    row joining or leaving is a connection event.

  • examples/cloudflare page: saving, loading, and a field error keep the
    space they will occupy. "Saving…" and "Loading…" use Loading.shown, so a
    fast answer does not flash, and none of them push the form or the list.

  • examples/widgets: a context menu opens at the pointer. A right-click
    records the point, and the menu opens there. The keyboard, which has no
    point, still opens under the row. Navigation and the hover card keep a
    hit target across the gap, so the pointer can move onto the panel.

  • examples/widgets: hover, placement, and keyboard on the showcase.
    A click after a hover leaves that section open. A popup sits under the
    trigger that opened it, and reopening a menu keeps the chosen item
    selected. Enter in the command field runs the highlighted command. Shut
    accordion panels stay mounted. Absent values in these models are
    Option, and the page theme reads tokens.

  • Examples: the todo app's chosen filter is one color. Its pill drew a grey
    line around the accent's tint, and its text went to the default color on
    hover; it is the tint and the accent's ink, hovered or not.

  • foldkit-cms: an entry opened from the server is Loading until its state
    is read too.
    The editor counted as open once its entry, draft and row were
    read; the lifecycle state, a read of its own, could answer a moment later,
    and what it draws (the state badge, the history, Publish) dropped in after
    the form and pushed the screen down. Something new waits for no state.

  • examples/cms: the studio opens an entry whole. Opening a page or a post
    from its list kept the list's place empty but for the editor's bar while the
    entry loaded, then drew the rest at once; the list now stays until the
    editor can draw. An image on a page keeps its room (4:3 until it loads) so
    the text around it does not move when it arrives. The studio's editor bar
    and the site's header are solid in the page's color: translucent over a
    blurred backdrop, they drew darker and more saturated than the page.