From cce2277f7ed322c3ce75e00b0e660e8a3e0f20a7 Mon Sep 17 00:00:00 2001 From: Pablo Torres Date: Sun, 23 Aug 2026 23:28:20 -0400 Subject: [PATCH] presetBrowser: fix dropping decoded amp model names The provided sample query of "All 5153 rigs" was not returning anything at all on FM3, firmware 13. I did have about 10 presets that should have matched it. AMP(TYPE=...) queries (and simple free-text search) could never match a real amp/block model because the workbench preset-browser mirror only carried the generic roster instance label ("Amp 1") and the device model string ("FM3") into matchEntryFromSummary, never the decoded per-family model names ForgeFX sends separately (e.g. "5153 100W Blue"). Thread summary.models/amps through AxisPresetBrowserEntrySummary and prefer them over the generic label in matchEntryFromSummary and axisPbRowBlockChips, matching src/lib/PresetBrowser.svelte's reference behavior. Co-Authored-By: Claude Sonnet 5 --- .../presetBrowserWorkbenchData.ts | 6 +++ .../presetBrowserWorkbenchQuery.ts | Bin 10619 -> 10867 bytes .../presetBrowserWorkbenchRowChips.ts | 7 +-- .../test/presetBrowserWorkbenchData.test.ts | 41 +++++++++++++++ .../test/presetBrowserWorkbenchQuery.test.ts | 47 ++++++++++++++++++ .../presetBrowserWorkbenchRowChips.test.ts | 12 +++++ 6 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchData.ts b/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchData.ts index c643e4b..d9eb0c9 100644 --- a/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchData.ts +++ b/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchData.ts @@ -40,6 +40,10 @@ export interface AxisPresetBrowserEntrySummary { folder: string | null; tags: string[]; blocks: AxisPresetBrowserBlockSummary[]; + /** Decoded per-family model names (e.g. { amp: ["5153 100W Blue"] }) — the source of truth for + * TYPE-style query matching; `blocks[].name` is only a generic roster instance label. */ + models: Record; + amps: string[]; /** Resolved cloud sync state (from cloud.stateOf via the host); 'none' when signed out. */ syncState: SyncState; /** A synthesized cloud-only row (host id starts with `cloud:`). */ @@ -235,6 +239,8 @@ function normalizeEntry( folder: entry.folder ?? null, tags: tagsOf?.(entry.id) ?? [], blocks, + models: entry.summary.models ?? {}, + amps: entry.summary.amps ?? [], syncState: syncStateOf?.(entry) ?? 'none', cloudOnly: entry.id.startsWith('cloud:'), converted, diff --git a/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchQuery.ts b/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchQuery.ts index e2bce9551d31d6a7b8d9ee6f9de7defe68c6dbc5..928c7298b288819c86487f870cff01a611440165 100644 GIT binary patch delta 342 zcmYk1y-LJD6oq?zg+&gj5(twGvXxjUh~aYNx39%6NpMM;xXHoBI$|{1I;nGlk{KzBFgR;Fq(KD91c_2A zmC5`bMymlI&#!Pbp`hFpRA5Wm@gQ2iD7!rXd_l-06=d5bnKB-9Lr2iEB^zIWL?sZc z($z82gDFAq~>TUXn^VBX!XS00`*u0dwW}j=vYm? Qg3{s)6ouBC1>}>t0f~_txBvhE diff --git a/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchRowChips.ts b/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchRowChips.ts index 1204107..80a59dd 100644 --- a/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchRowChips.ts +++ b/src/lib/axis-workbench/presetBrowser/presetBrowserWorkbenchRowChips.ts @@ -72,9 +72,10 @@ export function axisPbRowBlockChips(entry: AxisPresetBrowserEntrySummary): AxisP const slug = (block.slug ?? '').toLowerCase(); if (!slug || IO_SLUGS.has(slug)) continue; const cat = axisPbCatLabel(slug); - // The summary block "name" is the model/type name for that slot when decoded (e.g. "USA Clean"); - // when it just echoes the category we drop it so the chip stays "Cat". - const rawType = (block.name ?? '').trim(); + // The decoded model name for this family (e.g. "USA Clean") is preferred, mirroring the monolith's + // blocksOf typeName; `block.name` is only a generic roster instance label ("Amp 1") and is the + // fallback when nothing was decoded. When it just echoes the category we drop it so the chip stays "Cat". + const rawType = ((entry.models[slug] ?? [])[0] ?? block.name ?? '').trim(); const type = rawType && rawType.toLowerCase() !== cat.toLowerCase() ? rawType : null; const instance = block.instance != null ? `${cat} ${block.instance}` : cat; chips.push({ diff --git a/src/lib/axis-workbench/test/presetBrowserWorkbenchData.test.ts b/src/lib/axis-workbench/test/presetBrowserWorkbenchData.test.ts index e932c7d..c09f6e2 100644 --- a/src/lib/axis-workbench/test/presetBrowserWorkbenchData.test.ts +++ b/src/lib/axis-workbench/test/presetBrowserWorkbenchData.test.ts @@ -134,4 +134,45 @@ describe('Preset Browser Workbench data view', () => { number: null }); }); + + it('carries decoded amp/block model names through to AMP(TYPE=...) query matching (regression: the mirror used to drop them)', () => { + const fm3: AxisPresetBrowserLibEntryLike = { + id: 'dev:fm3-1', + source: 'device', + summary: { + number: 2, + name: '5153 Lead', + model: 'FM3', + scenes: [], + blocks: [{ effectId: 101, slug: 'amp', name: 'Amp 1', instance: 1 }], + models: { amp: ['5153 100W Blue'] }, + amps: ['5153 100W Blue'] + } + }; + const view = (conditions: Parameters[0]['conditions']) => + createAxisPresetBrowserDataView({ entries: [fm3], conditions }).visibleEntries.map((e) => e.id); + + expect(view([{ kind: 'block', block: 'amp', params: [{ name: 'TYPE', op: '=', val: '5153' }] }])).toEqual([ + 'dev:fm3-1' + ]); + expect(view([{ kind: 'block', block: 'amp', params: [{ name: 'TYPE', op: '=', val: 'marshall' }] }])).toEqual([]); + expect(view([{ kind: 'block', block: 'amp', params: [{ name: 'TYPE', op: '!=', val: 'marshall' }] }])).toEqual([ + 'dev:fm3-1' + ]); + expect(view([{ kind: 'block', block: 'amp', params: [{ name: 'TYPE', op: '!=', val: '5153' }] }])).toEqual([]); + }); + + it('does not throw on cloud-only entries with empty models/amps maps', () => { + const cloudOnly: AxisPresetBrowserLibEntryLike = { + id: 'cloud:9', + source: 'device', + summary: { number: 9, name: 'Cloud Only', model: 'FM3', scenes: [], blocks: [], models: {}, amps: [] } + }; + expect(() => + createAxisPresetBrowserDataView({ + entries: [cloudOnly], + conditions: [{ kind: 'block', block: 'amp', params: [{ name: 'TYPE', op: '=', val: '5153' }] }] + }) + ).not.toThrow(); + }); }); diff --git a/src/lib/axis-workbench/test/presetBrowserWorkbenchQuery.test.ts b/src/lib/axis-workbench/test/presetBrowserWorkbenchQuery.test.ts index c6d1b81..659dafc 100644 --- a/src/lib/axis-workbench/test/presetBrowserWorkbenchQuery.test.ts +++ b/src/lib/axis-workbench/test/presetBrowserWorkbenchQuery.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'; import { condsEqual, condsToQuery, + matchEntryFromSummary, matchNumeric, matchPreset, parseQuery, @@ -11,6 +12,7 @@ import { toSimpleConds, type AxisPbMatchEntry } from '../presetBrowser/presetBrowserWorkbenchQuery'; +import type { AxisPresetBrowserEntrySummary } from '../presetBrowser/presetBrowserWorkbenchData'; const entry = (over: Partial = {}): AxisPbMatchEntry => ({ name: 'Studio Clean', @@ -103,3 +105,48 @@ describe('Preset Browser matching', () => { expect(matchPreset(entry(), parseQuery('tag:Live + COMP'), 'studio')).toBe(false); }); }); + +describe('matchEntryFromSummary (regression: decoded models must survive summary normalization)', () => { + const summaryEntry = (over: Partial = {}): AxisPresetBrowserEntrySummary => ({ + id: 'dev:1', + sourceId: 'device', + sourceLabel: 'Device', + number: 2, + name: '5153 Lead', + model: 'FM3', // the device model string — must never leak into the amp model list + sceneCount: 0, + blockCount: 1, + fav: false, + folder: null, + tags: [], + blocks: [{ effectId: 101, slug: 'amp', name: 'Amp 1', instance: 1 }], + models: { amp: ['5153 100W Blue'] }, + amps: ['5153 100W Blue'], + syncState: 'none', + cloudOnly: false, + converted: false, + provenance: null, + ...over + }); + + it('matches TYPE against the decoded model name, not the generic block label or device string', () => { + const matched = matchEntryFromSummary(summaryEntry()); + expect(matchPreset(matched, parseQuery('AMP(TYPE=5153)'), '')).toBe(true); + expect(matchPreset(matched, parseQuery('AMP(TYPE=marshall)'), '')).toBe(false); + expect(matchPreset(matched, parseQuery('AMP(TYPE!=marshall)'), '')).toBe(true); + expect(matchPreset(matched, parseQuery('AMP(TYPE!=5153)'), '')).toBe(false); + expect(matchPreset(matched, [], '5153')).toBe(true); + // the device model string ("FM3") must not be searchable as if it were an amp type. + expect(matchPreset(matched, parseQuery('AMP(TYPE=FM3)'), '')).toBe(false); + }); + + it('falls back to the generic block label when no decoded model exists for that slug', () => { + const matched = matchEntryFromSummary(summaryEntry({ models: {}, amps: [] })); + expect(matchPreset(matched, parseQuery('AMP(TYPE=Amp 1)'), '')).toBe(true); + }); + + it('does not throw on an entry with empty models/amps maps (cloud-only shape)', () => { + const matched = matchEntryFromSummary(summaryEntry({ blocks: [], models: {}, amps: [] })); + expect(matchPreset(matched, parseQuery('AMP(TYPE=5153)'), '')).toBe(false); + }); +}); diff --git a/src/lib/axis-workbench/test/presetBrowserWorkbenchRowChips.test.ts b/src/lib/axis-workbench/test/presetBrowserWorkbenchRowChips.test.ts index 53afcca..975098b 100644 --- a/src/lib/axis-workbench/test/presetBrowserWorkbenchRowChips.test.ts +++ b/src/lib/axis-workbench/test/presetBrowserWorkbenchRowChips.test.ts @@ -25,6 +25,8 @@ function entry(over: Partial = {}): AxisPresetBro folder: null, tags: [], blocks: [], + models: {}, + amps: [], syncState: 'none', cloudOnly: false, converted: false, @@ -53,6 +55,16 @@ describe('row block chips (§4.3)', () => { expect(chips[1].label).toBe('Reverb'); }); + it('prefers the decoded model name over the generic block label (regression)', () => { + const chips = axisPbRowBlockChips( + entry({ + blocks: [{ effectId: 2, slug: 'amp', name: 'Amp 1', instance: 1 }], + models: { amp: ['5153 100W Blue'] } + }) + ); + expect(chips[0].label).toBe('Amp · 5153 100W Blue'); + }); + it('carries a title of "instance — TYPE"', () => { const chips = axisPbRowBlockChips( entry({ blocks: [{ effectId: 2, slug: 'drive', name: 'TS808 Mod', instance: 2 }] })