chore: add [draft-sync] console logs to trace script bootstrap autosave

This commit is contained in:
Diego Imbert
2026-06-05 20:51:00 +02:00
parent 479b255857
commit fbe0552cdd
4 changed files with 70 additions and 7 deletions
@@ -345,6 +345,12 @@
let pathError = $state('')
let loadingSave = $state(false)
console.log('[draft-sync] ScriptBuilder script-tag: enter', {
userDraftPath,
initialPath,
contentLen: script.content?.length ?? 0,
isEmpty: script.content == ''
})
if (script.content == '') {
// Suspend autosave around the bootstrap mutations — seeding the
// editor with the template's `initialCode` is a programmatic
@@ -355,7 +361,7 @@
// new drafts). Resumed in the async `.finally` so language
// switches AFTER bootstrap (which also call `initContent`) sync
// normally.
console.log('Suspending autosave for script bootstrap', userDraftPath)
console.log('[draft-sync] ScriptBuilder: bootstrap branch START', userDraftPath)
UserDraft.stopSync('script', userDraftPath)
if (template === 'wac_python') {
script.modules = {
@@ -372,9 +378,13 @@
}
}
}
console.log('[draft-sync] ScriptBuilder: calling initContent', userDraftPath)
initContent(script.language, script.kind, template).finally(() => {
console.log('[draft-sync] ScriptBuilder: initContent finally → restartSync', userDraftPath)
UserDraft.restartSync('script', userDraftPath)
})
} else {
console.log('[draft-sync] ScriptBuilder: bootstrap SKIPPED (content non-empty)', userDraftPath)
}
async function isTemplateScript() {
@@ -419,8 +429,16 @@
// doesn't run `inferArgs` on an empty `script.content` and toast
// "Could not parse code". If a template script is then loaded
// below we re-seed with the `templateScript=true` variant.
console.log('[draft-sync] initContent: SYNC seed content', userDraftPath, {
language,
template
})
script.content = initialCode(language, kind, template, false)
console.log('[draft-sync] initContent: awaiting isTemplateScript', userDraftPath)
const templateScript = await isTemplateScript()
console.log('[draft-sync] initContent: post-await', userDraftPath, {
hasTemplateScript: !!templateScript
})
if (templateScript) {
script.content = initialCode(language, kind, template, true)
}
+39 -5
View File
@@ -444,8 +444,13 @@ export const UserDraft = {
const ws = resolveWorkspace(opts)
const mk = mapKey(ws, itemKind, path)
const entry = entries.get(mk)
if (entry) entry.syncSuspended = true
else pendingSuspensions.add(mk)
if (entry) {
entry.syncSuspended = true
console.log('[draft-sync] stopSync (entry live)', mk)
} else {
pendingSuspensions.add(mk)
console.log('[draft-sync] stopSync (queued, no entry yet)', mk)
}
},
/**
@@ -458,9 +463,13 @@ export const UserDraft = {
restartSync(itemKind: UserDraftItemKind, path: string, opts?: UserDraftOptions): void {
const ws = resolveWorkspace(opts)
const mk = mapKey(ws, itemKind, path)
pendingSuspensions.delete(mk)
const hadPending = pendingSuspensions.delete(mk)
const entry = entries.get(mk)
if (entry) entry.syncSuspended = false
console.log('[draft-sync] restartSync', mk, {
entryLive: !!entry,
clearedPending: hadPending
})
},
/**
@@ -642,8 +651,13 @@ function acquireEntry(
const existing = entries.get(mk)
if (existing) {
existing.count++
console.log('[draft-sync] acquireEntry: reuse', mk, { newCount: existing.count })
return
}
console.log('[draft-sync] acquireEntry: NEW', mk, {
hasSeed: defaultValue !== undefined,
pendingSuspended: pendingSuspensions.has(mk)
})
// Seed the cell with the caller's `defaultValue` (deep-cloned so the
// cell owns its copy and the caller's baseline can't alias it). This is
// how editors report the deployed/draft state until the user edits —
@@ -691,22 +705,42 @@ function acquireEntry(
const stored = cell.val
if (stored !== undefined) readFieldsRecursively(stored.value)
const next = stored === undefined ? undefined : JSON.stringify(stored)
if (next === lastSerialized) return
if (next === lastSerialized) {
console.log('[draft-sync] effect: no change', mk, {
nextLen: next?.length ?? 0
})
return
}
const nextLen = next?.length ?? 0
const diff = nextLen - (lastSerialized?.length ?? 0)
lastSerialized = next
if (skipNextWrite) {
skipNextWrite = false
console.log('[draft-sync] effect: SWALLOW (skipNextWrite seed)', mk, {
nextLen,
diff
})
return
}
const entry = entries.get(mk)
if (entry?.skipNextSync) {
entry.skipNextSync = false
console.log('[draft-sync] effect: SWALLOW (skipNextSync)', mk, { nextLen, diff })
return
}
// `syncSuspended` swallows the POST but still advances
// `lastSerialized` (above) so when sync resumes the next
// real change is detected as a change — only the writes
// made during suspension are dropped from the server's view.
if (entry?.syncSuspended) return
if (entry?.syncSuspended) {
console.log('[draft-sync] effect: SWALLOW (syncSuspended)', mk, { nextLen, diff })
return
}
console.log('[draft-sync] effect: POST', mk, {
nextLen,
diff,
stack: new Error().stack?.split('\n').slice(1, 6).join('\n')
})
void UserDraftDbSyncer.save({
workspace,
itemKind,
+9 -1
View File
@@ -147,6 +147,9 @@ const pendingSaveOpts = new Map<string, UserDraftDbSyncerSaveOpts>()
async function postSave(opts: UserDraftDbSyncerSaveOpts): Promise<void> {
const key = draftKey(opts.workspace, opts.itemKind, opts.path)
console.log('[draft-sync] postSave START (sending POST)', key, {
valueIsNull: opts.value === null
})
try {
const resp = await DraftService.saveDraft({
workspace: opts.workspace,
@@ -169,8 +172,9 @@ async function postSave(opts: UserDraftDbSyncerSaveOpts): Promise<void> {
// newer `save()` that arrived during the POST replaces the entry
// and must survive for the next flush / debouncer round.
if (pendingSaveOpts.get(key) === opts) pendingSaveOpts.delete(key)
console.log('[draft-sync] postSave SUCCESS', key)
} catch (e) {
console.error('UserDraftDbSyncer.save failed', e)
console.error('[draft-sync] postSave FAILED', key, e)
}
}
@@ -284,6 +288,10 @@ export const UserDraftDbSyncer = {
async save(opts: UserDraftDbSyncerSaveOpts): Promise<void> {
const key = draftKey(opts.workspace, opts.itemKind, opts.path)
console.log('[draft-sync] UserDraftDbSyncer.save called', key, {
immediate: !!opts.immediate,
valueIsNull: opts.value === null
})
// Track the latest unconfirmed save BEFORE entering the pipeline
// so the unload flush has something to send even if the page
// hides before the debouncer fires.
@@ -150,6 +150,7 @@
// fires. `initialPath = ''` also makes ScriptBuilder open the
// metadata drawer on mount. Strip the single-use flag last.
if (page.url.searchParams.get('new_draft') === 'true') {
console.log('[draft-sync] route: new_draft branch START', draftPath)
// Suspend autosave for the whole new-draft bootstrap: the seed
// `setDraftAndMeta` AND ScriptBuilder's `initContent` (which
// fills `script.content` from a template) are both
@@ -171,9 +172,11 @@
} as unknown as EditableScript
initialPath = ''
savedScript = structuredClone(empty)
console.log('[draft-sync] route: about to setDraftAndMeta(empty)', draftPath)
scriptHandle.setDraftAndMeta(empty, {})
fullyLoaded = true
renderEditor = true
console.log('[draft-sync] route: new_draft branch DONE', draftPath)
return
}
if (hash) {