From 69d5aa6db99ce33481781ea6879d2cdaf94e7f96 Mon Sep 17 00:00:00 2001 From: Diego Imbert Date: Tue, 2 Jun 2026 14:16:48 +0200 Subject: [PATCH] fix: route draft-only deletes through UserDraftDbSyncer on home page --- .../lib/components/common/table/AppRow.svelte | 25 ++++++++++++++++--- .../components/common/table/FlowRow.svelte | 15 ++++++++++- .../components/common/table/ScriptRow.svelte | 15 ++++++++++- .../src/lib/components/home/ItemsList.svelte | 6 ++++- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib/components/common/table/AppRow.svelte b/frontend/src/lib/components/common/table/AppRow.svelte index 19eade88c6..11d09a69ca 100644 --- a/frontend/src/lib/components/common/table/AppRow.svelte +++ b/frontend/src/lib/components/common/table/AppRow.svelte @@ -6,6 +6,7 @@ import type ShareModal from '$lib/components/ShareModal.svelte' import { AppService, type ListableApp } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' + import { UserDraftDbSyncer } from '$lib/userDraftDbSyncer.svelte' import { createEventDispatcher } from 'svelte' import Button from '../button/Button.svelte' import Row from './Row.svelte' @@ -69,6 +70,22 @@ async function loadAppJson() { appExport?.open(app.path) } + + async function deleteApp(path: string): Promise { + // Draft-only items have no deployed row — the regular route would + // 404. Route the delete through the syncer instead; the `app` vs + // `raw_app` choice mirrors the row's own `raw_app` flag. + if (app.draft_only) { + await UserDraftDbSyncer.save({ + workspace: $workspaceStore ?? '', + itemKind: app.raw_app ? 'raw_app' : 'app', + path, + value: null + }) + } else { + await AppService.deleteApp({ workspace: $workspaceStore ?? '', path }) + } + } {#if menuOpen} @@ -165,11 +182,11 @@ // TODO // @ts-ignore if (event?.shiftKey) { - await AppService.deleteApp({ workspace: $workspaceStore ?? '', path }) + await deleteApp(path) dispatch('change') } else { deleteConfirmedCallback = async () => { - await AppService.deleteApp({ workspace: $workspaceStore ?? '', path }) + await deleteApp(path) dispatch('change') } } @@ -276,11 +293,11 @@ // TODO // @ts-ignore if (event?.shiftKey) { - await AppService.deleteApp({ workspace: $workspaceStore ?? '', path }) + await deleteApp(path) dispatch('change') } else { deleteConfirmedCallback = async () => { - await AppService.deleteApp({ workspace: $workspaceStore ?? '', path }) + await deleteApp(path) dispatch('change') } } diff --git a/frontend/src/lib/components/common/table/FlowRow.svelte b/frontend/src/lib/components/common/table/FlowRow.svelte index 2e243f4268..d50207a27a 100644 --- a/frontend/src/lib/components/common/table/FlowRow.svelte +++ b/frontend/src/lib/components/common/table/FlowRow.svelte @@ -8,6 +8,7 @@ import type ShareModal from '$lib/components/ShareModal.svelte' import { FlowService, type Flow } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' + import { UserDraftDbSyncer } from '$lib/userDraftDbSyncer.svelte' import { createEventDispatcher } from 'svelte' import Badge from '../badge/Badge.svelte' import Button from '../button/Button.svelte' @@ -83,7 +84,19 @@ async function deleteFlow(path: string): Promise { try { - await FlowService.deleteFlowByPath({ workspace: $workspaceStore!, path }) + // Draft-only items have no deployed row to delete — the regular + // route would 404. Route the delete through the syncer so the + // per-user draft row is removed instead. + if (flow.draft_only) { + await UserDraftDbSyncer.save({ + workspace: $workspaceStore!, + itemKind: 'flow', + path, + value: null + }) + } else { + await FlowService.deleteFlowByPath({ workspace: $workspaceStore!, path }) + } dispatch('change') sendUserToast(`Deleted flow ${path}`) } catch (err) { diff --git a/frontend/src/lib/components/common/table/ScriptRow.svelte b/frontend/src/lib/components/common/table/ScriptRow.svelte index 75ae4e36ab..794b35e188 100644 --- a/frontend/src/lib/components/common/table/ScriptRow.svelte +++ b/frontend/src/lib/components/common/table/ScriptRow.svelte @@ -9,6 +9,7 @@ import { ScriptService, type Script } from '$lib/gen' import { hubBaseUrlStore, userStore, workspaceStore } from '$lib/stores' + import { UserDraftDbSyncer } from '$lib/userDraftDbSyncer.svelte' import { createEventDispatcher } from 'svelte' import Badge from '../badge/Badge.svelte' @@ -102,7 +103,19 @@ } async function deleteScript(path: string): Promise { - await ScriptService.deleteScriptByPath({ workspace: $workspaceStore!, path }) + // Draft-only items have no deployed row to delete — the regular + // route would 404. Route the delete through the syncer so the + // per-user draft row is removed instead. + if (script.draft_only) { + await UserDraftDbSyncer.save({ + workspace: $workspaceStore!, + itemKind: 'script', + path, + value: null + }) + } else { + await ScriptService.deleteScriptByPath({ workspace: $workspaceStore!, path }) + } dispatch('change') sendUserToast(`Deleted script ${path}`) } diff --git a/frontend/src/lib/components/home/ItemsList.svelte b/frontend/src/lib/components/home/ItemsList.svelte index 752ad060ae..f7d48b8f8a 100644 --- a/frontend/src/lib/components/home/ItemsList.svelte +++ b/frontend/src/lib/components/home/ItemsList.svelte @@ -240,9 +240,13 @@ async function showCode(path: string, summary: string) { viewCodeTitle = summary || path await viewCodeDrawer?.openDrawer() + // `getDraft: true` so draft-only scripts (no deployed row at this + // path) still return their content via the per-user draft overlay + // instead of 404'ing. script = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, - path + path, + getDraft: true }) }