@@ -3,6 +3,7 @@ import { DebugSpaceEvents } from '@/components/debug-space-events';
33import { DebugSpaceState } from '@/components/debug-space-state' ;
44import { Button } from '@/components/ui/button' ;
55import { assertExhaustive } from '@/lib/assertExhaustive' ;
6+ import { uuid } from '@automerge/automerge' ;
67import { bytesToHex , hexToBytes } from '@noble/hashes/utils' ;
78import { createFileRoute } from '@tanstack/react-router' ;
89import { Effect , Exit } from 'effect' ;
@@ -12,6 +13,7 @@ import type {
1213 RequestAcceptInvitationEvent ,
1314 RequestCreateInvitationEvent ,
1415 RequestCreateSpaceEvent ,
16+ RequestCreateUpdate ,
1517 RequestListInvitations ,
1618 RequestListSpaces ,
1719 RequestSubscribeToSpace ,
@@ -57,6 +59,8 @@ type SpaceStorageEntry = {
5759 events : SpaceEvent [ ] ;
5860 state : SpaceState | undefined ;
5961 keys : { id : string ; key : string } [ ] ;
62+ updates : string [ ] ;
63+ lastUpdateClock : number ;
6064} ;
6165
6266const decodeResponseMessage = Schema . decodeUnknownEither ( ResponseMessage ) ;
@@ -79,6 +83,7 @@ const App = ({
7983 const [ websocketConnection , setWebsocketConnection ] = useState < WebSocket > ( ) ;
8084 const [ spaces , setSpaces ] = useState < SpaceStorageEntry [ ] > ( [ ] ) ;
8185 const [ invitations , setInvitations ] = useState < Invitation [ ] > ( [ ] ) ;
86+ const [ updatesInFlight , setUpdatesInFlight ] = useState < string [ ] > ( [ ] ) ;
8287
8388 // Create a stable WebSocket connection that only depends on accountId
8489 useEffect ( ( ) => {
@@ -128,6 +133,8 @@ const App = ({
128133 events : existingSpace ?. events ?? [ ] ,
129134 state : existingSpace ?. state ,
130135 keys : existingSpace ?. keys ?? [ ] ,
136+ updates : existingSpace ?. updates ?? [ ] ,
137+ lastUpdateClock : existingSpace ?. lastUpdateClock ?? - 1 ,
131138 } ;
132139 } ) ;
133140 } ) ;
@@ -163,12 +170,29 @@ const App = ({
163170 setSpaces ( ( spaces ) =>
164171 spaces . map ( ( space ) => {
165172 if ( space . id === response . id ) {
173+ let lastUpdateClock = space . lastUpdateClock ;
174+ const updates = [ ] ;
175+ if ( space . updates ) {
176+ updates . push ( ...space . updates ) ;
177+ }
178+ if ( response . updates ) {
179+ console . log ( 'response.updates' , response . updates , lastUpdateClock ) ;
180+ if ( response . updates . firstUpdateClock === lastUpdateClock + 1 ) {
181+ lastUpdateClock = response . updates . lastUpdateClock ;
182+ updates . push ( ...response . updates . updates ) ;
183+ } else {
184+ // TODO request missing updates from server
185+ }
186+ }
187+
166188 // TODO fix readonly type issue
167189 return {
168190 ...space ,
169191 events : response . events as SpaceEvent [ ] ,
170192 state : newState ,
171193 keys,
194+ lastUpdateClock,
195+ updates,
172196 } ;
173197 }
174198 return space ;
@@ -207,6 +231,40 @@ const App = ({
207231 setInvitations ( response . invitations . map ( ( invitation ) => invitation ) ) ;
208232 break ;
209233 }
234+ case 'update-confirmed' : {
235+ setSpaces ( ( spaces ) =>
236+ spaces . map ( ( space ) => {
237+ if ( space . id === response . spaceId && space . lastUpdateClock + 1 === response . clock ) {
238+ return { ...space , lastUpdateClock : response . clock } ;
239+ }
240+ return space ;
241+ } ) ,
242+ ) ;
243+ setUpdatesInFlight ( ( updatesInFlight ) => updatesInFlight . filter ( ( id ) => id !== response . ephemeralId ) ) ;
244+ break ;
245+ }
246+ case 'updates-notification' : {
247+ setSpaces ( ( spaces ) =>
248+ spaces . map ( ( space ) => {
249+ if ( space . id === response . spaceId ) {
250+ let lastUpdateClock = space . lastUpdateClock ;
251+ if ( response . updates . firstUpdateClock === space . lastUpdateClock + 1 ) {
252+ lastUpdateClock = response . updates . lastUpdateClock ;
253+ } else {
254+ // TODO request missing updates from server
255+ }
256+
257+ return {
258+ ...space ,
259+ updates : [ ...space . updates , ...response . updates . updates ] ,
260+ lastUpdateClock,
261+ } ;
262+ }
263+ return space ;
264+ } ) ,
265+ ) ;
266+ break ;
267+ }
210268 default :
211269 assertExhaustive ( response ) ;
212270 }
@@ -379,8 +437,56 @@ const App = ({
379437 </ Button >
380438 ) ;
381439 } ) }
440+ < h3 > Updates</ h3 >
441+ < Button
442+ onClick = { ( ) => {
443+ const ephemeralId = uuid ( ) ;
444+ setUpdatesInFlight ( ( updatesInFlight ) => [ ...updatesInFlight , ephemeralId ] ) ;
445+ setSpaces ( ( currentSpaces ) =>
446+ currentSpaces . map ( ( currentSpace ) => {
447+ if ( space . id === currentSpace . id ) {
448+ return { ...currentSpace , updates : [ ...currentSpace . updates , 'a' ] } ;
449+ }
450+ return currentSpace ;
451+ } ) ,
452+ ) ;
453+ const message : RequestCreateUpdate = {
454+ type : 'create-update' ,
455+ ephemeralId,
456+ update : 'a' ,
457+ spaceId : space . id ,
458+ } ;
459+ websocketConnection ?. send ( JSON . stringify ( message ) ) ;
460+ } }
461+ >
462+ Create an update
463+ </ Button >
464+ < h3 > Updates Content</ h3 >
465+ < p > last update clock: { space . lastUpdateClock } </ p >
466+ < p className = "text-xs" >
467+ { space . updates . map ( ( update , index ) => {
468+ return (
469+ // biome-ignore lint/suspicious/noArrayIndexKey: we need a unique identifier here
470+ < span key = { `${ update } -${ index } ` } className = "border border-gray-300" >
471+ { update }
472+ </ span >
473+ ) ;
474+ } ) }
475+ </ p >
476+ < h3 > Updates in flight</ h3 >
477+ < ul className = "text-xs" >
478+ { updatesInFlight . map ( ( updateInFlight ) => {
479+ return (
480+ < li key = { updateInFlight } className = "border border-gray-300" >
481+ { updateInFlight }
482+ </ li >
483+ ) ;
484+ } ) }
485+ </ ul >
486+ < hr />
382487 < h3 > State</ h3 >
383488 < DebugSpaceState state = { space . state } />
489+ < hr />
384490 < h3 > Events</ h3 >
385491 < DebugSpaceEvents events = { space . events } />
386492 < hr />
0 commit comments