From d57d68116ee8e5af583ef0bb1f87cf3f40d2b845 Mon Sep 17 00:00:00 2001
From: Owen Jacobson <owen@grimoire.ca>
Date: Thu, 20 Feb 2025 17:21:18 -0500
Subject: Let Svelte's `$derived` handling figure out update ordering for the
 channels list.

This fixes a bug. To reproduce:

1. Open the client and log in.
2. Create a new channel using the `create channel` UI.

The expected result - and the behaviour after this commit - is that the newly-created channel will be shown in the sidebar immediately. The buggy behaviour is that it was not, but would appear in the sidebar once the client is reloaded. The channel would also not appear for other clients until they reloaded.

I'm not actually completely sure of _why_ this fixes the bug, but it does.
---
 ui/routes/(app)/+layout.svelte | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

(limited to 'ui/routes/(app)/+layout.svelte')

diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte
index cbfef54..3752bef 100644
--- a/ui/routes/(app)/+layout.svelte
+++ b/ui/routes/(app)/+layout.svelte
@@ -19,14 +19,8 @@
   let loading = $state(true);
   let channel = $derived($page.params.channel);
 
-  let rawChannels;
-  channelsList.subscribe((val) => {
-    rawChannels = val.channels;
-  });
-  let rawMessages;
-  messages.subscribe((val) => {
-    rawMessages = val;
-  });
+  let rawChannels = $derived($channelsList.channels);
+  let rawMessages = $derived($messages);
 
   let enrichedChannels = $derived.by(() => {
     const channels = rawChannels;
-- 
cgit v1.2.3


From 60f6b298329728fc8a91cd8c688c9b38fa302f0d Mon Sep 17 00:00:00 2001
From: Owen Jacobson <owen@grimoire.ca>
Date: Thu, 20 Feb 2025 18:41:18 -0500
Subject: Remove the last lingering Svelte4-style event bindings.

---
 ui/routes/(app)/+layout.svelte            | 8 ++++----
 ui/routes/(app)/ch/[channel]/+page.svelte | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

(limited to 'ui/routes/(app)/+layout.svelte')

diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte
index 3752bef..5ec84d9 100644
--- a/ui/routes/(app)/+layout.svelte
+++ b/ui/routes/(app)/+layout.svelte
@@ -99,18 +99,18 @@
     }
   });
 
-  function beforeUnload(evt) {
-    evt.preventDefault();
+  function onbeforeunload(event) {
+    event.preventDefault();
     if (events !== null) {
       events.close();
     }
     // For some compat reasons?
-    evt.returnValue = '';
+    event.returnValue = '';
     return '';
   }
 </script>
 
-<svelte:window on:beforeunload={beforeUnload} />
+<svelte:window {onbeforeunload} />
 
 <svelte:head>
   <!-- TODO: unread count? -->
diff --git a/ui/routes/(app)/ch/[channel]/+page.svelte b/ui/routes/(app)/ch/[channel]/+page.svelte
index dbdb507..9335198 100644
--- a/ui/routes/(app)/ch/[channel]/+page.svelte
+++ b/ui/routes/(app)/ch/[channel]/+page.svelte
@@ -58,7 +58,7 @@
   }
 
   let lastReadCallback = null;
-  function handleScroll() {
+  function onscroll() {
     clearTimeout(lastReadCallback); // Fine if lastReadCallback is null still.
     lastReadCallback = setTimeout(setLastRead, 2 * 1000);
   }
@@ -66,7 +66,7 @@
 
 <svelte:window onkeydown={handleKeydown} />
 
-<div class="active-channel" on:scroll={handleScroll} bind:this={activeChannel}>
+<div class="active-channel" {onscroll} bind:this={activeChannel}>
   <ActiveChannel {messageRuns} />
 </div>
 <div class="create-message">
-- 
cgit v1.2.3


From 5cff84a375f64537c44f6418496f1dc1b24a1de8 Mon Sep 17 00:00:00 2001
From: Owen Jacobson <owen@grimoire.ca>
Date: Fri, 21 Feb 2025 13:08:14 -0500
Subject: Split "set up the event source" and "apply events to state" from one
 another.

---
 ui/lib/apiServer.js            | 70 +-----------------------------------------
 ui/lib/store.js                | 61 ++++++++++++++++++++++++++++++++++++
 ui/routes/(app)/+layout.svelte |  3 +-
 3 files changed, 64 insertions(+), 70 deletions(-)

(limited to 'ui/routes/(app)/+layout.svelte')

diff --git a/ui/lib/apiServer.js b/ui/lib/apiServer.js
index e541d43..bb0a587 100644
--- a/ui/lib/apiServer.js
+++ b/ui/lib/apiServer.js
@@ -61,73 +61,5 @@ export function subscribeToEvents(resumePoint) {
       resume_point: resumePoint
     }
   });
-  const evtSource = new EventSource(eventsUrl);
-  // TODO: this should process all incoming events and store them.
-  // TODO: eventually we'll need to handle expiring old info, so as not to use
-  //       infinite browser memory.
-  /*
-   * Known message types as of now:
-   * - created: a channel is created.
-   *   - action: ignore.
-   * - message: a message is created.
-   *   - action: display message in channel.
-   * - message_deleted: a message is deleted.
-   *   - action: replace message with <...>.
-   * - deleted: a channel is deleted.
-   *   - action: remove channel from sidebar.
-   */
-  evtSource.onmessage = (evt) => {
-    const data = JSON.parse(evt.data);
-
-    switch (data.type) {
-      case 'login':
-        onLoginEvent(data);
-        break;
-      case 'channel':
-        onChannelEvent(data);
-        break;
-      case 'message':
-        onMessageEvent(data);
-        break;
-    }
-  };
-
-  return evtSource;
-}
-
-function onLoginEvent(data) {
-  switch (data.event) {
-    case 'created':
-      logins.update((value) => value.addLogin(data.id, data.name));
-      break;
-  }
-}
-
-function onChannelEvent(data) {
-  switch (data.event) {
-    case 'created':
-      channelsList.update((value) => value.addChannel(data.id, data.name));
-      break;
-    case 'deleted':
-      channelsList.update((value) => value.deleteChannel(data.id));
-      messages.update((value) => value.deleteChannel(data.id));
-      break;
-  }
-}
-
-function onMessageEvent(data) {
-  switch (data.event) {
-    case 'sent':
-      messages.update((value) =>
-        value.addMessage(data.channel, data.id, {
-          at: data.at,
-          sender: data.sender,
-          body: data.body
-        })
-      );
-      break;
-    case 'deleted':
-      messages.update((value) => value.deleteMessage(data.id));
-      break;
-  }
+  return new EventSource(eventsUrl);
 }
diff --git a/ui/lib/store.js b/ui/lib/store.js
index 47ebbc2..c179dac 100644
--- a/ui/lib/store.js
+++ b/ui/lib/store.js
@@ -7,3 +7,64 @@ export const currentUser = writable(null);
 export const logins = writable(new Logins());
 export const channelsList = writable(new Channels());
 export const messages = writable(new Messages());
+
+export function onEvent(event) {
+  switch (event.type) {
+    case 'login':
+      onLoginEvent(event);
+      break;
+    case 'channel':
+      onChannelEvent(event);
+      break;
+    case 'message':
+      onMessageEvent(event);
+      break;
+  }
+}
+
+onEvent.fromJson = (event) => {
+  const parsed = JSON.parse(event);
+  return onEvent(parsed);
+};
+
+onEvent.fromMessage = (message) => {
+  const data = message.data;
+  return onEvent.fromJson(data);
+};
+
+function onLoginEvent(event) {
+  switch (event.event) {
+    case 'created':
+      logins.update((value) => value.addLogin(event.id, event.name));
+      break;
+  }
+}
+
+function onChannelEvent(event) {
+  switch (event.event) {
+    case 'created':
+      channelsList.update((value) => value.addChannel(event.id, event.name));
+      break;
+    case 'deleted':
+      channelsList.update((value) => value.deleteChannel(event.id));
+      messages.update((value) => value.deleteChannel(event.id));
+      break;
+  }
+}
+
+function onMessageEvent(event) {
+  switch (event.event) {
+    case 'sent':
+      messages.update((value) =>
+        value.addMessage(event.channel, event.id, {
+          at: event.at,
+          sender: event.sender,
+          body: event.body
+        })
+      );
+      break;
+    case 'deleted':
+      messages.update((value) => value.deleteMessage(event.id));
+      break;
+  }
+}
diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte
index 5ec84d9..dff9245 100644
--- a/ui/routes/(app)/+layout.svelte
+++ b/ui/routes/(app)/+layout.svelte
@@ -6,7 +6,7 @@
   import TinyGesture from 'tinygesture';
 
   import { boot, subscribeToEvents } from '$lib/apiServer';
-  import { currentUser, logins, channelsList, messages } from '$lib/store';
+  import { currentUser, logins, channelsList, messages, onEvent } from '$lib/store';
 
   import ChannelList from '$lib/components/ChannelList.svelte';
   import CreateChannelForm from '$lib/components/CreateChannelForm.svelte';
@@ -72,6 +72,7 @@
       case 200:
         onBooted(response.data);
         events = subscribeToEvents(response.data.resume_point);
+        events.onmessage = onEvent.fromMessage;
         break;
       case 401:
         currentUser.set(null);
-- 
cgit v1.2.3


From b9bc643af0d0523a70d76236d9277539da083b36 Mon Sep 17 00:00:00 2001
From: Owen Jacobson <owen@grimoire.ca>
Date: Fri, 21 Feb 2025 16:35:56 -0500
Subject: Add missing awaits on goto() calls

---
 ui/lib/components/LogOut.svelte                | 2 +-
 ui/routes/(app)/+layout.svelte                 | 8 ++++----
 ui/routes/(login)/invite/[invite]/+page.svelte | 2 +-
 ui/routes/(login)/login/+page.svelte           | 2 +-
 ui/routes/(login)/setup/+page.svelte           | 2 +-
 5 files changed, 8 insertions(+), 8 deletions(-)

(limited to 'ui/routes/(app)/+layout.svelte')

diff --git a/ui/lib/components/LogOut.svelte b/ui/lib/components/LogOut.svelte
index b699cfd..1cb8fb5 100644
--- a/ui/lib/components/LogOut.svelte
+++ b/ui/lib/components/LogOut.svelte
@@ -8,7 +8,7 @@
     const response = await logOut();
     if (200 <= response.status && response.status < 300) {
       currentUser.set(null);
-      goto('/login');
+      await goto('/login');
     }
   }
 </script>
diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte
index dff9245..a81efce 100644
--- a/ui/routes/(app)/+layout.svelte
+++ b/ui/routes/(app)/+layout.svelte
@@ -2,11 +2,11 @@
   import { page } from '$app/stores';
   import { goto } from '$app/navigation';
   import { browser } from '$app/environment';
-  import { onMount, onDestroy, getContext } from 'svelte';
+  import { getContext, onDestroy, onMount } from 'svelte';
   import TinyGesture from 'tinygesture';
 
   import { boot, subscribeToEvents } from '$lib/apiServer';
-  import { currentUser, logins, channelsList, messages, onEvent } from '$lib/store';
+  import { channelsList, currentUser, logins, messages, onEvent } from '$lib/store';
 
   import ChannelList from '$lib/components/ChannelList.svelte';
   import CreateChannelForm from '$lib/components/CreateChannelForm.svelte';
@@ -76,11 +76,11 @@
         break;
       case 401:
         currentUser.set(null);
-        goto('/login');
+        await goto('/login');
         break;
       case 503:
         currentUser.set(null);
-        goto('/setup');
+        await goto('/setup');
         break;
       default:
         // TODO: display error.
diff --git a/ui/routes/(login)/invite/[invite]/+page.svelte b/ui/routes/(login)/invite/[invite]/+page.svelte
index 132cbc1..0c01286 100644
--- a/ui/routes/(login)/invite/[invite]/+page.svelte
+++ b/ui/routes/(login)/invite/[invite]/+page.svelte
@@ -18,7 +18,7 @@
     if (200 <= response.status && response.status < 300) {
       username = '';
       password = '';
-      goto('/');
+      await goto('/');
     }
     pending = false;
   }
diff --git a/ui/routes/(login)/login/+page.svelte b/ui/routes/(login)/login/+page.svelte
index a1291ea..9157cef 100644
--- a/ui/routes/(login)/login/+page.svelte
+++ b/ui/routes/(login)/login/+page.svelte
@@ -16,7 +16,7 @@
     if (200 <= response.status && response.status < 300) {
       username = '';
       password = '';
-      goto('/');
+      await goto('/');
     }
     pending = false;
   }
diff --git a/ui/routes/(login)/setup/+page.svelte b/ui/routes/(login)/setup/+page.svelte
index f162ded..c63f198 100644
--- a/ui/routes/(login)/setup/+page.svelte
+++ b/ui/routes/(login)/setup/+page.svelte
@@ -16,7 +16,7 @@
     if (200 <= response.status && response.status < 300) {
       username = '';
       password = '';
-      goto('/');
+      await goto('/');
     }
     pending = false;
   }
-- 
cgit v1.2.3


From 25daf36b18086096651b381223ad8f03fbca5045 Mon Sep 17 00:00:00 2001
From: Owen Jacobson <owen@grimoire.ca>
Date: Fri, 21 Feb 2025 17:07:35 -0500
Subject: Retire use of $page store in favour of Sv5 page state

---
 ui/routes/(app)/+layout.svelte            | 4 ++--
 ui/routes/(app)/ch/[channel]/+page.svelte | 5 +++--
 2 files changed, 5 insertions(+), 4 deletions(-)

(limited to 'ui/routes/(app)/+layout.svelte')

diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte
index a81efce..6339abd 100644
--- a/ui/routes/(app)/+layout.svelte
+++ b/ui/routes/(app)/+layout.svelte
@@ -1,5 +1,5 @@
 <script>
-  import { page } from '$app/stores';
+  import { page } from '$app/state';
   import { goto } from '$app/navigation';
   import { browser } from '$app/environment';
   import { getContext, onDestroy, onMount } from 'svelte';
@@ -17,7 +17,7 @@
   let pageContext = getContext('page');
   let { children } = $props();
   let loading = $state(true);
-  let channel = $derived($page.params.channel);
+  let channel = $derived(page.params.channel);
 
   let rawChannels = $derived($channelsList.channels);
   let rawMessages = $derived($messages);
diff --git a/ui/routes/(app)/ch/[channel]/+page.svelte b/ui/routes/(app)/ch/[channel]/+page.svelte
index 9335198..84cb0ae 100644
--- a/ui/routes/(app)/ch/[channel]/+page.svelte
+++ b/ui/routes/(app)/ch/[channel]/+page.svelte
@@ -1,11 +1,11 @@
 <script>
   import { DateTime } from 'luxon';
-  import { page } from '$app/stores';
+  import { page } from '$app/state';
   import ActiveChannel from '$lib/components/ActiveChannel.svelte';
   import MessageInput from '$lib/components/MessageInput.svelte';
   import { channelsList, messages } from '$lib/store';
 
-  let channel = $derived($page.params.channel);
+  let channel = $derived(page.params.channel);
   let messageRuns = $derived($messages.inChannel(channel));
   let activeChannel;
 
@@ -58,6 +58,7 @@
   }
 
   let lastReadCallback = null;
+
   function onscroll() {
     clearTimeout(lastReadCallback); // Fine if lastReadCallback is null still.
     lastReadCallback = setTimeout(setLastRead, 2 * 1000);
-- 
cgit v1.2.3

