From 261f3bdb77799009344aab4a60686b7186ebd3b0 Mon Sep 17 00:00:00 2001
From: "netop://ウィビ" <paul@webb.page>
Date: Fri, 24 Apr 2026 14:17:38 -0700
Subject: Implement v0.4 subscriptions + v0.5 theming and plugin slots

- SSE and WebSocket fetchers via graphql-sse and graphql-ws,
  returning AsyncIterable results
- SessionStore.run consumes AsyncIterable streams with
  subscriptionMode "append" (timestamped) or "replace" and
  honors an AbortSignal for cancellation
- Chrome CSS variables documented in styles/theme.scss with
  prefers-color-scheme light/dark gating and .graphiql-light override
- Svelte 5 snippet slots on GraphiQL: toolbarExtras, tabExtras,
  resultFooter
---
 source/library/GraphiQL.svelte | 30 +++++++++++++++++++++++++-----
 1 file changed, 25 insertions(+), 5 deletions(-)

(limited to 'source/library/GraphiQL.svelte')

diff --git a/source/library/GraphiQL.svelte b/source/library/GraphiQL.svelte
index 0a8f01a..329dd6f 100644
--- a/source/library/GraphiQL.svelte
+++ b/source/library/GraphiQL.svelte
@@ -1,7 +1,7 @@
 <script lang="ts">
   /*** IMPORT ------------------------------------------- ***/
 
-  import { onMount } from "svelte";
+  import { onMount, type Snippet } from "svelte";
 
   /*** UTILITY ------------------------------------------ ***/
 
@@ -17,6 +17,7 @@
   import { HistoryStore } from "./state/history.svelte.ts";
   import { SchemaStore } from "./state/schema.svelte.ts";
   import { SessionStore } from "./state/session.svelte.ts";
+  import type { SubscriptionMode, Tab } from "./state/session.svelte.ts";
   import { createLocalStorage, createMemoryStorage } from "./state/storage.ts";
   import type { Storage } from "./state/storage.ts";
 
@@ -24,16 +25,24 @@
     fetcher: Fetcher;
     initialQuery?: string;
     namespace?: string;
+    resultFooter?: Snippet<[{ result: string }]>;
     storage?: Storage;
+    subscriptionMode?: SubscriptionMode;
+    tabExtras?: Snippet<[{ tab: Tab }]>;
     theme?: Extension;
+    toolbarExtras?: Snippet;
   };
 
   let {
     fetcher,
     initialQuery = "",
     namespace = "eol-graphiql",
+    resultFooter,
     storage,
-    theme
+    subscriptionMode = "append",
+    tabExtras,
+    theme,
+    toolbarExtras
   }: Props = $props();
 
   const resolvedStorage = storage ??
@@ -87,15 +96,23 @@
     schema.introspect(fetcher);
   });
 
+  let runAbort: AbortController | null = null;
+
   async function run() {
-    if (running)
+    if (running) {
+      runAbort?.abort();
       return;
+    }
 
     running = true;
+    runAbort = new AbortController();
 
     try {
       const tab = session.active;
-      const ok = await session.run(fetcher);
+      const ok = await session.run(fetcher, {
+        signal: runAbort.signal,
+        subscriptionMode
+      });
 
       if (ok && tab) {
         history.add({
@@ -107,6 +124,7 @@
         });
       }
     } finally {
+      runAbort = null;
       running = false;
     }
   }
@@ -247,6 +265,7 @@
     disabled={running || !session.active}
     docsAvailable={schema.schema !== null}
     {docsOpen}
+    extras={toolbarExtras}
     {historyOpen}
     onRun={run}
     onSelectOperation={(name) => {
@@ -261,6 +280,7 @@
     schemaLoading={schema.loading}/>
   <TabBar
     activeId={session.activeId}
+    extras={tabExtras}
     onAdd={() => session.addTab()}
     onClose={(id) => session.closeTab(id)}
     onRename={(id, title) => session.renameTab(id, title)}
@@ -310,7 +330,7 @@
       </div>
     </div>
     <div class="right">
-      <ResultViewer {theme} value={session.active?.result ?? ""}/>
+      <ResultViewer footer={resultFooter} {theme} value={session.active?.result ?? ""}/>
     </div>
     {#if docsOpen && schema.schema}
       <DocExplorer schema={schema.schema}/>
-- 
cgit v1.2.3

