Skip to content

v0.12.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 11:59
· 2380 commits to main since this release

foldkit-agent 0.5.0; foldkit-bundle 0.4.0; foldkit-cms 0.3.0;
foldkit-cms-drizzle 0.3.0; foldkit-durable 0.5.0; foldkit-entity 0.5.0;
foldkit-form 0.3.0; foldkit-mirror 0.4.0; foldkit-mixins 0.5.0;
foldkit-mixins-crud 0.5.0; foldkit-mixins-form 0.3.0;
foldkit-mixins-ui 0.5.0; foldkit-primitives 0.4.0; foldkit-remote
0.9.0; foldkit-remote-server 0.9.0; foldkit-richtext 0.2.0;
foldkit-richtext-dom 0.2.0; foldkit-ssr 0.2.0; foldkit-surface 0.5.1;
foldkit-sync 0.7.0; foldkit-remote-drizzle 0.8.1. Republished to re-pin:
foldkit-bundle-surface 0.2.1, foldkit-crud 0.4.1,
foldkit-mixins-richtext 0.1.1, foldkit-mixins-surface 0.4.1, foldkit-react
0.2.1 and the four agent adapters 0.4.1. Three packages are published for the first time at
0.1.0: foldkit-richtext-code, foldkit-richtext-code-shiki and
foldkit-richtext-markdown. foldkit-metadata and foldkit-react-codegen
are unchanged.

A page is assembled, not spread. assembly.runtime builds the runtime
config in one call from an update that already routes every placement, and
derived args come from the parent seed — so a routed page takes its search
text without a second fetch, and assembly.config is gone.

Remote asks once and says when. A query carries its selection and comes
back with the fields (Data.meta says when it arrived and whether it is
stale), and Data.satisfy prepares a Model for a render, which is what the
static build runs per page.

Static sites end to end. The browser entry loads no server renderer, a
takeover is an explicit decision (when/otherwise/fresh), a
determinism check renders every page across zones and locales, and the
staticSite plugin turns vite build into the whole static build.

An editor that replicates. Replicated folds character-identified ops
in any order to the same state, patchTo redraws a remote keystroke without
remounting, and task lists, overlays and decorations round out the canvas.

Views with less machinery. Input.field draws a validated field in one
call, FormView.fields draws per key with overrides, Button.view is one
call, and every adapter has toView; forms validate all without
submitting, and a submit waits or disables explicitly.

Styling without stylesheets. Slots declared by their style, cascade
layers as a value, a theme from a few knobs, Style.install,
Style.usedIn for the first paint, and a foundations plugin that writes
the sheet into the head.

State that survives. The journal pages history, vacuums itself, and
binds replicas to actors across schema 6; Sync exchanges over a socket that
reconnects, persists its outbox in IndexedDB v2, and takes server resets.

New packages

  • foldkit-richtext-code 0.1.0. Total grammars as tokenizers over the
    shared syntax-* kinds: a JSON lexer that never throws, for
    codeDecorations.
  • foldkit-richtext-code-shiki 0.1.0. Shiki grammars as tokenizers over
    the same kinds (colours discarded, scopes mapped, one stylesheet); the
    application owns the highlighter.
  • foldkit-richtext-markdown 0.1.0. Markdown round-trips for the editor
    document: task items, tight lists, and the words around them.

Upgrading from 0.11

  • Replace assembly.config with assembly.runtime (compose a narrow
    assembly.update(own) first where the parent adds Messages); placement
    factories now take the seed without the placement's own field.
  • Handle Loading and Failed from Cms.revisionsOf instead of an empty
    list; pass the RevisionHistory, not rows.
  • Send mutations with now, read answers as MutationAnswer, and read
    Data.more as an Option (Data.next/previous/fetch are gone);
    entry dependencies are typed, and an overlaid-missing field reads
    Failed, not Initial.
  • Expect Timer/Interval's first tick one interval after Started; match the
    new TimedOut/GoTo variants and the History group field.
  • Serve the envelope on the stamped root (not a pre-</body> script);
    SSR.entry takes no template/containerId/head and refuses meta
    plans at construction; the foldkit-ssr/replay entry is gone.
  • Open IndexedDB v2 fresh (old code cannot open it) and migrate durable to
    schema 6 (legacyReplicaId recovers actor-less replicas); the socket
    reconnects for as long as its layer lives.
  • Rebuild hand-built StyleValues through the brand; move the family
    text token to on-fill/ink (text['on-accent'] is gone).
  • Upgrade foldkit-mixins-surface with foldkit-mixins (its peer moved to
    ^0.5.0), and the agent adapters with foldkit-agent (^0.5.0).
  • A hand-written form control now satisfies Record<FormTag, …>; unknown
    FormView.fields keys are compile errors.
  • Stay on Effect 4.0.0-rc.116 (rc.117 is admitted untested); the peer range
    stops below rc.118, whose effect/rpc move breaks our
    effect/unstable/rpc import. The import moves with the workspace Effect
    upgrade, which widens the range again.

Breaking

  • foldkit-bundle: assembly.config is removed; shared tags reach every
    claimant and the parent (first-claimant-wins is gone).
  • foldkit-cms: revisionsOf returns RevisionHistory; historyCard
    takes it, not rows.
  • foldkit-remote, foldkit-remote-server: MutationSucceeded carries
    now; settleSuccess/reconcileMutation take MutationAnswer; query
    windowing is Data.more: Option with WindowGrown (next/previous/
    fetch removed); entry dependencies are typed; overlay-missing reads
    Failed.
  • foldkit-primitives: Timer/Interval tick late; TimedOut/GoTo break
    exhaustive matches; HistoryModel gains group.
  • foldkit-ssr: the envelope rides the stamped root; SSR.entry answers
    Rendered with a new contract; the replay entry is gone.
  • foldkit-sync, foldkit-durable: IndexedDB v2 and durable schema 6 do
    not open older stores; the socket reconnects while its layer lives.
  • foldkit-mixins: StyleValue is branded; the family text token is
    on-fill/ink.

Added

  • foldkit-mixins-ui, Badge recipe: a status pill with a dot, toned by
    an attribute's value — Recipes.Badge({ attribute: 'data-state', tones: { Published: 'success' } }).badge. Unlike the variant recipes every tone is
    present at once (one style serves badges in every state), so it takes the
    attribute and the value-to-tone map instead of a variant selection; values
    the map leaves out keep the base. The CMS demo adopts it for both badges
    and keeps only the state-to-tone map.

  • foldkit-mixins-crud, Loading/Empty/Failure views: what a read says
    before it has an answer, over any status slot — Loading.view is
    role="status" with aria-busy, Empty.view is role="status",
    Failure.view is role="alert", and Loading.shown fades busy text in
    late so a quick answer never flashes. ListView and DetailView draw
    these for their own states; the CMS demo adopts them for its lists, site
    reads, and editor branches.

  • foldkit-cms, entry views: the studio cards every CMS draws, on its
    own slot builders — Cms.stateBadge, Cms.revisionsOf, Cms.historyCard,
    Cms.moreCard, with HistoryCardSlots/MoreCardSlots/RevisionRow
    types. Who published and the archive icon stay the application's; words
    for states come from Cms.Display. The CMS demo adopts them and keeps
    only its slots, styles, and chair names.

  • foldkit-cms, entry views fixes: Cms.stateBadge writes a custom
    attribute for a new entry too, and Cms.revisionsOf/Cms.historyCard
    read a RevisionHistory so a failed or still-loading history no longer
    draws as nothing published. The CMS demo's stateIs copy is deleted in
    favor of Cms.stateIs.

  • foldkit-mixins-ui, Touch/Icons mechanisms: no slots, no views —
    Touch.target/Touch.targets floor controls at 44px where the pointer is
    coarse, Icons.glyph(size) draws the icon in --icon, and
    Icons.byAttribute(attribute, icons) sets it per value from resolved
    url(…) strings. The CMS demo adopts them and keeps only its Lucide
    paths.

  • foldkit-mixins-ui, Button primary/icon variants: the main action
    in ink (variant: 'primary', tone-independent) and the square icon-only
    button (variant: 'icon' with size: null, words as the accessible name).
    The CMS demo adopts them with ghost for its buttons and deletes its
    bespoke primaryButton/iconButton.

  • foldkit-mixins-ui, Segmented recipe: a tray of toggle buttons where
    pressing selects (role="group" + aria-pressed, not Tabs) — tray
    picks a muted tray or a plain row, size the text density, and the pressed
    option rises from the group so icon tiles share the rule. The CMS demo
    adopts it for its worklist tabs, viewport switcher, panel tabs, and
    inspector choices.

  • foldkit-mixins-ui, Input.view/Textarea.view: one call drawing a
    field — the value and Messages, a style, type/placeholder (rows for a
    textarea), and a draw placing the resolved bundles. The job application's
    button fork is deleted in the same spirit: its views call the package
    Button.view instead of their subset copy.

  • foldkit-mixins-ui, Input.field/Textarea.field: a text field in
    one call — a field's state drawn with its label, control, and description
    placed (first error, or the check while it runs), type/placeholder
    (rows for a textarea) riding through, and a draw placing the parts
    for layouts the default stack does not own. The waitlist example's
    per-kind overrides and the job application's field view adopt them and
    keep only their layouts and status marks; both now read the platform
    Checking… spelling.

  • foldkit-mixins-form, per-key element attrs: FormView.fields takes
    attrs beside overrides and styles — what one key's element takes
    beyond the base field view (type, placeholder, textarea rows). The
    default renderers forward what fits their element and ignore the rest, and
    overrides receive the same map. The waitlist example moves its email type
    there and drops the thread-through.

  • foldkit-mixins-form, submit gating: FormView.submodel takes the
    submit gate explicitly — canSubmit (a submit waits), a strict predicate
    (disabled through checks), or () => true (never pre-disable). The lenient
    default is unchanged.

  • foldkit-primitives, SSE view parity: SseView with isLive and
    viewOfSse(model, wanted), the same shape as the socket's SocketView —
    a page derives what the reader sees instead of keeping its own connection
    state.

  • foldkit-bundle, derived args: a placement's args is a static value
    or a factory from the parent seed, args: parent => ({ searchText: searchFromRoute(parent.route) }). The seed is what
    assembly.initial(rest) was given, before any placement initialised; the
    factory's parameter omits the placement's own field, and every factory sees
    the same base seed, so placement order never matters. The result is checked
    against the bundle's args Schema and retained for update, helpers,
    Subscriptions, and resources, never re-run against live state — a factory
    runs once per seed it has seen. On a Model
    no initialization produced, update derives per use without retaining.

  • Routing example, derived placement args: the People page is a Bundle
    placed once with its search text derived from the starting route, so
    init(url) is assembly.initial({ route }) with no second fetch and no
    post-init Message. Route changes after startup still arrive as Messages,
    folded through the same placement, and the view keeps its people slot.

  • foldkit-primitives, scroll keeping: keepScroll(options) in
    foldkit-primitives/dom — the window's scroll across an application's own
    navigations (the offset taken when the reader acts, a restore that holds
    while the screen settles, entries keyed by the Navigation API with offsets
    in sessionStorage). A stream sending no Messages, lifted with
    Subscription.persistent; the default storage key is foldkit:scroll.
    The CMS example adopts it in its studio and site subscriptions and deletes
    its hand-rolled scroll.ts.

  • SSR S1, browser entry without the server renderer: the CMS example's
    browser entry takes SSR and FOLDKIT_APP_ATTRIBUTE from
    foldkit-ssr/client — its hand-written root mark is gone, and the takeover
    plan rides the bundle instead of a lazy chunk (cheap now that it draws
    from the client side, with no server renderer). A static import walk from
    the entry fails on reaching foldkit/experimental/server.

  • SSR S6, build determinism: the CMS example renders every page twice
    under one time zone, once under zones at both extremes of the date line,
    and under two default locales in child processes — any difference fails
    the build. Building the check fixed two live leaks: generateSite takes
    the build's clock, threading it into the server and, via
    Data.satisfy's now, into the read stamps the envelope carries.

  • SSR S7, static site as a build step: foldkit-ssr/vite renders every
    path after the client bundle is written — generateStaticSite (paths or
    a thunk closing over what the site serves, per-path configs, template,
    head, flat/directory file layout, sitemap, robots) behind the staticSite
    plugin, which evaluates the site module through a server so the config
    names a file, never application code. foldkit-mixins/foundations writes
    the sheet module's stylesheet into the head the same way. The CMS example
    builds through both and deletes its hand-rolled script and plugin. Empty
    paths and two paths naming one file are refused before rendering.

  • SSR S5, deciding whether to take a page over: SSR.hydrate takes
    when (asked before anything is adopted), otherwise: 'render' (a
    declined page draws afresh where the served page is, replacing its markup
    in place; anything else contains it as a resume failure does), and fresh
    over the plan's new version (asked once). The CMS example collapses its
    takeover branch into one call: a page for another reader draws afresh
    instead of taking stale facts over as live ones.

  • foldkit-cms-drizzle, ImportItem<P>: the input of cms.import,
    named and exported — what a seed or a migration passes per entry (type,
    values, as, at?, entry?). The package's own tests and the CMS
    example's seed.ts read it instead of re-declaring it, and the README
    gains the seeding shape beside it: a fixed clock, named entries imported
    dependencies-first, as fixed once.

  • foldkit-bundle, assembly.runtime: the runtime config in one call for
    an application whose update already routes every placement. initial
    rest becomes init, or an init function returning
    assembly.initial(...) is used as init when the seed needs runtime
    input, like the URL. The update passes through checked (route a narrow
    one with assembly.update(own) first, or omit it when the parent adds no
    Messages); the own subscriptions and managedResources merge with the
    items', defaulting to the items', so an application that adds none passes
    neither. assembly.config is removed in its favour; the CMS
    siteConfig and the entity client use it. Passing an already-wired
    record (assembly.subscriptions()) back as own is a type error instead
    of a later duplicate-key failure.

Fixed

  • foldkit-bundle, placement typing: PlacedResources keeps
    onAcquired's parameters, so Scene.ManagedResource.acquire on a placed
    resource needs no stand-in value; a Link carries its top-level field, so
    placements.initial keeps its exact check through a custom Link, and an
    Option placement field may be given to start None.

  • foldkit-mixins, a slot named __proto__: forSlots kept its piece
    on a plain object, where the name set the prototype instead.

  • foldkit-remote, two policies without losing data: two
    Data.subscriptions calls over one domain no longer collect each other's
    data. Retention is the domain's: every call's retain entry roots the
    Surfaces and connections of every call, so reads under different policies
    can use a call each.

  • foldkit-remote, the default clock: it reads Date.now at each use
    instead of capturing it when the domain is made, so fake timers move plans
    and stamps. A read under staleWhileRevalidate planned at the exact
    millisecond a value expired no longer stalls.

  • foldkit-form, optional and Option keys: a key made with
    Schema.optionalKey and left empty is valid and left out of the value, as
    it already was for Schema.optional (an optional nested key too). A key
    typed Option (Schema.OptionFromNullOr) is edited as the value it holds
    and submits Option.some / Option.none(), instead of throwing "no
    control". A validation that changes nothing (a blur on an empty optional
    key) returns the Model it was given, so nothing redraws.

  • foldkit-mirror, a slow restore no longer loses the stored document: a
    key-value or kernel mirror holds each write until the Model has taken in
    what restore answered. Before, the write Subscription started from the
    initial Model and, once the throttle passed, replaced or deleted the stored
    slice while the restore was still reading it. The hold is per runtime and
    applies again on every restore; a failed read counts as an empty store.

  • foldkit-mixins, Inert.missingTokens reads inline styles: a
    var(--fk-…) in an inline style value is reported when nothing defines it,
    as a compiled rule's read already was; a token set inline still counts as
    defined.

  • foldkit-remote, query payloads in one response: Data.query(Query, input, { select }) sends the selection with the query, and the server
    returns the selected fields of the page's items with the edges. One
    ConnectionMerged merges the page and writes the fields, so a fresh list
    lands Ready without a second read. Edges-only servers still work: no
    selection sent means edges only, and absent entities read as empty. The
    shared requirement reader is exported as RemoteServer.readHelper, so a
    custom handler answers the same settled-fields payload.

  • foldkit-remote, Data.meta(model, projection): when what is shown
    was last received (updatedAt, newest server write among it, undefined
    when nothing shown was received), and whether any of it is stale or
    loading. Pure, for "updated 5s ago" without I/O.

  • CMS example, one studio application: posts and pages share a document,
    a runtime and an address (examples/cms/src/apps/studioApp.ts); moving
    between them swaps no application. Each section keeps its Model, update,
    view and subscriptions (its Remote domain folds its own Messages); a
    same-document navigation carries the address's query into its target.

  • foldkit-mixins-form, FormView.fields(form, { overrides, styles }):
    the whole form as define draws it, with per-key overrides and styles, plus
    one flat key's control for a layout the caller owns. Keys with no override
    render by kind, so a new key of a known kind needs nothing new; unknown
    keys are type errors. An override receives the control, field, id, validity,
    errors and Messages, with any h.

  • foldkit-primitives/net, the socket reads itself: the WebSocket bundle
    tracks opened beside status and lastError, words its own errors
    (Failed to connect to WebSocket before it opened, Connection error
    after, Connection timeout, Socket unavailable), and derives SocketView
    (Disconnected/Connecting/Connected/Error) from the socket and
    whether the page still wants it, with isOpen gating sends and frames. A
    page keeps one wantConnection boolean instead of a connection state
    machine; foldkit-websocket-chat is the first caller.

  • foldkit-mixins-ui, Button.view({ label, style, ... }): a button
    drawn as a button in one call (label, style, type, disabled, onClick),
    without the UiButton.view → Button.toView → h.button ceremony.
    toView stays for buttons drawn as something else.

  • foldkit-remote, Data.satisfy(model, actives, { passes? }): the Model
    with everything the active Surfaces read, for a render that fetches nothing.
    It prefetches each Surface that plans a read, cache-first, pass after pass
    until a pass plans nothing, so a read that depends on another (a page's
    Blocks on its document) is made too. Past the bound (8 passes) it fails with
    RemoteUnsatisfied, naming the Surfaces still reading.

  • foldkit-ssr, SSR.plan({ meta: model => Meta }): what a page says of
    itself to a search engine and a link preview (description, og:*,
    twitter:card, article:*, robots, alternates, JSON-LD), from the Model.
    The render writes it before </head>, escaped, and checks it like the view;
    SSR.hydrate replaces it as the Model changes, so a move to another post
    updates its description. SSR.page and SSR.entry now refuse a render that
    sets canonical or ogUrl into a template without the tag Foldkit fills,
    which Foldkit would otherwise leave out silently. The CMS example's
    hand-built head is now its plan's meta.

  • foldkit-ssr, styles for the first paint of a served page:
    SSR.render, SSR.entry and SSR.handle take styles: rendered => string, carried as the rendered root's last child where no template head
    can take it. Hydration adopts the nodes around it and drops it on its first
    patch; styles for a void root are refused with VoidRootWithStyles. The
    foldkit-ssr example ships its stylesheet this way again, as SSR.generate
    pages do in the head.

  • foldkit-ssr, SSR.sitemap(pages, { origin }) and
    SSR.robots({ origin, sitemap?, disallow? }):
    a generated site's sitemap
    (each page's full address, its modified date as the UTC day) and the
    robots.txt naming it. A duplicate path, a relative one, or a date Date
    cannot read is refused. The CMS example drops its own sitemapOf and
    robots.

  • foldkit-bundle, onMessage: a placement's onMessage (and
    (message, key) on a collection) observes each child Message as a parent
    Step after the child and its onOut; placements.update(own) types own's
    Message without the placements' wrappers (Bundle.OwnMessage).

  • foldkit-bundle, Links for Update.foldChild:
    Link.wrapper(Message.GotXMessage) builds a wrapper from a variant the union
    already declares, and Link.field / Link.optional writes return the parent
    when the child is unchanged, so Update.foldChild({ ...link, update }) no
    longer redraws for a Message the child ignores. foldkit-ui-showcase folds
    its 38 components this way. Link.foldInit(link, rest) is what
    Update.foldChildInit takes: the Link's toParentMessage with a
    toParentModel that writes the child into the rest of the parent.

  • foldkit-primitives/time, ticks({ intervalMs, onTick }): a clock
    entry whose running and interval are functions of the parent Model; a new
    interval applies from the next tick without a restart. Timer and Interval
    are built on it.

  • foldkit-primitives, keyboardEvents({ preventDefault }) cancels the
    default of the presses a predicate over KeyPress picks, decided in the
    listener.

  • foldkit-primitives, websocket: Sent carries { data }, and an
    optional connectTimeoutMs closes a socket still connecting and reports the
    new TimedOut.

  • foldkit-primitives, History.goTo(model, step) and the bundle's
    GoTo { step } jump to any kept step.

  • foldkit-mixins, slots declared by their style: Style.slots(pieces)
    declares an application's own Slots from their styles and returns
    { slots, style }; Style.slot(options, piece) gives one a capability,
    events or attributes. forSlots stays for a contract someone else published.

  • foldkit-mixins, short pieces: wherever a piece is taken, a
    declarations object means Style.self and a list means Style.compose.

  • foldkit-mixins/app: AppStyle.make({ palette, colorScheme?, global? })
    fixes the theme, the app layer and the page stylesheet once, giving t,
    L, slots, forSlots and stylesheet.

  • foldkit-mixins/utilities: Utilities (p, px, gap, text,
    font, rounded, color, bg, flex, truncate, …) are pieces over the
    Theme.tokens scales and Theme.oklch names, so a step the scale lacks is a
    type error.

  • foldkit-mixins, Style.install(css) puts a stylesheet in the page's
    head once and returns the element.

  • foldkit-mixins/testing: Inert.draw draws views that use
    h.submodel; Inert.css(nodes) and Inert.missingTokens(root, stylesheet)
    replace the checks every view test wrote by hand.

  • foldkit-mirror, Mirror.bootstrap and mirror.bootstrap(keys): a
    first-class pre-init seam. When a store's keys are already in hand at boot
    (Flags the server embedded, a synchronous read, a test fixture),
    Prefs.bootstrap(keys) folds them into the initial Model before the first
    render — the same conservative read reduce applies to a MirrorRestored,
    with no Command and no flash — and Mirror.bootstrap(initial, ...steps)
    composes store steps with the URL step (model => Filters.reduce(model, url)), keeping URL > store > initial. This is deliberately not part of
    Wiring: a bootstrap runs inside init before the Model exists, while
    Wiring.init runs startup Commands (such as restore) after it.

  • foldkit-mirror, Mirror.routing: Mirror.routing({ mirrors, urlChanged, init, update, routing }) returns init, update and routing
    for Runtime.makeApplication. URL mirrors read the starting URL and each URL
    Message before the application's own update routes it; its onUrlRequest
    and onUrlChange pass through unchanged. foldkit-query-sync uses it.

  • foldkit-ssr/client, a browser entry: SSR.plan, resume,
    hydrate, static, serving, Resume and the attribute names (including
    FOLDKIT_APP_ATTRIBUTE), importing nothing from
    foldkit/experimental/server: about 200 kB minified (60 kB gzip) less on a
    hydrating page. foldkit-ssr still exports everything.

  • foldkit-mixins-ui, toView on every adapter:
    X.toView(mixins, { h }, draw) fills a component's toView slot and hands
    draw the bundles with the Mixins applied, in a component's view or a
    Submodel's viewInputs. resolve stays for bundles already in hand, and its
    input is now optional. Every adapter exports its result type
    (ResolvedButton, ResolvedInput, ResolvedRadioGroup, …), and
    Textarea's textarea bundle is typed for h.textarea, so no cast.

  • foldkit-form, validating without submitting:
    Message.ValidatedAll() validates every key and row as a submit would and
    submits nothing; form.isValid(model) says whether a submit now would hand
    over the value at once (unlike canSubmit, a running or unrun check is not
    valid). A nested row's Message that changes nothing keeps the parent Model
    by identity instead of rebuilding its rows, so a no-op in a row renders
    nothing.

  • foldkit-mixins-form, a check running and a submit in flight: a key
    whose check runs shows a Checking… line in the new checking Slot
    (role="status", named in the control's aria-describedby, words
    words.checking), and each field's root carries data-validation with
    its state. While a submit waits for a check, or the new view input
    submitting says the application's request is in flight, the form is
    aria-busy with data-submitting and the submit button is disabled and
    reads words.submitting (default Submitting…).

  • foldkit-bundle, a shared tag reaches everyone who shares it: a Message
    tag wirings declare shared is folded by each of them in list order, and
    the parent's own update sees it after them. Before, the first claimant took
    it: of two URL mirrors only the first read the URL, and an application with
    a URL mirror never saw its own UrlChanged, so it could not route on it.

  • foldkit-mixins-crud, loading told from empty: a list's or a detail's
    status line is aria-busy while the first answer is awaited, as the table
    already was while refreshing, so a style can draw a quiet placeholder for
    loading and keep its empty state for empty.

  • foldkit-agent, correlating by the call: a completion's correlate
    receives a third argument, { invocation } (type Correlation), so a fact
    that carries the invocation's id from toMessage is told apart where the
    input is not. The todo app and the root README's example completed add_todo
    by matching the trimmed title, which mixed up two todos of one title; they now
    pass requestId: invocation.id through RequestedTodo to SubmittedTodo.

  • CMS example as a static site: pnpm --filter foldkit-example-cms build:sandbox builds the studio and the public site with the same server
    running in the page, on SQLite compiled to WebAssembly (sql.js), so it
    deploys with no backend. Each visitor's sandbox is kept in the browser's
    storage; ?reset starts afresh. The server takes its database
    (openServer(clock, sqlite)), and one endpoint module answers requests for
    both the HTTP server and the page.
    On a phone the studio's sidebar is a top bar (the brand and who is looking,
    then the sections in a row), the editor's bar wraps its actions under its
    status, the builder's panels scroll within a short height so the page
    shows, and a post list's cards fit a narrow canvas. The posts open with what
    the demo is and what to try, and the page says it is starting while the
    sandbox opens. The address keeps the open post, the search and the Archive
    tab, as it kept the open page, so a reload lands where one was, and opening
    or closing a post or a page is a step Back returns from. A page has its History
    and More (discard, unpublish, archive) under a fold below the editor's bar,
    as a post has them in its aside. The History is a timeline: each revision
    with its date, time and publisher, the newest marked Live while it is on the
    site, and Restore offered only where it would change something. Moving
    between the studio's sections and the site no longer paints white: the
    foundations' stylesheet is in the HTML. A wait says "Loading…" only once it
    is noticeable, an entry being read shows no "New" badge, and the pages list
    no longer says "Nothing yet." before it is read. A new screen starts at the
    top and Back, Forward or a reload return where it was. A chair's avatar is
    centred in its touch target on a phone. The page builder shows its panel
    tabs at every width it stacks at: an editor between 52rem and 64rem wide
    (a 1280px window's) stacked every panel above the page with no tabs, so a
    new page seemed to open without one. The address keeps a post's preview and
    the Builder's panel and preview width, so a reload comes back to them, and
    the worklist's search and tab go through a foldkit-mirror URL mirror
    (?archive=true where it was ?archive=1). The blog's eight posts explain
    how the demo is made (the CMS, the page builder, Composition, Form, Crud,
    Entity and Remote, and the styling), and a post's body can hold headings,
    lists and code. The site links to the demo's code on GitHub, and a
    published sandbox kept from the earlier seed is replaced by this one. A new
    post or page is in the address (new=<id>) from the moment it is started,
    so a reload comes back to it; its first save turns that into its own key.
    The public site is rendered at build time with foldkit-ssr: each
    published page and post is HTML with its text, styles, description, link
    preview, canonical address and article facts, beside a sitemap and
    robots.txt. The browser takes a generated page over without drawing or
    reading it again, while the visitor's sandbox still holds the seed. The
    studio is noindex. Moving between the studio's Posts and Pages no longer
    reloads the page: the two applications share one document and swap in
    place, keeping the loaded code and the in-page server, about 60 ms a move
    where a load took 220 (and 0.8–1 s on a slowed CPU).

  • Dropping a palette tile on the page's own space: PointerDrag's facts
    for a drag onto targets carry region, whether the pointer is inside the
    region, so over: null with region: true is its empty space.
    foldkit-builder gains DraggedOverPage(), which lands a tile where a
    press with nothing selected would put it, and foldkit-mixins-builder sends
    it; a tile dropped on an empty page, or below the last node, added nothing.

  • foldkit-primitives, FollowTabStop: a Mount keeping focus inside a
    container of roving tab stops on its stop, when a transition it did not see
    moves the stop or removes the focused descendant. TreeNavigation's
    Behavior attaches it, so the Builder's layers keep focus through a
    duplicate, a paste or a delete made by key, where focus stayed on the old
    row or fell to <body>. foldkit-builder moves layers.current off a
    removed node to the one after it, else before it, else its holder, rather
    than leaving it for the first row.

  • foldkit-cms: Cms.Display.State's words include withSchedule
    ('{state}, {schedule}'), where a state and its schedule were joined by a
    comma no application could word. A display renderer's context types its
    words as DisplayWords, where it had any.

  • foldkit-builder: its words. Builder.make(name, { words }) takes any of
    EditWords over editWords: what it announces of each edit, its commands'
    labels, and the refusals it makes itself, as text with blanks
    ('Moved {label}{at}'); refusal words those apply makes ({code},
    {message}). Announcements name a Block by its label, not its stored name.

  • foldkit-mixins-builder: the editor's words. Every word the drawn
    Builder shows of its own is one of BuilderWords (English defaults in
    builderWords), given as the view input words. Words are text, a value
    a blank in them ('Add {label}'), since a view input may hold no nested
    function. keysOf takes them, for the named keys (keyNames) and Ctrl,
    Alt and Shift, and no longer reads Object's own names for a key called
    constructor. A look's blank choice now reads "Default", as its buttons do.

  • foldkit-mixins: parts, each drawn again only when what it reads
    changed.
    SlotView.parts(Slots)<Input, Message>() makes parts that name
    the input keys they read and are given only those; assemble places them.
    slots.row.lazy(item, draw, args) draws one repeated item again only when
    its arguments or what the Mixins gave it changed. Over 1,000 nodes, a
    hover, a selection and a keystroke in the inspector each went from about
    50 ms to the next frame.

  • foldkit-mixins-builder: the editor as parts. BuilderView.parts(builder)
    gives Panels, Palette, Layers, Inspector, Toolbar, Crumbs,
    Viewports, Preview, Alert, Canvas and Live, each with its own
    Behaviors; BuilderView.assemble(render) places them among an
    application's elements, and define is the default layout.

  • foldkit-builder: the inspector is a form. A node's props, and the
    input of the action each event runs, are edited through foldkit-form
    forms of the Block's settings (settingsOf, inputOf), drawn by
    FormView, so a prop gets the control a form would give it, a value that
    does not decode shows its error and edits nothing, and a control of the
    application's own (Input.bundle, such as a color picker) works with no
    Builder code. BuilderView.define(builder, { settings: { field, form, renderers } }) styles the forms and draws the application's control kinds.

  • foldkit-composition: a Block's words are its own.
    Block.words({ label, description, group }) names a Block and says what it
    is for; the palette, the layers, the inspector, Catalog.describe,
    Composition.describe and the agent's operationSchema all read it.

  • foldkit-primitives/dom: Measure, a Mount that writes where marked
    elements are (--fk-<name>-x/-y/-w/-h/-display) for an overlay drawn over
    them. The drawn Builder's selection and hover are boxes placed this way.

  • foldkit-builder: one table of commands. PageBuilder.commands ({ id, label, keys, placement, run }) is every key, node action, toolbar button
    and shortcut listed; keyCommand is derived from it and Builder.make's
    commands changes it. Keys are written for the author's platform
    (BuilderView.inputs({ platform: 'mac' })).

  • foldkit-builder: a Block dragged from the palette is added where it
    is dropped (DragSource is Existing or New). foldkit-primitives:
    PointerDrag takes targets: { attribute, within }, so a drag may land on
    another region's elements.

  • foldkit-builder: copy, cut and paste. A node and all it holds is kept
    in the Model's clipboard and on the system clipboard as tagged JSON; a
    paste reads it back (readText, new in foldkit-primitives/dom), decodes
    it strictly, gives every node a new id and inserts it whole or refuses it.

  • foldkit-composition: Patterns. Catalog.make({ patterns }) holds
    arrangements of Blocks, checked where the Catalog is made;
    Op.usePattern({ pattern, ids, at }) inserts one, and operationSchema
    offers each with exactly its ids. The drawn palette offers them in a group
    of their own.

  • Text edited in place. A Block's view draws a text prop with
    field(key); the editor edits it on the canvas, frozen at the text it had
    when editing began so no redraw moves the caret, one undo step per session
    (History.close and History.revert, new in foldkit-primitives/state).
    EditableText, new in foldkit-primitives, reads what is typed as text,
    once per composition, and Renderer.fields says which props a node draws
    this way.

  • foldkit-mixins: container breakpoints. Style.responsive takes an
    at-rule as well as a media query, Theme.inContainer(name, breakpoints)
    writes a theme's breakpoints on a named container, and Style.at(prelude, piece) puts a selector under an at-rule. The Builder's frame is the page's
    container (PAGE_CONTAINER), so a look written this way follows a narrow
    preview in a wide window.

  • foldkit-mixins-builder: a narrow editor. BuilderView.narrow(width)
    shows one panel at a time, chosen by a group of pressed buttons, below that
    width of the editor itself.

  • foldkit-mixins: a Style's rules arrive with the Slot that draws them.
    Compiling a Style records each class with its CSS; in a browser, a class a
    Slot draws is appended once to one <style data-foldkit-styles> element
    (made only when there is something to add, declaring the standard layer
    order unless the page declares its own), unless a stylesheet on the page
    already carries it. A Style left out of an application's Style.stylesheet now
    draws instead of silently drawing nothing. Style.stylesheet is unchanged.

  • foldkit-mixins: Style.usedIn(html), the CSS of every compiled class a
    page's markup uses, after the layer order: what a server puts in the head.
    foldkit-ssr: SSR.page, SSR.generate and SSR.entry take
    head: rendered => string, put before the template's </head> (a template
    without one is refused when there is something to add; SSR.entry refuses it
    when it is made, and answers a head that throws 500).

  • foldkit-composition/foldkit: Renderer.render draws a node again only
    when what it reads changed.
    Inside a runtime render, each node's drawing is
    memoized on its node object, drawn children, read, visibility and marks, so
    an edit redraws the node it touched and its ancestors, and an unrelated Model
    change redraws no node. Each node's element is keyed by its id (the edit
    wrapper, or the Block's root in view mode unless the Block set a key).

  • foldkit-mixins/testing: Inert.draw, unslotted, fixedInline and
    bySlot,
    which check a package view's customization contract: every
    element it draws comes from a Slot, however deeply its views nest, and no
    fixed declaration is inline. They found markup outside any Slot in three
    packages, now Slots of their own: foldkit-mixins-builder's selectOption,
    foldkit-mixins-form's option and choiceLabel, and
    foldkit-mixins-crud's head, headRow and body.

  • foldkit-mixins/testing: Inert, queries over a view drawn with the
    inert builder
    (all, children, byTag, byRole, byLabel, text,
    value, classes, style, pressed), replacing a tree walker seven test
    files each defined, and the casts to hand-written node types that came with
    them.

  • Fixed declarations are rules, not inline styles, so a later layer can
    override them: Prose.style's measure and line height (with a new leading
    option), Style.grid's template and areas, and Style.stagger's delay (its
    --fk-index stays inline). The drawn Builder's frame writes
    --fk-frame-width, read by a default rule in components.

  • foldkit-mixins-builder: what the editor calls a Block.
    Block.words({ label, description, group }) (now in foldkit-composition) names a
    Block, says what it is for, and files it in a palette group. The palette is
    grouped; each button is named "Add ", shows the description, and is
    titled with where it would go ("Adds it inside the Section") or why it
    cannot. A layer row shows the label and the node's first text in brief, and
    a row that holds others has a toggle. New Slots: paletteGroup,
    paletteHeading, paletteLabel, paletteHint, rowToggle, rowLabel,
    rowSummary; palette buttons and rows carry data-block. A palette button's
    text is now the label, not Add <Block>: find it by its accessible name.

  • foldkit-mixins-builder: the inspector in parts. It opens with the
    selected Block's label, description and actions (now drawn there, each
    titled with its shortcut and carrying data-action), then its settings under
    Content, Style, Visibility and Interactions. A look of up to four values is a
    row of buttons, one pressed, instead of a select. Labels are the key spaced
    when there is no title ("Text", "Shown when audience is", "On press"), and
    viewport buttons read "Wide", "Medium", "Narrow". With nothing selected it
    says how to begin and lists the shortcuts. New Slots: inspectorHead,
    inspectorTitle, inspectorHint, inspectorSection,
    inspectorSectionTitle, choices, choice, shortcuts, shortcutKeys,
    shortcutWhat, and label and option for a field's name and a
    many-choice picker's choice, which were drawn outside any Slot.

  • foldkit-builder: Escape deselects in keyCommand.
    foldkit-mixins-builder takes the shortcuts on the canvas as well as the
    layers (the canvas is now focusable), and draws a breadcrumb of where the
    selection is (crumbs, one crumb button per node holding it, the page
    first, the current one aria-current="location").

  • foldkit-mixins-builder: pointing at a layer row marks its node on the
    page (the Builder's Hovered and Unhovered), and an empty page says how to
    begin, in the new empty Slot.

  • foldkit-surface: Action, a named capability that ends in a Message.
    Action.define({ name, description, input, toMessage }) declares one, and
    Action.run(action, data) decodes the data as its input before making the
    Message, so stored or untrusted data never executes.

  • foldkit-agent: Agent.action(action, { available?, authorize? }) exposes
    an Action as a variant, under the tag of the Message it makes; another tag is
    a type error, and a dispatch through one is refused. Agent.variant is
    unchanged.

  • foldkit-form: Input.bundle, a control with a Model of its own. A key's
    draft can now be a Bundle's Model, such as a color picker with a popover, a
    page builder, or a rich-text editor. Input.bundle(kind, { bundle, value, fill, settled? })
    says how the key's value is read from the Model and written into it. The
    Bundle's Messages travel as the form's new Control Message
    (form.control(key).send), its Commands answer as Control Messages, and
    its Subscriptions and Resources become the form's, keyed under the key. A
    Message is an edit, validated and counted by authoredChanged, only when it
    changes the key's value. model.fields[key].value is typed as the Bundle's
    Model. The Bundle may have no OutMessage and need no services, the key takes
    no check, and a nested form whose control has Subscriptions or Resources is
    refused.

  • foldkit-mixins-form: draws a control backed by a Bundle with the
    Bundle's own view inside a new control field slot, unless a renderer names
    its kind. A renderer receives bundle: { model, send }, wrapped for its row
    inside a nested form.

  • foldkit-cms: the editor runs a form control's Subscriptions and
    Resources
    while an entry is open, and stops them when it closes.

  • foldkit-composition (in development, not published): what a page is,
    as data.
    Block.define declares what may exist (a props Schema, Regions,
    the Content it provides), Catalog.make gathers a context's Blocks and
    roots, and Composition.Document is the tolerant codec of a stored page: any
    Block name, props as JSON. Composition.validate reports ten kinds of
    finding, from a missing node to a Region that rejects a child, and
    Composition.valid(catalog) asks the same as a Schema check for an operation
    that publishes. index, describe and Catalog.describe read a Document
    and a Catalog. Composition.apply edits a Document by Operations (insert,
    insert a tree, remove, move, duplicate, set and unset a prop, the reserved
    fields, a batch), refusing what an Operation would cause with no partial
    result, and never minting an id itself. History keeps undo snapshots,
    grouped without a clock. Composition.migrate moves stored pages forward
    through named migrations (renameBlock, renameProp, promoteUnknown, or
    one of your own), each rewriting a node or declining, and throws when one
    breaks the Document's structure. foldkit-composition/foldkit draws a
    Document with one ordinary Foldkit view per Block (a Block without one is a
    type error), placeholders for what cannot be drawn, and an edit mode that
    marks each node; the same Renderer draws inside a foldkit-ssr static
    region, so a served page sends none of the Document. Url refuses
    javascript: and every scheme but http, https, mailto and tel.
    foldkit-composition/richtext holds a rich-text body checked against its
    Kit. Phases 1 to 4 of the page builder design.

  • foldkit-builder (in development, not published): the page builder's
    state.
    Builder.make(name, { catalog, renderer, starters }) gives a Bundle
    that edits a composition Document by Operations, mints new ids in a Command,
    keeps a selection and undo History beside the page, and records why an edit
    was refused. builder.input places it as a form key's control, so the page is
    the key's value, a selection is not an edit, and a fill starts undo over. Its
    plain view (palette, layers, text props, undo, the page in edit mode) is drawn
    by foldkit-mixins-form with the rest of the form. Phase 5 of the page
    builder design.

  • The CMS example builds a page too. A Page content type whose document
    key is the page Builder goes through the post's whole story: autosave, a
    resumed draft, a preview drawn by the site's own views, publish, revisions,
    restore, a schedule and a conflict, with no CMS state added. Phase 6 of the
    page builder design.

  • foldkit-builder: the keyboard, the layers and announcements. The
    Builder places TreeNavigation and LiveAnnounce in its Model; moving focus
    in the layers selects the node. keyCommand(model, key, modifiers) gives the
    shortcuts: Alt with the arrows moves the selected node among its siblings,
    out of its parent or into the node above; Mod+D duplicates; Delete removes;
    Mod+Z, Mod+Shift+Z and Mod+Y undo and redo. Structural edits, undo, redo and
    refusals are announced to assistive technology. A selection made anywhere
    else, such as an insert or a click on the page, is where the layers' keys
    start from. offered lists the Blocks the palette offers, and
    inputWith(view) is the form control drawn by another view.

  • foldkit-mixins-builder (in development, not published): the page
    Builder, drawn.
    BuilderView.define(builder) draws a palette, the layers
    as an ARIA tree, the selected node's actions and props, undo and redo, a
    viewport picker, a refusal as an alert, a live region, and the page in edit
    mode through the site's own Renderer. Every element is a BuilderSlots
    Slot. The layers take TreeNavigation and the Builder's shortcuts, and the
    canvas takes Targets, so hovering and clicking the page mark and select
    its nodes. It adds no state and no Messages. The CMS example's page form
    draws its Builder this way. Phase 7 of the page builder design.
    The inspector labels a prop with its Schema's title, and a Block asks for a
    prop's control with Block.annotate(Builder.controls({ ... })), such as
    Input.multiline(), or Input.hidden() to leave it out. A row in the
    layers or a node on the page can be dragged onto another with the pointer:
    the Builder's drag says where a drop would land (dropAt), the target is
    marked only where the page allows it, and a drop is one undoable, announced
    move.

  • foldkit-composition: appearance. A Block offers appearance axes, each a
    list of variant values or token names, and validate and
    Op.setAppearance check a node's stored names against them
    (composition:invalid-appearance, composition:unknown-token). The new
    foldkit-composition/appearance subpath makes a look from a
    foldkit-mixins slot recipe and theme tokens: Appearance.attach(look)
    gives a Block its axes, look.draw({ appearance, h }) gives its view the
    Slots with the chosen Style attached, every piece compiled once, and
    look.styles is the stylesheet. A Renderer's views receive appearance,
    the choices the Block offers. The drawn Builder's inspector chooses them.
    A token axis given breakpoints is responsive: a node stores
    { base: 'sm', md: 'lg' }, drawn as rules the widest matching breakpoint
    wins. look.draw({ appearance, h, with }) attaches a Block's own Behaviors
    after the look, and a style property a Behavior owns that a choice also sets
    is mixins:style-property-conflict. A layout Block is a Mixins layout this way: the test site's Columns is
    Layout.switcher, with its ratio, when it stacks, and its gap as choices.
    Phase 8 of the page builder design.

  • foldkit-composition: operationSchema(catalog), the Operations a
    Catalog's pages take as a Schema, inserts naming only its Blocks with their
    stored props, for an agent tool's input. The CMS example's page agent edits
    a page through the Builder's own Applied, undoable like any edit, and a
    Block outside the Catalog is refused by the tool's schema.

  • foldkit-composition: actions. A Block names its events, a Catalog lists
    the actions its pages may reference (foldkit-surface Actions), and a node
    stores which action an event runs with literal input. validate and
    Op.setAction check them (composition:invalid-action,
    composition:unknown-action), and a Renderer's view gets on(event), the
    Message the action makes from input its Schema decoded first;
    Renderer.forMessages checks it routes every such Message, and a
    Renderer.make view, such as an editor's canvas, gets none. The drawn
    Builder's inspector picks each event's action and edits its input.

  • foldkit-composition/surface: Surface Blocks. SurfaceBlock.define
    places a foldkit-surface Surface where an author puts it, with params from
    its props; SurfaceBlock.reads and SurfaceBlock.active read the page's
    Surface Blocks as a Query Block's reads are, and value(data) reads a node's
    typed.

  • foldkit-composition: stateful Blocks. A Block marked stateful is
    backed by a Bundle the page's parent places once per node, keyed by its id.
    Composition.statefulNodes lists them; Stateful.sync (in /foldkit) keeps
    a placed collection in step with the page shown, adding, removing, and
    starting again a node whose props changed; Stateful.views and
    Stateful.html draw each node with its own item.

  • foldkit-form: a title given before a check names the key. Effect
    resolves a checked schema to its last check's annotations, so
    Schema.String.annotate({ title }).check(...) lost its label; the form now
    falls back to the schema's own annotations.

  • foldkit-composition: a Query Block gets its own window, cut to its
    first even when another read of the same query loaded more rows.
    Composition.valid describes itself with expected, not a title a form
    would take for the field's label.

  • foldkit-cms-drizzle: a publish patches the whole row back, as the
    handler left it, so the author's own screen shows what was published without
    the handler returning a patch.

  • foldkit-mixins-builder: an optional relation prop stored as null and
    decoded to an Option
    (Schema.OptionFromNullOr) offers its blank: the
    inspector asks the stored side.

  • foldkit-mixins-ui: Recipes.Button on a link is not underlined.

  • foldkit-cms: placed.storedEntry(model), the entry the server knows, as
    an Option: none while something new is not saved yet, so a link names only
    what exists.

  • foldkit-mixins-builder: relation pickers for Block props. A prop asks
    for Input.relationOne(Entity) or Input.relationMany(Entity) through
    Builder.controls, and its choices come in BuilderView.inputs({ options }), keyed 'Block.prop'.

  • foldkit-form: Input.relationOne and Input.relationMany, a picker for
    a key that holds ids without being a relation.

  • foldkit-mixins-form: a Bundle control's view takes inputs, from the
    form view's controls, by key. A key given none draws its view as before.

  • foldkit-mixins-builder: the canvas draws the page's data.
    BuilderView.submodel takes BuilderView.inputs({ data }), each node's read,
    which the page's parent gives through the form's controls, so a Query or
    Surface Block on the canvas shows its rows. builder.inputWith accepts a
    view with inputs; QueryBlock.active and SurfaceBlock.active type what they
    read.

  • foldkit-composition/remote: Query Blocks. QueryBlock.define makes a
    Block that names a foldkit-remote query and derives its input from props;
    QueryBlock.reads(Data, catalog, document) is the page's reads as one
    Projection keyed by node, fetched and resumed by Remote like any read; the
    Renderer hands each node its data, and the Block's rows(data) reads it
    typed. QueryBlock.active(name, App.owner, Data, …) is the read as an active Surface, so a
    server-rendered page resumes it with Remote.resume. The CMS example's pages can list the site's pages. Phase 9 of the
    page builder design.

  • foldkit-composition: conditions. A Catalog may declare a context
    Schema (an audience, a locale, a flag), and a node's when is a list of
    conditions over it, eq, isNull, isNotNull and contains, meant as
    foldkit-entity's Expr means them. validate and Op.setWhen check them
    (composition:invalid-condition, composition:unknown-context), and
    Renderer.render takes the context: a node whose when fails is left out,
    or marked data-composition-hidden in edit mode, and a page drawn without
    its context fails closed. Composition.holds(when, context) is the test.
    The Builder keeps what the author previews the page as (preview,
    PreviewChosen), and the drawn Builder offers a "Preview as" picker, draws
    the canvas for it, and edits a node's conditions in the inspector.

  • foldkit-composition/foldkit: the edit wrappers carry the marks.
    render in edit mode takes selected, hovered and drop, and puts
    data-composition-selected, data-composition-hovered and
    data-composition-drop on those nodes' wrappers, so an editor's CSS draws
    the selection and where a drop lands. A node whose view throws is a
    placeholder, as a node that cannot be drawn is.

  • foldkit-primitives: PointerDrag, dragging one marked element onto
    another.
    A Mount and a Behavior on a container: a press that moves past
    4px starts a drag, the element under the pointer is reported with the third
    of it the pointer is in (before, inside, after), and a release drops
    or Escape cancels. It writes no roles or keys, so it works on a tree's rows
    as on a canvas, and it swallows the click a drop ends with.

  • foldkit-primitives: TreeNavigation, keyboard navigation of a tree.
    After the WAI-ARIA tree pattern: Up and Down through the rows showing, Right
    opens or steps in, Left closes or steps out, Home and End. Rows are given in
    tree order with their parent; openness is stored as what was toggled away from
    openByDefault. Its Behavior writes the tree's ARIA attributes and a roving
    tab stop. For a file explorer, a page's layers or a nested menu.

  • foldkit-primitives: Targets, which marked descendant is under the
    pointer or was pressed.
    One Mount on a container reports TargetHovered
    once per change and TargetPressed with its modifiers, for the nearest
    descendant carrying a marking attribute, and can stop a press's default.

  • foldkit-primitives: an undo history groups steps. A Push may name
    a group, and consecutive pushes of the same group are one step, so typing a
    word undoes as a whole without a clock. The steps are exported as pure
    functions (History.start, push, undo, redo, clear) for a parent
    that records an edit in the same transition that makes it. Breaking for
    code that builds a HistoryModel by hand: it gains group, null for none.

  • foldkit-builder keeps its page in that history: its Model's page is a
    foldkit-primitives/state history whose present is the Document
    (builder.document(model) reads it), and foldkit-composition no longer has
    a History of its own. Undo is one mechanism in the repository, not two.

  • foldkit-ssr: its build configuration references the packages it builds
    from,
    so another project can reference it.

  • foldkit-ssr: SSR.render's result lists unnamed, each element and
    event whose handler is a function the page cannot name
    ({ element: 'button#point', event: 'pointerdown' }). On a page that waits
    to boot, SSR.entry and SSR.generate warn about each once per process.

  • foldkit-ssr: pnpm bench:manifest measures the resume manifest's size
    and decode time at 10, 100 and 1,000 rows; the SSR plan records the table.
    pnpm bench:manifest:browser times the same page in Chromium.

  • foldkit-ssr: the envelope lists the events its markers name, and
    Resume.listen takes them as events, so a deferred page no longer reads
    every element's attributes to find them. The page's list of bindings is
    checked by hand rather than through a Schema. At a thousand rows, decoding
    and listening take 12 ms in Chromium, down from 22.

  • foldkit-richtext: Replicated, collaborative rich text without a CRDT. A
    document state where every character has an identity: translate restates an
    edit as ops that name characters and blocks, applyOps folds them in any
    server order to the same state, project gives the editor a Document, and
    anchor/resolve keep a selection by its characters. invert gives the ops
    that undo an edit and nothing else. translate's continues option and
    coalesce make a burst of typing one op, and a Collect op removes deleted
    text, in two phases, through the log. examples/pages wires it to Sync and
    Durable. Since: RetypeContainer retypes the caret's block in place,
    unionChangeSet unions held IME patches, SetProps is refused only when
    it would make valid props invalid, transactions carry their normalized
    transactions, and a cached nodeIndex answers block lookups.

  • foldkit-richtext-dom: patchTo, and an overlay Command for
    decorations from application state, such as other people's carets.
    patchTo patches an externally replaced document without remounting (a
    remote keystroke keeps the caret), judging moved runs by both blocks;
    empty runs draw addressable spans, string data draws as
    data-decoration-*, input rules match on surrounding node kinds, and a
    non-moving transition keeps the Model by identity instead of clearing it.

  • foldkit-richtext-markdown, task items and tight lists: - [ ] at a
    list item's start makes it a task in place, and the printer records
    CommonMark spread, so tight items keep nested lists under their text.

  • foldkit-richtext-code and foldkit-richtext-code-shiki, first
    releases:
    total JSON and Shiki grammars as tokenizers over the shared
    syntax-* kinds, for codeDecorations (the application owns the
    highlighter).

  • foldkit-sync:

    • Sync.fact, which applies a durable fact in the intent's own transition.
    • onReinstall on Sync.mount.
    • Sync.durable(message).
    • A contract's coalesce, which merges a burst into one unsent operation.
    • Storage.append, a one-row write per submit, with an IndexedDB outbox
      store.
    • Server push through { notify: true } frames and Transport.changes.
    • Paged exchanges (more).
    • transport.socket, so presence shares the sync connection.
    • A presence throttle.
    • Server-reset recovery: a server's epoch goes back as exchange's
      optional third argument, and a new one makes the replica rebuild from the
      server's answer and resend its outbox.
  • foldkit-durable:

    • Journal.cursor, read(key, after, { limit }) and vacuum().
    • An in-memory snapshot with snapshotEvery.
    • Journal.epoch(key).
    • A replicaId option that binds each replica to the actor of its first
      commit. Sync's journalContract() supplies it.

Changed

  • foldkit-primitives, Timer and Interval's first tick now comes one
    interval after Started, not at once. History.push keeps an already-empty
    future, so a view reading it is not redrawn per push. The new TimedOut
    and GoTo variants break a match over every variant.

  • foldkit-mixins, a branded StyleValue (breaking for hand-built
    values):
    a StyleValue carries a brand, so a declarations object is never
    mistaken for one; NamedStyle.pieces and a recipeFor selection are
    StyleValues.

  • foldkit-ssr, Foldkit's config types: SSR.hydrate, render,
    generate, entry and handle accept Foldkit's makeApplication config
    types; routing's callbacks are checked against update's Message, and
    resources is Layer<never, never, never> (the old type rejected
    Foldkit's own Layer).

  • foldkit-ssr, SSR.entry answers OPTIONS (breaking): 204 with
    allow: GET, HEAD[, POST], OPTIONS instead of 405, and a new
    headers: request => HeadersInit sets headers on every response it answers.

  • examples/foldkit-ssr serves as upstream does: Effect's HTTP server on
    @effect/platform-node instead of a hand-written node:http host.

  • foldkit-ssr, the envelope rides the stamped root (breaking):
    SSR.render carries the envelope as data-foldkit-plus-resume on the
    rendered root, beside Foldkit's own stamps, instead of a script before
    </body>; hydration adopts the nodes and drops the attribute on its first
    patch. SSR.resume reads it there, refusing a page with no stamped root or
    more than one. SSR.entry answers Rendered and takes no template,
    containerId or head: the host owns the template, through handleRequest
    or the foldkit Vite plugin's dev server, and a plan with meta is refused
    at construction (serve it with SSR.generate, which keeps the template,
    head and meta). The build id is the FOLDKIT_BUILD_ID deployment both
    bundles are compiled with. The foldkit-ssg and foldkit-ssr examples serve
    through foldkit({ ssr }), and the CMS example stamps pages with the same
    deployment id instead of the entry script's address.

  • foldkit-remote, typed entry dependencies: Data.subscriptions and
    Remote.fold(...).subscriptions type each entry's dependencies
    (ReadDependencies, the new LiveDependencies, RetentionRoots) instead of
    any; the retain entry requires no services.

  • foldkit-cms, drafts: a draft stores the form's Model settled, as it is
    shown again, so nothing in flight is saved; a Builder's undo history was,
    up to 200 copies of the page, and a page of about 150 blocks could no
    longer be saved ("This draft is too large to save").

  • foldkit-mixins-builder, words: selectAHolder is nothingHoldsIt, and
    says what it now means: with nothing selected a tile goes anywhere that can
    hold it, so a disabled one has nowhere yet. The empty page says to begin
    with a block the palette offers.

  • foldkit-cms, editor status: a publish or a schedule the form's own
    checks stop is Incomplete (a new EditorStatus) until the next edit, and
    one waiting for a check reads Publishing; before, the status stayed what
    the last save said, "Saved", so a refused publish read as a success.
    EditorForm gains engine.isValidating, which Form.make provides.

  • foldkit-builder, dropping: where the node dragged over has no place
    for what is dragged, it lands by the nearest node holding it that does,
    before or after it, and drag.over names that holder; a Section dragged
    onto a Section's last Heading landed only on the Section's thin bottom edge.
    dropAt agrees.

  • foldkit-mixins-builder: a click on a layers row makes it current, as
    the keys do (TreeNavigation's Focused), rather than sending Selected,
    so a narrow editor keeps the layers showing instead of switching to
    Settings and hiding the row that had just taken focus.

  • CMS example: a Quote's text and a Callout's body keep the lines broken
    with Shift+Enter once editing ends, in the editor and on the public page
    (white-space: pre-line on their looks).

  • foldkit-primitives, PointerDrag: an Escape that cancels a drag is
    caught on the way down and stopped there, so the focused element does not
    also act on it; in the Builder, Escape during a canvas drag cancelled the
    drag and deselected the node.

  • foldkit-mixins-builder: the palette's tiles and the command buttons
    are disabled by aria-disabled rather than disabled, so Undo pressed on
    the last step, or a tile whose place fills, keeps focus instead of dropping
    it to <body>. Style [aria-disabled="true"]; the CMS example does.

  • foldkit-primitives, EditableText: when Enter or Escape ends an edit
    and the view then removes the field or makes it no longer editable, focus
    comes back to the container instead of falling to <body>, where the next
    key (an undo, a Delete) did nothing. The Builder's canvas is that container.

  • foldkit-primitives, PointerDrag: targets.within is looked for
    nearest first, under the container's closest ancestor holding a match,
    not the first on the page, so the second of two editors dropped its tiles
    onto the first's page. foldkit-mixins-builder finds its canvas by a
    data-builder-canvas attribute rather than an id built from the Builder's
    name, which a name with a space or a colon broke (no drop landed, or every
    move threw).

  • foldkit-primitives, Measure: a change in the subtree is measured as
    its records arrive, so an editor's selection box moves in the frame the
    selection is drawn in rather than one after; and it measures again when an
    image or a font inside loads and on each frame of a transition or an
    animation inside, which moved a target without resizing it and left the box
    where it was.

  • foldkit-mixins, per-item memo (slots.x.lazy): a static Style's
    contribution is compared too, so two views apart only by a Style no longer
    reuse each other's rows (a theme switch kept the old theme's rows); and an
    item drawn with no runtime frame (a test, a server) keeps no bookkeeping.

  • foldkit-builder, text edited in place: Escape puts the text back even
    where another edit came between, a commit writes nothing the page already
    holds, a second ask for the field being edited begins nothing, and an undo
    or a redo ends editing. A session in which nothing was typed writes nothing
    when it ends, so leaving a field untouched keeps an agent's edit made
    meanwhile (the Model's editing gains typed). A Block named as one of Object's own names is not
    taken for one with starting props, and a pasted tree is checked by its own
    ids, so a refusal names what was copied rather than freshly minted ids.

  • foldkit-builder, the inspector: an optional prop or input emptied is
    taken away (unsetProp, or the input written without it) instead of left
    as it was; a value the Block refuses leaves its field showing the node's
    value, not one that looks accepted; an action's input is written with only
    the keys its Schema names; and a held form Message with a key its variant
    lacks is not decoded. Settings gains keys.

  • foldkit-builder, placing and commands: placeFor puts a Block that
    is no root, with nothing near the selection to take it, last in the last
    Region on the page with room that accepts it, so a node cut and pasted
    back comes back rather than being refused with a minted id; a paste with
    no place at all is refused before ids are minted, as builder:no-place
    (a new word, noPlace). Duplicate is not offered where its Region is
    full. keyCommand tries each command a key names in turn, so a command an
    application adds on a built one's key runs where that one has nothing to
    do.

  • foldkit-composition: field(key) takes only a prop that is exactly
    string; a list of names ('plain' | 'accent') is no longer a field. An
    id that is one of Object's own names (toString, constructor,
    __proto__) is an id as any other: a tree naming one it lacks was a
    TypeError from apply, and one it held was refused as taken; a stored
    Document naming one it lacks was a TypeError from validate, index and
    describe, and is now composition:missing-node.
    Renderer.fields no longer shares the page's memo, which made a view that
    called it redraw every node on every change. Composition.treeRefusal is
    exported, to check a tree by its own ids before it is rekeyed.

  • foldkit-mixins-builder: the default layout is four regions. define
    draws regions holding start (palette, layers), bar (toolbar, crumbs,
    viewports, preview), stage (alert, canvas) and end (inspector), where it
    drew ten sibling panels; root is always the container builder, so a
    Style may follow the editor's own width. A Style that placed each panel on
    the root's grid places the regions instead.

  • foldkit-composition/foldkit: absence is an Option. RenderContext.on(event)
    returns an Option of the Message, and Renderer.render's edit options
    selected, hovered, drop and editing take Options, as an editor's
    Model holds them, rather than undefined.

  • foldkit-mixins-builder: BuilderView.describe and BuilderView.controls
    are gone,
    for Block.words (in foldkit-composition) and
    Builder.controls (in foldkit-builder). The toolbar's history, undo
    and redo Slots are toolbar and toolbarAction, and ACTIONS and
    SHORTCUTS are the command table. foldkit-builder: DragStarted
    takes a source, and dropAt a DragSource, not an id.

  • foldkit-primitives: Measure attaches nothing when it cannot start.
    Where there is no ResizeObserver (jsdom), it had already attached a
    MutationObserver, which then threw on every change to the page.

  • A value that may be absent is an Option, not null or undefined,
    across the read and builder APIs. Breaking:

    • foldkit-surface: Surface.at's params function returns
      Option<Params> (none: inactive), and projectionOf returns
      Option<Projection>.
    • foldkit-remote: Data.active(name, projectionOf) makes an application's
      read of the domain an active Surface; it replaces spelling one out from
      contract, and throws for a domain on a raw optic, which names no
      application.
    • foldkit-crud: detail.at's id and list.at's input return an
      Option; more(model) is an Option<Command>. Crud.options throws
      without lists.
    • foldkit-cms: the editor's entry, pageId, resumed, state and
      error reads return Options; an editor domain gives active instead of
      contract.
    • foldkit-builder: the Model's selected, hovered, refused and drag
      are Options, stored as null. Selected takes an id and Deselected
      clears it; likewise Hovered/Unhovered, DraggedOver({ id, zone })/
      DraggedOff, and PreviewChosen/PreviewCleared. placeFor, moveBy,
      dropAt and keyCommand return Options.
    • foldkit-remote: a preview (Data.overlay) of an entity the server has
      not seen, missing a field its Selection reads, reads Failed with an
      Overlaid error naming the fields, not Initial for good.
    • foldkit-composition: SurfaceBlock.reads, QueryBlock.reads, value
      and the documentOf of their active are Options; the reads gain
      data(model), each node's value as a Renderer takes it. Block.stored(block, key) is how a prop is stored, and StoredPropsOf<B> its type.
  • foldkit-mixins: a family's text token is on-fill, and each family
    gains ink.
    accent.text read as "accent-colored text" and was the text
    on the accent's fill. It is now accent['on-fill'] (--fk-accent-on-fill),
    and accent.ink is the accent as text on the base surface, for every family.
    text['on-accent'], the same value as accent['on-fill'], is removed.
    Prose's mark drew its text in the on-fill color over the accent's tint; it
    uses ink.

  • foldkit-remote: a query read shows at most its window, and "load more"
    grows the window.
    Remote keeps one connection per query and input, so a
    read of first: 3 now shows three rows even when a first: 50 read of the
    same query loaded more, with hasNext set when it cut any. Two windows of one
    connection are one query, for the wider; a window wider than what the
    connection holds fetches only the rows it lacks (before, it was never
    fetched). Data.more(model, projection) returns the Model with the read's
    window one page larger, an Option; Data.next, Data.previous,
    Data.fetch and the fold's fetch are removed. Remote.query and
    Remote.queryMessage still run one page by hand. The store gains grown
    and the WindowGrown Message.

  • foldkit-bundle: complete's update error names a common cause: an
    unannotated parameter on a callback written inline in the config, which keeps
    TypeScript from inferring the config at all.

  • foldkit-composition: Renderer.render of a Renderer that sends nothing
    takes any application's builder,
    so a view passes its own h instead of
    inertHtml.

  • foldkit-remote-drizzle: query is written without type arguments; the
    README says how the principal is inferred from where's parameter.
    foldkit-cms-drizzle dropped its own.

  • foldkit-mixins-crud: ListSlots and DetailSlots gain badge, and a
    Display renderer's context carries it, so a value drawn as a label of its own
    is styled as a Slot. foldkit-cms's state renderer draws in it.

  • foldkit-cms-drizzle: cms.import({ type, values, as, at?, entry? })
    publishes content that exists already (a seed, or another CMS's) by the
    publish path, in one transaction, instead of an application writing the CMS's
    tables by hand.

  • foldkit-cms: the Entry has a server-derived may, the transitions the
    reader may ask by the server's allow; the placed editor gives
    may(model, transition), and Cms.transitions lists them all.
    foldkit-cms-drizzle supplies it; its Asked type is removed for
    foldkit-cms's Transition, which it duplicated.

  • foldkit-mixins-form: FormView's submits: false draws a form without
    its submit button.

  • foldkit-primitives: KeepInView({ selector }), a Mount that scrolls
    whatever newly matches in its subtree into view. foldkit-mixins-builder
    mounts it on the layers panel and the canvas, so a new selection (an inserted
    Block, say) is in view. foldkit-composition exports MARK_ATTRIBUTE.

  • foldkit-form: a number-literal schema resolves to a Select, whose
    options may be text or numbers and whose value is the chosen option itself.
    Control.parse now receives the control's data. foldkit-mixins-builder's
    inspector draws the select and stores a number as a number.

  • foldkit-entity: Selected<typeof selection>, the value a Selection
    reads.

  • foldkit-cms: the editor's Commands carry the form's requirements, not
    any.
    EditorForm, EditorContent and Editor.make gain a Services
    parameter (default never), so an application's update types its Commands
    as RemoteClient and its runner needs no cast.

  • foldkit-entity: Words.of(schema), a schema's title and
    description as Options, including a title given before a check (which
    Effect 4 resolves past). foldkit-form, foldkit-crud's column labels and
    foldkit-mixins-builder's inspector read words through it; the inspector and
    Crud no longer lose such a title.

  • foldkit-remote-drizzle: drizzleWrites and returning.row.
    drizzleWrites is the provided database's insert, update and delete,
    typed by each table's columns, so a handler writes without casting
    DrizzleDatabase; returning.row(binding, id) reads back the row a handler
    wrote as patches, every column and each one relation as its ref.
    foldkit-cms-drizzle uses both instead of its own casts.

  • foldkit-crud: list.more(model) is the Model showing one page more, an
    Option, instead of a Command.

  • foldkit-composition: QueryBlock.reads no longer cuts rows itself;
    Remote does.

  • foldkit-mixins-ui: outline and ghost buttons read --fk-ink first,
    so on a band that sets --fk-ink: currentColor their text is the band's
    color instead of the tone's ink, which does not read there.

  • foldkit-mixins: Defaults.headings reads --fk-heading first, so a
    container drawn in its own color sets --fk-heading: currentColor and its
    headings take its color; elsewhere they stay text-overt.

  • foldkit-mixins-form: FieldSlots gains group and affix, for a
    control drawn with text beside it; foldkit-cms's slug renderer draws its
    prefix and input in them instead of in bare spans with a
    data-cms-slug-prefix attribute.

  • foldkit-primitives: TreeNavigation writes each row's level as
    --fk-tree-level
    beside aria-level, so one calc indents any depth.

  • foldkit-composition: the edit marks are one attribute.
    data-composition-mark is selected or hovered and replaces
    data-composition-selected and data-composition-hovered; a node both
    hovered and selected is selected. Restyle [data-composition-selected] as
    [data-composition-mark='selected'].

  • foldkit-mixins: a recipe's variants is optional, so a recipe that is
    only its base (Style.recipeFor(Slots)({ base })) no longer writes
    variants: {}.

  • foldkit-ssr: Messages answered before boot reach update before any
    other Message,
    instead of being replayed through a Subscription after the
    first render. An event dispatched in the task that boots the page, such as
    the rest of a burst of typing, reached the runtime before the replay, so
    the text ended on its first character. The runtime still starts from the
    resumed Model, so its first render is the served markup; its update is
    wrapped to run the answered Messages ahead of the first one it processes.
    The foldkit-ssr.replay entry is gone.

  • foldkit-ssr: deferred boot no longer depends on hydrate rendering its
    first frame before it returns.
    Until that frame commits the page keeps
    answering from its markers, and it then sends the live page the events only
    it can answer.

  • foldkit-mirror: reduce returns the Model it was given when the store
    holds what the Model already does,
    so the URL change a mirror's own write
    causes, a navigation that leaves its keys alone, and a restore that finds
    nothing new no longer render the page again. A structured field's text is
    remembered by the value's identity, so the write entry no longer serializes
    it on every Model change.

  • foldkit-bundle: a placement or collection item returns the parent Model
    itself when the child's update returns its own Model,
    so a Message that
    changes nothing renders nothing.

  • foldkit-primitives: a Message that changes nothing returns the Model it
    was given,
    across state, time, motion, media, interaction, device, network
    and event primitives, and the navigation and selection Behaviors describe
    their items once per render instead of once per item. Virtual caches its
    row offsets per keys array and heights record. MediaStream's Started
    while live no longer stays at requesting, and Presence's Hide while
    hidden no longer shows the content for the hide duration.

  • foldkit-remote: the common Messages that change nothing return the
    Model they were given,
    so Data.reduce and the wiring keep the application's root on a
    duplicate live event, a repeated ReadStarted, an empty Hydrated, or a
    retention pass that collects nothing. An entity's or list row's decoded
    value is the same object after a refetch or live patch that brought equal
    data to every field of its entity, so a keyed row's lazy view does not
    re-run. The live entry no longer closes and reopens its stream on every
    event, and a pending request no longer rebuilds the visible store on each
    overlay change. A list read now shows a live insert
    or an optimistic connection change that wrote no entity; it used to keep
    the list from before it. The unused shouldWake export is removed.

  • foldkit-remote: MutationSucceeded carries now, the clock reading of
    the answer, and the entities it writes are dated by it. They were dated 0, so
    under a freshness policy every value a mutation wrote was already expired and
    read again at once, and one a read had just dated was set back. Data.mutate
    and Remote.mutateInto take a now option (default Date.now), as
    ObserveOptions and LiveOptions do. settleSuccess and
    reconcileMutation take the answer as one MutationAnswer object
    ({ entities, deleted?, now }, plus connections? for settleSuccess)
    instead of positional arguments, so the clock cannot be left out. A
    hand-built MutationSucceeded adds the field, and a stored one without it
    no longer decodes.

  • foldkit-remote: the read entry plans once per Remote state and query. A
    Model change the Remote model is not part of, such as typing in a field,
    reuses the plan until the next freshness deadline instead of walking every
    row again: about 1 ms to 0.02 ms per change for a list of 400 rows.

  • foldkit-remote: freshness follows what the server said and when. A
    pending optimistic patch keeps the date of the value under it, so under a
    freshness policy a request in flight no longer makes a fresh entity read as
    expired and refetch it; an entity only a request holds (a temporary id) is
    never aged, planned or timed. A field the server settled without a value
    now ages out with its entity on the read entry's timer, instead of waiting
    for an unrelated Model change. EntityWrite takes an optional updatedAt.

  • Tag branching the type checker can check. Every package branches on a
    _tag it can type: Schema ASTs are SchemaAST.AST, tested with Effect's
    guards, instead of a hand-written { _tag: string }; switches list every
    variant or end in absurd; and casts before a _tag read are gone.
    foldkit-entity exports SchemaShape (isNullish, present, isText),
    the one reading of an optional Schema that cms, crud, form, remote
    and remote-drizzle each had a copy of. foldkit-cms's EditorForm.field
    returns a FieldValidation.Field<unknown>. pnpm tags:check finds branching
    the checker cannot vouch for.

  • foldkit-form: DraftKind gains 'model', and NestedForm gains
    control and Message.Control. A switch over DraftKind handles the new
    case, and a hand-written NestedForm adds the two members; forms made by
    Form.make have them.

  • foldkit-durable schema 6: schema 5 adds epoch and replica-binding
    tables; schema 6 records the replica on each operation. Files from schema 4
    or 5 upgrade in place;
    retained operations restore their actor bindings. A custom journal with
    compacted older operations needs legacyReplicaId to recover the replica
    from an operation id; without it opening is refused. foldkit-sync supplies
    this callback. The snapshot cache also follows the document epoch after a
    reset through another handle. Older builds refuse a schema 6 file.

  • foldkit-sync: the socket transport reconnects for as long as its layer
    lives.
    maxRetries now counts consecutive failures after which queued work
    fails fast, instead of ending the transport. Replica.start retries a failed
    exchange on a backoff, and every failed exchange sets status.lastError.

  • foldkit-sync: the IndexedDB database moves to version 2 (the outbox
    store); code from before it cannot open a database this version wrote.