From 444a04cd7ab5b251be0f5bc114abc109f7d50e69 Mon Sep 17 00:00:00 2001 From: Milan Rother Date: Mon, 18 May 2026 08:56:42 +0200 Subject: [PATCH 1/5] feat(ports): updateNodePortName + interface inherits parent port names (#289) --- src/lib/stores/graph/helpers.ts | 4 +-- src/lib/stores/graph/index.ts | 1 + src/lib/stores/graph/ports.ts | 44 +++++++++++++++++++++++++++++++++ src/lib/stores/graph/state.ts | 8 +++--- 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/lib/stores/graph/helpers.ts b/src/lib/stores/graph/helpers.ts index 27df8da4..eeaa2250 100644 --- a/src/lib/stores/graph/helpers.ts +++ b/src/lib/stores/graph/helpers.ts @@ -69,7 +69,7 @@ export function deriveInterfaceNode( outputs: parentSubsystem.inputs.map((port, i) => ({ id: `${interfaceNode.id}-output-${i}`, nodeId: interfaceNode.id, - name: `in ${i}`, + name: port.name, direction: 'output' as const, index: i, color: port.color @@ -77,7 +77,7 @@ export function deriveInterfaceNode( inputs: parentSubsystem.outputs.map((port, i) => ({ id: `${interfaceNode.id}-input-${i}`, nodeId: interfaceNode.id, - name: `out ${i}`, + name: port.name, direction: 'input' as const, index: i, color: port.color diff --git a/src/lib/stores/graph/index.ts b/src/lib/stores/graph/index.ts index 09fcccd8..8d8e6722 100644 --- a/src/lib/stores/graph/index.ts +++ b/src/lib/stores/graph/index.ts @@ -84,6 +84,7 @@ export const graphStore = { removeInputPort: ports.removeInputPort, addOutputPort: ports.addOutputPort, removeOutputPort: ports.removeOutputPort, + updateNodePortName: ports.updateNodePortName, // ==================== ANNOTATION OPERATIONS ==================== addAnnotation: annotations.addAnnotation, diff --git a/src/lib/stores/graph/ports.ts b/src/lib/stores/graph/ports.ts index 535a6349..82309784 100644 --- a/src/lib/stores/graph/ports.ts +++ b/src/lib/stores/graph/ports.ts @@ -331,3 +331,47 @@ export function syncPortNamesFromLabels( updateNodeById(nodeId, () => updated); } } + +/** + * Rename a single port. For regular nodes this writes the new name into + * `node.{inputs|outputs}[index].name`. For Interface nodes, port names are + * derived from the parent Subsystem at read time, so we write to the parent + * instead (with the direction flipped, because Interface input ↔ Subsystem + * output). + */ +export function updateNodePortName( + nodeId: string, + direction: PortDirection, + index: number, + name: string +): void { + const currentGraph = getCurrentGraph(); + const node = currentGraph.nodes.get(nodeId); + if (!node) return; + + const path = get(currentPath); + + if (node.type === NODE_TYPES.INTERFACE && path.length > 0) { + const parentId = path[path.length - 1]; + const parentPath = path.slice(0, -1); + const parentPortsKey = direction === 'input' ? 'outputs' : 'inputs'; + + updateParentSubsystem(parentPath, parentId, (parent) => { + const ports = parent[parentPortsKey] as PortInstance[]; + if (index < 0 || index >= ports.length) return parent; + if (ports[index].name === name) return parent; + const newPorts = ports.map((p, i) => (i === index ? { ...p, name } : p)); + return { ...parent, [parentPortsKey]: newPorts }; + }); + return; + } + + updateNodeById(nodeId, (n) => { + const portsKey = direction === 'input' ? 'inputs' : 'outputs'; + const ports = n[portsKey] as PortInstance[]; + if (index < 0 || index >= ports.length) return n; + if (ports[index].name === name) return n; + const newPorts = ports.map((p, i) => (i === index ? { ...p, name } : p)); + return { ...n, [portsKey]: newPorts }; + }); +} diff --git a/src/lib/stores/graph/state.ts b/src/lib/stores/graph/state.ts index 239ab544..2ee10883 100644 --- a/src/lib/stores/graph/state.ts +++ b/src/lib/stores/graph/state.ts @@ -95,11 +95,13 @@ export function getCurrentGraph(): { ...node, name: subsystem.name, color: subsystem.color, - // Subsystem inputs → Interface outputs (signals coming in) + // Subsystem inputs → Interface outputs (signals coming in). + // Port names come from the parent — that's the single source + // of truth so user-customised labels survive across renders. outputs: subsystem.inputs.map((port, i) => ({ id: `${node.id}-output-${i}`, nodeId: node.id, - name: `in ${i}`, + name: port.name, direction: 'output' as const, index: i, color: port.color @@ -108,7 +110,7 @@ export function getCurrentGraph(): { inputs: subsystem.outputs.map((port, i) => ({ id: `${node.id}-input-${i}`, nodeId: node.id, - name: `out ${i}`, + name: port.name, direction: 'input' as const, index: i, color: port.color From a59d0d00de5393962288915da734c315573c4a6e Mon Sep 17 00:00:00 2001 From: Milan Rother Date: Mon, 18 May 2026 08:57:34 +0200 Subject: [PATCH 2/5] feat(properties-dialog): port labels section (#289) --- .../dialogs/BlockPropertiesDialog.svelte | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/lib/components/dialogs/BlockPropertiesDialog.svelte b/src/lib/components/dialogs/BlockPropertiesDialog.svelte index a71b6428..4b556950 100644 --- a/src/lib/components/dialogs/BlockPropertiesDialog.svelte +++ b/src/lib/components/dialogs/BlockPropertiesDialog.svelte @@ -21,6 +21,8 @@ import { NODE_TYPES } from '$lib/constants/nodeTypes'; import { exportRecordingData } from '$lib/utils/csvExport'; import { createRecordingDataState } from '$lib/stores/recordingData.svelte'; + import { getPortLabelConfigs } from '$lib/nodes/uiConfig'; + import { PORT_NAME } from '$lib/constants/handles'; // Code preview state (declared early — referenced by subscription below) let showCode = $state(false); @@ -177,6 +179,24 @@ closeNodeDialog(); } + // Port-label editing — hide for blocks whose port names are driven by a + // regular param (Scope.labels, Adder.operations, …); for those the param + // itself is the source of truth and editing port names directly would be + // overwritten on the next param change. + const hasParamDrivenPortLabels = $derived(node ? getPortLabelConfigs(node.type).length > 0 : false); + const showPortLabels = $derived( + !!node && !hasParamDrivenPortLabels && (node.inputs.length > 0 || node.outputs.length > 0) + ); + + function handlePortNameChange(direction: 'input' | 'output', index: number, value: string) { + if (!node) return; + const id = node.id; + const trimmed = value.trim(); + const fallback = direction === 'input' ? PORT_NAME.input(index) : PORT_NAME.output(index); + const name = trimmed === '' ? fallback : trimmed; + historyStore.mutate(() => graphStore.updateNodePortName(id, direction, index, name)); + } + // Check if node is a recording node (Scope or Spectrum) const isRecordingNode = $derived(node?.type === 'Scope' || node?.type === 'Spectrum'); @@ -408,6 +428,39 @@
No configurable parameters
{/if} + + {#if showPortLabels} +
+
Port labels
+
+ {#each node.inputs as port, i (port.id)} +
+ + handlePortNameChange('input', i, e.currentTarget.value)} + /> +
+ {/each} + {#each node.outputs as port, i (port.id)} +
+ + handlePortNameChange('output', i, e.currentTarget.value)} + /> +
+ {/each} +
+
+ {/if} + {/if} From 1c806fd68e7fd53ee8809f04a695eca0b44491a9 Mon Sep 17 00:00:00 2001 From: Milan Rother Date: Mon, 18 May 2026 09:03:55 +0200 Subject: [PATCH 3/5] refactor(properties-dialog): port labels as toggled view, like code view (#289) --- .../dialogs/BlockPropertiesDialog.svelte | 121 +++++++++++------- 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/src/lib/components/dialogs/BlockPropertiesDialog.svelte b/src/lib/components/dialogs/BlockPropertiesDialog.svelte index 4b556950..e6d9bff7 100644 --- a/src/lib/components/dialogs/BlockPropertiesDialog.svelte +++ b/src/lib/components/dialogs/BlockPropertiesDialog.svelte @@ -26,6 +26,7 @@ // Code preview state (declared early — referenced by subscription below) let showCode = $state(false); + let showPortLabels = $state(false); let previewCode = $state(''); let editorContainer = $state(undefined); let editorView: import('@codemirror/view').EditorView | null = null; @@ -51,10 +52,12 @@ node = graphStore.getNode(id) || null; // Reset to properties view when opening a new node showCode = false; + showPortLabels = false; destroyEditor(); } else { node = null; showCode = false; + showPortLabels = false; destroyEditor(); } }); @@ -146,11 +149,23 @@ const blockCode = generateBlockCode(node, allNodes, allConnections); previewCode = header + blockCode; copied = false; + showPortLabels = false; showCode = true; setTimeout(() => initEditor(), 0); } } + // Toggle port-labels view (same mutual-exclusion pattern as code view) + function togglePortLabelsView() { + if (showPortLabels) { + showPortLabels = false; + } else { + showCode = false; + destroyEditor(); + showPortLabels = true; + } + } + function copyToClipboard() { navigator.clipboard.writeText(previewCode); copied = true; @@ -184,7 +199,7 @@ // itself is the source of truth and editing port names directly would be // overwritten on the next param change. const hasParamDrivenPortLabels = $derived(node ? getPortLabelConfigs(node.type).length > 0 : false); - const showPortLabels = $derived( + const hasEditablePortLabels = $derived( !!node && !hasParamDrivenPortLabels && (node.inputs.length > 0 || node.outputs.length > 0) ); @@ -276,6 +291,8 @@
{#if showCode} Python Code + {:else if showPortLabels} + Port Labels {:else}
{/if} - {:else} + {:else if !showPortLabels} {/if} {/if} - - + + {#if showPortLabels || (!showCode && hasEditablePortLabels)} + + {/if} + + {#if !showPortLabels} + + {/if} @@ -371,6 +401,40 @@
Loading...
{/if}
+ {:else if showPortLabels} + + {#if node.inputs.length === 0 && node.outputs.length === 0} +
No ports to label
+ {:else} +
+
+ {#each node.inputs as port, i (port.id)} +
+ + handlePortNameChange('input', i, e.currentTarget.value)} + /> +
+ {/each} + {#each node.outputs as port, i (port.id)} +
+ + handlePortNameChange('output', i, e.currentTarget.value)} + /> +
+ {/each} +
+
+ {/if} {:else} {#if typeDef.params.length > 0} @@ -428,45 +492,12 @@
No configurable parameters
{/if} - - {#if showPortLabels} -
-
Port labels
-
- {#each node.inputs as port, i (port.id)} -
- - handlePortNameChange('input', i, e.currentTarget.value)} - /> -
- {/each} - {#each node.outputs as port, i (port.id)} -
- - handlePortNameChange('output', i, e.currentTarget.value)} - /> -
- {/each} -
-
- {/if} - {/if}
- {#if !showCode} + {#if !showCode && !showPortLabels} From 255e0f87200bd4bc02293eb50b5402ee2749ccfd Mon Sep 17 00:00:00 2001 From: Milan Rother Date: Mon, 18 May 2026 09:07:24 +0200 Subject: [PATCH 4/5] style(properties-dialog): separator between input and output port labels (#289) --- src/lib/components/dialogs/BlockPropertiesDialog.svelte | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/components/dialogs/BlockPropertiesDialog.svelte b/src/lib/components/dialogs/BlockPropertiesDialog.svelte index e6d9bff7..94e882f6 100644 --- a/src/lib/components/dialogs/BlockPropertiesDialog.svelte +++ b/src/lib/components/dialogs/BlockPropertiesDialog.svelte @@ -420,6 +420,9 @@ /> {/each} + {#if node.inputs.length > 0 && node.outputs.length > 0} +
+ {/if} {#each node.outputs as port, i (port.id)}
@@ -520,6 +523,12 @@ min-width: 0; } + .port-divider { + height: 1px; + background: var(--border); + margin: 4px 0; + } + /* Pin button */ .pin-btn { flex-shrink: 0; From 7eef9d786f0a8ae774619e95a0b80facf41e1016 Mon Sep 17 00:00:00 2001 From: Milan Rother Date: Mon, 18 May 2026 09:08:45 +0200 Subject: [PATCH 5/5] style(properties-dialog): full-width port-label separator (#289) --- src/lib/components/dialogs/BlockPropertiesDialog.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/components/dialogs/BlockPropertiesDialog.svelte b/src/lib/components/dialogs/BlockPropertiesDialog.svelte index 94e882f6..ebff1051 100644 --- a/src/lib/components/dialogs/BlockPropertiesDialog.svelte +++ b/src/lib/components/dialogs/BlockPropertiesDialog.svelte @@ -526,7 +526,8 @@ .port-divider { height: 1px; background: var(--border); - margin: 4px 0; + /* Extend past the dialog-body padding so the line spans edge-to-edge. */ + margin: var(--space-xs) calc(-1 * var(--space-md)); } /* Pin button */