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 `