Repository navigation
v0.11.0
foldkit-remote, foldkit-remote-server and foldkit-remote-drizzle 0.8.0;
foldkit-surface 0.5.0; foldkit-bundle 0.3.0; foldkit-mixins,
foldkit-mixins-ui, foldkit-mixins-surface and foldkit-mixins-crud 0.4.0;
foldkit-primitives 0.3.0; foldkit-crud, foldkit-entity, foldkit-agent
and the four agent adapters, and foldkit-durable 0.4.0; foldkit-form,
foldkit-mixins-form, foldkit-cms, foldkit-cms-drizzle and
foldkit-bundle-surface 0.2.0; foldkit-mirror 0.3.0; foldkit-sync 0.6.0;
foldkit-react and foldkit-react-codegen 0.2.0. Every one of them moves to
Foldkit 0.163 and Effect 4.0.0-rc.116; foldkit-metadata is unchanged at
0.1.0. Four packages are published for the first time at 0.1.0:
foldkit-ssr, foldkit-richtext, foldkit-richtext-dom and
foldkit-mixins-richtext.
A page is declared once. Bundle.compose states a parent's own fields and
Messages and the bundles it places, and derives its Model, Message union,
placements and assembly, so a page no longer spreads each placement's fields
and cases into Schemas it writes by hand. Bundle.lazy keeps a bundle's
update and view out of the boot chunk.
Interaction and styling you do not have to rebuild. foldkit-primitives
gains an interaction subpath of Bundles for roving focus, typeahead, grid
navigation, selection, press, focus scopes, dismissable layers and live
announcements. foldkit-mixins gains cascade layers as a value, a theme from a
few knobs, layout pieces, and multi-slot recipes, which foldkit-mixins-ui
ships for its components.
Remote tells a withheld field from a deleted entity, forgets everything at
a change of principal with Data.forget, and refreshes a value when it ages
out rather than when the Model next changes.
Server rendering and rich text, first releases. foldkit-ssr renders on
the server, hands the browser the Model the server reached instead of rerunning
init, and can answer events before the runtime boots. foldkit-richtext and
its DOM adapter give an editor whose document lives in the application Model.
All four new packages are early and may change between minor versions.
New packages
foldkit-ssr0.1.0. Server rendering that hands the browser the Model
the server reached, not the inputs to rebuild it.SSR.plannames the slice
of the Model the browser owns, checked against the Surfaces it reads.
SSR.renderandSSR.pagerender on the server,SSR.generateat build
time, andSSR.entryserves through Foldkit'shandleRequest.
SSR.hydrateadopts the server's HTML without runninginit.
SSR.staticregions belong to the server alone, and a part such as
Remote.resume(Data)carries a package's state across. Resumable pages,
forms that work with scripts off, and lazy bundles are listed under Added.foldkit-richtext0.1.0. Pure semantic documents and text
transactions. ADocumentis versioned data with stable node ids, and
runorapply, called fromupdate, returns the next state, a
ChangeSetand a position map, or a diagnostic with no partial result. It
holds no store and does no I/O. Unknown blocks are preserved, migrations
keep identities, a rendering registry declares node kinds, andtoHtmland
toTextserialize a document.foldkit-richtext-dom0.1.0. The DOM adapter for an editable
foldkit-richtextsubtree. It renders a document into real DOM once,
patches only the nodes aChangeSetnames, and turns browser events into
commands the application'supdatedecides on. Subpaths hold the host
mount, events, HTML import, a read-only view, a marks toolbar, and an editor
Bundle (foldkit-richtext-dom/editor-bundle).foldkit-mixins-richtext0.1.0. The editor's mark toolbar as a
MixinsSlotView(markToolbar,MarkToolbarSlots), so each button is a
slot an application styles.
Upgrading from 0.10
- Move
foldkitand@foldkit/uito0.163.0, andeffect,
@effect/platform-browserand any@effect/*package to4.0.0-rc.116. - Deploy a Remote client and server together: the wire protocol is now 4, and a
read you wrote by hand, a test fake included, returnssettledbeside
entities. - Replace
Theme.variable(theme, group, name)withTheme.ref(theme)[group][name].
Breaking
foldkit-remote,foldkit-remote-server:ReadBatchResultrequires
settled, the protocol is 4 and the cache version 5, so a cache persisted by
0.10 is discarded. See the entry under Changed.foldkit-surface: anActiveSurfacecarries a requiredmessages, so one
built by hand, rather than throughSurface.atorSurface.when, adds it.foldkit-mixins:Theme.variableis removed; declarations are type-checked
against csstype; a style property set by a Behavior and by anything else is
refused (mixins:style-property-conflict).foldkit-bundle: an array collection's item must carry its key as its id;
writing one that does not throws.foldkit-cms: a hand-builtEditorFormneedsauthoredChanged, which a
form made byFormalready has.
Added
-
foldkit-bundle:Bundle.compose, a parent in one declaration. A
parent's own fields, then throughpipeits own Messages
(Bundle.withMessages), the bundles it places (Bundle.withChild,
Bundle.withEach), integration wiring (Bundle.withWiring) and its
services (Bundle.withServices). It derives the Model, the Message union
with each child'sGot<Field>Message, each placement under
children.<field>, and the assembly, so a parent no longer spreads
declare(...).fieldsand.casesinto Schemas it writes by hand. Each step
is typed by the parent so far, so anonOutknows the Model. A child whose
config is made from the parent itself, such as a Crud editor whoseonOut
needs the Surface application built from this Model, is added without one
and given it later withBundle.configure; until then itschildrenand
placementsare type errors naming what waits. It builds the same values
declare,parent,atandassembledo, and those stay for a parent
whose Model already exists or a placement with a custom Link. -
foldkit-bundle:Bundle.lazy, a bundle whoseupdateandviewload
on demand. The declaration (Model,Message,args,init,
subscriptions,resources,helpers) stays in the boot chunk; the bodies
are aBundle.Bodya chunk exports. A Message before the load returns a
Command that loads them and yields it again, so nothing is lost;while
renders meanwhile;load()preloads once.Placed.viewand a collection's
views now take any builder withsubmodelandOnClick(BuilderLike), so
a wrapper of Foldkit's builder can be handed to them. -
foldkit-surface: an active Surface carriesmessages.Surface.at
andSurface.whennow give the tags of the Messages the Surface lists, so a
tool holding a plan's Surfaces knows what each may send;foldkit-ssrreads
it to keep a page's bindings to them. Breaking for code that builds an
ActiveSurfaceby hand rather than throughSurface.atorSurface.when:
addmessages, an empty list for a Remote requirement that sends nothing,
asfoldkit-crudandfoldkit-cmsnow do. -
foldkit-ssr: resumable pages. On top
of rendering on the server and handing the browser its slice of the Model,
a page can now answer before its runtime boots.Resume.builder(h)writes
in the server's markup which Message each element causes, and the browser
answers events from those markers until a plan'sstart: 'idle' | 'on-interaction'boots it, replaying what was answered. A page may only
dispatch Messages its active Surfaces list. Withfallback: 'server'a form
posts its Message andSSR.handlerunsupdateand its Commands on the
server, so the form works with scripts off. The configuration'slazylist
loads eachBundle.lazybefore a page renders or boots. A tampered envelope
is refused rather than thrown on, and a post whose Commands never settle is
stopped after 100 steps. See the package README. -
Remote.resume(Data): Remote's state for a server-rendered page. A
resume part forfoldkit-ssrthat sends what the page's
active Surfaces read, field by field through relations, each connection
with its boundaries, and the live cursors of the entities captured; nothing
else of the store. ASnapshotstays the tool for a cache that survives a
reload. The part also vouches for Remote's Subscription entries, which
Data.subscriptionsand the fold'ssubscriptionsmark as safe to start
late, so a page that resumes Remote's data may defer its boot. -
Mirror.foldandRemote.fold: a library's Messages under one variant
of the application's union. SpreadingMirror.messagesor
Remote.messagesinto the union leavesupdateunable to match it
exhaustively, because the library's tags remain in the union after the
reducesguard. A fold gives the shape Foldkit gives a Submodel: declare
GotPrefsMessage: { message: Mirror.Message }or
GotRemoteMessage: { message: Remote.Message }, fold the mirror or the
domain under it, and match the whole union. Everything the library produces
yields the wrapper with the lift recorded: a kv mirror'srestore(and the
fold'sinitStep that runs it), a domain'sfetch, a mutation's Command,
and its Subscription entries. A Story resolves a restore or a mutation by
the library's own answer.Mirror.MessageandRemote.Messageare the
union Schemas for the wrapper field. The spread and thereducesguard stay
for anupdatethat only reduces; wiring stays the bundle path. -
foldkit-primitives: afoldkit-primitives/interactionsubpath for
interaction state a view's slots reflect. Each primitive is a namespace
(RovingTabindex,Press, ...) exporting itsbundle,Model,Message,
Args, afoldkit-mixinsBehavior (behavior) that wires it to a view's
slots, and its pure functions.foldkit-mixinsis a new optional peer,
needed only for this subpath. -
foldkit-primitives: keyboard navigation and selection over a set of
items.RovingTabindexkeeps one tab stop by the current item's id, so a
reorder keeps it; its puremove()handles arrows by orientation, looping,
Home, End, PageUp, PageDown, skipping disabled items and RTL, and under
virtualDOM focus stays on the container witharia-activedescendant.
Typeaheadfinds the item whose label starts with the typed characters, and
a repeated character cycles.ListNavigationcombines the two with one key
handler.GridNavigationmoves in two dimensions over rows of cells, with
Home and End per row and Ctrl+Home and Ctrl+End per grid.Selectionkeeps
{ selected, anchor }insingle,multipleornonemode, with a Shift
range throughRangedand the purebetween(), and writesaria-selected. -
foldkit-primitives: focus management.FocusScope(a Mount in
/dom, with a Behavior in/interaction) focusesinitialFocusor the
first tabbable element, keeps Tab inside the scope undercontain, and
returns focus on unmount underrestore.InputModality(in/events)
records whether the keyboard or a pointer was used last, and
FocusVisible.behaviorwritesdata-focus-visibleunder keyboard for a
design system that decides focus rings in the Model. -
foldkit-primitives: pointer and press input.Presstreats a pointer
press, Enter, Space and an assistive-technology click as one activation. It
ignores the ghost click after a touch and writesdata-pressed. The
placement must handle itsPressed { pointerType, shiftKey }.LongPress
emitsLongPressedafterthresholdMs, reusing the eventsPressreports.
Move(a Mount in/dom) captures the pointer and reports each move as the
distance from where it went down. The parent's update decides what a drag
means. -
foldkit-primitives: overlay layers.DismissLayeris one Bundle
placed once that owns the document's pointerdown and Escape listeners. It
gives the placementDismiss { ids }for the layers a press outside or an
Escape closes. Nested layers and triggers work without registration.
ScrollLockandHideOutsideare Mounts over Foldkit'sDom.lockScroll
andDom.inertOthers, attached throughLayers.scrollLockand
Layers.hideOutside. -
foldkit-primitives:LiveAnnounce, screen-reader announcements from
update. One Bundle holds a polite and an assertive region.say()
builds the Message,view()renders the regions, a burst is read once after
debounceMs, the text clears afterclearAfterMs, and a repeated text is
read again. -
foldkit-primitives: reduced motion as a service.Motionin
foldkit-primitives/motionis read when a transition starts. Provide
Motion.livefor the user'sprefers-reduced-motion, orMotion.reduced/
Motion.fullin tests. Under reduced motion a presence exits at once and a
tween or spring jumps to its end, using the same Messages. With no service
provided, motion is unchanged. -
foldkit-bundle: exports theDeclaredandDeclaredEachtypes, for
a helper that reads a placement's field and wraps its Messages. -
foldkit-mixins: cascade layers as a value, and a stylesheet built from
pieces.Layers.define(names)returnsnames,declare(the@layer
statement as a piece),in(name, piece)andlayer(name); a layer name
outside the order is a type error.Layers.standardis the shipped order:
reset, tokens, theme, defaults, components, layouts, variants, utilities, app. A slot style takes its layer when it is compiled, with
Style.forSlots(S)(pieces, { layer: L.layer('app') }), so the view and the
sheet share the same class names.Style.stylesheetnow takes bare
StyleValues besideNamedStyles and hoists the layer order first. Once a
sheet declares an order, it refuses a rule outside it
(style:unlayered-rule), because an unlayered rule beats every layer,app
included. A sheet with no order behaves as before. -
foldkit-mixins: a theme from a few knobs, underfoldkit-mixins/theme.
Theme.oklch(knobs)derives surfaces, text, outlines, and the accent,
secondary, tertiary and feedback colors from an accent and a few knobs.
Every derived value is a CSS expression, so one knob override recolors the
page in the browser.Theme.tokensholds the non-color scales, with
densityandradius-factorknobs.Theme.rootandTheme.scopedturn a
theme into:rootand selector rules for the sheet, andTheme.scoped
type-checks its overrides against the theme.Theme.ref(theme)gives every
token as a typedvar(--fk-…), so a missing name is a type error.
Theme.compose,Theme.lightDarkandTheme.breakpointWidthscomplete the
set, and the root export gainsref,composeandlightDark. The palette
needs relative color syntax andlight-dark(). -
foldkit-mixins: layout, element defaults and prose as subpath
entries.foldkit-mixins/layoutshipsLayout.stack,cluster,split,
sidebar,switcher,reel,center,frame,pad,autoGrid,
intrinsicandaside.foldkit-mixins/defaultsships element defaults
(reset,body,headings,links,code,controls,all), and
foldkit-mixins/proseshipsProse.style. They are unlayered, so the page
places them withLayers.in.foldkit-mixins/layersre-exportsLayers. -
foldkit-mixins: more Style pieces.Style.selfis a rule on the
element's own class, for declarations that must sit in a layer rather than
inline.statesstyles perdata-statevalue.responsivetakes a map
keyed by breakpoint names typed from the record you pass.enterwrites a
@starting-stylerule, andallowDiscrete,varsand
viewTransitionNameround it out.Style.griddeclares a template whose
area(name)accepts only the areas it names. TheSelectornamespace
builds the stringspseudoandnesttake. Every piece's declarations are
typed asDeclarations, csstype's camelCase properties plus custom
properties. -
foldkit-mixins: multi-slot recipes, per-item styles and
forCapability.Style.recipeFor(Slots)gives each slot of a contract a
base, variants per axis, defaults and compound matches, and returns the
pieces forStyle.forSlots. Itsextend(patch)adjusts a shipped recipe
without forking it.Style.perItemcomputes a piece from the item a slot
is rendered for, andStyle.stagger({ stepMs })staggers a list with no
timer.Style.forCapabilitystyles every public slot with a given
capability. -
foldkit-mixins: slots rendered once per item, and ready-made stateless
Behaviors.slots.x.attrs(base, item)takes an optionalSlotItem
({ index, id?, count? }), which reaches a Behavior's attributes and
mount.Behaviors.Collectiondescribes a parent's array once, with ids,
disabled items andaria-posinset/aria-setsize, and refuses duplicate ids
(mixins:duplicate-item-id).Behaviors.Disclosure,
Behaviors.ToggleState,Behaviors.FieldAssociationand
Behaviors.SpinValueput a parent's state into ARIA and keyboard handling. -
foldkit-mixins-ui:Recipesfor Button, Input, Textarea, Checkbox,
Switch, Dialog and Tabs. Each is aStyle.recipeForover the package's
slot contract, such as
Recipes.Button({ tone: 'danger', variant: 'outline', size: 'sm' }).
Values referenceTheme.tokensand aTheme.oklchpalette, so the page
must ship those tokens. Bases sit in thecomponentslayer and variants in
variants, all as layered rules, so anapp-layer style overrides them.
State comes from the ARIA attributes@foldkit/uialready writes. -
foldkit-mixins-ui:Patterns,HoverIntentandAnchor.
Patternshas anA11y.patternper adapter.Patterns.cataloggives each
one's tier and the behavior it leaves to the browser, so
A11y.validate(Patterns.Tabs, MySlots)checks a custom view.
HoverIntent(HoverIntentSlots) adapts@foldkit/ui's hover intent.
Anchor.behavior(Slots)({ floating, config })positions a floating slot
with@foldkit/ui/anchor. -
foldkit-remote:Data.forget, the one boundary for a change of
principal. Called fromupdateon a login, a logout or a switch of
organization, it returns the Model with every server-derived fact gone
(values, tombstones, unavailable fields, connections, live cursors,
failures) and performs no I/O. Every active Surface's read and live entries
restart, and a read or stream begun before is interrupted rather than
landing after. A mutation in flight is treated as applied, so its answer
writes nothing.Remote.forget(bound, model)is the unbound form. -
foldkit-remote: a value ages out as a Message. Under
staleWhileRevalidatea page that sat still past itsmaxAgewas never
refreshed, because the plan ran only when the Model changed. The read entry
now sleeps until the earliest value it holds ages out, under the Effect
clock, and emitsRefreshStartedfor what is due; the plan that follows
fetches it. A test controls this with the Effect test clock. -
foldkit-form:authoredChanged(before, after). It says whether a
completed transition changed what the author wrote, by comparing the two
Models: a blur, a refused edit or a repeated value isfalse; a changed
draft or an added, removed or changed row istrue. Nested rows recurse.
foldkit-cms's editor now autosaves on it instead of on the form's Message
tags, so a Bundle-backed or stateful control autosaves like a text field. -
foldkit-mixins-form: every control carriesname=<key>, and a
relation picker's checkboxes also carryvalue, so a plain form post (for
examplefoldkit-ssr'sfallback: 'server') carries the drafts.
Changed
-
Foldkit 0.163.0 and Effect 4.0.0-rc.116. Every package that peers
foldkitnow requires^0.163.0, every package peers
effect@^4.0.0-rc.116, andfoldkit-mixins-uipeers@foldkit/ui@^0.163.0.
Foldkit itself now peers@effect/platform-browser@4.0.0-rc.116, so an
application installs that alongsideeffect.foldkit-durabledepends on
@effect/sql-sqlite-node@4.0.0-rc.116.Upgrading from 0.10.0: pin
effect,@effect/platform-browser, and any
@effect/*package to4.0.0-rc.116, andfoldkitand@foldkit/uito
0.163.0. ReplaceevowithmodifyFieldsfromfoldkit/struct; the
package READMEs and the skill already do.@effect/vitestat rc.116 needs
Vitest 5. A test that opened a@foldkit/uiDialog through
Dialog.init({ isOpen: true })opens it throughDialog.bootnow, because
initalways returns a closed Dialog since@foldkit/ui0.161. -
foldkit-form: a nested form's Commands are lifted with
Command.mapMessages. The row lift wrapped the inner form's answer by
hand, which dispatched correctly but left Story and Scene unable to replay
the wrap: resolving a row's check handed the parent update the inner form's
bareChecked. Resolving it now yields theNestedMessage the parent
handles. Theatandrowargs the old lift added are gone; nothing read
them. -
foldkit-react-codegenlowersOnKeyDownSelftoonKeyDownguarded by
event.target === event.currentTarget.OnBeforeInput,
OnBeforeInputPreventDefault,OnKeyDownSelfPreventDefault, and
OnCancelPreventDefaultreportFKREACT0002like the other attributes with
no one-line React form. -
Docs use
Schema.Optionfor optional Model fields andmatchover a
Message. Foldkit's recommended Oxlint preset now rejectsSchema.NullOr
in a Model andswitchon a Message tag; the surface, sync,
mixins-surface, and mirror snippets, and the skill's one-screen example,
no longer trip it. -
foldkit-remote,foldkit-remote-server: a withheld field is settled on
the wire instead of tombstoning its entity. Before, a field the server
answered without was planned again. The second read came back with no
entity, and the client tombstoned it, dropping every field it already held.
ReadBatchResultnow has a requiredsettled: Array<{ entity, id, fields }>:
fields the server will not answer with.RemoteServersettles what
authorizewithheld and what a Source left out of a record. The client
marks those fields unavailable: they are not planned again, a later write
clears them, and a refresh forgets them. An id is tombstoned only when it is
neither returned nor settled. A Selection naming such a field readsFailed
with anUnavailableerror.REMOTE_PROTOCOL_VERSIONis now 4 and
REMOTE_CACHE_VERSIONis now 5, so a persisted cache from 0.10.0 is
discarded. Breaking for a hand-writtenRemoteClient,RemoteRpcClient
or server read handler, including test fakes: returnsettled: [](or the
fields you withheld) besideentities. Deploy client and server together. -
foldkit-mixins:Theme.variableis replaced byTheme.ref.
Breaking.Theme.ref(theme)is built once per theme and reads like the
token it names. MigrateTheme.variable(theme, 'surface', 'base')to
Theme.ref(theme).surface.base. -
foldkit-mixins: declarations are type-checked against csstype.
Breaking at the type level only:inline,self,pseudo,nest,
media,supports,containerand the other pieces took any
Record<string, string>. A misspelled or kebab-case property is now a type
error. Write it in camelCase, or as a--customproperty. Compiled CSS is
unchanged, andcsstypeis a new dependency. -
foldkit-mixins: a style property a Behavior sets has one owner.
Breaking. When a Behavior sets a property throughh.Styleand a Style
piece, the base or another Behavior sets the same one, resolving now raises
mixins:style-property-conflict, naming both. Before, the last writer won
silently. To migrate, remove the property from one of the two. -
foldkit-mixins: rule pieces work underStyle.whenInput. Before, a
pseudo, nest or media piece insidewhenInputwas refused with
style:conditional-rules-unsupported. Now each compiles to its own class,
which ships in the sheet and is present only while the input matches. That
diagnostic code is gone. -
foldkit-cms: the editor autosaves onauthoredChanged. A hand-built
EditorFormnow needs it; a form made byFormalready has it.
Fixed
foldkit-surface:Projection.pickno longer merges two fields that share
a name. A picked field is named by its last key, sopost.idand
viewer.idboth becameid. When their schemas were the same object, which
Schema.Stringalways is, they were merged without a word:getkept one
value and dropped the other, andsetwrote that one value into both
fields. Mirror, Sync and Agent contexts are built frompick, so a URL
mirror or a replica could overwrite one field with another. Bothpickand
Projection.composenow refuse the collision when the projection is built,
naming both paths. The same field picked twice is still kept once.foldkit-bundle: four routing and assembly bugs. A nested placement
listed after its outer placement now gets its Messages, because routing asks
the deepest placements first.Link.collectionByIdthrows, naming both, when
an item's id is not its key; before, the item could never be read back and
its Messages reached nothing. A tag is shared only when every claimant
declares it shared, and a placement never shares. A nested child under an
absent outer child no longer runs its init Commands.foldkit-mixins:Style.nestandStyle.pseudoscope every selector
of a comma list. Only the first selector got the element's class, so the
rest matched page-wide. Each top-level selector is now scoped. A selector
that writes&places the class there instead of gaining a prefix. Rules
without a comma compile to the same text and class.