From 46288b6143efae4dfdf6fe068b97a1e8831fce6a Mon Sep 17 00:00:00 2001 From: Guilhem Date: Tue, 16 Jun 2026 17:11:01 +0200 Subject: [PATCH] fix(frontend): session Drafts drawer uses raw_app kind for the raw-app diff (#9617) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): session Drafts drawer uses raw_app kind for the raw-app diff Follow-up to #9601. DraftDiffDrawer mapped a raw_app row back to `app` before calling getDraftDiffValues(), but that helper sends `rawApp:true` only for the exact kind `raw_app` (which a never-deployed raw app needs). With `app` it hit the normal app endpoint and 404'd instead of rendering the added diff. `raw_app` isn't in the deploy-kind maps anyway, so just pass the row kind through. Caught by the Codex auto-review on #9601, which posted after that PR had already merged (locked conversation), so the fix lands separately here. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(frontend): show friendly draft path + summary for all kinds in session Drafts drawer A never-deployed app/raw_app is parked at a synthetic `u/.../draft_` storage path with the user's typed name in the draft JSON's `draft_path`; the Drafts drawer rendered that UUID path. The list endpoint already returns `draft_path` and `summary` for every kind, but `fetchDrafts` dropped them and the drawer only had the lazily-derived summary. Thread both through the shared row: `WorkspaceDiffDrawer` gains optional `displayPath` (shown in tree/header/search, while `path` stays the storage key for value-loading, item keys and edit links) and `summary` (preferred over the value-derived one, shown before the diff loads). `DraftDiffDrawer` populates them from the draft list (`draft_path ?? path`, `summary`). Both fields are opt-in via `?? path` / lazy fallback, so ForkDiffDrawer — the other consumer of the component — is unchanged. The symptom only surfaced for apps/raw apps because their storage path diverges from the friendly name; scripts already kept a readable path. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(frontend): use friendly display path for single-segment draft tree nodes buildTree splits displayPathOf(d), but the `< 2 parts` branch still named the file node from the storage `path` — a draft whose friendly path is a bare name (no `/`) would show `…/draft_` in the sidebar tree. Name it from displayPathOf(d) too, consistent with the rest of the tree/header/search. Addresses Codex and claude review nits on #9617. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .../sessions/DraftDiffDrawer.svelte | 22 +++++++++--- .../sessions/WorkspaceDiffDrawer.svelte | 34 +++++++++++++------ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/components/sessions/DraftDiffDrawer.svelte b/frontend/src/lib/components/sessions/DraftDiffDrawer.svelte index b855c4591a..0716398131 100644 --- a/frontend/src/lib/components/sessions/DraftDiffDrawer.svelte +++ b/frontend/src/lib/components/sessions/DraftDiffDrawer.svelte @@ -61,7 +61,18 @@ const baseKind = it.raw_app ? 'raw_app' : it.kind const kind = DEPLOY_KIND_BY_DRAFT_KIND[baseKind] ?? baseKind donly[`${kind}/${it.path}`] = it.draft_only - return { kind, path: it.path, status: it.draft_only ? 'added' : 'modified' } + // A never-deployed app/raw_app is parked at a synthetic `…/draft_` + // storage path with the user's typed name in `draft_path`; show that + // (matches the home list) while `path` stays the storage key for loading. + // `summary` comes straight from the draft row, so it shows for every kind + // up front instead of only after the diff value loads. + return { + kind, + path: it.path, + displayPath: it.draft_path ?? it.path, + summary: it.summary, + status: it.draft_only ? 'added' : 'modified' + } }) draftOnlyByKey = donly } catch (e) { @@ -75,10 +86,11 @@ async function loadValues(d: DiffRow): Promise<{ before: unknown; after: unknown }> { const draftOnly = draftOnlyByKey[`${d.kind}/${d.path}`] ?? false - // getDraftDiffValues works on the draft itemKind ('app' for raw apps too); - // map the deploy-style display kind back to it. - const kind: DraftKind = - d.kind === 'raw_app' ? 'app' : ((DRAFT_KIND_BY_DEPLOY_KIND[d.kind] ?? d.kind) as DraftKind) + // getDraftDiffValues keys on the draft itemKind: `raw_app` must stay + // `raw_app` (the helper sends rawApp:true only for that exact kind, which a + // never-deployed raw app needs, else it hits the normal app endpoint and + // 404s). Only the trigger display kinds map back from their deploy-style names. + const kind = (DRAFT_KIND_BY_DEPLOY_KIND[d.kind] ?? d.kind) as DraftKind const { deployed, draft } = await getDraftDiffValues(kind, d.path, workspaceId, draftOnly) // draft_only items have never been deployed → render as "added" (empty // before), matching how the fork drawer renders added items. diff --git a/frontend/src/lib/components/sessions/WorkspaceDiffDrawer.svelte b/frontend/src/lib/components/sessions/WorkspaceDiffDrawer.svelte index 3eae6f9205..472ea1515b 100644 --- a/frontend/src/lib/components/sessions/WorkspaceDiffDrawer.svelte +++ b/frontend/src/lib/components/sessions/WorkspaceDiffDrawer.svelte @@ -8,6 +8,13 @@ status: DiffStatus ahead?: number behind?: number + /** Human-facing path; defaults to `path`. Lets a draft parked at a + * synthetic storage path (`…/draft_`) show its friendly typed path + * while keys, value-loading and edit links stay keyed on `path`. */ + displayPath?: string + /** Summary supplied by the data source. Preferred over the one derived + * from the loaded diff value, and shown before that value loads. */ + summary?: string } @@ -88,6 +95,12 @@ return `${d.kind}/${d.path}` } + // Friendly path for display only; `path` stays the storage key everywhere + // keys/loads happen, so a never-deployed draft still loads from `…/draft_`. + function displayPathOf(d: DiffRow): string { + return d.displayPath ?? d.path + } + const KIND_LABELS: Record = { script: 'Script', flow: 'Flow', @@ -183,9 +196,9 @@ } const folderCache = new Map() for (const d of rows) { - const parts = d.path.split('/') + const parts = displayPathOf(d).split('/') if (parts.length < 2) { - root.children.push({ type: 'file', name: d.path, diff: d }) + root.children.push({ type: 'file', name: displayPathOf(d), diff: d }) continue } const scopeKey = parts.slice(0, 2).join('/') @@ -226,8 +239,8 @@ } function searchableText(d: DiffRow): string { - const parts = [d.path, KIND_LABELS[d.kind] ?? d.kind] - const s = summaries[itemKey(d)] + const parts = [displayPathOf(d), KIND_LABELS[d.kind] ?? d.kind] + const s = summaries[itemKey(d)] ?? d.summary if (s) parts.push(s) return parts.join(' ') } @@ -433,12 +446,12 @@ { highlightedKey = key scrollToDiff(node.diff) @@ -556,6 +569,7 @@ {@const StatusIcon = statusIcons[status]} {@const loaded = loadedDiffs[key]} {@const editUrl = editUrlFor?.(d)} + {@const dpath = displayPathOf(d)}
- {d.path} + {dpath} {:else} -
- {d.path} +
+ {dpath}
{/if}