fix(drafts): make the home-page View/Edit JSON action work on draft-only apps

The "View/Edit JSON" entry on the home page called `AppService.getAppByPath`
without `get_draft=true`, so for draft-only items at `u/{user}/draft_{uuid}`
the backend 404'd with "App not found at path …". Pass `get_draft=true`
and render the synthesized stand-in's editable shape:

- App drafts come back as `{summary, value, path, policy, ...}` — `value`
  is the App definition the editor was working on; show that.
- Raw-app drafts come back as the flattened
  `{files, runnables, data, summary, policy, ...}` with no nested `value`;
  show the whole shape.

On save, draft-only items can't go through `updateApp` (no deployed row).
Route the edit through `UserDraftDbSyncer.save` (with `immediate: true`
so `await` resolves after the POST lands) and relabel the button
"Save draft" + Save icon. Deployed items keep the existing "Deploy"
flow unchanged.
This commit is contained in:
Diego Imbert
2026-06-07 23:36:27 +02:00
parent 05b61b7e80
commit e21d3e0c5f
@@ -6,16 +6,19 @@
import JsonEditor from '../../JsonEditor.svelte'
import { AppService } from '$lib/gen'
import { UserDraft } from '$lib/userDraft.svelte'
import { UserDraftDbSyncer } from '$lib/userDraftDbSyncer.svelte'
import { sendUserToast } from '$lib/toast'
import { userStore, workspaceStore } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import { Globe, Loader2 } from 'lucide-svelte'
import { Globe, Loader2, Save } from 'lucide-svelte'
let jsonViewerDrawer: Drawer | undefined = $state()
let code: string = $state('')
let path: string = ''
let loading = $state(true)
let isDraftOnly = $state(false)
let isRawApp = $state(false)
const dispatch = createEventDispatcher()
let app: any | undefined = undefined
@@ -24,24 +27,55 @@
loading = true
jsonViewerDrawer?.toggleDrawer()
path = path_l
const fapp = await AppService.getAppByPath({
// `get_draft=true` so draft-only items at `u/{user}/draft_{uuid}`
// resolve to the synthesized draft stand-in instead of 404'ing the
// home-page "View/Edit JSON" menu entry. Deployed apps continue to
// return the deployed payload unchanged (see WithDraftOverlay).
const fapp = (await AppService.getAppByPath({
workspace: $workspaceStore!,
path
})
path,
getDraft: true
})) as any
app = { ...fapp }
code = JSON.stringify(fapp.value, null, 4)
isDraftOnly = !!fapp.no_deployed
isRawApp = !!fapp.raw_app
// For deployed (or app drafts overlaid on a deployed row) the
// editable shape is the App definition under `.value`. For raw-app
// drafts there is no nested `.value` — the editable shape is the
// whole flattened draft (`{files, runnables, data, ...}`).
const display = isDraftOnly && isRawApp ? fapp : fapp.value
code = JSON.stringify(display, null, 4)
loading = false
}
export async function saveApp() {
await AppService.updateApp({
workspace: $workspaceStore!,
path,
requestBody: { ...app, value: JSON.parse(code) }
})
dispatch('change')
UserDraft.remove('app', path)
sendUserToast('App deployed')
const parsed = JSON.parse(code)
if (isDraftOnly) {
// Draft-only items have no deployed row — `updateApp` would
// 404. Route the edit through the syncer (`immediate: true`
// so the caller's `await` resolves only after the POST lands)
// so the user keeps editing under the draft path until they
// rename + deploy from the regular editor.
const value = isRawApp ? { ...app, ...parsed } : { ...app, value: parsed }
await UserDraftDbSyncer.save({
workspace: $workspaceStore!,
itemKind: isRawApp ? 'raw_app' : 'app',
path,
value,
immediate: true
})
dispatch('change')
sendUserToast('Draft saved')
} else {
await AppService.updateApp({
workspace: $workspaceStore!,
path,
requestBody: { ...app, value: parsed }
})
dispatch('change')
UserDraft.remove('app', path)
sendUserToast('App deployed')
}
}
</script>
@@ -55,8 +89,13 @@
{#snippet actions()}
{#if !$userStore?.operator}
<Button on:click={saveApp} startIcon={{ icon: Globe }} variant="accent" size="xs">
Deploy
<Button
on:click={saveApp}
startIcon={{ icon: isDraftOnly ? Save : Globe }}
variant="accent"
size="xs"
>
{isDraftOnly ? 'Save draft' : 'Deploy'}
</Button>
{/if}
{/snippet}