From f0d2cdb7b25fb827c83e3b6132dc3c39723be8cc Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 7 Aug 2026 08:20:41 -0400 Subject: [PATCH] fix(aiui): node-content surfaces stay open when the reply text infers no tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reproduced live: 'show me paid for peer files' returned surfaces=1 with 3 purchased images over the bridge, then updatePanelFromText set panelOpen from the REGEX-inferred tab list — a plain markdown list matches nothing, so the panel closed and the user saw prose only. panelOpen now follows orderedTabs (Archy tabs lead). Regression test pins the exact turn. Co-Authored-By: Claude --- .../__tests__/useContentPanel.test.ts | 24 +++++++++++++++++++ .../app/src/composables/useContentPanel.ts | 7 +++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/aiui/packages/app/src/composables/__tests__/useContentPanel.test.ts b/aiui/packages/app/src/composables/__tests__/useContentPanel.test.ts index a4efb610..81acf12f 100644 --- a/aiui/packages/app/src/composables/__tests__/useContentPanel.test.ts +++ b/aiui/packages/app/src/composables/__tests__/useContentPanel.test.ts @@ -109,6 +109,30 @@ describe('useContentPanel', () => { // is still a real answer from the node, distinct from "never asked". expect(panel.archyContentActive.value).toBe(true) }) + + // Regression (live on archi-dev-box 2026-08-07): "show me paid for peer + // files" — the turn's surface carried 3 purchased images, the reply text + // was a plain markdown list matching no extraction pattern, and + // updatePanelFromText then set panelOpen from the regex-only tab list, + // CLOSING the panel setArchyContent had just opened. The probe showed + // surfaces=1 arriving; the user saw only prose. + it('keeps the panel open on node content when the reply text infers no tabs', () => { + const img = { id: 'i1', title: 'signal-test.jpeg', url: '/content/abc', mimeType: 'image/jpeg' } as unknown as ImageItem + panel.setArchyContent({ images: [img] }) + expect(panel.panelOpen.value).toBe(true) + expect(panel.activeTab.value).toBe('image') + + // The turn's prose answer arrives after the surfaces and fires the + // text path: a bullet list with no tags, no links, no patterns. + panel.updatePanelFromText( + "You've purchased 4 items from peer nodes:\n\n- signal-test.jpeg (170 KB) — 100 sats\n- Photos/got it!.jpg (253 KB) — 100 sats\n\nThese are mostly photos.", + 'show me paid for peer files', + ) + + expect(panel.panelOpen.value).toBe(true) + expect(panel.availableTabs.value[0]).toBe('image') + expect(panel.panelImages.value).toHaveLength(1) + }) }) it('openPlaceDetail and closePlaceDetail work', () => { diff --git a/aiui/packages/app/src/composables/useContentPanel.ts b/aiui/packages/app/src/composables/useContentPanel.ts index 255bae1f..5b0ce213 100644 --- a/aiui/packages/app/src/composables/useContentPanel.ts +++ b/aiui/packages/app/src/composables/useContentPanel.ts @@ -327,7 +327,12 @@ export function useContentPanel() { else if (hasNostr) panelTitle.value = 'Nostr' else panelTitle.value = 'Content' - panelOpen.value = tabs.length > 0 + // Open from the ORDERED tab list (Archy tabs + inferred), not the + // regex-only `tabs`: a node-supplied grid whose reply text happens to + // match no extraction pattern (a plain markdown list of purchased + // files) used to CLOSE the panel setArchyContent had just opened — + // the probe showed surfaces=1 images=3 arriving and the user saw prose. + panelOpen.value = orderedTabs.length > 0 } /**