From ff8d5745fbb64ee2402300255eed9acd6ad6936c Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 08:29:41 +0200 Subject: [PATCH 01/21] Add optional rightColumn snippet to ResizablePanel for two-column layouts --- src/lib/components/ResizablePanel.svelte | 47 ++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/lib/components/ResizablePanel.svelte b/src/lib/components/ResizablePanel.svelte index 0f428449..21bd1ba5 100644 --- a/src/lib/components/ResizablePanel.svelte +++ b/src/lib/components/ResizablePanel.svelte @@ -26,6 +26,10 @@ toolbar?: import('svelte').Snippet; footer?: import('svelte').Snippet; children?: import('svelte').Snippet; + /** Optional second column rendered next to the main content. When set, + * the panel body splits into two columns; without it, the panel + * layout is unchanged. */ + rightColumn?: import('svelte').Snippet; } let { @@ -46,7 +50,8 @@ actions, toolbar, footer, - children + children, + rightColumn }: Props = $props(); // Calculate dynamic max height for bottom panels (viewport - nav bar - gaps) @@ -228,8 +233,15 @@ {@render toolbar()} {/if} -
- {@render children?.()} +
+
+ {@render children?.()} +
+ {#if rightColumn} +
+ {@render rightColumn()} +
+ {/if}
{#if footer} {/if} -
+
{@render children?.()}
- {#if rightColumn} + {#if showRightColumn}
- {@render rightColumn()} + {@render rightColumn!()}
{/if}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 4cda348b..16c471d0 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1369,6 +1369,7 @@ maxWidth={500 + DETAIL_COLUMN_WIDTH} bottomOffset={leftPanelBottomOffset()} title="Blocks" + rightColumnActive={nodeLibraryDetailVisible} onClose={() => showNodeLibrary = false} onWidthChange={(w) => (nodeLibraryWidth = Math.min( @@ -1418,6 +1419,7 @@ maxWidth={400 + DETAIL_COLUMN_WIDTH} bottomOffset={leftPanelBottomOffset()} title="Events" + rightColumnActive={eventsPanelDetailVisible} onClose={() => showEventsPanel = false} onWidthChange={(w) => (eventsPanelWidth = Math.min( From b075ad97ef2bf0ffe7f3f648f2255f95608136af Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 09:27:24 +0200 Subject: [PATCH 08/21] Fix DocumentationSection alwaysExpanded reload loop and drop block-type subtitle --- .../dialogs/shared/DocumentationSection.svelte | 7 +++++-- .../panels/library-detail/NodeBlockDetail.svelte | 13 ------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/lib/components/dialogs/shared/DocumentationSection.svelte b/src/lib/components/dialogs/shared/DocumentationSection.svelte index a0223c6b..b9b2eb6c 100644 --- a/src/lib/components/dialogs/shared/DocumentationSection.svelte +++ b/src/lib/components/dialogs/shared/DocumentationSection.svelte @@ -73,14 +73,17 @@ }); // Reset state when docstring changes. In alwaysExpanded mode the toggle - // state stays true and we re-load the new content. + // state stays true and we re-load the new content. The loadDocs call + // must be untracked — it reads renderedDocs internally, and without + // untrack the subsequent write to renderedDocs would re-trigger this + // effect in an infinite loop. $effect(() => { const _ = docstring || docstringHtml; if (_) { cleanupCodeBlocks(); renderedDocs = ''; if (alwaysExpanded) { - loadDocs(); + untrack(() => loadDocs()); } else { expanded = false; } diff --git a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte index 4a0ae139..83f643a1 100644 --- a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte +++ b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte @@ -34,10 +34,6 @@ {/if} -
- {node.category} -
-
@@ -96,15 +92,6 @@ letter-spacing: 0; } - .detail-subtitle { - flex-shrink: 0; - padding: var(--space-sm) var(--space-md) 0; - font-size: 10px; - color: var(--text-disabled); - text-transform: uppercase; - letter-spacing: 0.5px; - } - .detail-preview { flex-shrink: 0; display: flex; From 2d88e6033846ef91cedf5ab0febfc89016c83e08 Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 09:33:14 +0200 Subject: [PATCH 09/21] Drop detail-header bar in library detail panels --- .../panels/library-detail/EventDetail.svelte | 50 ++++++------------- .../library-detail/NodeBlockDetail.svelte | 50 ++++++------------- 2 files changed, 28 insertions(+), 72 deletions(-) diff --git a/src/lib/components/panels/library-detail/EventDetail.svelte b/src/lib/components/panels/library-detail/EventDetail.svelte index 9021e6e4..195eab93 100644 --- a/src/lib/components/panels/library-detail/EventDetail.svelte +++ b/src/lib/components/panels/library-detail/EventDetail.svelte @@ -27,17 +27,16 @@
-
- {event.name} - {#if toolboxLabel} - {toolboxLabel} - {/if} -
-
+ {#if toolboxLabel} +
+ {toolboxLabel} +
+ {/if} +
@@ -53,33 +52,22 @@ overflow: hidden; } - .detail-header { + .detail-preview { flex-shrink: 0; display: flex; align-items: center; - justify-content: space-between; - gap: var(--space-sm); - height: var(--header-height); - padding: 0 var(--space-md); - background: var(--surface-raised); - border-bottom: 1px solid var(--border); - font-size: var(--font-base); - font-weight: 500; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; + justify-content: center; + padding: var(--space-md); } - .detail-title { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + .detail-meta { + flex-shrink: 0; + display: flex; + justify-content: center; + padding: 0 var(--space-md) var(--space-sm); } .toolbox-badge { - flex-shrink: 0; padding: 1px 6px; font-family: var(--font-mono); font-size: 9px; @@ -88,16 +76,6 @@ background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); - text-transform: none; - letter-spacing: 0; - } - - .detail-preview { - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - padding: var(--space-md); } .detail-docs { diff --git a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte index 83f643a1..5f57234c 100644 --- a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte +++ b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte @@ -27,17 +27,16 @@
-
- {node.name} - {#if toolboxLabel} - {toolboxLabel} - {/if} -
-
+ {#if toolboxLabel} +
+ {toolboxLabel} +
+ {/if} +
@@ -53,33 +52,22 @@ overflow: hidden; } - .detail-header { + .detail-preview { flex-shrink: 0; display: flex; align-items: center; - justify-content: space-between; - gap: var(--space-sm); - height: var(--header-height); - padding: 0 var(--space-md); - background: var(--surface-raised); - border-bottom: 1px solid var(--border); - font-size: var(--font-base); - font-weight: 500; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; + justify-content: center; + padding: var(--space-md); } - .detail-title { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + .detail-meta { + flex-shrink: 0; + display: flex; + justify-content: center; + padding: 0 var(--space-md) var(--space-sm); } .toolbox-badge { - flex-shrink: 0; padding: 1px 6px; font-family: var(--font-mono); font-size: 9px; @@ -88,16 +76,6 @@ background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); - text-transform: none; - letter-spacing: 0; - } - - .detail-preview { - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - padding: var(--space-md); } .detail-docs { From c608d96e3b9c4c5e67f9e224950e20efd3e12ee5 Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 09:40:28 +0200 Subject: [PATCH 10/21] Use section-title style header for library detail with inline toolbox info --- .../panels/library-detail/EventDetail.svelte | 62 ++++++++++++------- .../library-detail/NodeBlockDetail.svelte | 62 ++++++++++++------- 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/src/lib/components/panels/library-detail/EventDetail.svelte b/src/lib/components/panels/library-detail/EventDetail.svelte index 195eab93..64b3ba50 100644 --- a/src/lib/components/panels/library-detail/EventDetail.svelte +++ b/src/lib/components/panels/library-detail/EventDetail.svelte @@ -27,15 +27,17 @@
-
- -
- - {#if toolboxLabel} -
- {toolboxLabel} +
+
+ {event.name} + {#if toolboxLabel} + {toolboxLabel} + {/if} +
+
+
- {/if} +
@@ -52,37 +54,51 @@ overflow: hidden; } - .detail-preview { + .detail-section { flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; padding: var(--space-md); } - .detail-meta { - flex-shrink: 0; + .section-title { display: flex; - justify-content: center; - padding: 0 var(--space-md) var(--space-sm); + align-items: baseline; + gap: var(--space-sm); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-muted); + margin-bottom: var(--space-sm); + } + + .title-text { + flex-shrink: 0; } - .toolbox-badge { - padding: 1px 6px; + .title-meta { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; font-family: var(--font-mono); font-size: 9px; font-weight: 400; - color: var(--text-muted); - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-sm); + color: var(--text-disabled); + text-transform: none; + letter-spacing: 0; + } + + .detail-preview { + display: flex; + align-items: center; + justify-content: center; } .detail-docs { flex: 1; min-height: 0; overflow-y: auto; - padding: var(--space-md); + padding: 0 var(--space-md) var(--space-md); font-size: 11px; } diff --git a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte index 5f57234c..e9d8053e 100644 --- a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte +++ b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte @@ -27,15 +27,17 @@
-
- -
- - {#if toolboxLabel} -
- {toolboxLabel} +
+
+ {node.name} + {#if toolboxLabel} + {toolboxLabel} + {/if} +
+
+
- {/if} +
@@ -52,37 +54,51 @@ overflow: hidden; } - .detail-preview { + .detail-section { flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; padding: var(--space-md); } - .detail-meta { - flex-shrink: 0; + .section-title { display: flex; - justify-content: center; - padding: 0 var(--space-md) var(--space-sm); + align-items: baseline; + gap: var(--space-sm); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-muted); + margin-bottom: var(--space-sm); + } + + .title-text { + flex-shrink: 0; } - .toolbox-badge { - padding: 1px 6px; + .title-meta { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; font-family: var(--font-mono); font-size: 9px; font-weight: 400; - color: var(--text-muted); - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-sm); + color: var(--text-disabled); + text-transform: none; + letter-spacing: 0; + } + + .detail-preview { + display: flex; + align-items: center; + justify-content: center; } .detail-docs { flex: 1; min-height: 0; overflow-y: auto; - padding: var(--space-md); + padding: 0 var(--space-md) var(--space-md); font-size: 11px; } From a3200cbf1208df41b984a171ee528aee86549298 Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 09:41:59 +0200 Subject: [PATCH 11/21] Match toolbox info typography to section title and put in parentheses --- src/lib/components/panels/library-detail/EventDetail.svelte | 6 +----- .../components/panels/library-detail/NodeBlockDetail.svelte | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/lib/components/panels/library-detail/EventDetail.svelte b/src/lib/components/panels/library-detail/EventDetail.svelte index 64b3ba50..d1905a01 100644 --- a/src/lib/components/panels/library-detail/EventDetail.svelte +++ b/src/lib/components/panels/library-detail/EventDetail.svelte @@ -31,7 +31,7 @@
{event.name} {#if toolboxLabel} - {toolboxLabel} + ({toolboxLabel}) {/if}
@@ -80,12 +80,8 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-family: var(--font-mono); - font-size: 9px; font-weight: 400; color: var(--text-disabled); - text-transform: none; - letter-spacing: 0; } .detail-preview { diff --git a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte index e9d8053e..7a461028 100644 --- a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte +++ b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte @@ -31,7 +31,7 @@
{node.name} {#if toolboxLabel} - {toolboxLabel} + ({toolboxLabel}) {/if}
@@ -80,12 +80,8 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-family: var(--font-mono); - font-size: 9px; font-weight: 400; color: var(--text-disabled); - text-transform: none; - letter-spacing: 0; } .detail-preview { From 60dd13fa43d2924c132c583258c9fb4f7997142c Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 09:44:07 +0200 Subject: [PATCH 12/21] Make library detail column wider via configurable rightColumnWidth --- src/lib/components/ResizablePanel.svelte | 7 +++++-- src/routes/+page.svelte | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/components/ResizablePanel.svelte b/src/lib/components/ResizablePanel.svelte index c74abc74..a7af2989 100644 --- a/src/lib/components/ResizablePanel.svelte +++ b/src/lib/components/ResizablePanel.svelte @@ -35,6 +35,8 @@ * there's something to show (so the column doesn't eat panel width * while empty). Defaults to true when a `rightColumn` is supplied. */ rightColumnActive?: boolean; + /** Width of the right column in px. */ + rightColumnWidth?: number; } let { @@ -57,7 +59,8 @@ footer, children, rightColumn, - rightColumnActive = true + rightColumnActive = true, + rightColumnWidth = 320 }: Props = $props(); const showRightColumn = $derived(!!rightColumn && rightColumnActive); @@ -246,7 +249,7 @@ {@render children?.()}
{#if showRightColumn} -
+
{@render rightColumn!()}
{/if} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 16c471d0..74e2d615 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -215,7 +215,7 @@ // Library detail-column hover state. When the user hovers a tile, the // library panel grows by DETAIL_COLUMN_WIDTH and renders a detail view // of the hovered block. - const DETAIL_COLUMN_WIDTH = 320; + const DETAIL_COLUMN_WIDTH = 400; let nodeLibraryDetailVisible = $state(false); let eventsPanelDetailVisible = $state(false); let nodeLibraryHoveredItem = $state(null); @@ -1370,6 +1370,7 @@ bottomOffset={leftPanelBottomOffset()} title="Blocks" rightColumnActive={nodeLibraryDetailVisible} + rightColumnWidth={DETAIL_COLUMN_WIDTH} onClose={() => showNodeLibrary = false} onWidthChange={(w) => (nodeLibraryWidth = Math.min( @@ -1420,6 +1421,7 @@ bottomOffset={leftPanelBottomOffset()} title="Events" rightColumnActive={eventsPanelDetailVisible} + rightColumnWidth={DETAIL_COLUMN_WIDTH} onClose={() => showEventsPanel = false} onWidthChange={(w) => (eventsPanelWidth = Math.min( From 525b428d4fedd4feac22c18999d29bc10669d2de Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 09:45:30 +0200 Subject: [PATCH 13/21] Add switch delay so brushing past tiles doesn't flip detail content --- src/lib/components/panels/EventsPanel.svelte | 47 +++++++++++---- src/lib/components/panels/NodeLibrary.svelte | 60 +++++++++++++++----- 2 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/lib/components/panels/EventsPanel.svelte b/src/lib/components/panels/EventsPanel.svelte index be159c04..54b88ada 100644 --- a/src/lib/components/panels/EventsPanel.svelte +++ b/src/lib/components/panels/EventsPanel.svelte @@ -34,21 +34,47 @@ // Hover-detail state — same pattern as NodeLibrary. const HOVER_OPEN_DELAY = 250; + const HOVER_SWITCH_DELAY = 200; const HOVER_CLOSE_DELAY = 120; let hoveredItem = $state(null); let hoverOpenTimer: ReturnType | null = null; + let hoverSwitchTimer: ReturnType | null = null; let hoverCloseTimer: ReturnType | null = null; + function clearHoverTimers() { + if (hoverOpenTimer !== null) { + clearTimeout(hoverOpenTimer); + hoverOpenTimer = null; + } + if (hoverSwitchTimer !== null) { + clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = null; + } + if (hoverCloseTimer !== null) { + clearTimeout(hoverCloseTimer); + hoverCloseTimer = null; + } + } + function handleMouseEnter(item: EventTypeDefinition) { if (hoverCloseTimer !== null) { clearTimeout(hoverCloseTimer); hoverCloseTimer = null; } + if (hoveredItem === item) { + if (hoverSwitchTimer !== null) { + clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = null; + } + return; + } if (hoveredItem !== null) { - if (hoveredItem !== item) { + if (hoverSwitchTimer !== null) clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = setTimeout(() => { + hoverSwitchTimer = null; hoveredItem = item; onhoveritem?.(item); - } + }, HOVER_SWITCH_DELAY); return; } if (hoverOpenTimer !== null) clearTimeout(hoverOpenTimer); @@ -65,6 +91,10 @@ clearTimeout(hoverOpenTimer); hoverOpenTimer = null; } + if (hoverSwitchTimer !== null) { + clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = null; + } if (hoveredItem === null) return; if (hoverCloseTimer !== null) clearTimeout(hoverCloseTimer); hoverCloseTimer = setTimeout(() => { @@ -76,14 +106,7 @@ } function hideDetailNow() { - if (hoverOpenTimer !== null) { - clearTimeout(hoverOpenTimer); - hoverOpenTimer = null; - } - if (hoverCloseTimer !== null) { - clearTimeout(hoverCloseTimer); - hoverCloseTimer = null; - } + clearHoverTimers(); const wasShown = hoveredItem !== null; hoveredItem = null; if (wasShown) { @@ -97,6 +120,10 @@ clearTimeout(hoverCloseTimer); hoverCloseTimer = null; } + if (hoverSwitchTimer !== null) { + clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = null; + } } export function dismissDetail() { diff --git a/src/lib/components/panels/NodeLibrary.svelte b/src/lib/components/panels/NodeLibrary.svelte index aa5314a1..cf24de7c 100644 --- a/src/lib/components/panels/NodeLibrary.svelte +++ b/src/lib/components/panels/NodeLibrary.svelte @@ -41,11 +41,28 @@ // brushing past tiles on the way to the detail column doesn't flicker // through every block in between. const HOVER_OPEN_DELAY = 250; + const HOVER_SWITCH_DELAY = 200; const HOVER_CLOSE_DELAY = 120; let hoveredItem = $state(null); let hoverOpenTimer: ReturnType | null = null; + let hoverSwitchTimer: ReturnType | null = null; let hoverCloseTimer: ReturnType | null = null; + function clearHoverTimers() { + if (hoverOpenTimer !== null) { + clearTimeout(hoverOpenTimer); + hoverOpenTimer = null; + } + if (hoverSwitchTimer !== null) { + clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = null; + } + if (hoverCloseTimer !== null) { + clearTimeout(hoverCloseTimer); + hoverCloseTimer = null; + } + } + // Collapsed categories let collapsedCategories = $state>(new Set()); @@ -122,13 +139,25 @@ clearTimeout(hoverCloseTimer); hoverCloseTimer = null; } - // If detail is already open, switch immediately so the user can hop - // between blocks without waiting for a re-open delay each time. + // Already showing this exact item — drop any in-flight switch so + // it doesn't replace us with itself. + if (hoveredItem === node) { + if (hoverSwitchTimer !== null) { + clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = null; + } + return; + } + // Detail is open on a different item: schedule a switch. Brushing + // past tiles on the way to the detail column won't flip content + // because the cursor leaves before this timer fires. if (hoveredItem !== null) { - if (hoveredItem !== node) { + if (hoverSwitchTimer !== null) clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = setTimeout(() => { + hoverSwitchTimer = null; hoveredItem = node; onhoveritem?.(node); - } + }, HOVER_SWITCH_DELAY); return; } // First-time open: wait a moment so brushing past tiles doesn't @@ -143,11 +172,15 @@ } function scheduleHideDetail() { - // Pending open got cancelled — user left before we'd have shown it. + // Cancel pending open / switch — user moved off before we'd commit. if (hoverOpenTimer !== null) { clearTimeout(hoverOpenTimer); hoverOpenTimer = null; } + if (hoverSwitchTimer !== null) { + clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = null; + } if (hoveredItem === null) return; if (hoverCloseTimer !== null) clearTimeout(hoverCloseTimer); hoverCloseTimer = setTimeout(() => { @@ -159,14 +192,7 @@ } function hideDetailNow() { - if (hoverOpenTimer !== null) { - clearTimeout(hoverOpenTimer); - hoverOpenTimer = null; - } - if (hoverCloseTimer !== null) { - clearTimeout(hoverCloseTimer); - hoverCloseTimer = null; - } + clearHoverTimers(); const wasShown = hoveredItem !== null; hoveredItem = null; if (wasShown) { @@ -176,13 +202,17 @@ } /** Called from the parent when the cursor enters the detail column — - * cancel any pending dismiss so the column stays open while the user - * reads the docs. */ + * cancel any pending dismiss / switch so the column stays open and + * doesn't swap content from a transient last-tile hover. */ export function keepDetailAlive() { if (hoverCloseTimer !== null) { clearTimeout(hoverCloseTimer); hoverCloseTimer = null; } + if (hoverSwitchTimer !== null) { + clearTimeout(hoverSwitchTimer); + hoverSwitchTimer = null; + } } /** Called from the parent when the cursor leaves the detail column — From ffc821aefb682c546d5104823156a9c3fa9fa58d Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 10:03:27 +0200 Subject: [PATCH 14/21] Add CanvasBlockPreview matching BaseNode visuals with port handles --- .../library-detail/CanvasBlockPreview.svelte | 144 ++++++++++++++++++ .../library-detail/NodeBlockDetail.svelte | 4 +- 2 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 src/lib/components/panels/library-detail/CanvasBlockPreview.svelte diff --git a/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte b/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte new file mode 100644 index 00000000..5d963b5b --- /dev/null +++ b/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte @@ -0,0 +1,144 @@ + + +
+
+ {node.name} +
+ + {#each Array(inputCount) as _, i} +
+ {/each} + {#each Array(outputCount) as _, i} +
+ {/each} +
+ + diff --git a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte index 7a461028..191f24ca 100644 --- a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte +++ b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte @@ -3,7 +3,7 @@ import { nodeRegistry, BUILTIN_SOURCE } from '$lib/nodes/registry'; import { toolboxes } from '$lib/toolbox/store'; import type { ToolboxConfig } from '$lib/toolbox/types'; - import NodePreview from '$lib/components/nodes/NodePreview.svelte'; + import CanvasBlockPreview from './CanvasBlockPreview.svelte'; import DocumentationSection from '$lib/components/dialogs/shared/DocumentationSection.svelte'; interface Props { @@ -35,7 +35,7 @@ {/if}
- +
From 812c862d4cdd33db0dbf28d7814b5824b75f7cc5 Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 10:06:12 +0200 Subject: [PATCH 15/21] Use muted text color for block name in CanvasBlockPreview --- .../components/panels/library-detail/CanvasBlockPreview.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte b/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte index 5d963b5b..6e20f515 100644 --- a/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte +++ b/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte @@ -84,7 +84,7 @@ .cbp-name { font-size: 10px; - color: var(--text); + color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; From 69fb1bed311da55c56cba443e4a83a3b9839e1ba Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 10:10:51 +0200 Subject: [PATCH 16/21] Add vertical padding around detail preview and separator before docs --- src/lib/components/panels/library-detail/EventDetail.svelte | 4 +++- .../components/panels/library-detail/NodeBlockDetail.svelte | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/components/panels/library-detail/EventDetail.svelte b/src/lib/components/panels/library-detail/EventDetail.svelte index d1905a01..9cc90ade 100644 --- a/src/lib/components/panels/library-detail/EventDetail.svelte +++ b/src/lib/components/panels/library-detail/EventDetail.svelte @@ -57,6 +57,7 @@ .detail-section { flex-shrink: 0; padding: var(--space-md); + border-bottom: 1px solid var(--border); } .section-title { @@ -88,13 +89,14 @@ display: flex; align-items: center; justify-content: center; + padding: var(--space-lg) 0; } .detail-docs { flex: 1; min-height: 0; overflow-y: auto; - padding: 0 var(--space-md) var(--space-md); + padding: var(--space-md); font-size: 11px; } diff --git a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte index 191f24ca..31cb11e9 100644 --- a/src/lib/components/panels/library-detail/NodeBlockDetail.svelte +++ b/src/lib/components/panels/library-detail/NodeBlockDetail.svelte @@ -57,6 +57,7 @@ .detail-section { flex-shrink: 0; padding: var(--space-md); + border-bottom: 1px solid var(--border); } .section-title { @@ -88,13 +89,14 @@ display: flex; align-items: center; justify-content: center; + padding: var(--space-lg) 0; } .detail-docs { flex: 1; min-height: 0; overflow-y: auto; - padding: 0 var(--space-md) var(--space-md); + padding: var(--space-md); font-size: 11px; } From 3fd078d74e4ab5d5d7ff4fe57e3483f73b46c018 Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 10:13:40 +0200 Subject: [PATCH 17/21] Use raw port array length for preview handles to match canvas behavior --- .../panels/library-detail/CanvasBlockPreview.svelte | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte b/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte index 6e20f515..371dc82f 100644 --- a/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte +++ b/src/lib/components/panels/library-detail/CanvasBlockPreview.svelte @@ -9,10 +9,8 @@ let { node }: Props = $props(); - const minInputs = $derived(node.ports?.minInputs ?? 1); - const minOutputs = $derived(node.ports?.minOutputs ?? 1); - const inputCount = $derived(Math.max(minInputs, node.ports?.inputs?.length ?? 0)); - const outputCount = $derived(Math.max(minOutputs, node.ports?.outputs?.length ?? 0)); + const inputCount = $derived(node.ports?.inputs?.length ?? 0); + const outputCount = $derived(node.ports?.outputs?.length ?? 0); const shapeClass = $derived(getShapeCssClass(node)); const isSubsystemType = $derived(isSubsystem(node) || node.category === 'Subsystem'); From 89530c1681d80f2b3a0f551de5ee7e5070b9b3ae Mon Sep 17 00:00:00 2001 From: milanofthe Date: Wed, 6 May 2026 10:24:43 +0200 Subject: [PATCH 18/21] Polish: timer cleanup on destroy, race-safe docstring loads, tour wording --- .../dialogs/shared/DocumentationSection.svelte | 13 +++++++++++-- src/lib/components/panels/EventsPanel.svelte | 3 +++ src/lib/components/panels/NodeLibrary.svelte | 3 +++ src/lib/tours/scripts/start.ts | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib/components/dialogs/shared/DocumentationSection.svelte b/src/lib/components/dialogs/shared/DocumentationSection.svelte index b9b2eb6c..93c87d26 100644 --- a/src/lib/components/dialogs/shared/DocumentationSection.svelte +++ b/src/lib/components/dialogs/shared/DocumentationSection.svelte @@ -34,18 +34,27 @@ // Check if we have any documentation to show const hasDocumentation = $derived(!!docstring || !!docstringHtml); + // Generation counter: each loadDocs invocation gets a unique id; if a + // newer load starts while an older one is still in flight, the older + // resolution must not overwrite the newer's result. + let loadGen = 0; + async function loadDocs() { if (renderedDocs) return; const html = docstringHtml || docstring; if (!html) return; + const gen = ++loadGen; loading = true; try { - renderedDocs = await renderDocstring(html); + const result = await renderDocstring(html); + if (gen !== loadGen) return; // superseded by a newer load + renderedDocs = result; } catch (e) { + if (gen !== loadGen) return; console.error('Failed to render docstring:', e); renderedDocs = '

Failed to render documentation.

'; } - loading = false; + if (gen === loadGen) loading = false; } async function toggle() { diff --git a/src/lib/components/panels/EventsPanel.svelte b/src/lib/components/panels/EventsPanel.svelte index 54b88ada..3476aa9e 100644 --- a/src/lib/components/panels/EventsPanel.svelte +++ b/src/lib/components/panels/EventsPanel.svelte @@ -1,4 +1,5 @@