Files
windmill/frontend/src/lib/components/sessions/appDraftCodec.test.ts
T
GuilhemandClaude Opus 4.8 e4bfeb29bc fix(frontend): persist session-editor draft path/summary edits + per-line diff tooltips (#9622)
* fix(frontend): persist raw-app draft path edits in the session editor

Renaming a raw app's path in the session preview editor never triggered a draft
save: the header surfaced the typed path as `pendingDraftPath`, but
RawAppEditorView ignored it (it never reached runtime.rawApp.val), and the
RawAppDraft codec didn't serialize a path field — so the autosave signature
(JSON.stringify(draft)) was unchanged and nothing was written. The rename was
lost and the home/review/Drafts lists kept the original `draft_path`.

- appDraftCodec: make `draft_path` a real draft + runtime field, serialized by
  runtimeRawAppToDraft and round-tripped by applyDraftToRuntimeRawApp, so a path
  change moves the sig and fires a save.
- sessionRuntime.loadRawApp (+ inline rawApp.val type): seed `draft_path` from
  the loaded draft so it survives reloads.
- RawAppEditorView: bind the header's `pendingDraftPath`, mirror it into
  runtime.rawApp.val.draft_path (guarded so the initial undefined can't clobber
  the seed or fire a spurious save), and seed the path widget from
  `draft_path ?? path`. Mirrors the full-page /apps_raw/edit route.
- appDraftCodec.test: add a draft_path round-trip test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): per-line tooltips in workspace item diff rows

The summary line now shows the full summary on hover and the path line the
full path, instead of one row-level title surfacing the path everywhere.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): persist flow/script draft path edits in the session editor

The session sync dedups on a per-kind signature that omitted the path, so a
rename never moved the signature and never autosaved. Add path/draft_path to
the flow signature, and derive draft_path in the script codec (scripts bind
the Path widget to script.path directly) so the rename both autosaves and
shows the typed name in the home/Drafts lists. Mirrors the raw-app fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): commit editor summary edits live instead of on blur

EditableInput only fired onSave on Enter/blur, so the summary in the shared
editor header only updated when the field lost focus. Add an opt-in
commitOnInput that fires onSave per keystroke and enable it for the header
summary, so flow/script/raw-app summaries autosave as you type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): preserve renamed never-deployed script path on session re-seed

loadScript seeded a draft-only script's baseline path from the storage key, so
re-running it with the draft still in memory (e.g. a script→script switch)
reset the path to draft_<uuid> and the next autosave dropped draft_path,
clobbering the rename. Seed from the draft's own draft_path/path instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): clear raw-app draft_path when the path rename is reverted

The mirror effect only ever set draft_path, so reverting/clearing the path
field left a stale friendly name in the draft (persisted by the codec and shown
in the home/Drafts lists). Track whether a real typed path was surfaced so a
revert clears draft_path while the initial pre-bind undefined still can't
clobber the loadRawApp-seeded value. Mirrors the script codec's drop-on-revert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:19:12 +02:00

81 lines
2.5 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import {
runtimeRawAppToDraft,
applyDraftToRuntimeRawApp,
type RuntimeRawApp,
type RawAppDraft
} from './appDraftCodec'
function runtime(over: Partial<RuntimeRawApp> = {}): RuntimeRawApp {
return {
summary: 'app',
path: 'f/foo/app',
files: { '/index.tsx': 'x' },
runnables: {},
data: { tables: [] } as any,
policy: { execution_mode: 'publisher' },
...over
}
}
describe('appDraftCodec — custom_path round-trip', () => {
it('carries custom_path from runtime → draft', () => {
const draft = runtimeRawAppToDraft(runtime({ custom_path: 'my-url' }))
expect(draft.custom_path).toBe('my-url')
})
it('carries custom_path from draft → runtime', () => {
const base = runtime({ custom_path: undefined })
const dv: RawAppDraft = {
summary: 'app',
files: {},
runnables: {},
data: { tables: [] } as any,
policy: { execution_mode: 'publisher' },
custom_path: 'kept-url'
}
expect(applyDraftToRuntimeRawApp(base, dv).custom_path).toBe('kept-url')
})
it('survives a full runtime → draft → runtime round-trip', () => {
const original = runtime({ custom_path: 'round-trip-url' })
const back = applyDraftToRuntimeRawApp(
runtime({ custom_path: undefined }),
runtimeRawAppToDraft(original)
)
expect(back.custom_path).toBe('round-trip-url')
// runtime-only `path` is preserved from the target, not the draft
expect(back.path).toBe('f/foo/app')
})
it('falls back to the runtime custom_path when the draft omits it', () => {
const base = runtime({ custom_path: 'existing' })
const dv: RawAppDraft = {
summary: 'app',
files: {},
runnables: {},
data: { tables: [] } as any
}
expect(applyDraftToRuntimeRawApp(base, dv).custom_path).toBe('existing')
})
})
describe('appDraftCodec — draft_path round-trip', () => {
it('serializes draft_path so a path edit changes the draft (and its sig)', () => {
const draft = runtimeRawAppToDraft(runtime({ draft_path: 'u/admin/friendly' }))
expect(draft.draft_path).toBe('u/admin/friendly')
// The autosave keys on JSON.stringify(draft); without draft_path a rename
// would be invisible and never persist.
expect(JSON.stringify(draft)).toContain('u/admin/friendly')
})
it('survives a full runtime → draft → runtime round-trip', () => {
const original = runtime({ draft_path: 'u/admin/renamed' })
const back = applyDraftToRuntimeRawApp(
runtime({ draft_path: undefined }),
runtimeRawAppToDraft(original)
)
expect(back.draft_path).toBe('u/admin/renamed')
})
})