Primitives / foldkit-primitives
Sixty seconds: follow the color scheme
A bundle is placed in a parent, which gains a field for its state and a wrapper Message for its transitions:
ts
import { Schema } from 'effect'
import type { HtmlBuilder } from 'foldkit/html'
import { Bundle } from 'foldkit-bundle'
import { MediaQuery } from 'foldkit-primitives/media'
const Page = Bundle.compose({ theme: Schema.String }).pipe(
Bundle.withMessages({ ThemeSet: { theme: Schema.String } }),
Bundle.withChild('dark', MediaQuery, { args: { query: '(prefers-color-scheme: dark)' } }),
)
type Model = typeof Page.Model.Type
type Message = typeof Page.Message.Type
const { placements } = Page
const config = placements.complete({
init: () => placements.initial({ theme: 'light' }),
update: placements.update(model => ({ model })),
view: (model: Model, h: HtmlBuilder<Message>) =>
h.div([], [model.dark.matches ? 'Dark mode' : 'Light mode']),
subscriptions: placements.subscriptions(),
})What each line does, and does not do:
Bundle.compose(...).pipe(withChild('dark', MediaQuery, …))states the parent: its own fieldtheme, its own MessageThemeSet, andMediaQueryunderdark. The Model is{ theme, dark: { matches } }; the Message union gainsGotDarkMessage. Nothing runs.placements.initial({ theme: 'light' })gives the fields no placement owns;darkstarts at{ matches: false }.placements.update(own)routesGotDarkMessageto the bundle and everything else to your own update.placements.subscriptions()is thematchMediastream, lifted. The browser is touched only when the runtime subscribes, which then correctsmatchesat once.configis an ordinary Foldkit application config. Hand it to the runtime.
The Solid line this replaces is createMediaQuery('(prefers-color-scheme: dark)').
The difference is where the answer lives: in the Model, where replay,
DevTools, an agent, and a server render all see it.