From aefa96c3a5d45df66f800f47ec7e7a822ab06921 Mon Sep 17 00:00:00 2001 From: bzeiss Date: Sat, 22 Aug 2026 08:21:09 +0200 Subject: [PATCH 01/17] corrected linux package dependencies --- .github/workflows/native-platform.yml | 20 ++++++++++++++++++++ README.md | 8 ++++++++ apps/axkdeck/src-tauri/tauri.linux.conf.json | 12 ++++++++++++ docs/axklib/compatibility.md | 2 +- tools/python/tests/test_release_tools.py | 16 ++++++++++++++++ 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/workflows/native-platform.yml b/.github/workflows/native-platform.yml index 7f882fbd..da3cc63f 100644 --- a/.github/workflows/native-platform.yml +++ b/.github/workflows/native-platform.yml @@ -519,6 +519,26 @@ jobs: rpm=$(find "$bundle/rpm" -type f -name '*.rpm' -print -quit) test -n "$deb" test -n "$rpm" + deb_dependencies=$(dpkg-deb -f "$deb" Depends) + rpm_dependencies=$(rpm -qp --requires "$rpm") + printf 'DEB dependencies: %s\n' "$deb_dependencies" + printf 'RPM dependencies:\n%s\n' "$rpm_dependencies" + for dependency in \ + libwebkit2gtk-4.1-0 libgtk-3-0 libc++1-18 libc++abi1-18 libunwind-18; do + case "$deb_dependencies" in + *"$dependency"*) ;; + *) + echo "DEB package is missing dependency: $dependency" >&2 + exit 1 + ;; + esac + done + for dependency in webkit2gtk4.1 gtk3 libcxx llvm-libunwind; do + if ! grep -Fx "$dependency" <<< "$rpm_dependencies"; then + echo "RPM package is missing dependency: $dependency" >&2 + exit 1 + fi + done dpkg-deb -x "$deb" "$scan/deb" rpm -Kv "$rpm" bsdtar -xf "$GITHUB_WORKSPACE/$rpm" -C "$scan/rpm" diff --git a/README.md b/README.md index 62515f09..cda5dc86 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,14 @@ an insufficient runtime from Microsoft; silent `/S` installations perform that prerequisite step without a prompt. The installer does not bundle a fixed WebView2 runtime and does not replace a newer installed version. +Linux packages use the system WebKitGTK runtime and LLVM 18 C++ runtime. The +RPM declares `webkit2gtk4.1`, `gtk3`, `libcxx`, and `llvm-libunwind`. The DEB +declares `libwebkit2gtk-4.1-0`, `libgtk-3-0`, `libc++1-18`, `libc++abi1-18`, +and `libunwind-18`. Debian or Ubuntu releases that do not provide the LLVM 18 +runtime packages in their standard repositories require an appropriate LLVM +package source before the DEB can be installed. An older generic `libc++1` +package is not a compatible substitute. + ## Command Line The CLI exposes the same image and object operations for scripts and batch diff --git a/apps/axkdeck/src-tauri/tauri.linux.conf.json b/apps/axkdeck/src-tauri/tauri.linux.conf.json index a18f9b62..dca4de4c 100644 --- a/apps/axkdeck/src-tauri/tauri.linux.conf.json +++ b/apps/axkdeck/src-tauri/tauri.linux.conf.json @@ -1,5 +1,17 @@ { "bundle": { + "linux": { + "deb": { + "depends": [ + "libc++1-18", + "libc++abi1-18", + "libunwind-18" + ] + }, + "rpm": { + "depends": ["libcxx", "llvm-libunwind"] + } + }, "targets": ["deb", "rpm"] } } diff --git a/docs/axklib/compatibility.md b/docs/axklib/compatibility.md index c0d95a42..780e12e7 100644 --- a/docs/axklib/compatibility.md +++ b/docs/axklib/compatibility.md @@ -17,7 +17,7 @@ version. See [Versioning And Build Identity](versioning.md). | Reference build | Build toolchain | Architectures | | --- | --- | --- | | Windows library and CLI | Visual Studio 18 MSVC, dynamic CRT | x64, ARM64 | -| Linux library and CLI | GCC 14, distribution libstdc++/glibc | x64, ARM64 | +| Linux library and CLI | Clang 18, LLVM libc++ 18, distribution glibc | x64, ARM64 | | macOS library and CLI | Apple Clang from Xcode 16.4 | universal x64 + ARM64 | A shared library built from source is compatible only with the matching diff --git a/tools/python/tests/test_release_tools.py b/tools/python/tests/test_release_tools.py index 525982af..b23711b8 100644 --- a/tools/python/tests/test_release_tools.py +++ b/tools/python/tests/test_release_tools.py @@ -842,6 +842,11 @@ def test_desktop_contract_and_rpm_inspection_are_cross_platform() -> None: root = Path(__file__).resolve().parents[3] attributes = (root / ".gitattributes").read_text(encoding="utf-8") prettier = json.loads((root / "apps/axkdeck/.prettierrc.json").read_text(encoding="utf-8")) + linux_bundle = json.loads( + (root / "apps/axkdeck/src-tauri/tauri.linux.conf.json").read_text( + encoding="utf-8" + ) + ) workflow = (root / ".github/workflows/native.yml").read_text(encoding="utf-8") native_platform = (root / ".github/workflows/native-platform.yml").read_text( encoding="utf-8" @@ -850,7 +855,18 @@ def test_desktop_contract_and_rpm_inspection_are_cross_platform() -> None: assert "/apps/axkdeck/src/lib/generated/axklibApiV1.ts text eol=lf" in attributes assert prettier["endOfLine"] == "lf" + assert linux_bundle["bundle"]["linux"]["deb"]["depends"] == [ + "libc++1-18", + "libc++abi1-18", + "libunwind-18", + ] + assert linux_bundle["bundle"]["linux"]["rpm"]["depends"] == [ + "libcxx", + "llvm-libunwind", + ] assert "for command in curl wget file pkg-config patchelf rpm bsdtar dpkg-deb" in workflow_with_platform + assert 'dpkg-deb -f "$deb" Depends' in workflow_with_platform + assert 'rpm -qp --requires "$rpm"' in workflow_with_platform assert 'rpm -Kv "$rpm"' in workflow_with_platform assert 'bsdtar -xf "$GITHUB_WORKSPACE/$rpm" -C "$scan/rpm"' in workflow_with_platform assert "rpm2cpio" not in workflow_with_platform From 00dfe79970ce6df3c6ac68a8494ebda3620eed61 Mon Sep 17 00:00:00 2001 From: bzeiss Date: Sat, 22 Aug 2026 10:15:26 +0200 Subject: [PATCH 02/17] Sorting of partitions and volumes reflect the hardware ordering. #18 --- apps/axkdeck/src/App.svelte | 2 +- .../features/workspace/WorkspaceShell.svelte | 3 + .../src/lib/components/ImageNavigator.svelte | 9 ++- .../src/lib/components/ImageNavigator.test.ts | 54 +++++++++++++ .../src/lib/components/TreeNode.svelte | 40 +++++++--- .../src/lib/components/TreeNode.test.ts | 78 +++++++++++++++++++ apps/axkdeck/src/lib/samplerTreeOrder.test.ts | 60 ++++++++++++++ apps/axkdeck/src/lib/samplerTreeOrder.ts | 37 +++++++++ docs/axklib/names-and-paths.md | 7 ++ 9 files changed, 276 insertions(+), 14 deletions(-) create mode 100644 apps/axkdeck/src/lib/samplerTreeOrder.test.ts create mode 100644 apps/axkdeck/src/lib/samplerTreeOrder.ts diff --git a/apps/axkdeck/src/App.svelte b/apps/axkdeck/src/App.svelte index 8bd9db0e..edc23357 100644 --- a/apps/axkdeck/src/App.svelte +++ b/apps/axkdeck/src/App.svelte @@ -546,7 +546,6 @@ async function saveRemoteConnection(input: RemoteServerSettingsInput): Promise { await connectionActions.saveRemote(input); } - async function switchToLocalConnection(): Promise { await connectionActions.useLocal(); } @@ -613,6 +612,7 @@ sequenceExportAvailable={imageSessionWorkflow.sequenceExportAvailable} mediaConversionAvailable={imageSessionWorkflow.mediaConversionAvailable} allocationInspectionAvailable={imageSessionWorkflow.allocationInspectionAvailable} + samplerOrderingEnabled={imageSessionWorkflow.imageFormat === 'sfs'} openConnectionSettings={() => void openConnectionSettings()} openImage={() => void imageSessionWorkflow.chooseAndOpen()} createImage={() => void imageSessionWorkflow.chooseHardDiskDirectory()} diff --git a/apps/axkdeck/src/features/workspace/WorkspaceShell.svelte b/apps/axkdeck/src/features/workspace/WorkspaceShell.svelte index 05e0c4fe..9145b2df 100644 --- a/apps/axkdeck/src/features/workspace/WorkspaceShell.svelte +++ b/apps/axkdeck/src/features/workspace/WorkspaceShell.svelte @@ -84,6 +84,7 @@ sequenceExportAvailable: boolean; mediaConversionAvailable: boolean; allocationInspectionAvailable: boolean; + samplerOrderingEnabled?: boolean; openConnectionSettings: () => void; openImage: () => void; createImage: () => void; @@ -147,6 +148,7 @@ sequenceExportAvailable, mediaConversionAvailable, allocationInspectionAvailable, + samplerOrderingEnabled = false, openConnectionSettings, openImage, createImage, @@ -371,6 +373,7 @@ audioExportEnabled={audioExportAvailable} mediaConversionEnabled={mediaConversionAvailable} allocationInspectionEnabled={allocationInspectionAvailable} + {samplerOrderingEnabled} onimageaction={imageAction} onloadchildren={(parentId, offset, limit) => sessionId === null diff --git a/apps/axkdeck/src/lib/components/ImageNavigator.svelte b/apps/axkdeck/src/lib/components/ImageNavigator.svelte index 26844337..c70daad7 100644 --- a/apps/axkdeck/src/lib/components/ImageNavigator.svelte +++ b/apps/axkdeck/src/lib/components/ImageNavigator.svelte @@ -1,5 +1,6 @@ + +{name} +{metadata ? `${metadata} · ` : ''}{objectSizeSummary(object)} diff --git a/apps/axkdeck/src/lib/components/ObjectWorkspace.svelte b/apps/axkdeck/src/lib/components/ObjectWorkspace.svelte index 0c8feb31..c4c754eb 100644 --- a/apps/axkdeck/src/lib/components/ObjectWorkspace.svelte +++ b/apps/axkdeck/src/lib/components/ObjectWorkspace.svelte @@ -16,6 +16,7 @@ type PackageExportSelectionState, } from '../objectSelection'; import { compareNamedItems } from '../naturalSort'; + import { objectSizeSummary, objectSizeTooltip } from '../objectSizePresentation'; import type { SamplerObject } from '../transport'; import type { ObjectRenameTarget, PackageExportObject, Program, WaveDataItem, WorkspaceView } from '../types'; import { fixedVirtualWindow, virtualViewport, type VirtualViewportState } from '../virtualList'; @@ -384,6 +385,7 @@ class:selected={selection.items.some((item) => item.objectId === program.objectId)} class="program-row" data-navigation-index={index} + title={objectSizeTooltip(program.object)} aria-pressed={selection.items.some((item) => item.objectId === program.objectId)} onclick={(event) => { if (updateSelection(selectionMode(event), program.objectId) === 'replace') @@ -395,6 +397,7 @@ > {program.slot} {program.name} + {objectSizeSummary(program.object)} {:else}

No matching Programs

diff --git a/apps/axkdeck/src/lib/components/ObjectWorkspace.test.ts b/apps/axkdeck/src/lib/components/ObjectWorkspace.test.ts index ab040118..fc70fe06 100644 --- a/apps/axkdeck/src/lib/components/ObjectWorkspace.test.ts +++ b/apps/axkdeck/src/lib/components/ObjectWorkspace.test.ts @@ -16,6 +16,7 @@ function object(objectType: string, name: string): SamplerObject { categoryName: objectType, sfsId: 0, storedSizeBytes: 128, + sizeWithDependenciesBytes: null, sampleRate: 0, rootKey: 0, frameCount: 0, @@ -90,6 +91,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 2, + sizeWithDependenciesBytes: null, waveform: [], previewState: 'idle' as const, object: waveObject, @@ -137,6 +139,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 2, + sizeWithDependenciesBytes: null, waveform: [], previewState: 'idle' as const, object: waveObject, @@ -193,6 +196,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 2, + sizeWithDependenciesBytes: null, waveform: [], previewState: 'idle' as const, object: waveObject, @@ -242,6 +246,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 2, + sizeWithDependenciesBytes: null, waveform: [], previewState: 'idle' as const, object: waveObject, @@ -317,7 +322,7 @@ describe('ObjectWorkspace', () => { }); it('renders Programs as factual list rows without fabricated keyboard metadata', () => { - const programObject = object('PROG', '001'); + const programObject = { ...object('PROG', '001'), sizeWithDependenciesBytes: 4096 }; render(ObjectWorkspace, { props: { ...common, @@ -342,6 +347,8 @@ describe('ObjectWorkspace', () => { expect(document.querySelector('.program-keyboard')).toBeNull(); expect(document.querySelector('.program-list')).toBeTruthy(); expect(document.querySelector('.program-row')).toBeTruthy(); + expect(screen.getByText('128 B · 4 KiB incl. deps.')).toBeTruthy(); + expect(document.querySelector('[title*="Object size with deps.: 4 KiB"]')).toBeTruthy(); expect(document.querySelector('.object-card')).toBeNull(); expect(screen.getByRole('searchbox', { name: 'Search Programs' })).toBeTruthy(); }); @@ -582,6 +589,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 2, + sizeWithDependenciesBytes: null, waveform: [], previewState: 'idle' as const, object: waveObject, @@ -631,6 +639,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 200, + sizeWithDependenciesBytes: null, waveform: [{ minimum: -10, maximum: 10 }], previewState: 'ready' as const, object: waveObject, @@ -765,6 +774,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 2, + sizeWithDependenciesBytes: null, waveform: [], previewState: 'idle' as const, object: waveObject, @@ -807,6 +817,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 2, + sizeWithDependenciesBytes: null, waveform: [], previewState: 'idle' as const, object: waveObject, @@ -846,6 +857,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 2, + sizeWithDependenciesBytes: null, waveform: [], previewState: 'idle' as const, object: waveObject, @@ -889,6 +901,7 @@ describe('ObjectWorkspace', () => { bitDepth: '16-bit', channels: 'Mono' as const, storedSizeBytes: 2, + sizeWithDependenciesBytes: null, waveform: [], previewState: 'idle' as const, object: waveObject, diff --git a/apps/axkdeck/src/lib/components/ProgramWorkspace.svelte b/apps/axkdeck/src/lib/components/ProgramWorkspace.svelte index 4c651d79..65554925 100644 --- a/apps/axkdeck/src/lib/components/ProgramWorkspace.svelte +++ b/apps/axkdeck/src/lib/components/ProgramWorkspace.svelte @@ -12,6 +12,7 @@ updatePackageExportSelection, type PackageExportSelectionState, } from '../objectSelection'; + import { objectSizeSummary, objectSizeTooltip } from '../objectSizePresentation'; import type { SystemProgramContexts, SystemProgramPart } from '../transport'; import type { ObjectRenameTarget, PackageExportObject, Program } from '../types'; import CollectionToolbar from './CollectionToolbar.svelte'; @@ -375,12 +376,14 @@ class:selected={selection.items.some((item) => item.objectId === program.objectId)} class="program-row" data-navigation-index={index} + title={objectSizeTooltip(program.object)} aria-pressed={selection.items.some((item) => item.objectId === program.objectId)} onclick={(event) => selectProgram(event, program)} oncontextmenu={(event) => openMenu(event, program)} onkeydown={(event) => navigatePrograms(event, index)} > {program.slot}{program.name} + {objectSizeSummary(program.object)} {:else}

No matching Programs

diff --git a/apps/axkdeck/src/lib/components/ProgramWorkspace.test.ts b/apps/axkdeck/src/lib/components/ProgramWorkspace.test.ts index 113f9a8c..2004e870 100644 --- a/apps/axkdeck/src/lib/components/ProgramWorkspace.test.ts +++ b/apps/axkdeck/src/lib/components/ProgramWorkspace.test.ts @@ -22,6 +22,7 @@ function program(programNumber: number, name: string): Program { categoryName: 'PROG', sfsId: programNumber, storedSizeBytes: 128, + sizeWithDependenciesBytes: null, sampleRate: 0, rootKey: 0, frameCount: 0, diff --git a/apps/axkdeck/src/lib/components/TreeNode.svelte b/apps/axkdeck/src/lib/components/TreeNode.svelte index 1a7b0321..c20dfda9 100644 --- a/apps/axkdeck/src/lib/components/TreeNode.svelte +++ b/apps/axkdeck/src/lib/components/TreeNode.svelte @@ -1,6 +1,8 @@ @@ -245,92 +272,100 @@ onname={ondestinationname} /> - {#if hasUnvalidatedChanges} -

- {selectedCount === 0 - ? 'Select at least one package.' - : 'Selection or volume names changed. Check conflicts to recalculate.'} -

- {/if} - - +
+
+ +
- {#if !hasUnvalidatedChanges && plan?.opaqueSequences.length} -
- {#each plan.opaqueSequences as sequence (`${sequence.packageIndex}:${sequence.nodeId}`)} - {@const packageIndex = sequence.packageIndex ?? 0} - {@const item = selectedItems[packageIndex]} - {@const selected = item - ? (opaqueSequenceActions[opaqueKey(item.id, sequence.nodeId)] ?? sequence.action) - : sequence.action} - {#if item}
- Sequence “{sequence.name || 'Unnamed'}” could not be decoded - Choose whether to preserve its original bytes or leave it out. - - -
{/if} - {/each} -
- {/if} + {#if showResults} +
+ {#if !hasUnvalidatedChanges && plan?.opaqueSequences.length} +
+ {#each plan.opaqueSequences as sequence (`${sequence.packageIndex}:${sequence.nodeId}`)} + {@const packageIndex = sequence.packageIndex ?? 0} + {@const item = selectedItems[packageIndex]} + {@const selected = item + ? (opaqueSequenceActions[opaqueKey(item.id, sequence.nodeId)] ?? + sequence.action) + : sequence.action} + {#if item}
+ Sequence “{sequence.name || 'Unnamed'}” could not be decoded + Choose whether to preserve its original bytes or leave it out. + + +
{/if} + {/each} +
+ {/if} - {#if !hasUnvalidatedChanges && plan} - - {/if} + {#if !hasUnvalidatedChanges && plan} + + {/if} - {#if visibleConflicts.length > 0} - - {:else if plan?.valid && !hasUnvalidatedChanges && !error} -

Ready to import

- {/if} + {#if visibleConflicts.length > 0} + + {/if} + {#if error}{/if} +
+ {/if} +
{/if} - {#if status === 'planning'}

Planning batch import…

{/if} - {#if error}{/if}