experimental-a2 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +360 -0
- package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
- package/dist/actor-DBbe5xeE.d.ts.map +1 -0
- package/dist/actor-client.d.ts +2 -5
- package/dist/actor-client.d.ts.map +1 -1
- package/dist/actor-client.js +13 -4
- package/dist/actor-client.js.map +1 -1
- package/dist/actor-react.d.ts +5 -3
- package/dist/actor-react.d.ts.map +1 -1
- package/dist/actor-react.js +20 -10
- package/dist/actor-react.js.map +1 -1
- package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
- package/dist/actor-shared-t7w8xIKP.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +42 -23
- package/dist/actor.js.map +1 -1
- package/dist/{ai-CrLYwNEx.js → ai-UrWfKeuv.js} +966 -541
- package/dist/ai-UrWfKeuv.js.map +1 -0
- package/dist/ai-client.d.ts +27 -0
- package/dist/ai-client.d.ts.map +1 -0
- package/dist/ai-client.js +29 -0
- package/dist/ai-client.js.map +1 -0
- package/dist/ai-react.d.ts +96 -0
- package/dist/ai-react.d.ts.map +1 -0
- package/dist/ai-react.js +142 -0
- package/dist/ai-react.js.map +1 -0
- package/dist/ai-server.browser.js +2 -2
- package/dist/ai-server.browser.js.map +1 -1
- package/dist/ai-server.d.ts +80 -36
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +466 -265
- package/dist/ai-server.js.map +1 -1
- package/dist/{ai-HJ9fHfYI.d.ts → ai-vpGtcaCB.d.ts} +115 -101
- package/dist/ai-vpGtcaCB.d.ts.map +1 -0
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
- package/dist/client-COaiaDub.js.map +1 -0
- package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
- package/dist/client-qRnRUzBS.d.ts.map +1 -0
- package/dist/client.d.ts +4 -2
- package/dist/client.js +1 -1
- package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
- package/dist/contract-BYKASlCV.js.map +1 -0
- package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
- package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
- package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
- package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
- package/dist/internal-CbVvEwad.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/react-BbJ-g6Uu.d.ts +86 -0
- package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
- package/dist/react.d.ts +2 -122
- package/dist/react.js +107 -100
- package/dist/react.js.map +1 -1
- package/dist/reducer-BAXc8JyF.js +87 -0
- package/dist/reducer-BAXc8JyF.js.map +1 -0
- package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
- package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
- package/dist/reducer-runtime-DQwz_J59.js +28 -0
- package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +33 -10
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -0
- package/dist/scheduler-vercel.d.ts +4 -9
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +25 -44
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
- package/dist/server-CGM8WzRG.d.ts.map +1 -0
- package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
- package/dist/server-Rj64fNvX.js.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
- package/dist/session-clear-DGI4jTQ5.js.map +1 -0
- package/dist/session-clear-Dshpuhff.d.ts +12 -0
- package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
- package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
- package/dist/store-Gpkq7WBo.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +182 -69
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
- package/dist/store-polling-4_y8lETm.js.map +1 -0
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +399 -108
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
- package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.d.ts.map +1 -1
- package/dist/store-redis-http.js +293 -11
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
- package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +11 -11
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +166 -32
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-Cx7AAPGq.js +23 -0
- package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
- package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
- package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
- package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
- package/dist/wire-DjfHOvdW.js.map +1 -0
- package/docs/01-quickstart.mdx +2 -2
- package/docs/actors/01-introduction.mdx +4 -0
- package/docs/actors/04-routes.mdx +14 -1
- package/docs/concepts/01-contracts.mdx +95 -0
- package/docs/concepts/04-state.mdx +3 -3
- package/docs/guides/01-timers.mdx +22 -14
- package/docs/guides/02-cancellation.mdx +1 -1
- package/docs/guides/03-react.mdx +268 -328
- package/docs/guides/04-local-first.mdx +32 -73
- package/docs/guides/05-production.mdx +40 -27
- package/docs/guides/06-ai-agents.mdx +680 -580
- package/docs/guides/09-presence.mdx +3 -7
- package/docs/guides/10-transports.mdx +5 -5
- package/docs/index.mdx +10 -18
- package/docs/reference/01-api.mdx +662 -195
- package/docs/reference/02-errors.mdx +2 -1
- package/examples/playground/DEPLOY.md +52 -6
- package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
- package/examples/playground/app/a0/[appId]/page.tsx +48 -0
- package/examples/playground/app/a0/a0.css +818 -0
- package/examples/playground/app/a0/app-list.tsx +211 -0
- package/examples/playground/app/a0/app-view.tsx +55 -0
- package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
- package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
- package/examples/playground/app/a0/apps.test.ts +116 -0
- package/examples/playground/app/a0/apps.ts +87 -0
- package/examples/playground/app/a0/chat.tsx +364 -0
- package/examples/playground/app/a0/columns.tsx +109 -0
- package/examples/playground/app/a0/context-menu.tsx +116 -0
- package/examples/playground/app/a0/cookies.ts +46 -0
- package/examples/playground/app/a0/crash-on-request.ts +72 -0
- package/examples/playground/app/a0/crashes/route.ts +3 -0
- package/examples/playground/app/a0/crashes.test.ts +33 -0
- package/examples/playground/app/a0/crashes.ts +28 -0
- package/examples/playground/app/a0/devtools.tsx +164 -0
- package/examples/playground/app/a0/events/route.ts +6 -0
- package/examples/playground/app/a0/flags.ts +30 -0
- package/examples/playground/app/a0/layout.tsx +38 -0
- package/examples/playground/app/a0/list/route.ts +10 -0
- package/examples/playground/app/a0/model.test.ts +56 -0
- package/examples/playground/app/a0/model.ts +58 -0
- package/examples/playground/app/a0/page.tsx +14 -0
- package/examples/playground/app/a0/preview-pane.tsx +145 -0
- package/examples/playground/app/a0/preview.test.ts +53 -0
- package/examples/playground/app/a0/preview.ts +26 -0
- package/examples/playground/app/a0/sandbox-log.ts +69 -0
- package/examples/playground/app/a0/sandbox.ts +191 -0
- package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
- package/examples/playground/app/a0/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/server.ts +158 -0
- package/examples/playground/app/a0/session.ts +47 -0
- package/examples/playground/app/a0/shell.tsx +150 -0
- package/examples/playground/app/a0/short/events/route.ts +9 -0
- package/examples/playground/app/a0/splitter.tsx +120 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
- package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
- package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
- package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
- package/examples/playground/app/agent/events/route.ts +5 -2
- package/examples/playground/app/agent/ingress.test.ts +98 -0
- package/examples/playground/app/agent/model-input.test.tsx +58 -0
- package/examples/playground/app/agent/model-input.tsx +115 -0
- package/examples/playground/app/agent/model.test.ts +73 -0
- package/examples/playground/app/agent/model.ts +58 -26
- package/examples/playground/app/agent/new-agent-session.tsx +37 -19
- package/examples/playground/app/agent/page.tsx +4 -4
- package/examples/playground/app/agent/server.ts +15 -14
- package/examples/playground/app/agent/session.ts +5 -6
- package/examples/playground/app/agent/telemetry.test.ts +168 -0
- package/examples/playground/app/agent/telemetry.ts +30 -0
- package/examples/playground/app/agent/trace.test.ts +23 -0
- package/examples/playground/app/agent/trace.ts +18 -0
- package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
- package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
- package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
- package/examples/playground/app/canvas/model.ts +53 -53
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
- package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
- package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
- package/examples/playground/app/chat/[chatId]/session.ts +2 -19
- package/examples/playground/app/chat/agent/route.ts +9 -0
- package/examples/playground/app/chat/model.test.ts +46 -119
- package/examples/playground/app/chat/model.ts +82 -128
- package/examples/playground/app/chat/scheduler/route.ts +2 -2
- package/examples/playground/app/chat/server.test.ts +108 -0
- package/examples/playground/app/chat/server.ts +69 -73
- package/examples/playground/app/components/activity-feed.tsx +30 -1
- package/examples/playground/app/components/activity-history.test.tsx +35 -0
- package/examples/playground/app/components/demo-icons.tsx +89 -0
- package/examples/playground/app/components/demos.tsx +33 -0
- package/examples/playground/app/components/fast-link.test.tsx +58 -0
- package/examples/playground/app/components/fast-link.tsx +55 -0
- package/examples/playground/app/components/site-header.tsx +96 -0
- package/examples/playground/app/counter/counter-client.tsx +3 -3
- package/examples/playground/app/counter/model.test.ts +3 -3
- package/examples/playground/app/counter/model.ts +11 -21
- package/examples/playground/app/counter/page.tsx +2 -15
- package/examples/playground/app/counter/session.ts +2 -2
- package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
- package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
- package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
- package/examples/playground/app/documents/[documentId]/session.ts +2 -2
- package/examples/playground/app/documents/model.ts +27 -27
- package/examples/playground/app/globals.css +420 -207
- package/examples/playground/app/initial-state.test.tsx +54 -0
- package/examples/playground/app/layout.tsx +7 -23
- package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
- package/examples/playground/app/orders/[orderId]/session.ts +2 -2
- package/examples/playground/app/orders/model.ts +49 -49
- package/examples/playground/app/page.tsx +13 -102
- package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
- package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
- package/examples/playground/app/recovery/model.ts +30 -30
- package/examples/playground/instrumentation.ts +5 -0
- package/examples/playground/package.json +10 -2
- package/examples/playground/scripts/a0-template.ts +77 -0
- package/examples/playground/vercel.json +18 -0
- package/package.json +3 -1
- package/src/actor-client.ts +16 -4
- package/src/actor-react.ts +25 -11
- package/src/actor-shared.ts +21 -6
- package/src/actor.ts +76 -31
- package/src/ai-agent-actions.ts +219 -0
- package/src/ai-agent.ts +2343 -0
- package/src/ai-app-state.ts +91 -0
- package/src/ai-client-state.ts +39 -2
- package/src/ai-client.ts +62 -0
- package/src/ai-context-schema.ts +25 -5
- package/src/ai-context.ts +90 -40
- package/src/ai-control-server.ts +94 -55
- package/src/ai-control.ts +2 -0
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +382 -58
- package/src/ai-progress-batches.ts +26 -0
- package/src/ai-projector.ts +95 -14
- package/src/ai-react.ts +146 -0
- package/src/ai-server.browser.ts +2 -2
- package/src/ai-server.ts +20 -1951
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +343 -276
- package/src/bootstrap-stream.ts +72 -0
- package/src/bounded-cache.ts +55 -0
- package/src/client-render-session.ts +49 -0
- package/src/client.ts +688 -151
- package/src/contract.ts +92 -42
- package/src/deterministic-id.ts +37 -0
- package/src/equal-json.ts +32 -0
- package/src/errors.ts +13 -1
- package/src/index.ts +5 -1
- package/src/initial-snapshot.ts +39 -0
- package/src/internal.ts +45 -0
- package/src/postgres-notification-scope.ts +14 -3
- package/src/react.ts +171 -206
- package/src/redis-http-reply.ts +256 -0
- package/src/redis-http-stream.ts +73 -0
- package/src/redis-http-subscriptions.ts +13 -5
- package/src/reducer-runtime.ts +52 -0
- package/src/reducer.ts +92 -10
- package/src/scheduler-qstash.ts +49 -20
- package/src/scheduler-task.ts +34 -2
- package/src/scheduler-vercel.ts +32 -84
- package/src/server-fetch.ts +139 -35
- package/src/server.ts +957 -237
- package/src/session-clear.ts +23 -0
- package/src/session-socket.ts +154 -49
- package/src/sse.ts +71 -4
- package/src/store-memory.ts +220 -78
- package/src/store-polling.ts +18 -4
- package/src/store-postgres.ts +461 -114
- package/src/store-redis-core.ts +1260 -434
- package/src/store-redis-http.ts +18 -2
- package/src/store-redis-notify.ts +260 -85
- package/src/store-redis.ts +19 -9
- package/src/store-sqlite.ts +259 -45
- package/src/store.ts +46 -1
- package/src/stream-buffer.ts +43 -0
- package/src/telemetry.ts +13 -1
- package/src/wire.ts +241 -13
- package/dist/actor-ohPC-81x.d.ts.map +0 -1
- package/dist/actor-shared-B5tJfzt-.js.map +0 -1
- package/dist/ai-CrLYwNEx.js.map +0 -1
- package/dist/ai-HJ9fHfYI.d.ts.map +0 -1
- package/dist/client-BYzHjkwU.js.map +0 -1
- package/dist/client-CzyacQpJ.d.ts.map +0 -1
- package/dist/contract-CKRg_E4q.js.map +0 -1
- package/dist/errors-DCk6ch5n.js.map +0 -1
- package/dist/errors-DvhSXnxk.d.ts.map +0 -1
- package/dist/internal-Dq2qYxou.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/reducer-DEMjEY_O.js +0 -29
- package/dist/reducer-DEMjEY_O.js.map +0 -1
- package/dist/reducer-otzHjuJj.d.ts.map +0 -1
- package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
- package/dist/server-BD5ckxZb.js.map +0 -1
- package/dist/server-DbD7IJVK.d.ts.map +0 -1
- package/dist/store-DyZM6fS5.d.ts.map +0 -1
- package/dist/store-polling-CmxUbV93.js.map +0 -1
- package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
- package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
- package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
- package/dist/validate-XKT4FSNn.js +0 -30
- package/dist/validate-XKT4FSNn.js.map +0 -1
- package/dist/wire-BO5wWCb1.js.map +0 -1
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
- package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
- package/examples/playground/app/agent/compaction-settings.ts +0 -49
- package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
- package/examples/playground/app/agent/compaction-timeline.ts +0 -208
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
/** A 24px line icon: ink strokes, with its `accent` parts in orange. */
|
|
4
|
+
function Glyph({ children }: { children: ReactNode }): ReactNode {
|
|
5
|
+
return (
|
|
6
|
+
<svg
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeWidth={1.5}
|
|
11
|
+
strokeLinecap="square"
|
|
12
|
+
aria-hidden
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</svg>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const A0Icon = (): ReactNode => (
|
|
20
|
+
<Glyph>
|
|
21
|
+
<rect x="3" y="4" width="18" height="16" />
|
|
22
|
+
<path d="M3 8h18" />
|
|
23
|
+
<path className="accent" d="M7 12l2.5 2L7 16M12 16h5" />
|
|
24
|
+
</Glyph>
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
export const OrdersIcon = (): ReactNode => (
|
|
28
|
+
<Glyph>
|
|
29
|
+
<path d="M3 4h18v5H3zM5 9v11h14V9" />
|
|
30
|
+
<path className="accent" d="M10 13h4" />
|
|
31
|
+
</Glyph>
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
export const ChatIcon = (): ReactNode => (
|
|
35
|
+
<Glyph>
|
|
36
|
+
<path d="M3 4h18v13H10l-4 3.5V17H3z" />
|
|
37
|
+
<rect className="fill" x="7" y="10" width="2" height="2" />
|
|
38
|
+
<rect className="fill" x="11" y="10" width="2" height="2" />
|
|
39
|
+
<rect className="accent-fill" x="15" y="10" width="2" height="2" />
|
|
40
|
+
</Glyph>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
export const AgentIcon = (): ReactNode => (
|
|
44
|
+
<Glyph>
|
|
45
|
+
<rect x="3" y="3" width="18" height="18" />
|
|
46
|
+
<path className="accent" d="M12 7v10M7.7 9.5l8.6 5M7.7 14.5l8.6-5" />
|
|
47
|
+
</Glyph>
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
export const CanvasIcon = (): ReactNode => (
|
|
51
|
+
<Glyph>
|
|
52
|
+
<rect x="3" y="3" width="10" height="10" />
|
|
53
|
+
<circle cx="13" cy="13" r="5" />
|
|
54
|
+
<path className="accent-fill" d="M13 13l8.5 3.4-3.6 1.3-1.3 3.6z" />
|
|
55
|
+
</Glyph>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
export const DocumentsIcon = (): ReactNode => (
|
|
59
|
+
<Glyph>
|
|
60
|
+
<path d="M5 3h10l4 4v14H5z" />
|
|
61
|
+
<path d="M8 10h8M8 14h4M8 18h7" />
|
|
62
|
+
<path className="accent" d="M14 12v4" />
|
|
63
|
+
</Glyph>
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
export const RecoveryIcon = (): ReactNode => (
|
|
67
|
+
<Glyph>
|
|
68
|
+
<path d="M20.5 12a8.5 8.5 0 1 1-2.5-6l2.5 2.5" />
|
|
69
|
+
<path d="M20.5 3.5v5h-5" />
|
|
70
|
+
<rect className="accent-fill" x="10" y="10" width="4" height="4" />
|
|
71
|
+
</Glyph>
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
export const VaultIcon = (): ReactNode => (
|
|
75
|
+
<Glyph>
|
|
76
|
+
<rect x="3" y="4" width="18" height="15" />
|
|
77
|
+
<circle cx="11" cy="11.5" r="3.5" />
|
|
78
|
+
<path className="accent" d="M11 11.5l2-2" />
|
|
79
|
+
<path d="M17.5 9.5v4M6 19v2M18 19v2" />
|
|
80
|
+
</Glyph>
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
export const CounterIcon = (): ReactNode => (
|
|
84
|
+
<Glyph>
|
|
85
|
+
<rect x="3" y="6" width="18" height="12" />
|
|
86
|
+
<path className="accent" d="M6.5 12h5M9 9.5v5" />
|
|
87
|
+
<path d="M16.5 9v6M15 10.5L16.5 9" />
|
|
88
|
+
</Glyph>
|
|
89
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
A0Icon,
|
|
4
|
+
AgentIcon,
|
|
5
|
+
CanvasIcon,
|
|
6
|
+
ChatIcon,
|
|
7
|
+
CounterIcon,
|
|
8
|
+
DocumentsIcon,
|
|
9
|
+
OrdersIcon,
|
|
10
|
+
RecoveryIcon,
|
|
11
|
+
VaultIcon,
|
|
12
|
+
} from './demo-icons'
|
|
13
|
+
|
|
14
|
+
export type Demo = {
|
|
15
|
+
href: string
|
|
16
|
+
name: string
|
|
17
|
+
icon: ReactNode
|
|
18
|
+
/** The demo fills the window and places the site nav itself. */
|
|
19
|
+
fullWindow?: true
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Every demo, in the order the header menu and the home page list them. */
|
|
23
|
+
export const demos: Demo[] = [
|
|
24
|
+
{ href: '/a0', name: 'A0', icon: <A0Icon />, fullWindow: true },
|
|
25
|
+
{ href: '/orders', name: 'Orders', icon: <OrdersIcon /> },
|
|
26
|
+
{ href: '/chat', name: 'Chat', icon: <ChatIcon /> },
|
|
27
|
+
{ href: '/agent', name: 'Agent', icon: <AgentIcon /> },
|
|
28
|
+
{ href: '/canvas', name: 'Canvas', icon: <CanvasIcon /> },
|
|
29
|
+
{ href: '/documents', name: 'Documents', icon: <DocumentsIcon /> },
|
|
30
|
+
{ href: '/recovery', name: 'Recovery', icon: <RecoveryIcon /> },
|
|
31
|
+
{ href: '/vault', name: 'Vault', icon: <VaultIcon /> },
|
|
32
|
+
{ href: '/counter', name: 'Counter', icon: <CounterIcon /> },
|
|
33
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {
|
|
3
|
+
cleanup,
|
|
4
|
+
createEvent,
|
|
5
|
+
fireEvent,
|
|
6
|
+
render,
|
|
7
|
+
screen,
|
|
8
|
+
} from '@testing-library/react'
|
|
9
|
+
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
|
10
|
+
import { FastLink } from './fast-link'
|
|
11
|
+
|
|
12
|
+
const push = vi.fn<(href: string) => void>()
|
|
13
|
+
vi.mock('next/navigation', () => ({ useRouter: () => ({ push }) }))
|
|
14
|
+
|
|
15
|
+
// Whether each click reached the browser's default; then stop jsdom's
|
|
16
|
+
// unimplemented navigation.
|
|
17
|
+
let navigatedByClick: boolean[] = []
|
|
18
|
+
const record = (event: MouseEvent): void => {
|
|
19
|
+
navigatedByClick.push(!event.defaultPrevented)
|
|
20
|
+
event.preventDefault()
|
|
21
|
+
}
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
push.mockReset()
|
|
24
|
+
navigatedByClick = []
|
|
25
|
+
document.addEventListener('click', record)
|
|
26
|
+
})
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
document.removeEventListener('click', record)
|
|
29
|
+
cleanup()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const link = () => screen.getByRole('link', { name: 'Apps' })
|
|
33
|
+
const press = (): void => {
|
|
34
|
+
const event = createEvent.pointerDown(link())
|
|
35
|
+
// jsdom has no PointerEvent, so the plain event gets its fields here.
|
|
36
|
+
Object.defineProperties(event, {
|
|
37
|
+
pointerType: { value: 'mouse' },
|
|
38
|
+
button: { value: 0 },
|
|
39
|
+
})
|
|
40
|
+
fireEvent(link(), event)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
it('navigates on the press and swallows only that click', () => {
|
|
44
|
+
render(<FastLink href="/a0">Apps</FastLink>)
|
|
45
|
+
press()
|
|
46
|
+
expect(push).toHaveBeenCalledWith('/a0')
|
|
47
|
+
fireEvent.click(link(), { detail: 1 })
|
|
48
|
+
fireEvent.click(link(), { detail: 1 })
|
|
49
|
+
expect(navigatedByClick).toEqual([false, true])
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('lets a keyboard activation through after a press that never clicked', () => {
|
|
53
|
+
render(<FastLink href="/a0">Apps</FastLink>)
|
|
54
|
+
press()
|
|
55
|
+
// The pointer left before release, so no click followed the press.
|
|
56
|
+
fireEvent.click(link(), { detail: 0 })
|
|
57
|
+
expect(navigatedByClick).toEqual([true])
|
|
58
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import type { ComponentProps, ReactNode } from 'react'
|
|
3
|
+
import { useRef } from 'react'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import { useRouter } from 'next/navigation'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A Link that navigates on mouse down instead of on click, which saves the
|
|
9
|
+
* press-to-release time. Anything else keeps the browser's behavior: modified
|
|
10
|
+
* presses (open in a new tab or window), other buttons, and touch, where a
|
|
11
|
+
* press can still turn into a scroll.
|
|
12
|
+
*/
|
|
13
|
+
export function FastLink({
|
|
14
|
+
href,
|
|
15
|
+
onPointerDown,
|
|
16
|
+
onClick,
|
|
17
|
+
...props
|
|
18
|
+
}: Omit<ComponentProps<typeof Link>, 'href'> & { href: string }): ReactNode {
|
|
19
|
+
const router = useRouter()
|
|
20
|
+
const navigated = useRef(false)
|
|
21
|
+
return (
|
|
22
|
+
<Link
|
|
23
|
+
href={href}
|
|
24
|
+
{...props}
|
|
25
|
+
onPointerDown={(event) => {
|
|
26
|
+
onPointerDown?.(event)
|
|
27
|
+
navigated.current = false
|
|
28
|
+
if (
|
|
29
|
+
event.defaultPrevented ||
|
|
30
|
+
event.pointerType !== 'mouse' ||
|
|
31
|
+
event.button !== 0 ||
|
|
32
|
+
event.metaKey ||
|
|
33
|
+
event.ctrlKey ||
|
|
34
|
+
event.shiftKey ||
|
|
35
|
+
event.altKey ||
|
|
36
|
+
props.target
|
|
37
|
+
)
|
|
38
|
+
return
|
|
39
|
+
navigated.current = true
|
|
40
|
+
router.push(href)
|
|
41
|
+
}}
|
|
42
|
+
onClick={(event) => {
|
|
43
|
+
onClick?.(event)
|
|
44
|
+
const pressed = navigated.current
|
|
45
|
+
navigated.current = false
|
|
46
|
+
// The press already navigated, so its click doesn't navigate again. A
|
|
47
|
+
// keyboard activation (detail 0) is never that click, even when an
|
|
48
|
+
// earlier press never got one.
|
|
49
|
+
if (pressed && event.detail !== 0) {
|
|
50
|
+
event.preventDefault()
|
|
51
|
+
}
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import type { FocusEvent, ReactNode } from 'react'
|
|
3
|
+
import { useEffect, useRef } from 'react'
|
|
4
|
+
import { FastLink } from './fast-link'
|
|
5
|
+
import { usePathname } from 'next/navigation'
|
|
6
|
+
import { Asterisk, ChevronDown } from 'lucide-react'
|
|
7
|
+
import { type Demo, demos } from './demos'
|
|
8
|
+
|
|
9
|
+
const close = (details: HTMLDetailsElement | null): void =>
|
|
10
|
+
details?.removeAttribute('open')
|
|
11
|
+
|
|
12
|
+
function useCurrentDemo(): { pathname: string; current: Demo | undefined } {
|
|
13
|
+
const pathname = usePathname()
|
|
14
|
+
const current = demos.find(
|
|
15
|
+
(demo) => pathname === demo.href || pathname.startsWith(`${demo.href}/`),
|
|
16
|
+
)
|
|
17
|
+
return { pathname, current }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The site header, unless the demo lays out the whole window itself. */
|
|
21
|
+
export function SiteHeader(): ReactNode {
|
|
22
|
+
const { current } = useCurrentDemo()
|
|
23
|
+
if (current?.fullWindow) return null
|
|
24
|
+
return (
|
|
25
|
+
<header className="site-header">
|
|
26
|
+
<SiteNav />
|
|
27
|
+
</header>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The wordmark, then a menu that switches between the demos. */
|
|
32
|
+
export function SiteNav(): ReactNode {
|
|
33
|
+
const { pathname, current } = useCurrentDemo()
|
|
34
|
+
const menu = useRef<HTMLDetailsElement>(null)
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const outside = (event: PointerEvent): void => {
|
|
38
|
+
if (!menu.current?.contains(event.target as Node)) close(menu.current)
|
|
39
|
+
}
|
|
40
|
+
const escape = (event: KeyboardEvent): void => {
|
|
41
|
+
const details = menu.current
|
|
42
|
+
if (event.key !== 'Escape' || !details?.open) return
|
|
43
|
+
const hadFocus = details.contains(document.activeElement)
|
|
44
|
+
close(details)
|
|
45
|
+
if (hadFocus) details.querySelector('summary')?.focus()
|
|
46
|
+
}
|
|
47
|
+
document.addEventListener('pointerdown', outside)
|
|
48
|
+
document.addEventListener('keydown', escape)
|
|
49
|
+
return () => {
|
|
50
|
+
document.removeEventListener('pointerdown', outside)
|
|
51
|
+
document.removeEventListener('keydown', escape)
|
|
52
|
+
}
|
|
53
|
+
}, [])
|
|
54
|
+
|
|
55
|
+
const leave = (event: FocusEvent<HTMLDetailsElement>): void => {
|
|
56
|
+
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
57
|
+
close(event.currentTarget)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<>
|
|
63
|
+
<FastLink href="/" className="brand">
|
|
64
|
+
<Asterisk size={18} strokeWidth={2.5} aria-hidden />
|
|
65
|
+
<span>A2</span>
|
|
66
|
+
</FastLink>
|
|
67
|
+
<details ref={menu} className="demo-menu" onBlur={leave}>
|
|
68
|
+
<summary>
|
|
69
|
+
<span aria-hidden>/</span>
|
|
70
|
+
<span className="sr-only">Demo:</span>
|
|
71
|
+
{current?.name ?? 'Playground'}
|
|
72
|
+
<ChevronDown size={13} aria-hidden />
|
|
73
|
+
</summary>
|
|
74
|
+
<nav aria-label="Demos">
|
|
75
|
+
{demos.map((demo) => (
|
|
76
|
+
<FastLink
|
|
77
|
+
key={demo.href}
|
|
78
|
+
href={demo.href}
|
|
79
|
+
aria-current={
|
|
80
|
+
demo !== current
|
|
81
|
+
? undefined
|
|
82
|
+
: pathname === demo.href
|
|
83
|
+
? 'page'
|
|
84
|
+
: 'true'
|
|
85
|
+
}
|
|
86
|
+
onClick={() => close(menu.current)}
|
|
87
|
+
>
|
|
88
|
+
{demo.name}
|
|
89
|
+
{demo === current ? <Asterisk size={12} aria-hidden /> : null}
|
|
90
|
+
</FastLink>
|
|
91
|
+
))}
|
|
92
|
+
</nav>
|
|
93
|
+
</details>
|
|
94
|
+
</>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
@@ -34,9 +34,9 @@ export function CounterClient(): ReactNode {
|
|
|
34
34
|
<span className="badge live">shared</span>
|
|
35
35
|
</header>
|
|
36
36
|
<p className="lede">
|
|
37
|
-
Every click appends one fact to a durable log.
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
Every click appends one fact to a durable log. The subscription starts
|
|
38
|
+
with the current value from the server, then keeps folding live events
|
|
39
|
+
in your browser.
|
|
40
40
|
</p>
|
|
41
41
|
|
|
42
42
|
<section className="counter-panel" aria-label="Shared counter controls">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ContractEvent } from 'experimental-a2'
|
|
2
2
|
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import { counter
|
|
3
|
+
import { counter } from './model'
|
|
4
4
|
|
|
5
5
|
type CounterEvents = typeof counter.events
|
|
6
6
|
type CounterEvent<
|
|
@@ -27,8 +27,8 @@ describe('counter reducer', () => {
|
|
|
27
27
|
event(2, 'incremented'),
|
|
28
28
|
event(3, 'decremented'),
|
|
29
29
|
].reduce(
|
|
30
|
-
(current, nextEvent) =>
|
|
31
|
-
|
|
30
|
+
(current, nextEvent) => counter.reducer.fold(current, nextEvent),
|
|
31
|
+
counter.reducer.initialState,
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
expect(state).toEqual({ value: 1 })
|
|
@@ -1,31 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { contract } from 'experimental-a2'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const counterState = z.object({ value: z.number().int() })
|
|
5
|
+
export type CounterState = z.output<typeof counterState>
|
|
6
|
+
|
|
7
|
+
export const counter = contract({
|
|
5
8
|
name: 'counter',
|
|
6
9
|
events: {
|
|
7
10
|
incremented: z.object({}),
|
|
8
11
|
decremented: z.object({}),
|
|
9
12
|
},
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const counterState = z.object({ value: z.number().int() })
|
|
13
|
-
|
|
14
|
-
export type CounterState = z.output<typeof counterState>
|
|
15
|
-
|
|
16
|
-
export const counterReducer = counter
|
|
17
|
-
.reducer({
|
|
13
|
+
reducer: {
|
|
18
14
|
name: 'counter-value',
|
|
19
15
|
initialState: { value: 0 },
|
|
20
16
|
stateSchema: counterState,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
case 'decremented':
|
|
27
|
-
return { value: state.value - 1 }
|
|
28
|
-
default:
|
|
29
|
-
return state
|
|
30
|
-
}
|
|
31
|
-
})
|
|
17
|
+
fold: (state, event) => ({
|
|
18
|
+
value: state.value + (event.type === 'incremented' ? 1 : -1),
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
})
|
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
|
-
import { counterReducer } from './model'
|
|
3
|
-
import { counterServer } from './server'
|
|
4
2
|
import { CounterClient } from './counter-client'
|
|
5
3
|
import { SessionProvider } from './session'
|
|
6
4
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
const COUNTER_SESSION_ID = 'shared'
|
|
10
|
-
|
|
11
|
-
export default async function CounterPage(): Promise<ReactNode> {
|
|
12
|
-
const session = counterServer.session(COUNTER_SESSION_ID)
|
|
13
|
-
const { state, index } = await session.state(counterReducer)
|
|
14
|
-
|
|
5
|
+
export default function CounterPage(): ReactNode {
|
|
15
6
|
return (
|
|
16
|
-
<SessionProvider
|
|
17
|
-
sessionId={COUNTER_SESSION_ID}
|
|
18
|
-
initialState={state}
|
|
19
|
-
initialIndex={index}
|
|
20
|
-
>
|
|
7
|
+
<SessionProvider sessionId="shared">
|
|
21
8
|
<CounterClient />
|
|
22
9
|
</SessionProvider>
|
|
23
10
|
)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import { createClient } from 'experimental-a2/client'
|
|
3
3
|
import { createReact } from 'experimental-a2/react'
|
|
4
|
-
import {
|
|
4
|
+
import { counter } from './model'
|
|
5
5
|
|
|
6
6
|
export const counterClient = createClient({
|
|
7
|
-
|
|
7
|
+
contract: counter,
|
|
8
8
|
api: '/counter/events',
|
|
9
9
|
})
|
|
10
10
|
|
|
@@ -23,10 +23,14 @@ export function CodeMirrorEditor({
|
|
|
23
23
|
const host = useRef<HTMLDivElement>(null)
|
|
24
24
|
const view = useRef<EditorView | null>(null)
|
|
25
25
|
const [editable] = useState(() => new Compartment())
|
|
26
|
+
const [theme] = useState(() => new Compartment())
|
|
26
27
|
|
|
27
28
|
useEffect(() => {
|
|
28
29
|
if (!host.current) return
|
|
29
30
|
|
|
31
|
+
// CodeMirror's own defaults (cursor, search panel, tooltips) follow the
|
|
32
|
+
// system theme along with the page.
|
|
33
|
+
const dark = window.matchMedia('(prefers-color-scheme: dark)')
|
|
30
34
|
const body = doc.getText('body')
|
|
31
35
|
const editor = new EditorView({
|
|
32
36
|
parent: host.current,
|
|
@@ -34,6 +38,7 @@ export function CodeMirrorEditor({
|
|
|
34
38
|
doc: body.toString(),
|
|
35
39
|
extensions: [
|
|
36
40
|
basicSetup,
|
|
41
|
+
theme.of(EditorView.darkTheme.of(dark.matches)),
|
|
37
42
|
EditorView.lineWrapping,
|
|
38
43
|
EditorView.contentAttributes.of({
|
|
39
44
|
'aria-label': 'Document body',
|
|
@@ -60,12 +65,18 @@ export function CodeMirrorEditor({
|
|
|
60
65
|
}),
|
|
61
66
|
})
|
|
62
67
|
view.current = editor
|
|
68
|
+
const followTheme = (): void =>
|
|
69
|
+
editor.dispatch({
|
|
70
|
+
effects: theme.reconfigure(EditorView.darkTheme.of(dark.matches)),
|
|
71
|
+
})
|
|
72
|
+
dark.addEventListener('change', followTheme)
|
|
63
73
|
|
|
64
74
|
return () => {
|
|
75
|
+
dark.removeEventListener('change', followTheme)
|
|
65
76
|
editor.destroy()
|
|
66
77
|
view.current = null
|
|
67
78
|
}
|
|
68
|
-
}, [doc, awareness, editable])
|
|
79
|
+
}, [doc, awareness, editable, theme])
|
|
69
80
|
|
|
70
81
|
useEffect(() => {
|
|
71
82
|
view.current?.dispatch({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
+
import type { StateSnapshot } from 'experimental-a2'
|
|
2
3
|
import type { CSSProperties, ChangeEvent, ReactNode } from 'react'
|
|
3
4
|
import {
|
|
4
5
|
useCallback,
|
|
@@ -25,11 +26,11 @@ const ADJECTIVES = ['Bright', 'Calm', 'Curious', 'Kind', 'Swift'] as const
|
|
|
25
26
|
const ANIMALS = ['Fox', 'Heron', 'Otter', 'Panda', 'Wren'] as const
|
|
26
27
|
const COLORS = ['teal', 'amber', 'blue', 'violet', 'rose'] as const
|
|
27
28
|
const COLOR_HEX: Record<DocumentActor['color'], string> = {
|
|
28
|
-
teal: '#
|
|
29
|
-
amber: '#
|
|
30
|
-
blue: '#
|
|
31
|
-
violet: '#
|
|
32
|
-
rose: '#
|
|
29
|
+
teal: '#3ccf9f',
|
|
30
|
+
amber: '#f2b54b',
|
|
31
|
+
blue: '#6ea8fe',
|
|
32
|
+
violet: '#a98bff',
|
|
33
|
+
rose: '#ff7a93',
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
type SyncState = 'saved' | 'saving' | 'error'
|
|
@@ -154,12 +155,10 @@ function useYText(doc: Y.Doc, name: string): string {
|
|
|
154
155
|
|
|
155
156
|
export function DocumentSession({
|
|
156
157
|
documentId,
|
|
157
|
-
|
|
158
|
-
initialIndex,
|
|
158
|
+
initial,
|
|
159
159
|
}: {
|
|
160
160
|
documentId: string
|
|
161
|
-
|
|
162
|
-
initialIndex: number
|
|
161
|
+
initial: StateSnapshot<DocumentState>
|
|
163
162
|
}): ReactNode {
|
|
164
163
|
// Minted before the provider connects, so the session carries its
|
|
165
164
|
// participant id from the first frame. The rendered UI keeps its
|
|
@@ -172,8 +171,8 @@ export function DocumentSession({
|
|
|
172
171
|
<SessionProvider
|
|
173
172
|
sessionId={documentId}
|
|
174
173
|
api="/documents/events"
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
initial={initial}
|
|
175
|
+
|
|
177
176
|
{...(identity ? { participant: identity.id } : {})}
|
|
178
177
|
>
|
|
179
178
|
<DocumentClient documentId={documentId} />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import { createReact } from 'experimental-a2/react'
|
|
3
|
-
import {
|
|
3
|
+
import { documents } from '../model'
|
|
4
4
|
|
|
5
5
|
export const { SessionProvider, useSession } = createReact({
|
|
6
|
-
|
|
6
|
+
contract: documents,
|
|
7
7
|
})
|
|
@@ -11,6 +11,13 @@ export const documentActorSchema = z.object({
|
|
|
11
11
|
|
|
12
12
|
export type DocumentActor = z.output<typeof documentActorSchema>
|
|
13
13
|
|
|
14
|
+
const documentState = z.object({
|
|
15
|
+
updates: z.array(z.string()),
|
|
16
|
+
contributors: z.array(documentActorSchema),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export type DocumentState = z.output<typeof documentState>
|
|
20
|
+
|
|
14
21
|
export const documents = a2.contract({
|
|
15
22
|
name: 'documents',
|
|
16
23
|
events: {
|
|
@@ -22,34 +29,27 @@ export const documents = a2.contract({
|
|
|
22
29
|
presence: {
|
|
23
30
|
awareness: z.string().min(1),
|
|
24
31
|
},
|
|
25
|
-
})
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
updates: z.array(z.string()),
|
|
29
|
-
contributors: z.array(documentActorSchema),
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
export type DocumentState = z.output<typeof documentState>
|
|
33
|
-
|
|
34
|
-
export const documentsReducer = documents
|
|
35
|
-
.reducer({
|
|
33
|
+
reducer: {
|
|
36
34
|
name: 'document-updates',
|
|
37
35
|
initialState: { updates: [], contributors: [] },
|
|
38
36
|
stateSchema: documentState,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
}
|
|
37
|
+
fold: (state, event) => {
|
|
38
|
+
switch (event.type) {
|
|
39
|
+
case 'document.updated':
|
|
40
|
+
return {
|
|
41
|
+
updates: [...state.updates, event.payload.update],
|
|
42
|
+
contributors: [
|
|
43
|
+
...state.contributors.filter(
|
|
44
|
+
(actor) => actor.id !== event.payload.actor.id,
|
|
45
|
+
),
|
|
46
|
+
event.payload.actor,
|
|
47
|
+
],
|
|
48
|
+
}
|
|
49
|
+
default:
|
|
50
|
+
return state
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
export const documentsReducer = documents.reducer
|