From a4d2590fe729f3063a2a11eb0634c78a45480fbc Mon Sep 17 00:00:00 2001 From: sukuwc Date: Tue, 21 Apr 2026 10:27:58 +0200 Subject: [PATCH 01/11] SUKU midi action block updated to support rx options --- package-lock.json | 8 +-- package.json | 2 +- src/renderer/config-blocks/Midi.svelte | 89 +++++++++++++++++++------- 3 files changed, 71 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0efc8ef85..500280d7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@intechstudio/grid-protocol": "1.20260302.1321", - "@intechstudio/grid-uikit": "1.20260303.1559", + "@intechstudio/grid-uikit": "^1.20260421.817", "@intechstudio/profile-cloud-webcomponent": "1.20251107.1414", "adm-zip": "^0.5.10", "axios": "^1.13.5", @@ -2110,9 +2110,9 @@ } }, "node_modules/@intechstudio/grid-uikit": { - "version": "1.20260303.1559", - "resolved": "https://registry.npmjs.org/@intechstudio/grid-uikit/-/grid-uikit-1.20260303.1559.tgz", - "integrity": "sha512-OE4gYErV6a5NCyjRpnX7Nn0tyTmwhui+NT/LzbofaVwmLrWP/VpqXMHWWmW+QSqOmIPIyFUD53ZpQLS7zm83fw==", + "version": "1.20260421.817", + "resolved": "https://registry.npmjs.org/@intechstudio/grid-uikit/-/grid-uikit-1.20260421.817.tgz", + "integrity": "sha512-qbg5S5Z+tSaYpWc6EIjkL3Lt3Kh26M0zs+b2v6uduzym/G38NNOwJXntOFA2+NIrSwDsUciEKExnfXONNEHogA==", "dependencies": { "@melt-ui/svelte": "^0.86.6", "color-convert": "^2.0.1", diff --git a/package.json b/package.json index 5bb4ba942..c94c79c4e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@intechstudio/grid-protocol": "1.20260302.1321", - "@intechstudio/grid-uikit": "1.20260303.1559", + "@intechstudio/grid-uikit": "^1.20260421.817", "@intechstudio/profile-cloud-webcomponent": "1.20251107.1414", "adm-zip": "^0.5.10", "axios": "^1.13.5", diff --git a/src/renderer/config-blocks/Midi.svelte b/src/renderer/config-blocks/Midi.svelte index 240187880..5541e3db3 100644 --- a/src/renderer/config-blocks/Midi.svelte +++ b/src/renderer/config-blocks/Midi.svelte @@ -46,10 +46,16 @@ - {#if tabs !== undefined} -
-
- {#each tabs as element} - handleTabButtonClicked(element)} - /> - {/each} -
- {/if} - Send MIDI + + + - {#each scriptSegments as script, i} + { + const { value, validationError } = e.detail; + scriptSegments[0] = value; + validators[0].value = !validationError; + sendData(); + }} + on:change={() => dispatch("sync")} + postProcessor={GridScript.shortify} + preProcessor={GridScript.humanize} + /> + {#if mode === 0} + { + const { value, validationError } = e.detail; + scriptSegments[1] = value; + validators[1].value = !validationError; + sendData(); + }} + on:change={() => dispatch("sync")} + postProcessor={GridScript.shortify} + preProcessor={GridScript.humanize} + /> + {/if} + {#if mode !== 2 && mode !== 3} { const { value, validationError } = e.detail; - script = value; - validators[i].value = !validationError; + scriptSegments[2] = value; + validators[2].value = !validationError; sendData(); }} on:change={() => dispatch("sync")} postProcessor={GridScript.shortify} preProcessor={GridScript.humanize} /> - {/each} + {/if} + { + const { value, validationError } = e.detail; + scriptSegments[3] = value; + validators[3].value = !validationError; + sendData(); + }} + on:change={() => dispatch("sync")} + postProcessor={GridScript.shortify} + preProcessor={GridScript.humanize} + /> + + {#if mode === 2 || mode === 3} + +
+
+ { + const { value, validationError } = e.detail; + nrpnMSB = value; + validators[4].value = !validationError; + scriptSegments[2] = calculateNRPNCC(nrpnMSB, nrpnLSB); + sendData(); + }} + on:change={() => dispatch("sync")} + postProcessor={GridScript.shortify} + preProcessor={GridScript.humanize} + /> + { + const { value, validationError } = e.detail; + nrpnLSB = value; + validators[5].value = !validationError; + scriptSegments[2] = calculateNRPNCC(nrpnMSB, nrpnLSB); + sendData(); + }} + on:change={() => dispatch("sync")} + postProcessor={GridScript.shortify} + preProcessor={GridScript.humanize} + /> +
+
+ + + +
+ { + const { value, validationError } = e.detail; + scriptSegments[2] = value; + validators[2].value = !validationError; + nrpnMSB = `(${value})//128`; + nrpnLSB = `(${value})%128`; + sendData(); + }} + on:change={() => dispatch("sync")} + postProcessor={GridScript.shortify} + preProcessor={GridScript.humanize} + /> +
+
+ {/if}
diff --git a/src/renderer/config-blocks/MidiFourteenBit.svelte b/src/renderer/config-blocks/MidiFourteenBit.svelte index a0395783c..161a3db3e 100644 --- a/src/renderer/config-blocks/MidiFourteenBit.svelte +++ b/src/renderer/config-blocks/MidiFourteenBit.svelte @@ -9,7 +9,7 @@ short: "gmsh", name: "MidiFourteenBit", rendering: "standard", - category: "midi", + category: "deprecated", displayName: "MIDI 14", color: "#DA4167", defaultLua: "gms(0,176,0,val//128) gms(0,176,32,val%128)", @@ -52,7 +52,7 @@ import { LocalDefinitions } from "../runtime/runtime.store"; import { ActionData, GridAction, GridEvent } from "./../runtime/runtime"; import SendFeedback from "../main/user-interface/SendFeedback.svelte"; - import TabButton from "../main/user-interface/TabButton.svelte"; + import { Script } from "./_script_parsers.js"; import { Validator } from "./validators"; import { Grid } from "../lib/_utils.js"; @@ -197,32 +197,12 @@ $: if ($event) { renderSuggestions(); } - const tabs = [ - { name: "MIDI", short: "gms" }, - { name: "14 bit MIDI", short: "gmsh" }, - { name: "SysEX", short: "gmss" }, - { name: "NRPN MIDI", short: "gmnp" }, - ]; - - function handleTabButtonClicked(element) { - dispatch("replace", { short: element.short }); - } - {#if tabs !== undefined} -
-
- {#each tabs as element} - handleTabButtonClicked(element)} - /> - {/each} -
- {/if} - +
+ This block is deprecated. Use the MIDI block with CC 14-bit mode instead. +
{#each scriptSegments as script, i} - {#if tabs !== undefined} -
-
- {#each tabs as element} - handleTabButtonClicked(element)} - /> - {/each} -
- {/if} - +
+ This block is deprecated. Use the MIDI block with NRPN mode instead. +
- {#if tabs !== undefined} -
-
- {#each tabs as element} - handleTabButtonClicked(element)} - /> - {/each} -
- {/if} -
Enter comma separated sysex bytes or variables. diff --git a/src/renderer/main/panels/configuration/components/ActionPicker.svelte b/src/renderer/main/panels/configuration/components/ActionPicker.svelte index f2f1f36ab..ab62e4112 100644 --- a/src/renderer/main/panels/configuration/components/ActionPicker.svelte +++ b/src/renderer/main/panels/configuration/components/ActionPicker.svelte @@ -226,7 +226,11 @@ } if (get(appSettings).persistent.userLevelMinimalist === true) { - comp = comp.filter((e) => !e.information.hiddenInMinimalist); + comp = comp.filter( + (e) => + !e.information.hiddenInMinimalist && + e.information.category !== "deprecated", + ); } //Group components by category @@ -252,6 +256,7 @@ "special", "code", "timer", + "deprecated", ]; comp.sort(function (a, b) { return ( From 6f1b67bae852dac8a26cf2f1a531c612012dd477 Mon Sep 17 00:00:00 2001 From: sukuwc Date: Tue, 21 Apr 2026 12:39:08 +0200 Subject: [PATCH 03/11] SUKU short name added for midirx register function --- src/renderer/config-blocks/Midi.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/config-blocks/Midi.svelte b/src/renderer/config-blocks/Midi.svelte index 1842cd507..caa432235 100644 --- a/src/renderer/config-blocks/Midi.svelte +++ b/src/renderer/config-blocks/Midi.svelte @@ -124,7 +124,7 @@ } } - const rxParams = extractParam(data.script, "midirx_register"); + const rxParams = extractParam(data.script, "gmrr"); if (rxParams !== null) { const match = rxParams.match(/\{(true|false),(true|false)\}/); feature1 = match ? match[1] === "true" : false; @@ -149,7 +149,7 @@ if (feature1 || feature2) { const f1 = feature1 ? "true" : "false"; const f2 = feature2 ? "true" : "false"; - fullScript += ` self:midirx_register(-1,${scriptSegments[0]},${scriptSegments[1]},${scriptSegments[2]},{${f1},${f2}})`; + fullScript += ` self:gmrr(-1,${scriptSegments[0]},${scriptSegments[1]},${scriptSegments[2]},{${f1},${f2}})`; } dispatch("update-action", { short: action.short, From f6cdb7b2f7fb66e2561f60d356ab022192344ac1 Mon Sep 17 00:00:00 2001 From: sukuwc Date: Tue, 28 Apr 2026 13:14:11 +0200 Subject: [PATCH 04/11] SUKU mode added to rx registration --- src/renderer/config-blocks/Midi.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/config-blocks/Midi.svelte b/src/renderer/config-blocks/Midi.svelte index caa432235..72f0e9dce 100644 --- a/src/renderer/config-blocks/Midi.svelte +++ b/src/renderer/config-blocks/Midi.svelte @@ -149,7 +149,7 @@ if (feature1 || feature2) { const f1 = feature1 ? "true" : "false"; const f2 = feature2 ? "true" : "false"; - fullScript += ` self:gmrr(-1,${scriptSegments[0]},${scriptSegments[1]},${scriptSegments[2]},{${f1},${f2}})`; + fullScript += ` self:gmrr(-1,${scriptSegments[0]},${scriptSegments[1]},${scriptSegments[2]},{${f1},${f2}},${String(mode)})`; } dispatch("update-action", { short: action.short, From 016ba2c618ea4bc28cc74b890e8985a436add845 Mon Sep 17 00:00:00 2001 From: SukuWc <32985513+SukuWc@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:21:50 +0200 Subject: [PATCH 05/11] Suku large file support (#1482) * SUKU chunked upload and download implemented * SUKU retryCount display added, chunk size tuned * SUKU refresh button added * SUKU folder handling improved --- .../panels/FileManager/FileManager.svelte | 149 ++++++++++++++---- .../main/user-interface/ActiveChanges.svelte | 3 + src/renderer/runtime/engine.store.ts | 5 +- 3 files changed, 127 insertions(+), 30 deletions(-) diff --git a/src/renderer/main/panels/FileManager/FileManager.svelte b/src/renderer/main/panels/FileManager/FileManager.svelte index 32f74f304..5d3e0216d 100644 --- a/src/renderer/main/panels/FileManager/FileManager.svelte +++ b/src/renderer/main/panels/FileManager/FileManager.svelte @@ -171,7 +171,9 @@ clickTimer = setTimeout(() => { clickTimer = null; selectedEntry = entry.name; - readFile(entry.name); + if (entry.type !== "dir") { + readFile(entry.name); + } }, 250); } } @@ -194,23 +196,28 @@ let savedContent: string | null = null; let rawContent: string | null = null; let readingFile = false; + let downloadProgress: { current: number; total: number } | null = null; let savingFile = false; + let uploadProgress: { current: number; total: number } | null = null; $: fileDirty = fileContent !== null && fileContent !== savedContent; let luaSyntaxError: string | null = null; - $: savePacketSize = (() => { + const CHUNK_SIZE = 50; // raw content chars per write chunk — small for testing + const READ_CHUNK_SIZE = 50; // bytes per read chunk — small for testing + + $: contentInfo = (() => { if (!fileContent || !selectedEntry) return null; try { - const path = currentPath + selectedEntry; const content = selectedLanguage === "lua" ? GridScript.compressScript(fileContent) : fileContent; luaSyntaxError = null; - const lua = `local f=io.open(${JSON.stringify(path)},"w") if not f then return false end f:write("${luaEscape(content)}") f:close() return true`; - return ``.length; + const bytes = content.length; + const chunks = Math.max(1, Math.ceil(bytes / CHUNK_SIZE)); + return { bytes, chunks }; } catch (e) { luaSyntaxError = String(e); return null; @@ -300,15 +307,43 @@ } const path = currentPath + entry; readingFile = true; + downloadProgress = null; fileContent = null; savedContent = null; + rawContent = null; try { - const lua = `local f = io.open(${JSON.stringify(path)}, "r") if not f then return nil end local c = f:read("*a") f:close() return c`; - const result = await sendLua(lua, target.dx, target.dy); - rawContent = result[0] != null ? String(result[0]) : null; + const sizeResult = await sendLua( + `local f=io.open(${JSON.stringify(path)},"r") if not f then return nil end local n=0 local c=f:read(256) while c do n=n+#c c=f:read(256) end f:close() return n`, + target.dx, + target.dy, + ); + if (sizeResult[0] == null) { + throw new Error("Could not read file size"); + } + const fileSize = Number(sizeResult[0]); + + let assembled = ""; + if (fileSize > 0) { + const totalChunks = Math.ceil(fileSize / READ_CHUNK_SIZE); + for (let i = 0; i < totalChunks; i++) { + const offset = i * READ_CHUNK_SIZE; + const result = await sendLua( + `local f=io.open(${JSON.stringify(path)},"r") if not f then return nil end f:seek("set",${offset}) local c=f:read(${READ_CHUNK_SIZE}) f:close() collectgarbage("collect") return c`, + target.dx, + target.dy, + ); + if (result[0] == null) { + throw new Error(`Read failed at chunk ${i + 1}/${totalChunks}`); + } + assembled += String(result[0]); + downloadProgress = { current: i + 1, total: totalChunks }; + } + } + + rawContent = assembled; selectedLanguage = detectLanguage(entry); fileContent = - rawContent !== null && selectedLanguage === "lua" + selectedLanguage === "lua" ? GridScript.expandScript(rawContent) : rawContent; savedContent = fileContent; @@ -316,17 +351,22 @@ } catch (e) { fileContent = null; savedContent = null; + rawContent = null; } finally { readingFile = false; + downloadProgress = null; } } async function saveFile() { if (!target || !selectedEntry || fileContent === null) return; savingFile = true; + uploadProgress = null; error = null; try { const path = currentPath + selectedEntry; + const tmpPath = path + ".tmp"; + let content: string; try { content = @@ -337,26 +377,64 @@ error = `Syntax error: ${e}`; return; } - const lua = `local f=io.open(${JSON.stringify(path)},"w") if not f then return false end f:write("${luaEscape(content)}") f:close() return true`; - const result = await sendLua(lua, target.dx, target.dy, false); - if (result[0] === true) { - savedContent = fileContent; - // Invalidate Lua require() cache so the updated module is picked up next call - const moduleName = selectedEntry.replace(/\.lua$/i, ""); - if (selectedEntry.toLowerCase().endsWith(".lua")) { - await sendLua( - `package.loaded[${JSON.stringify(moduleName)}] = nil`, - target.dx, - target.dy, - ); + + const expectedSize = content.length; + + // Split on raw content boundaries so escape sequences are never split + const rawChunks: string[] = []; + for (let i = 0; i < content.length; i += CHUNK_SIZE) { + rawChunks.push(content.slice(i, i + CHUNK_SIZE)); + } + if (rawChunks.length === 0) rawChunks.push(""); + + for (let i = 0; i < rawChunks.length; i++) { + const mode = i === 0 ? "w" : "a"; + const escaped = luaEscape(rawChunks[i]); + const lua = `local f=io.open(${JSON.stringify(tmpPath)},"${mode}") if not f then return false end f:write("${escaped}") f:close() collectgarbage("collect") return true`; + const result = await sendLua(lua, target.dx, target.dy, false); + if (result[0] !== true) { + error = `Write failed at chunk ${i + 1}/${rawChunks.length}`; + return; } - } else { - error = `Save failed: ${JSON.stringify(result)}`; + uploadProgress = { current: i + 1, total: rawChunks.length }; + } + + const renameResult = await sendLua( + `return os.rename(${JSON.stringify(tmpPath)}, ${JSON.stringify(path)})`, + target.dx, + target.dy, + ); + if (renameResult[0] !== true) { + error = `Rename failed: ${renameResult[1] ?? "unknown"}`; + return; + } + + const sizeResult = await sendLua( + `local f=io.open(${JSON.stringify(path)},"r") if not f then return nil end local n=0 local c=f:read(256) while c do n=n+#c c=f:read(256) end f:close() return n`, + target.dx, + target.dy, + ); + const actualSize = sizeResult[0]; + if (actualSize !== expectedSize) { + error = `Size mismatch: expected ${expectedSize} B, got ${actualSize} B`; + return; + } + + savedContent = fileContent; + + if (selectedEntry.toLowerCase().endsWith(".lua")) { + const moduleName = selectedEntry.replace(/\.lua$/i, ""); + await sendLua( + `package.loaded[${JSON.stringify(moduleName)}] = nil`, + target.dx, + target.dy, + ); } } catch (e) { error = String(e); } finally { savingFile = false; + uploadProgress = null; } } @@ -562,6 +640,7 @@
{:else}
+ startOp("newFile")} text="New File" /> startOp("newFolder")} @@ -621,8 +700,10 @@ {/if}
e.name === selectedEntry)?.type === 'dir' ? 'hidden' : ''}" > @@ -630,8 +711,10 @@

{selectedEntry ?? ""}{fileDirty ? " •" : ""}

- {#if savePacketSize !== null} - {savePacketSize} B + {#if contentInfo !== null} + {contentInfo.bytes} B · {contentInfo.chunks} chunks {/if}
@@ -646,7 +729,11 @@ />
@@ -654,7 +741,11 @@

{luaSyntaxError}

{/if} {#if readingFile} -

Reading...

+

+ {downloadProgress + ? `Reading ${downloadProgress.current}/${downloadProgress.total}` + : "Reading..."} +

{/if}
writeBuffer: {$buffer?.array.length}
+
+ retryCount: {$buffer?.retryCount} +
{/if}
diff --git a/src/renderer/runtime/engine.store.ts b/src/renderer/runtime/engine.store.ts index e76b31d84..2e74a7988 100644 --- a/src/renderer/runtime/engine.store.ts +++ b/src/renderer/runtime/engine.store.ts @@ -178,6 +178,7 @@ export type PendingBufferProcess = { export type WriteBufferData = { array: BufferElement[]; current: PendingBufferProcess | undefined; + retryCount: number; }; export class WriteBuffer implements Readable { @@ -193,6 +194,7 @@ export class WriteBuffer implements Readable { this._internal = writable({ array: [], current: undefined, + retryCount: 0, }); } @@ -232,7 +234,7 @@ export class WriteBuffer implements Readable { } public clear() { - this.set({ array: [], current: undefined }); + this.set({ array: [], current: undefined, retryCount: 0 }); waiter?.destroy(); waiter = undefined; } @@ -329,6 +331,7 @@ export class WriteBuffer implements Readable { break; } case ResponseStatus.TIMEOUT: { + this.update((s) => ({ ...s, retryCount: s.retryCount + 1 })); resolve(this.sendToGrid(bufferElement)); // RETRY recursively until processed break; } From fc53c5a87b4164222e000010119ad0446dacbe6b Mon Sep 17 00:00:00 2001 From: SukuWc <32985513+SukuWc@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:38:25 +0200 Subject: [PATCH 06/11] SUKU lua framing removed (#1469) * SUKU lua framing removed * SUKU l framing fixed * SUKU virtual module default config compatibility fixed --- src/renderer/lib/_utils.ts | 5 +--- .../panels/DebugMonitor/SendEvaluate.svelte | 2 +- .../main/panels/DebugMonitor/SendRaw.svelte | 2 +- .../panels/FileManager/FileManager.svelte | 2 +- .../panels/profileCloud/ProfileCloud.svelte | 5 +--- src/renderer/runtime/connection-simulator.ts | 7 +++-- src/renderer/runtime/runtime.ts | 6 ----- src/renderer/serialport/instructions.ts | 26 +++++-------------- 8 files changed, 16 insertions(+), 39 deletions(-) diff --git a/src/renderer/lib/_utils.ts b/src/renderer/lib/_utils.ts index 9428f126c..72fc1c854 100644 --- a/src/renderer/lib/_utils.ts +++ b/src/renderer/lib/_utils.ts @@ -294,10 +294,7 @@ export namespace Grid { } export namespace Protocol { - export const scriptStart = ""; - export const maxScriptLength = - grid.getProperty("CONFIG_LENGTH") - scriptEnd.length - scriptStart.length; + export const maxScriptLength = grid.getProperty("CONFIG_LENGTH"); export function getLayerSuggestions(type: ElementType) { switch (type) { diff --git a/src/renderer/main/panels/DebugMonitor/SendEvaluate.svelte b/src/renderer/main/panels/DebugMonitor/SendEvaluate.svelte index 67d4fc756..e7fb206c2 100644 --- a/src/renderer/main/panels/DebugMonitor/SendEvaluate.svelte +++ b/src/renderer/main/panels/DebugMonitor/SendEvaluate.svelte @@ -58,7 +58,7 @@ if (!runtime) return; const code = editor.getValue(); - const script = ``; + const script = `${GridScript.compressScript(code)}`; const size = script.length.toString(16).padStart(4, "0"); const classBody = `\x02086e0001` + `04` + size + script + `\x03`; diff --git a/src/renderer/main/panels/DebugMonitor/SendRaw.svelte b/src/renderer/main/panels/DebugMonitor/SendRaw.svelte index 22b129da4..4a05bd5b2 100644 --- a/src/renderer/main/panels/DebugMonitor/SendRaw.svelte +++ b/src/renderer/main/panels/DebugMonitor/SendRaw.svelte @@ -15,7 +15,7 @@ export let target: { dx: number; dy: number }; - let rawInput = `\x02085e001b\x03`; + let rawInput = `\x02085e0012print("INFO: foo")\x03`; async function handleSendRawClicked() { const runtime = get(runtime_manager).active?.runtime; diff --git a/src/renderer/main/panels/FileManager/FileManager.svelte b/src/renderer/main/panels/FileManager/FileManager.svelte index 5d3e0216d..528accce2 100644 --- a/src/renderer/main/panels/FileManager/FileManager.svelte +++ b/src/renderer/main/panels/FileManager/FileManager.svelte @@ -64,7 +64,7 @@ const runtime = get(runtime_manager).active?.runtime; if (!runtime) throw new Error("No runtime"); - const script = ``; + const script = `${compress ? GridScript.compressScript(code) : code}`; const size = script.length.toString(16).padStart(4, "0"); const classBody = `\x02086e0001` + `04` + size + script + `\x03`; const classArray: number[] = Array.from(classBody, (c) => c.charCodeAt(0)); diff --git a/src/renderer/main/panels/profileCloud/ProfileCloud.svelte b/src/renderer/main/panels/profileCloud/ProfileCloud.svelte index 39a8ba9ae..25204c23f 100644 --- a/src/renderer/main/panels/profileCloud/ProfileCloud.svelte +++ b/src/renderer/main/panels/profileCloud/ProfileCloud.svelte @@ -290,10 +290,7 @@ return Promise.reject(ProfileCloud.ErrorText.EMPTY_SNIPPET); } - const script = - Grid.Protocol.scriptStart + - selected.map((e) => e.toLua()).join("") + - Grid.Protocol.scriptEnd; + const script = selected.map((e) => e.toLua()).join(""); config.type = "snippet"; config.configs = script; diff --git a/src/renderer/runtime/connection-simulator.ts b/src/renderer/runtime/connection-simulator.ts index 602bd9cfb..7dba71f72 100644 --- a/src/renderer/runtime/connection-simulator.ts +++ b/src/renderer/runtime/connection-simulator.ts @@ -24,10 +24,13 @@ export class VirtualModule { const events = grid.get_element_events(type); return { events: events.map((e) => { + const cfg = e.defaultConfig.startsWith("")) + : e.defaultConfig; return { value: Number(e.value), - config: e.defaultConfig, - stored: e.defaultConfig, + config: cfg, + stored: cfg, }; }), }; diff --git a/src/renderer/runtime/runtime.ts b/src/renderer/runtime/runtime.ts index 1d19b9625..03a3eae9a 100644 --- a/src/renderer/runtime/runtime.ts +++ b/src/renderer/runtime/runtime.ts @@ -386,12 +386,6 @@ export class GridAction extends RuntimeNode { let actionString = script.replace(/\s{2,10}/g, " "); - if (actionString.startsWith(Grid.Protocol.scriptStart)) { - actionString = actionString - .split(Grid.Protocol.scriptStart)[1] - .split(Grid.Protocol.scriptEnd)[0]; - } - const matches = [ ...actionString.matchAll(/--\[\[@(.*?)\]\]\s*(.*?)(?=(--\[\[@|$))/gs), ]; diff --git a/src/renderer/serialport/instructions.ts b/src/renderer/serialport/instructions.ts index 09050e91c..979b867d4 100644 --- a/src/renderer/serialport/instructions.ts +++ b/src/renderer/serialport/instructions.ts @@ -126,8 +126,6 @@ export namespace GridInstruction { virtual: boolean = false, ) { super(virtual); - const actionString = - Grid.Protocol.scriptStart + config + Grid.Protocol.scriptEnd; this.buffer_element = { id: uuidv4(), virtual: virtual, @@ -145,8 +143,8 @@ export namespace GridInstruction { PAGENUMBER: page, ELEMENTNUMBER: element, EVENTTYPE: event, - ACTIONLENGTH: actionString.length, - ACTIONSTRING: actionString, + ACTIONLENGTH: config.length, + ACTIONSTRING: config, }, }, responseRequired: true, @@ -164,12 +162,7 @@ export namespace GridInstruction { public executeOn(connection: GridConnection): Promise { const configLength = this.buffer_element.descr.class_parameters.ACTIONLENGTH; - if ( - configLength >= - Grid.Protocol.maxScriptLength + - Grid.Protocol.scriptStart.length + - Grid.Protocol.scriptEnd.length - ) { + if (configLength >= Grid.Protocol.maxScriptLength) { logger.set({ type: "alert", mode: 0, @@ -191,8 +184,6 @@ export namespace GridInstruction { virtual: boolean = false, ) { super(virtual); - const actionString = - Grid.Protocol.scriptStart + script + Grid.Protocol.scriptEnd; this.buffer_element = { id: uuidv4(), virtual: virtual, @@ -204,8 +195,8 @@ export namespace GridInstruction { class_name: InstructionClassName.IMMEDIATE, class_instr: InstructionClass.EXECUTE, class_parameters: { - ACTIONLENGTH: actionString.length, - ACTIONSTRING: actionString, + ACTIONLENGTH: script.length, + ACTIONSTRING: script, }, }, }; @@ -214,12 +205,7 @@ export namespace GridInstruction { public executeOn(connection: GridConnection): Promise { const configLength = this.buffer_element.descr.class_parameters.ACTIONLENGTH; - if ( - configLength >= - Grid.Protocol.maxScriptLength + - Grid.Protocol.scriptStart.length + - Grid.Protocol.scriptEnd.length - ) { + if (configLength >= Grid.Protocol.maxScriptLength) { //TODO: Reject handling logging logger.set({ type: "alert", From e8bc3991629940d08c93f468c774cd1c29a23225 Mon Sep 17 00:00:00 2001 From: sukuwc Date: Tue, 28 Apr 2026 14:06:57 +0200 Subject: [PATCH 07/11] SUKU nrpn fix --- src/renderer/config-blocks/MidiNRPN.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/config-blocks/MidiNRPN.svelte b/src/renderer/config-blocks/MidiNRPN.svelte index 1a0dd02ed..d3bb7ab8f 100644 --- a/src/renderer/config-blocks/MidiNRPN.svelte +++ b/src/renderer/config-blocks/MidiNRPN.svelte @@ -107,7 +107,7 @@ function handleActionChange(data: ActionData) { // Extract all contents const matches = []; - const regex = /gms\((.*?[^)])\)(?=\s|$)/g; + const regex = /gms\((.*?[^)])\)(?=\s|gms|$)/g; let match; while ((match = regex.exec(data.script)) !== null) { @@ -126,6 +126,8 @@ } } + if (midiMSB.length < 2 || midiLSB.length < 1) return; + value = midiMSB[1].split("//")[0]; if (value.startsWith("(") && value.endsWith(")")) { value = value.slice(1, -1); From 0f9f683133a7a89196627f1321d64befe7ad2b54 Mon Sep 17 00:00:00 2001 From: sukuwc Date: Tue, 28 Apr 2026 15:38:58 +0200 Subject: [PATCH 08/11] SUKU responsive layout of action list fixed --- src/renderer/config-blocks/SimpleColor.svelte | 30 ++++++++++--------- src/renderer/main/RightPanelContainer.svelte | 2 +- src/renderer/main/_actions/move.action.ts | 6 ++++ .../panels/configuration/ActionList.svelte | 2 +- .../components/DynamicWrapper.svelte | 8 ++--- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/renderer/config-blocks/SimpleColor.svelte b/src/renderer/config-blocks/SimpleColor.svelte index dc3d81beb..a07a7c995 100644 --- a/src/renderer/config-blocks/SimpleColor.svelte +++ b/src/renderer/config-blocks/SimpleColor.svelte @@ -260,20 +260,22 @@ postProcessor={GridScript.shortify} preProcessor={GridScript.humanize} /> - { - const { value } = e.detail; - data.updateAlphaSliderValue(value); - sendData($data); - }} - on:commit={() => { - dispatch("sync"); - }} - /> + + { + const { value } = e.detail; + data.updateAlphaSliderValue(value); + sendData($data); + }} + on:commit={() => { + dispatch("sync"); + }} + /> diff --git a/src/renderer/main/RightPanelContainer.svelte b/src/renderer/main/RightPanelContainer.svelte index eabd06aa2..6538c3f90 100644 --- a/src/renderer/main/RightPanelContainer.svelte +++ b/src/renderer/main/RightPanelContainer.svelte @@ -19,7 +19,7 @@
diff --git a/src/renderer/main/_actions/move.action.ts b/src/renderer/main/_actions/move.action.ts index 4255b3122..1f1dfcdf8 100644 --- a/src/renderer/main/_actions/move.action.ts +++ b/src/renderer/main/_actions/move.action.ts @@ -118,6 +118,12 @@ export function draggable(node: HTMLElement, params: DragParameters) { return; } + // Scrollbars are not DOM elements; the node itself is the event target. + // Bail out if the click lands in the scrollbar region (beyond client area). + if (e.offsetX > node.clientWidth || e.offsetY > node.clientHeight) { + return; + } + if (!movable) { return; } diff --git a/src/renderer/main/panels/configuration/ActionList.svelte b/src/renderer/main/panels/configuration/ActionList.svelte index 044afb148..e30249633 100644 --- a/src/renderer/main/panels/configuration/ActionList.svelte +++ b/src/renderer/main/panels/configuration/ActionList.svelte @@ -145,7 +145,7 @@ return dragged && dragged.length > 0; }, }} - class="overflow-y-scroll justify-start w-full h-full pl-2 pr-3" + class="overflow-y-scroll overflow-x-hidden justify-start w-full h-full pl-2 pr-3" > {#if $event?.config.length === 0 && $draggedActions.length === 0 && $profileCloudConfigDrag?.configType !== "snippet"} diff --git a/src/renderer/main/panels/configuration/components/DynamicWrapper.svelte b/src/renderer/main/panels/configuration/components/DynamicWrapper.svelte index 70ae1af3e..c9b7f6bef 100644 --- a/src/renderer/main/panels/configuration/components/DynamicWrapper.svelte +++ b/src/renderer/main/panels/configuration/components/DynamicWrapper.svelte @@ -230,7 +230,7 @@ } } }} - class="dynamicWrapper activator-button flex flex-grow outline-none" + class="dynamicWrapper activator-button flex flex-grow min-w-0 outline-none" class:cursor-pointer={ctrlIsDown} > @@ -238,7 +238,7 @@
{#if ($action.toggled && $action.information.toggleable) || typeof header === "undefined"} -
+
Date: Tue, 28 Apr 2026 15:58:23 +0200 Subject: [PATCH 09/11] SUKU responsive breakpoint added --- src/renderer/config-blocks/Midi.svelte | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/renderer/config-blocks/Midi.svelte b/src/renderer/config-blocks/Midi.svelte index 72f0e9dce..e07d680a3 100644 --- a/src/renderer/config-blocks/Midi.svelte +++ b/src/renderer/config-blocks/Midi.svelte @@ -67,6 +67,8 @@ export let action: GridAction; let event = action.parent as GridEvent; + let containerWidth = 0; + $: isWide = containerWidth > 360; const dispatch = createEventDispatcher(); @@ -331,18 +333,21 @@ } - + + + + Send MIDI - - - Date: Wed, 29 Apr 2026 15:20:51 +0200 Subject: [PATCH 10/11] SUKU midi parameter suggestion fixed for 14 bit --- src/renderer/config-blocks/Midi.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/config-blocks/Midi.svelte b/src/renderer/config-blocks/Midi.svelte index e07d680a3..252785c6a 100644 --- a/src/renderer/config-blocks/Midi.svelte +++ b/src/renderer/config-blocks/Midi.svelte @@ -274,7 +274,7 @@ } else if (mode === 1) { param1 = [ makeAuto( - `Auto (${Grid.Auto.getMidi(action, Grid.Auto.Value.MIDI_P1)})`, + `Auto (${Grid.Auto.getMidi(action, Grid.Auto.Value.MIDI_P1) % 32})`, ), ...make14BitCCs(), ]; From 607b5ea5f58b6c619a670cdd6026f02f4f016ce0 Mon Sep 17 00:00:00 2001 From: sukuwc Date: Wed, 29 Apr 2026 16:03:32 +0200 Subject: [PATCH 11/11] SUKU legacy lua framing stripped from cloud snippets, debug log added Co-Authored-By: Claude Sonnet 4.6 --- src/renderer/main/panels/profileCloud/ProfileCloud.ts | 4 ++++ src/renderer/runtime/runtime.ts | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/renderer/main/panels/profileCloud/ProfileCloud.ts b/src/renderer/main/panels/profileCloud/ProfileCloud.ts index bbc7e0bdc..2865e9335 100644 --- a/src/renderer/main/panels/profileCloud/ProfileCloud.ts +++ b/src/renderer/main/panels/profileCloud/ProfileCloud.ts @@ -88,6 +88,10 @@ export class ProfileCloudEvent { .findEvent(target.event.value); const snippet = GridSnippetData.createFromCloudData(config); + console.log( + "[ProfileCloud] snippet lua:", + snippet.actions.map((a) => a.toLua()).join(""), + ); loadSnippet(snippet, event, target.index).catch(); break; diff --git a/src/renderer/runtime/runtime.ts b/src/renderer/runtime/runtime.ts index 03a3eae9a..6a046f6a5 100644 --- a/src/renderer/runtime/runtime.ts +++ b/src/renderer/runtime/runtime.ts @@ -386,6 +386,12 @@ export class GridAction extends RuntimeNode { let actionString = script.replace(/\s{2,10}/g, " "); + // Strip legacy framing from old cloud data + const framingMatch = actionString.match(/^<\?lua\s(.*)\s\?>$/s); + if (framingMatch) { + actionString = framingMatch[1]; + } + const matches = [ ...actionString.matchAll(/--\[\[@(.*?)\]\]\s*(.*?)(?=(--\[\[@|$))/gs), ];