From f05fadcc7313a78a48dcfe263a57eb3c936c3b61 Mon Sep 17 00:00:00 2001
From: "PC-JASPER\\jasper"
Date: Thu, 10 Sep 2026 12:26:43 +0200
Subject: [PATCH 01/11] harcoded table of allowed datatables for wod and era5
---
src/lib/beacon-api/client.ts | 8 ++++++++
src/lib/services/open-nodes.ts | 15 +++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/src/lib/beacon-api/client.ts b/src/lib/beacon-api/client.ts
index 1eec434..e040d70 100644
--- a/src/lib/beacon-api/client.ts
+++ b/src/lib/beacon-api/client.ts
@@ -5,6 +5,7 @@ import { Utils } from '@/utils';
import { addToast } from '@/stores/toasts';
import { BeaconClient as BeaconSdkClient } from '@beacon/client';
import { normalizeUrl, splitNodeUrl } from '@/services/beacon-node-url';
+import { PUBLIC_NODE_TABLES } from '@/services/open-nodes';
import {
isAbortError,
@@ -300,6 +301,13 @@ export class BeaconClient {
async getTables(): Promise> {
const url = this.buildUrl('/api/tables');
const response: Array = await this.fetch(url);
+
+ // Temporary fix: a public demo node shows only its curated tables.
+ const allowedTables = PUBLIC_NODE_TABLES[this.baseUrl];
+ if (allowedTables) {
+ return response.filter((table) => allowedTables.includes(table));
+ }
+
return response;
}
diff --git a/src/lib/services/open-nodes.ts b/src/lib/services/open-nodes.ts
index 10e7688..81de4a2 100644
--- a/src/lib/services/open-nodes.ts
+++ b/src/lib/services/open-nodes.ts
@@ -21,6 +21,21 @@ export const OPEN_NODES_URL = 'https://beacon-datalake.org/public-nodes.json';
/** The time after which the fetch of the list counts as a failure. */
const FETCH_TIMEOUT_MS = 10_000;
+/**
+ * Temporary fix: the tables to show for a public node, keyed by normalized URL.
+ * A public node can hold tables not meant for the demo, so the data-tables page
+ * shows only this set for these nodes. Remove once the public list carries its
+ * own table set.
+ */
+export const PUBLIC_NODE_TABLES: Record = {
+ [normalizeUrl('https://beacon-wod.maris.nl/')]: ['easy-wod'],
+ [normalizeUrl('https://beacon-era5.maris.nl/')]: [
+ 'era5_daily_total_precipitation',
+ 'era5_daily_mean_2m_temperature',
+ 'era5_daily_max_2m_temperature'
+ ]
+};
+
/** One node of the public list. */
export type OpenNode = {
name: string;
From 2c8ad3ee1886728fa912881ecfcfbd51c0297076 Mon Sep 17 00:00:00 2001
From: "PC-JASPER\\jasper"
Date: Thu, 10 Sep 2026 12:41:45 +0200
Subject: [PATCH 02/11] welcome toast stating beta
---
src/routes/+page.svelte | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index e08c71e..a9a24fa 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -10,9 +10,12 @@
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import Button from '@/components/buttons/Button.svelte';
+ import { addToast } from '@/stores/toasts';
import { asset, resolve } from '$app/paths';
+ const BETA_TOAST_STORAGE_KEY = 'beacon-studio.home.beta-toast-shown';
+
// The Connected Nodes card cycles through every configured node, so
// each one needs a true status dot. `ensureFresh` skips a check that is not due.
$effect(() => {
@@ -34,6 +37,16 @@
// The app can run on the same host as a Beacon node. Add that node once.
void ensureHostNode(window.location.origin);
+ if (!localStorage.getItem(BETA_TOAST_STORAGE_KEY)) {
+ addToast({
+ type: 'info',
+ timeout: 20000,
+ message:
+ 'Beacon Studio is still in Beta stage, please provide any feedback by clicking "Feedback" in the bottom left corner.'
+ });
+ localStorage.setItem(BETA_TOAST_STORAGE_KEY, 'true');
+ }
+
const timer = setInterval(() => (cycleIndex += 1), CYCLE_INTERVAL_MS);
return () => clearInterval(timer);
});
From f49aa03867fa78e16347b289ee9c33d7283189c6 Mon Sep 17 00:00:00 2001
From: "PC-JASPER\\jasper"
Date: Thu, 10 Sep 2026 12:52:54 +0200
Subject: [PATCH 03/11] auto select first table if default not available
---
.../components/query-builder/QueryBuilder.svelte | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/src/lib/components/query-builder/QueryBuilder.svelte b/src/lib/components/query-builder/QueryBuilder.svelte
index 2cdc6cd..3329dcc 100644
--- a/src/lib/components/query-builder/QueryBuilder.svelte
+++ b/src/lib/components/query-builder/QueryBuilder.svelte
@@ -106,11 +106,9 @@
client = BeaconClient.new(node);
let tables: string[];
- let default_table: string;
try {
tables = await client.getCachedTables();
- default_table = await client.getCachedDefaultTable();
} catch (error) {
console.error('Could not read the tables of the Beacon node.', error);
loadError = (error as Error)?.message || 'The Beacon node did not answer.';
@@ -118,6 +116,20 @@
return;
}
+ // A node can have no default table configured, so this is an offer, not a
+ // requirement. Fall back to the first table when it fails or is unusable.
+ let default_table: string | undefined;
+
+ try {
+ default_table = await client.getCachedDefaultTable();
+ } catch (error) {
+ console.warn('Could not read the default table of the Beacon node.', error);
+ }
+
+ if (!default_table || !tables.includes(default_table)) {
+ default_table = tables[0];
+ }
+
// By default, select the first table, or restore the table from the draft/seed.
const seedTable = typeof pendingSeed?.from === 'string' ? pendingSeed.from : null;
const draftTable = initialDraft?.tableName || null;
From b808578b40178dc9f3481c32070c2a00c8b7f1b2 Mon Sep 17 00:00:00 2001
From: "PC-JASPER\\jasper"
Date: Thu, 10 Sep 2026 13:13:36 +0200
Subject: [PATCH 04/11] fix: warning popup on shared link data table not
available even though it is
---
.../components/query-builder/QueryBuilder.svelte | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/src/lib/components/query-builder/QueryBuilder.svelte b/src/lib/components/query-builder/QueryBuilder.svelte
index 3329dcc..b2ef0da 100644
--- a/src/lib/components/query-builder/QueryBuilder.svelte
+++ b/src/lib/components/query-builder/QueryBuilder.svelte
@@ -101,6 +101,16 @@
// re-mounts this component with a client.
if (!node) return;
+ // A share link can swap the active block's node while this call is in
+ // flight: the workbench first paints the previously active block, then
+ // its `onMount` opens the link and points the block at the linked node.
+ // `{#key}` then remounts this component, but the old async call keeps
+ // running. `node` is read live, so it would see the *new* node while
+ // `tables` below still holds the *old* one. Compare against the node
+ // this call started for, and drop a stale answer instead of reporting
+ // a mismatch against the wrong node.
+ const requestedNode = node;
+
loadError = null;
loaded = false;
client = BeaconClient.new(node);
@@ -110,12 +120,15 @@
try {
tables = await client.getCachedTables();
} catch (error) {
+ if (node !== requestedNode) return;
console.error('Could not read the tables of the Beacon node.', error);
loadError = (error as Error)?.message || 'The Beacon node did not answer.';
loaded = true;
return;
}
+ if (node !== requestedNode) return;
+
// A node can have no default table configured, so this is an offer, not a
// requirement. Fall back to the first table when it fails or is unusable.
let default_table: string | undefined;
@@ -126,6 +139,8 @@
console.warn('Could not read the default table of the Beacon node.', error);
}
+ if (node !== requestedNode) return;
+
if (!default_table || !tables.includes(default_table)) {
default_table = tables[0];
}
From a742c3f9eb0227b59a5defdf3ded0f39b68339e7 Mon Sep 17 00:00:00 2001
From: "PC-JASPER\\jasper"
Date: Thu, 10 Sep 2026 15:01:16 +0200
Subject: [PATCH 05/11] horizontal scrolling available in table explorer
---
src/lib/components/visualisation/DataTable.svelte | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/lib/components/visualisation/DataTable.svelte b/src/lib/components/visualisation/DataTable.svelte
index 4e9235e..4a93b5f 100644
--- a/src/lib/components/visualisation/DataTable.svelte
+++ b/src/lib/components/visualisation/DataTable.svelte
@@ -169,14 +169,16 @@
min-height: 0;
table.dataset-table {
- width: 100%;
+ min-width: 100%;
+ width: max-content;
border-collapse: collapse;
- thead th,
+ thead th,
tbody td {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
border-bottom: 1px solid #ddd;
+ white-space: nowrap;
}
&.small {
From 8c6d8251279bd87b10963c40e707d01dca0b36ef Mon Sep 17 00:00:00 2001
From: "PC-JASPER\\jasper"
Date: Thu, 10 Sep 2026 16:29:42 +0200
Subject: [PATCH 06/11] fix table explorer horizontal scrolling
---
src/lib/components/visualisation/DataTable.svelte | 4 ++--
src/routes/visualisations/table-explorer/+page.svelte | 3 +++
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/lib/components/visualisation/DataTable.svelte b/src/lib/components/visualisation/DataTable.svelte
index 4a93b5f..0ef43dc 100644
--- a/src/lib/components/visualisation/DataTable.svelte
+++ b/src/lib/components/visualisation/DataTable.svelte
@@ -159,14 +159,14 @@
flex-direction: column;
flex-grow: 1;
min-height: 0;
+ min-width: 0;
overflow: hidden;
-
-
.table-wrapper {
overflow: auto;
flex-grow: 1;
min-height: 0;
+ min-width: 0;
table.dataset-table {
min-width: 100%;
diff --git a/src/routes/visualisations/table-explorer/+page.svelte b/src/routes/visualisations/table-explorer/+page.svelte
index 7d109e3..43fe319 100644
--- a/src/routes/visualisations/table-explorer/+page.svelte
+++ b/src/routes/visualisations/table-explorer/+page.svelte
@@ -315,6 +315,7 @@
.page-wrapper {
flex-grow: 1;
min-height: 0;
+ min-width: 0;
display: flex;
flex-direction: column;
gap: 1rem;
@@ -324,6 +325,7 @@
.vertical-tabs-wrapper {
flex-grow: 1;
min-height: 0;
+ min-width: 0;
display: flex;
flex-direction: row;
gap: 1rem;
@@ -331,6 +333,7 @@
.content.page-container {
flex-grow: 1;
min-height: 0;
+ min-width: 0;
display: flex;
flex-direction: column;
From f6eba42d707bc20f54f27b10d52592262e70c6db Mon Sep 17 00:00:00 2001
From: "PC-JASPER\\jasper"
Date: Thu, 10 Sep 2026 16:45:45 +0200
Subject: [PATCH 07/11] fix: query builder saturation after sharedquery
restored after initial fix for warning on available data tables.
---
src/lib/components/query-builder/QueryBuilder.svelte | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/lib/components/query-builder/QueryBuilder.svelte b/src/lib/components/query-builder/QueryBuilder.svelte
index b2ef0da..cbd1b85 100644
--- a/src/lib/components/query-builder/QueryBuilder.svelte
+++ b/src/lib/components/query-builder/QueryBuilder.svelte
@@ -109,7 +109,11 @@
// `tables` below still holds the *old* one. Compare against the node
// this call started for, and drop a stale answer instead of reporting
// a mismatch against the wrong node.
- const requestedNode = node;
+ //
+ // Compare the URL, not the object. The node list re-emits on an edit or
+ // a health check, which gives the same node a new object. That is not a
+ // new node, and this call must survive it.
+ const requestedUrl = node.url;
loadError = null;
loaded = false;
@@ -120,14 +124,14 @@
try {
tables = await client.getCachedTables();
} catch (error) {
- if (node !== requestedNode) return;
+ if (node?.url !== requestedUrl) return;
console.error('Could not read the tables of the Beacon node.', error);
loadError = (error as Error)?.message || 'The Beacon node did not answer.';
loaded = true;
return;
}
- if (node !== requestedNode) return;
+ if (node?.url !== requestedUrl) return;
// A node can have no default table configured, so this is an offer, not a
// requirement. Fall back to the first table when it fails or is unusable.
@@ -139,7 +143,7 @@
console.warn('Could not read the default table of the Beacon node.', error);
}
- if (node !== requestedNode) return;
+ if (node?.url !== requestedUrl) return;
if (!default_table || !tables.includes(default_table)) {
default_table = tables[0];
From 53e6600c004aa0b2e0ec7207c8f001b75502411f Mon Sep 17 00:00:00 2001
From: "PC-JASPER\\jasper"
Date: Fri, 11 Sep 2026 09:25:37 +0200
Subject: [PATCH 08/11] fix: scatterplot/crossectionplot not drawing when no z
axis selected
---
AGENTS.md | 1 +
.../components/palette/PalettePicker.svelte | 29 +++--
.../components/plots/PlotConfigPanel.svelte | 37 +++++-
src/lib/plots/plot-config.ts | 7 +-
src/lib/plots/uplot-render.ts | 105 +++++++++++-------
5 files changed, 121 insertions(+), 58 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index efb686f..023867c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -148,6 +148,7 @@ Imports point one way only:
- One shared worker keeps tables loaded by key across navigations. Don't `new ArrowProcessingWorkerManager()` per page or `terminate()` the shared instance.
- Avoid blocking the main thread with large Arrow transforms.
- Preserve guards like `isLoading` / `firstLoad` around query execution.
+- A canvas path holds about 150,000 arcs. Above that the browser drops the fill and reports no error, so the plot draws blank. `fillPointChunks` in `plots/uplot-render.ts` flushes the path every 10,000 arcs, and it also draws faster than one large path. Draw every point set through it. A palette bucket needs the same flush: the buckets divide the Z range, not the row count, so one bucket can hold every row.
- `QueryWorkspace.blocks` gets a new array, with new block objects, on every write to the block collection — including `markBlockRun`/`markBlockRunning` and any draft update. Do not read `workspace.activeBlock` (or a query object derived from it) directly inside an `$effect`. That makes the effect re-fire after its own write, in a loop that never stops. Track primitive values instead (block id, a stringified compiled query) and read the live block/query with `untrack`. See `src/routes/visualisations/table-explorer/+page.svelte` for the pattern.
## Editing Guidance for Agents
diff --git a/src/lib/components/palette/PalettePicker.svelte b/src/lib/components/palette/PalettePicker.svelte
index d8f0d8e..c0090d5 100644
--- a/src/lib/components/palette/PalettePicker.svelte
+++ b/src/lib/components/palette/PalettePicker.svelte
@@ -25,6 +25,7 @@
onSelect,
reverse = false,
showSolids = true,
+ showGradients = true,
id = undefined
}: {
/** The id of the palette now in use. */
@@ -33,6 +34,8 @@
/** Preview the palette the way it is drawn, turned around or not. */
reverse?: boolean;
showSolids?: boolean;
+ /** False offers the single colours alone, for a plot that paints no value. */
+ showGradients?: boolean;
/** DOM id of the trigger, so a `
{/if}
- {:else}
- Bind a column to the colour axis in step 2 to pick a palette.
+ {:else}
+ patchStyle({ palette: id })}
+ />
+
+
+ Every point takes this colour. Bind a column to the colour axis in step 2 to paint
+ the points by value.