refactor(drafts): remove unused list_drafts endpoint and dev inspector

This commit is contained in:
centdix
2026-06-08 17:21:11 +02:00
parent 6d68924959
commit eed01040a2
9 changed files with 8 additions and 289 deletions
@@ -1,67 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path,\n typ as \"typ!: UserDraftItemKind\",\n created_at as \"saved_at!\"\n FROM draft\n WHERE workspace_id = $1 AND email = $2\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "typ!: UserDraftItemKind",
"type_info": {
"Custom": {
"name": "draft_kind",
"kind": {
"Enum": [
"script",
"flow",
"app",
"raw_app",
"resource",
"variable",
"trigger_schedule",
"trigger_webhook",
"trigger_default_email",
"trigger_email",
"trigger_http",
"trigger_websocket",
"trigger_postgres",
"trigger_kafka",
"trigger_nats",
"trigger_mqtt",
"trigger_sqs",
"trigger_gcp",
"trigger_azure",
"trigger_poll",
"trigger_cli",
"trigger_nextcloud",
"trigger_google",
"trigger_github"
]
}
}
}
},
{
"ordinal": 2,
"name": "saved_at!",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "6f857451605a0453c82b4faada248689c02edde28433eb388092d30c94922e76"
}
-27
View File
@@ -7910,33 +7910,6 @@ paths:
format: date-time
required: [status, current_timestamp]
/w/{workspace}/drafts/list_drafts:
get:
summary: list the current user's drafts in a workspace (metadata only)
operationId: listDrafts
tags:
- draft
parameters:
- $ref: "#/components/parameters/WorkspaceId"
responses:
"200":
description: list of (path, kind, saved_at) tuples
content:
application/json:
schema:
type: array
items:
type: object
properties:
path:
type: string
typ:
$ref: "#/components/schemas/UserDraftItemKind"
saved_at:
type: string
format: date-time
required: [path, typ, saved_at]
/w/{workspace}/drafts/get_draft/{kind}/{path}:
get:
summary: fetch the current user's draft at a path
-31
View File
@@ -24,7 +24,6 @@ pub fn workspaced_service() -> Router {
Router::new()
.route("/get/{kind}/{*path}", get(get_draft_for_user))
.route("/save_draft/{kind}/{*path}", post(save_draft))
.route("/list_drafts", get(list_drafts))
.route("/get_draft/{kind}/{*path}", get(get_draft))
}
@@ -165,36 +164,6 @@ async fn save_draft(
}
}
#[derive(Serialize, Debug)]
pub struct DraftListItem {
pub path: String,
pub typ: UserDraftItemKind,
pub saved_at: chrono::DateTime<chrono::Utc>,
}
/// Metadata-only listing of the current user's drafts in a workspace.
/// Excludes the legacy NULL-email rows. Ordered most-recently-saved first.
async fn list_drafts(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
) -> Result<Json<Vec<DraftListItem>>> {
let rows = sqlx::query_as!(
DraftListItem,
r#"SELECT path,
typ as "typ!: UserDraftItemKind",
created_at as "saved_at!"
FROM draft
WHERE workspace_id = $1 AND email = $2
ORDER BY created_at DESC"#,
&w_id,
&authed.email,
)
.fetch_all(&db)
.await?;
Ok(Json(rows))
}
#[derive(Serialize, Debug)]
pub struct OwnDraft {
pub value: sqlx::types::Json<Box<serde_json::value::RawValue>>,
@@ -151,13 +151,7 @@ vi.mock('$lib/gen', async () => {
)
}
return { value: entry.value, saved_at: entry.saved_at }
}),
listDrafts: vi.fn(async () =>
[...draftDb.store.entries()].map(([k, v]) => {
const slash = k.indexOf('/')
return { typ: k.slice(0, slash), path: k.slice(slash + 1), saved_at: v.saved_at }
})
)
})
})
}
})
@@ -179,7 +173,7 @@ import {
setOpenPreviewHandler
} from './core'
import { UserDraft, __resetUserDraftForTesting } from '$lib/userDraft.svelte'
import { clearGlobalDrafts } from './userDraftAdapter'
import { clearEphemeralSecretVariableDraftValues } from './userDraftAdapter'
import { bundleRawAppDraft } from './rawAppBundlerBridge'
import {
AppService,
@@ -273,7 +267,7 @@ describe('global AI tools', () => {
__resetUserDraftForTesting()
localStorage.clear()
draftDb.reset()
await clearGlobalDrafts(WORKSPACE)
clearEphemeralSecretVariableDraftValues(WORKSPACE)
vi.clearAllMocks()
})
@@ -10,9 +10,8 @@
*
* When the mode is ready to ship to everyone, replace every call to
* `isGlobalAiEnabled()` with `true` and delete this file. The references are
* intentionally narrow (chat mode visibility, custom prompt settings, the
* `change_mode` tool enum, and the `/global_drafts` dev route) so the rip-out
* is a small grep.
* intentionally narrow (chat mode visibility, custom prompt settings, and the
* `change_mode` tool enum) so the rip-out is a small grep.
*/
const STORAGE_KEY = 'wm_dev_global_ai'
@@ -37,24 +37,6 @@ const TRIGGER_KIND_BY_DRAFT_KIND = Object.fromEntries(
])
) as Partial<Record<UserDraftItemKind, TriggerKind>>
const GLOBAL_DRAFT_KINDS = [
'script',
'flow',
'raw_app',
'trigger_schedule',
'trigger_http',
'trigger_websocket',
'trigger_kafka',
'trigger_nats',
'trigger_postgres',
'trigger_mqtt',
'trigger_sqs',
'trigger_gcp',
'trigger_azure',
'resource',
'variable'
] as const satisfies UserDraftItemKind[]
const secretVariableDraftValues = new Map<string, Map<string, string>>()
function clone<T>(value: T): T {
@@ -103,7 +85,7 @@ export function clearEphemeralSecretVariableDraftValue(workspace: string, path:
if (workspaceValues.size === 0) secretVariableDraftValues.delete(workspace)
}
function clearEphemeralSecretVariableDraftValues(workspace: string): void {
export function clearEphemeralSecretVariableDraftValues(workspace: string): void {
secretVariableDraftValues.delete(workspace)
}
@@ -569,24 +551,3 @@ export async function deleteGlobalDraft(
})
if (type === 'variable') clearEphemeralSecretVariableDraftValue(workspace, storagePath)
}
export async function clearGlobalDrafts(workspace: string): Promise<void> {
// `UserDraft.list` only sees in-tab mounted entries post #9351, so
// enumerate the user's persisted drafts from the DB instead and delete
// each global-mode kind.
const kinds = new Set<UserDraftItemKind>(GLOBAL_DRAFT_KINDS)
const drafts = await DraftService.listDrafts({ workspace })
for (const draft of drafts) {
if (!kinds.has(draft.typ)) continue
await UserDraftDbSyncer.save({
workspace,
itemKind: draft.typ,
path: draft.path,
value: null,
immediate: true,
force: true,
throwOnError: true
})
}
clearEphemeralSecretVariableDraftValues(workspace)
}
+2 -2
View File
@@ -480,8 +480,8 @@ export const UserDraft = {
/**
* List currently-mounted live entries for `workspace`. Without the
* localStorage layer, "list" is meaningful only for in-tab entries —
* for a workspace-wide view across sessions, call
* `DraftService.listDrafts` instead.
* for a workspace-wide view across sessions, use the deployed list
* endpoints with `includeDraftOnly` (which flag the caller's drafts).
*/
list<V = unknown>(opts?: UserDraftListOptions): UserDraftEntry<V>[] {
const ws = resolveWorkspace(opts)
@@ -1,5 +0,0 @@
export function load() {
return {
stuff: { title: 'Global AI drafts' }
}
}
@@ -1,105 +0,0 @@
<script lang="ts">
import { Button } from '$lib/components/common'
import { DraftService, type ListDraftsResponse } from '$lib/gen'
import { isGlobalAiEnabled } from '$lib/components/copilot/chat/global/gate'
import { goto } from '$lib/navigation'
import { workspaceStore } from '$lib/stores'
import { Trash2 } from 'lucide-svelte'
import { onMount } from 'svelte'
type DraftRow = ListDraftsResponse[number]
let enabled = $state(false)
let drafts = $state<DraftRow[]>([])
async function loadDrafts() {
drafts = $workspaceStore ? await DraftService.listDrafts({ workspace: $workspaceStore }) : []
}
// `value: null` is the canonical delete; `force` skips the conflict check
// (this is a debug tool, the user just wants the row gone).
async function removeDraft(ws: string, row: DraftRow): Promise<void> {
await DraftService.saveDraft({
workspace: ws,
kind: row.typ,
path: row.path,
requestBody: { value: null, force: true }
})
}
onMount(() => {
// Dev-only route. Bounce to home when the global mode gate is closed.
enabled = isGlobalAiEnabled()
if (!enabled) {
goto('/')
return
}
void loadDrafts()
// Drafts live in the DB; poll so the inspector reflects writes from this
// and other tabs (same-tab saves emit no DOM event).
const interval = window.setInterval(() => void loadDrafts(), 1000)
return () => window.clearInterval(interval)
})
function draftKey(row: DraftRow): string {
return `${row.typ}:${row.path}`
}
async function deleteDraft(row: DraftRow) {
if (!$workspaceStore) return
await removeDraft($workspaceStore, row)
await loadDrafts()
}
async function clearAll() {
const ws = $workspaceStore
if (!ws) return
await Promise.all(drafts.map((row) => removeDraft(ws, row)))
await loadDrafts()
}
</script>
{#if enabled}
<div class="p-6 max-w-5xl mx-auto">
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-semibold">Global drafts</h1>
<p class="text-sm text-tertiary">
Dev-only inspector for the current user's DB-backed drafts.
</p>
</div>
<Button
variant="default"
startIcon={{ icon: Trash2 }}
disabled={drafts.length === 0}
onclick={clearAll}
>
Clear all
</Button>
</div>
{#if drafts.length === 0}
<p class="text-sm text-tertiary">No drafts.</p>
{:else}
<ul class="space-y-2">
{#each drafts as draft (draftKey(draft))}
<li class="border rounded p-3 flex items-center justify-between gap-2">
<div class="font-mono text-sm min-w-0 truncate">
<span class="font-semibold">{draft.typ}</span>
<span class="text-tertiary">·</span>
<span>{draft.path}</span>
<span class="text-tertiary text-xs ml-2">{draft.saved_at}</span>
</div>
<Button
variant="default"
startIcon={{ icon: Trash2 }}
iconOnly
onclick={() => deleteDraft(draft)}
/>
</li>
{/each}
</ul>
{/if}
</div>
{/if}