1import { Match as M, Schema as S } from "effect";
2import type { Update } from "foldkit";
3import type { Document, HtmlBuilder } from "foldkit/html";
4import { defineMessageUnion } from "foldkit/message";
5
6// MODEL
7
8export const Model = S.Struct({ count: S.Number });
9export type Model = typeof Model.Type;
10
11// FLAGS
12//
13// What the server knew when it rendered, and what the browser is handed back
14// so it can rebuild the same first Model. The server reads it from the
15// request; hydration decodes it from the page rather than guessing. Flags
16// ship in the HTML, so they are public — never put a secret here.
17
18export const Flags = S.Struct({ initialCount: S.Number });
19export type Flags = typeof Flags.Type;
20
21// MESSAGE
22
23export const Message = defineMessageUnion({
24 ClickedDecrement: {},
25 ClickedIncrement: {},
26 ClickedReset: {},
27});
28export const { ClickedDecrement, ClickedIncrement, ClickedReset } = Message;
29export type Message = typeof Message.Type;
30
31// UPDATE
32
33export const update = (
34 model: Model,
35 message: Message,
36): Update.Return<Model, Message> =>
37 M.value(message).pipe(
38 M.withReturnType<Update.Return<Model, Message>>(),
39 M.tagsExhaustive({
40 ClickedDecrement: () => ({ model: { count: model.count - 1 } }),
41 ClickedIncrement: () => ({ model: { count: model.count + 1 } }),
42 ClickedReset: () => ({ model: { count: 0 } }),
43 }),
44 );
45
46// INIT
47//
48// Runs on the server to produce the rendered Model, then again in the browser
49// with the same Flags. Both sides must reach the same Model or hydration
50// rebuilds the tree it was supposed to adopt.
51
52export const init = (flags: Flags): Update.Return<Model, Message> => ({
53 model: { count: flags.initialCount },
54});
55
56// VIEW
57
58export const view = (model: Model, h: HtmlBuilder<Message>): Document => ({
59 title: `Counter: ${model.count}`,
60 body: h.div(
61 [h.Id("app")],
62 [
63 h.p([h.Id("count")], [model.count.toString()]),
64 h.div(
65 [],
66 [
67 h.button([h.OnClick(ClickedDecrement())], ["-"]),
68 h.button([h.OnClick(ClickedReset())], ["Reset"]),
69 h.button([h.OnClick(ClickedIncrement())], ["+"]),
70 ],
71 ),
72 ],
73 ),
74});