From 7bb9c4acb144eda96eef4a4e9a8d83abb91e7c09 Mon Sep 17 00:00:00 2001 From: xarmian Date: Tue, 4 Aug 2026 14:52:48 +0000 Subject: [PATCH] fix(attachments): close the final-review findings across the feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the orchestrator's full-diff pass over main...HEAD — the altitude per-task reviews structurally cannot reach. - Opening the panel on an ALREADY-archived parent left Open, Download and Copy link enabled against endpoints that 404: the host only handled the archive TRANSITION, and the strip's event carries complete metadata, which is exactly what lets the panel skip its probe. The panel is now told the parent is archived and probes anyway, landing in the authoritative missing state it already knows how to render. It probes through the INVALIDATING path, because reachability is an existence question and the cache can hold an `ok` observed before the archive — the same lesson as the image placeholder earlier on this branch. - A deleted chip stayed inert after its node was repointed at a different attachment: the uuid-swap path cleared `deleted` and the CSS but not `disabled`, giving a chip that announces itself as live and does nothing. Reachable through a collaborative peer's edit. - actions.ts claimed to be "rendered twice". It has one consumer today; the viewer is phase 3a. Says so now, including that the image NodeView's threaded address is held open for the same phase — a list with a single consumer is worth re-justifying if 3a stops coming. - The shared confirmation claimed to own the prompt wording while StorageTab built its own inline. Both builders now live in that module side by side: an item surface can check the body it has and must hedge about the ones it cannot, while a workspace-wide list has nothing to check and should say what happens to the blob instead. - Descriptor `description` was never rendered; it is the row's tooltip now rather than a dead field. Both behavioural fixes are mutation-tested. --- web/src/lib/attachments/actions.ts | 16 ++++++-- .../AttachmentDeleteConfirm.svelte | 25 +++++++++-- .../attachments/AttachmentDetailsPanel.svelte | 25 ++++++++++- .../attachments/AttachmentPanelHost.svelte | 18 ++++++++ .../AttachmentPanelHost.svelte.test.ts | 37 +++++++++++++---- web/src/lib/components/common/MenuItem.svelte | 5 +++ .../lib/components/editor/attachment-chip.ts | 9 +++- .../editor/attachmentChipPanel.svelte.test.ts | 41 +++++++++++++++++++ .../lib/components/settings/StorageTab.svelte | 6 ++- 9 files changed, 162 insertions(+), 20 deletions(-) diff --git a/web/src/lib/attachments/actions.ts b/web/src/lib/attachments/actions.ts index 3b7d7601..3ea8e4ca 100644 --- a/web/src/lib/attachments/actions.ts +++ b/web/src/lib/attachments/actions.ts @@ -1,11 +1,19 @@ /** - * Attachment actions — defined once, rendered twice (PLAN-2392 DR-5). + * Attachment actions — defined once, rendered by whoever needs them + * (PLAN-2392 DR-5). * * "The panel and the viewer share one action list" is a promise with no source * of truth unless the list IS the source of truth. So the actions live here as - * descriptors: the options panel draws them as a menu/sheet, the image viewer - * draws them as an inline toolbar, and neither owns the set. Adding an action - * means adding one descriptor here. + * descriptors: a surface renders them, none of them owns the set, and adding an + * action means adding one descriptor here. + * + * TODAY THERE IS ONE CONSUMER: the options panel, which draws them as a + * menu/sheet. The unified image viewer — the second renderer, an inline + * toolbar over the same list — arrives in phase 3a, which is also what will + * consume the `address` option now threaded onto the image NodeView. Stated + * plainly because "rendered twice" read as a description of the present and + * was not one; a list with a single consumer is a shape held open on purpose, + * and worth re-justifying if 3a ever stops coming. * * TWO DESCRIPTOR SHAPES, not one, because the ELEMENT is part of the contract * (DR-5, round 35/36): diff --git a/web/src/lib/components/attachments/AttachmentDeleteConfirm.svelte b/web/src/lib/components/attachments/AttachmentDeleteConfirm.svelte index 9dcae214..21034090 100644 --- a/web/src/lib/components/attachments/AttachmentDeleteConfirm.svelte +++ b/web/src/lib/components/attachments/AttachmentDeleteConfirm.svelte @@ -28,13 +28,18 @@ the app's existing behaviours on both surfaces rather than a second implementation. - The PROMPT TEXT is `attachmentDeletePrompt` below, shared for the same - reason the markup is: the hedged arm's honesty is the substance of DR-5, and - two copies of it drift. + The PROMPT TEXT lives in this module too, but note there are TWO builders, + not one: `attachmentDeletePrompt` for an item surface, which can check the + body it has and must hedge about the ones it cannot, and + `workspaceAttachmentDeletePrompt` for the workspace-wide storage list, where + a reference check would be meaningless and the honest thing to say is what + happens to the blob. Different questions, so different copy — deliberately. + What must not drift is that BOTH are written here, next to each other, where + a change to one is read alongside the other. -->