mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 16:02:14 +00:00
4e63ca2cd429ecf55f27a431dfb2c0fd85e2c8c8
6575 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
51d8db6602 |
feat: automatic git-to-windmill sync (polling, webhooks, in-app PRs + checks) (#9552)
* docs: add design doc for automatic git-to-windmill pull sync
* docs: add migration plan and implementation phases to git-sync pull design
* feat(git-sync): add auto_pull settings schema and pull enqueue primitive
Adds AutoPullSettings/AutoPullMode/AutoPullStatus on GitRepositorySettings
(workspace_settings.git_sync JSONB), the GIT_SYNC_PULL_SCRIPT_PATH constant,
and should_pull/effective_poll_interval_s helpers with unit tests. Exports the
EE enqueue_git_pull_job primitive. Foundation for repo→Windmill auto-pull.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(git-sync): poll repos and auto-pull new commits into the workspace
Phase 1 of automatic repo → Windmill sync. A monitor task (EE-licensed,
single-replica via advisory lock) git ls-remotes each auto-pull-enabled
repository ~every minute and enqueues a pull when the tracked branch moves,
reusing the {workspace_id}:git_sync concurrency key so pulls serialize with
in-flight push commits.
- windmill-store: background (no-authed) resolver get_git_repo_head_for_autopull
that resolves the repo resource (incl. $var: refs) and ls-remotes; GitHub-App
repos are skipped here and will sync via webhooks (phase 2).
- monitor.rs: poll/reconcile/persist with optimistic sha advance and failure
status; targeted jsonb update so concurrent settings edits aren't clobbered.
- edit_git_sync_repository: preserve server-owned auto_pull state on UI save.
- openapi: AutoPullSettings/AutoPullMode/AutoPullStatus + auto_pull field.
- frontend: per-repo "Automatically deploy changes from Git" toggle with last
sync status; demote the GitHub Actions link to an advanced CI option.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(git-sync): wire webhook lifecycle + receiver; share reconcile logic
OSS side of phase 2 auto-pull webhooks:
- edit_git_sync_repository creates/removes the repo webhook on save (EE-gated,
best-effort → falls back to polling).
- monitor poller now delegates to the shared windmill_git_sync reconcile/persist
helpers (also used by the webhook receiver), removing duplicated logic.
- export the shared reconcile/persist/failure helpers; bump EE ref.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(git-sync): bump EE ref for phase 3 in-app PR creation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(git-sync): show webhook vs polling status on the auto-pull toggle
When a repo has an active webhook (auto_pull.webhook_id set), the status line
reads "instant via webhook"; otherwise it reads the ~1-minute polling cadence.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(git-sync): post PR diff check on dry-run completion (phase 4)
Worker completion hook in process_completed_job: when a DeploymentCallback job
carrying the __git_sync_pr_check marker finishes, parse the dry-run SyncResponse
and patch the GitHub check run with the diff summary (success/neutral/failure).
Export enqueue_git_pull_dry_run; bump EE ref.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(git-sync): bump EE ref (drop unused GHES webhook_secret)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* revert(git-sync): defer phase 4 PR diff checks (OSS side)
Remove the worker completion hook that posted the PR check run, drop the
enqueue_git_pull_dry_run re-export and the orphaned sqlx cache, bump EE ref.
Phases 1-3 (polling, webhooks, in-app PR creation) are unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Revert "revert(git-sync): defer phase 4 PR diff checks (OSS side)"
This reverts commit
|
||
|
|
7b813d1f74 |
fix(frontend): sanitize job result markup, gate it on unsandboxed public apps (#10127)
* fix(frontend): sanitize html and svg result rendering Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(frontend): add dompurify to lockfile root deps Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): keep sanitizing rich results on public app surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(frontend): gate risky app markup on unsandboxed public surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): derive app markup isolation from the real origin Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(frontend): use the design-system danger alert for the markup gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
24750e6ef1 |
fix(raw-apps): prevent and surface the silent blank screen from an unmounted #root (#10150)
* fix(raw-apps): prevent and surface the silent blank screen from an unmounted #root
An `index.tsx` written as a bare `export default function App() {...}` with
no mount call builds and runs without throwing: the preview executes the
bundle against an empty `<div id="root">` and auto-renders nothing, so the
JSX never runs, nothing reaches the console or the runtime-error overlay,
and the app is blank with no diagnostic.
Prevent it: the raw-app system prompt and the in-chat app prompt now state
that `index.tsx` is the mount entrypoint, show the mount shim for React,
Svelte and Vue, and call out that a bare component fails silently.
Surface it: when a build still mounts nothing, the preview harness posts
`emptyRender` and the editor shows an error overlay naming the missing
call. The harness reports only when nothing is on screen AND the app never
looked `#root` up, so an app that mounted but paints nothing yet (a fetch
in flight, an unresolved Suspense) is never flagged; `renderAppeared`
withdraws the overlay if a deferred mount lands late.
The handlers stay dormant until the builder tarball that emits these
messages is pinned via `ui_builder_artifact.json`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(raw-apps): pin the UI builder artifact that emits emptyRender
Activates the "Nothing was mounted" overlay: the pinned tarball predates
the harness change, so the host handlers were dormant until now.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: drop a screenshot accidentally committed at the repo root
Not referenced anywhere; the PR's screenshots are hosted externally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(raw-apps): make the unmounted-app guidance framework-aware
The detector fires for every raw-app framework, but the overlay and the
prompts named React's `index.tsx` and `createRoot` unconditionally. Svelte
and Vue apps mount from `index.ts` via `mount` / `createApp`, so the
guidance pointed at a nonexistent entrypoint and an unavailable API.
Derive the entrypoint and mount call from the app's files, keyed off file
extensions rather than the template filenames, which users rename.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
fa03984a14 | fix(ai): show the question in askUserQuestion tool-call labels (#10153) | ||
|
|
568dbbee85 |
fix(frontend): show friendly draft path for draft-only items in pickers (#10136)
* fix(frontend): show friendly draft path for draft-only items in pickers * fix(frontend): dedupe current draft item and scope tab picker by friendly path * fix(frontend): key live draft picker entries by storage path * fix(frontend): fall back to the current leaf when the picker highlight key vanishes * fix(frontend): remount session tab picker when the friendly scope arrives * fix(frontend): stamp staged tab path for deployed items with undeployed renames * fix(frontend): expose staged flow/raw-app renames through the live draft registration |
||
|
|
7fda6a0534 |
feat(frontend): flatten workspace pickers, whole-tab picker trigger (#10145)
* fix(frontend): open session preview picker from whole tab, anchor to tab edge Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(frontend): flatten session preview picker to workspace home level Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(frontend): flatten chat context picker workspace tree Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): show root loading state in flat drill pickers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): make tab-strip keyboard activation work inside dnd zones Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): return focus to tab after active-tab picker closes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): move focus with selection on arrow-key tab navigation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cc305b1d97 |
refactor(frontend): reorder sidebar settings menu, move logout to user submenu (#10149)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4ee1d32101 |
feat: display openai reasoning summaries in ai chat (#10147)
* feat(frontend): display openai reasoning summaries in ai chat with unverified-org fallback Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): scope hidden-thinking hint per workspace/provider and skip summary on explicit reasoning-off Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): compose responses fallbacks in either error order and track all unavailable summary keys Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0694b84da7 | fix(frontend): surface real tool call errors in AI chat (#10146) | ||
|
|
0ea570570e |
feat(ai-sessions): CRUD markdown artifacts in sessions (#10046)
* feat: add IndexedDB persistence layer for AI-chat artifacts * feat: add reactive store for AI-chat artifacts * feat: add artifact chat tools and wire store lifecycle * feat: add markdown artifact viewer with source toggle * feat: surface session artifacts in the preview panel and chat list * feat: tell the copilot when to use artifacts in the session prompt * test(ai_evals): add artifact case and wire artifact helpers for session context * fix(copilot): keep in-memory artifacts across same-session resyncs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: unify session composer edits/artifacts/jobs into a status line Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: add an artifacts section to the session preview picker Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: share markdown prose presets and restyle the artifact viewer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: unify session status popovers into one keyboard-navigable shell Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: reset first-block top margin in all markdown prose presets Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: open the preview picker on the artifacts branch for an active artifact Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: keep artifact picker scope independent of branch hydration state Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com> |
||
|
|
8c725d9e44 |
fix(apps): honor presigned S3 signature on app display/preview routes (#10141)
The app provenance gate short-circuits on a valid presigned signature, but only the raw download_s3_file route parsed it. The parquet/csv/table-count/file-preview/metadata routes discarded sig/exp and always fell through to the provenance gate, so a presigned S3 object rendered as a table showed "File restricted" for any viewer who did not produce it. Thread sig/exp through every apps_u S3 display route and forward the presigned bearer from ParqetCsvTableRenderer/DisplayResult. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9705d60284 |
fix(frontend): keep session-exit URL clean by syncing new_draft strip with the router (#10101)
* fix(frontend): keep session-exit URL clean by syncing new_draft strip with the router Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(frontend): correct replaceState comment and test-mock wording per review Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(frontend): correct replaceState comment and drop drafting-history phrasing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
af177cefe0 |
fix(frontend): graceful small-screen timeframe picker on the runs page (#10073)
* fix(frontend): prevent runs timeframe calendar popover overflow on small screens The Runs page timeframe picker rendered its popover as a wide 3-column row (preset list + two side-by-side calendars). With the right-aligned trigger and a center-anchored `bottom` placement, the popup ran off the right edge on narrow viewports. Anchor the popover to the right edge (`placement="bottom-end"`) and make its content reflow to a vertical stack below the `sm` breakpoint, capped at `max-w-[calc(100vw-2rem)] max-h-[80vh] overflow-auto` so it can never exceed the viewport. The desktop side-by-side layout is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(frontend): compact runs timeframe picker with a Start/End toggle on small screens The two-calendar desktop popover needs ~780px (two min-w-9 grids + presets + popover padding); below that it overflows. Under 800px, show a single calendar with a Start/End toggle picking which bound it edits, using set-start/set-end so each bound keeps its date and HH:MM time inputs — the same precision the desktop start/end pair offers. On short/landscape viewports the compact panel is scroll-contained within the popover's fitViewport height (contentClasses overflow-y-auto, scoped to the small layout) so its lower controls stay reachable. The desktop two-calendar layout is unchanged. Presets are shared between both layouts via a snippet, and the active range is preserved across the breakpoint since both branches drive the same value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(frontend): let InlineCalendarInput month/year selects portal, use in compact timeframe picker Add an opt-in `portalSelects` prop to InlineCalendarInput that portals the month/year dropdowns to the body (default keeps them in-flow, so existing consumers are unchanged). The compact runs timeframe picker enables it so the dropdowns escape its scroll-contained (overflow-y-auto) popover instead of being clipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2fe999f66c | fix(frontend): treat a displaced draft save as superseded, not failed (#10094) | ||
|
|
bd3adc9781 |
fix(frontend): only carry custom-tag overrides on 'Run again' (#10137)
* fix(frontend): only carry custom-tag overrides on 'Run again' Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(frontend): keep literal overrides on dynamic-tag reruns Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ebe31aeeac |
feat(dev-workspace): reflect existing protection rules in lock toggles (#10093)
* feat(dev-workspace): reflect existing protection rules in lock toggles When creating or attaching a dev workspace, the "block direct edits" and "prevent forking" toggles now check the root workspace's current protection rules. If a restriction is already enforced by an existing rule, its toggle is shown on but locked, with a note, instead of offering a fresh default that could misrepresent the effect. The value sent to the backend is derived so it stays consistent with what the locked toggle shows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: clarify fail-open comment on dev-workspace lock toggles Reword the protection-rule fetch comment so the fallback path isn't misread as dropping protection: a failed fetch falls back to the editable default-on toggle, and any real rule still enforces server-side. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dev-workspace): lock protection toggles until rules load The lock toggles derived alreadyBlocks* from an async fetch, so during the load window (and the first frame before loading flips) they were editable and the effective value could be false. A user could turn a lock off and submit before an existing rule was detected, omitting the reserved rule and silently leaving prod unprotected once that existing rule was later removed. Treat "rules not yet known" (loading || current === undefined) the same as "already enforced": lock the toggle on and keep the effective value true during that window, so the request can never submit false before the fetch resolves. Submission stays available (a hung fetch degrades to over-protection, not a blocked form). Also fixes the stale-value flash when switching base workspace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dev-workspace): honor rule bypasses and guard stale protection fetches Two issues in the protection-rule awareness for the dev-workspace lock toggles: - Bypassable rules became unconditional locks. alreadyBlocks* used isRuleActiveInRulesets, which ignores bypass_users/bypass_groups, and forced the request flag to true. The reserved dev_workspace_lock rule is created with empty bypass lists, so layering it over an existing rule that let specific users through revoked their deploy/forking access. Switch to isRuleUnconditionallyActiveInRulesets so a toggle is only shown as already enforced (locked) when an existing rule has no bypasses; a bypassable rule stays editable, making the lock the user's explicit choice. - A stale protection fetch could apply another base's rules. The generated client can't take an abort signal, so a delayed response for a previous base could overwrite the newly selected one. Tag each result with its workspace and only trust a result matching the current base; also throw AbortError from a superseded fetch so it can't overwrite current. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: condense protection helper comment to four lines Trim the isRuleUnconditionallyActiveInRulesets doc comment to satisfy the AGENTS.md ≤4-line comment rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(dev-workspace): align already-enforced note under the toggle label The note used ml-8, landing under the toggle switch rather than aligned with the switch edge or the label, so it read as floating. Bump to ml-11 so it lines up under the label as helper text for that toggle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
88030d0f55 |
fix(tree-view): align file indentation with sibling folders (#10115)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
af3e3fe667 |
fix(ai-chat): size AI-created flow notes to fit their text (#10091)
* fix(ai-chat): size AI-created flow notes to fit their text Free notes created via the flow AI chat omit `size` (the tool prompt tells the model to let the editor size them). validateFlowNotes seeded a fixed 275x60 box, but free notes never grow to fit content, so multi-line markdown overflowed the box. Estimate height from the text instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-chat): stack auto-placed flow notes by height to avoid overlap Auto-placed free notes were staggered by a fixed index*84px step, but notes can now be up to 600px tall, so consecutive generated notes overlapped. Track a running y-cursor and advance it by each note's real height. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-chat): advance note stack cursor past preserved column notes A round-tripped note keeps its existing auto-column geometry ({-375, y}); the stack cursor ignored it, so a newly added geometry-less note landed on top. Preserved notes overlapping the auto-stack column now advance the cursor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(ai-chat): trim estimateFreeNoteSize comment per AGENTS.md Keep only the non-obvious fixed-height renderer constraint; drop the implementation narration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7ebfad382a |
feat(ai-agent): give tools a real description instead of the tool name (#10083)
* feat(ai-agent): use a real tool description instead of the tool name Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-agent): render tool-name error full width and hoist it above the description Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-agent): make tool description field hug its content so a single line is vertically centered Add an optional minHeight param to the autosize action (default unchanged at 30px) and pass minHeight 0 for the tool description so an empty/one-line field no longer reserves the 30px floor and leaves dead space below the text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(ai-agent): regenerate OpenFlow-derived prompts, CLI guidance, and copilot zod schema for tool description Fixes the check-freshness CI failure (system_prompts + skills.gen.ts) and makes the flow copilot's openFlow.json / openFlowZod.gen.ts aware of the new AgentTool.description field so AI-authored tools can set it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f3cd5d9f7f |
fix(sessions): pending-draft debounce follow-ups (delete-cancel, keystroke de-transient, teardown count) (#10087)
* fix(sessions): cancel pending draft-prompt flush on delete deleteSession removed the record from memory and IndexedDB but left the debounced draft-prompt flush timer running; it would fire afterward and persistTouched the deleted session back into IndexedDB, resurrecting a draft deleted inside the 400ms window on the next reload. Clear the per-session timer in deleteSession. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): de-transient drafts on keystroke; count pending in workspace teardown Two follow-ups to the pending-sessions feature (#10076), surfaced by codex review: - setSessionDraftPrompt clears `transient` synchronously so a draft typed into is no longer treated as a reusable blank by createSession. Previously the flag only cleared 400ms later via the debounced flush, so pressing `+` right after typing reopened the same draft instead of spawning a second pending session. Only the IndexedDB write stays debounced. - countSessionsForWorkspace counts on `workspace_id ?? pending_workspace_id`, so the archive/delete confirmation includes persisted unsent drafts, matching reconcileSessionsLifecycle which tears them down alongside committed sessions. Adds regression tests that drive the real keystroke transition and the pending draft count. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): reuse-guard on draftPrompt, not a synchronous transient clear Addresses codex-review P1 on #10087: the previous approach cleared `transient` synchronously on a keystroke to stop createSession reusing a just-typed draft. But `transient` also means "in-memory only, not yet in IndexedDB" — clearing it before the 400ms flush left the draft in neither bucket, so a reconcile landing inside the window (hydrateSessions rebuilds the list as in-memory-transients + DB rows) dropped the unsaved draft and dangled currentSessionId. Separate the two concepts instead: keep `transient` as pure persistence state (the draft survives hydration), and define a reusable blank as `transient && !draftPrompt`. createSession's reuse probe and its non-reuse drop both key on isReusableBlank, so a typed-but-unflushed draft is neither reused nor discarded, and setSessionDraftPrompt no longer touches `transient`. Adds a regression test that interleaves a first-touch debounce with reconcile and asserts the draft stays in memory (and currentSessionId intact); updates the keystroke test to the real transient-preserving transition. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): treat a typed-then-erased draft as touched, not a reusable blank Addresses codex-review P2 on #10087. isReusableBlank used `!s.draftPrompt`, so a draft typed into then erased back to '' (draftPrompt === '', flush still pending) was classed as a reusable blank: pressing `+` within 400ms reused it, but after the flush cleared `transient` the same `+` created a new session — inconsistent across the debounce boundary, and in another family the non-reuse drop removed the draft while its pending timer later persisted it back. setSessionDraftPrompt only sets draftPrompt on a genuine edit (mount-time '' is a no-op via the equality guard), so `draftPrompt === undefined` cleanly means "never edited". Key isReusableBlank on that instead of falsiness. Adds a type-then-erase-before-`+` regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(sessions): trim new comment blocks to AGENTS.md 4-line limit Addresses codex-review P2 on #10087: condense the setSessionDraftPrompt, countSessionsForWorkspace, and isReusableBlank comment blocks to <=4 lines per the AGENTS.md rule. Comment-only, no logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(sessions): trim last two comment blocks to AGENTS.md 4-line limit Follow-up to codex/pi P2 nits on #10087: condense the keystroke-transition test comment (5→4 lines) and the countSessionsForWorkspace comment (→3 lines). All new comment blocks in the PR are now ≤4 lines. Comment-only, no logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2c702efec0 |
fix(frontend): stop spurious raw-app reload that 404s on "Start without AI" (#10099)
* fix(frontend): stop spurious raw-app reload that 404s on "Start without AI" Creating a new raw app and clicking "Start without AI" surfaced an "App not found" toast. The page's load effect re-ran loadApp() mid-bootstrap and fetched the draft via getAppByPath before the first autosave POST had landed → 404. Root cause: the effect used the legacy run() from svelte/legacy without untrack, so loadApp()'s synchronous reactive read of the draft-hint SvelteMap (getLocalDraftHint via shouldSeedNewDraft, added in #10044) subscribed the effect. The first autosave optimistically flips that hint (#9351) before its debounced POST, re-firing the effect → spurious loadApp() → getAppByPath on a not-yet-persisted draft. Convert the block to $effect + untrack so it depends only on page.params.path / $workspaceStore, matching the sibling apps/edit and flows/edit routes. Autosave and draft persistence are unchanged; only the phantom reload is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(frontend): tighten untrack invariant comment to ≤4 lines Per AGENTS.md comment policy (Codex review nit). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cfc3f292ad |
fix(apps): allow setting sandbox isolation and public access before first deploy (#10085)
* fix(apps): allow enabling sandbox isolation before first deploy Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): allow setting public access mode before first deploy Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
32f32d9a29 |
feat(ai-chat): port flow-group and sticky-note instructions to global chat (#10090)
* feat(ai-chat): port flow-group and sticky-note instructions to global mode Global-mode AI chat inherited only the bare FlowGroup schema and had no sticky-note support, so it never proactively segmented flows into groups and could not author flow-wide notes. Flow mode carried this guidance inline in its own prompt and set_flow_json tool. Bring global mode to parity: - Enrich write_flow's `groups` description (color palette + fields) and add a `notes` field mirroring flow mode's set_flow_json. - Thread `notes` through editableFlowToDraftValue and the write_flow handler so notes reach FlowValue.value and survive the deploy round-trip. Reads and patch_flow_json already carried notes via the shared editableFlowJson helpers. - Expand getFlowInstructions with the groups/notes organizing guidance (strongly-recommended proactive grouping, color palette, when-to-use-which) and mention notes in the write/read/compact-view/structural-edit bullets. Add a write_flow -> read_workspace_item notes round-trip test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ai-chat): trim write_flow groups/notes schema descriptions The write_flow tool schema is re-sent every chat loop iteration, so the verbose groups/notes descriptions were a per-iteration token tax that duplicated the on-demand getFlowInstructions() prose. Trim the .describe() calls to the correctness-critical bits (color palette, type "free", null semantics) and point to get_instructions for the full field reference, which getFlowInstructions() already carries. Addresses CI review feedback (Claude + Pi). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
eff9076e91 |
fix(sessions): reopen script test panel when preview goes full screen (#10082)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f2869d8c1a |
fix(apps): load themes when selecting the Resources → Theme tab (#10086)
The Resources page dispatches per-tab data loads from the Tabs `on:selected` handler and `reload()`, but both only handled `cache` and `states` — selecting the Theme tab never called `loadTheme()`, so `themeResources` stayed undefined and the tab rendered empty even though app themes existed. The reload `$effect` reads `tab` inside `untrack`, so it didn't re-fire on tab change either (only a filter or workspace change did, which is why typing in the filter "fixed" it). Add the missing `theme` branch in both places. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
15391f6399 |
perf(runs): index-bound batch re-run selection with a lossless completed_at bound (WIN-2168) (#10074)
"Re-run all jobs matching filters" selects completed jobs via list_filtered_uuids windowed by started_before/started_after (the timeframe). v2_job_completed has no index on started_at (only completed_at), so that filter alone forces a workspace-wide seq scan — a query observed at ~48s on a large instance. started_at >= minTs implies completed_at >= minTs (a job completes at/after it starts), so adding completedAfter = minTs is a lossless bound: it drops no row the started_at window keeps, but lets the (workspace_id, completed_at DESC) index start the scan at the window's lower edge instead of scanning the whole table. The selected cohort is unchanged (started_at stays the exact filter); this is purely a plan improvement. EXPLAIN: seq scan -> completed_at index scan. Not completedBefore: a job can start in-window but finish after maxTs, and bounding completed_at above would drop it. Scoped to re-run; batch cancel (v2_job_queue, small) is untouched. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bfcec7e8ac |
feat(sessions): support many pending sessions persisted in IndexedDB (#10076)
* feat(sessions): support many pending sessions persisted in IndexedDB Allow several unsent AI sessions to be set up in parallel. Split the transient flag into "in-memory, not yet persisted" (unsent is derived from workspace_id), persist a pending session to IndexedDB on first touch with its own draftPrompt, show pending sessions in the sidebar under the family filter, and reconcile them by pending_workspace_id. The + button reuses the untouched draft in the active family so idle clicks don't pile blank entries; touching one spawns a fresh blank. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(sessions): focus composer when + reuses the untouched draft When there are no pending changes, `+` reuses the active family's untouched draft instead of creating a new session (unchanged). But when the reused draft is the one already on screen, currentSessionId doesn't change, so nothing navigated and the click gave no feedback. Bump a composerFocusRequest nonce in the reuse branch and have SessionWrapper's focus effect depend on it, so the composer re-focuses and the user can type right away. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sessions): per-session debounce for draft prompt flush A single module-level flush timer let a keystroke in one pending draft cancel a sibling draft's pending first-touch flush, so the earlier draft was never written and its typed prompt vanished on reload. Key the debounce per session so parallel drafts persist independently. Also collapse the touch rationale repeated across the preview-tab/collapse/size setters onto persistTouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
207ce8649c |
fix(ai-agent): don't mark repeated tool calls as failed in flow graph (#10075)
* fix(ai-agent): don't mark repeated tool calls as failed in flow graph Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai-agent): cover reporter's mixed repeated-tool-call scenario Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
710a13a59d |
fix(apps): cover script/flow component outputs in deployed-app S3 provenance gate (#10070)
* fix(apps): cover script/flow component outputs in deployed-app S3 provenance gate Deployed apps read S3 files on-behalf of the app author for logged-in viewers (#10048). A confused-deputy guard confines those reads to files the app "produced", but the recent-production check only matched inline `appscript`/ `preview` jobs nested under the app path. Files produced by the deployed script/flow components an app is wired to run (e.g. a SQL query persisted to S3) were therefore denied "File restricted" for every viewer, admins included. Expand the provenance check to also match completed `script`/`flow`/`flowscript`/ `flownode` jobs whose `runnable_path` is one of the app's declared triggerables, and accept the author identity via `permissioned_as = on_behalf_of` (not only `created_by = caller`) so files produced on-behalf of the author are covered. Reads outside the app's declared triggerables stay denied. Adds a regression test seeding a script-kind produced file that reproduces the "File restricted" denial before the fix and passes after. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): key S3 provenance on on-behalf identity + cover flow steps (review) Addresses the CI review on the S3 provenance gate: - P1 (confused deputy): the recent-production check keyed on `created_by = caller`, so a viewer who can run a declared script/flow directly (outside the app, with un-pinned inputs) could craft a result naming an author-only key and read it back through the app as the author. Key provenance instead on the producing job's `permissioned_as` matching the on-behalf identity the download reads as (the author in author-mode); a viewer's direct run has `permissioned_as = viewer` and no longer clears the gate. Drops `created_by` from both the appscript/preview and script/flow branches, closing the same latent hole in the pre-existing inline-script branch. - P2 (dead flow-step branch): `flowscript`/`flownode` jobs have `runnable_path = <flow_path>/<step_id>`, which exact `= ANY(...)` never matched. Split script vs flow triggerable paths; flow kinds now match the flow's own job (bare path) and its step jobs via a `<flow_path>/%` prefix, bounded to declared flows. - P2 (test realism): the regression test now uses the production component-prefixed triggerable key format (`<id>:script/...`), exercises a flow-step-produced key, and asserts a viewer's own direct run of a declared script stays denied (the P1 case). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): tie deployed-app S3 provenance to an app-origination marker (review) Second CI-review round flagged that `permissioned_as` still does not prove a job was app-launched: a runnable configured with its own `on_behalf_of` makes a direct `/jobs/run` resolve `permissioned_as` to that identity (the app author), so a viewer with run access could execute a declared runnable directly, craft an S3 result, and read it back through the app. The flow-path `LIKE fp || '/%'` match also let `_`/`%` in a declared path admit unrelated flows. Introduce a real app-origination marker instead of inferring provenance: - Add `JobTriggerKind::App`; `execute_component` stamps every app-launched job with `trigger_kind = 'app'` + `trigger = <app path>`. A direct `/jobs/run` cannot set this, so it is the authoritative signal that a file was produced *by the app*. - The provenance gate's recent-production check collapses to `trigger_kind = 'app' AND trigger = <this app path>` (+ the 3h window and result containment). This drops the forgeable `created_by`/`permissioned_as`/ `runnable_path`/kind logic entirely and removes the `LIKE` wildcard issue. - Provenance is scoped to THIS app's path, so another app's jobs (even same author) do not authorize this app's reads. Regression test rewritten to the marker model: an app-produced key clears for viewer and admin; a direct run whose `permissioned_as` resolves to the author stays denied (the forgery); another app's output stays denied. Adds `app` to the OpenAPI JobTriggerKind enum. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(apps): assert execute_component stamps trigger_kind='app' at runtime Adds an end-to-end test that runs a real script component through the app runtime (`apps_u/execute_component`) and asserts the enqueued job carries the app-origination marker `trigger_kind = 'app'` + `trigger = <app path>` (not the runnable path). The provenance-gate tests seed the marker directly; this proves the runtime actually produces the exact marker the gate depends on. execute_component commits the job row and returns its id, so the assertion reads the row directly — no worker needed to run the job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(triggers): reject trigger_kind=app for suspended-job reassignment (review) `JobTriggerKind::App` (added for the app-origination S3 marker) became a valid value for the resume/cancel suspended-trigger routes, whose handler derives the table name `<kind>_trigger`. There is no `app_trigger` table, so both endpoints would fail with a missing-relation database error (500). Reject `App` in `get_suspended_trigger` alongside webhook/schedule so it returns a clean 400. Adds a regression test asserting the reassignment route returns 400 (not 500) for trigger_kind=app. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): don't stamp app-origination marker on preview runs (review) The app-origination marker (trigger_kind='app') was stamped unconditionally, including preview mode. A preview lets a `jobs:run` caller supply arbitrary `raw_code` against ANY app path without that app's deployed policy (raw_code with no path/id skips all app authorization), so a preview returning `{"s3":"<author-only-key>"}` would forge the exact marker the S3 provenance gate trusts and read the victim app author's file. Gate the marker on `!is_preview`: only deployed, policy-checked executions are app-provenanced. Preview/editor S3 display does not rely on this marker (the editor routes reads through the force_viewer allowlist), so nothing legitimate regresses. Adds a regression test asserting a preview run's job is not stamped trigger_kind='app'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): editor-authorize preview marker + per-viewer S3 provenance isolation (review) Closes the codex P1 (preview forgery) without breaking editor preview downloads, and adds cross-viewer isolation to the provenance gate. - Preview marker now requires app write: `execute_component` stamps the app-origination marker on a preview only when the caller can EDIT that app (`require_is_writer`), instead of never stamping previews. An app editor already wields the app's author identity (they can deploy a component that reads the same file), so marking their own preview is no escalation and keeps preview-produced S3 results downloadable in the editor; a `jobs:run`-only caller who cannot edit the app still cannot forge the marker. Deployed runs are unchanged (always marked). - Per-viewer isolation: the provenance gate now also requires `j.created_by = <this caller>`. The security boundary stays the un-forgeable `trigger_kind='app'` marker; `created_by` is an additional filter ANDed under it, so it only narrows — a viewer can only download keys their OWN app runs produced, not another viewer's result. Restores the per-caller scoping #10048 had, now safe on top of the marker. Tests: preview marked iff caller can edit the app; cross-viewer isolation (another viewer's app-marked key denied, no admin bypass); direct-run and other-app keys still denied; deployed run still stamped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): require apps:write scope (not just writer ACL) to mark preview provenance (review) require_is_writer checks the user's underlying ACL but ignores token scopes, so a writer's token deliberately scoped to apps:run/apps:read/jobs:run but WITHOUT apps:write could still mark a preview and forge provenance — even though that token cannot deploy the app (update_app requires apps:write), breaking the "any marked caller can deploy equivalent code" rationale. Require BOTH apps:write:<path> scope (check_scopes) AND the writer ACL (require_is_writer) before stamping a preview's app-origination marker. Deployed runs unchanged. Adds a scope-restricted-writer token to the test (apps:run/read + jobs:run, no apps:write) and asserts its preview stays unmarked; retains the full-editor positive case and the non-editor negative case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): never app-provenance preview runs; read editor S3 as the caller (review) Simplifies the preview handling: a preview executes as the *caller* (Viewer mode), never as the author, so its results must be read back as the caller — never author-mode — and must never carry the app-origination marker. This removes the whole `require_is_writer` / `apps:write` / `can_preserve_on_behalf_of` reasoning (which was also unsound: a writer's token or session may not be able to deploy a component running as the app's on-behalf identity, so marking their preview could still escalate). - Backend: mark the app-origination marker for deployed runs only (`!is_preview`). - Frontend: `getS3File` (AppImage/AppPdf/AppDownload) now routes editor/preview reads through the viewer-scoped `job_helpers/download_s3_file` endpoint (reads as the caller), matching what DisplayResult/ParqetCsvTableRenderer already do; only a deployed app view uses the provenance-gated `apps_u` endpoint. This is the path that previously relied on marking previews, so nothing regresses. Test: a preview is never app-provenanced (owner's own preview and a non-editor's both stay unmarked). Cross-viewer isolation, deployed marking, and the reassignment guard are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): app components run on-behalf of the app, not the referenced runnable (review) Root-causes codex's on-behalf-preview finding: `execute_component` was overriding the app's resolved on-behalf identity with the referenced script/flow's OWN `on_behalf_of` (its `on_behalf_of_email`). That is wrong in the app context — the app's execution mode should govern: - A Viewer-mode app could execute a component AS the referenced runnable's on_behalf identity (privilege confusion / escalation), instead of as the viewer. - A preview would run as that identity rather than as the caller, so its S3 output could not be read back as the caller — the download-identity mismatch codex flagged. Always use the app-resolved identity (author in author-mode, caller in viewer/preview); a referenced runnable's own `on_behalf_of` no longer leaks into app execution. Direct `/jobs/run` still honors a runnable's `on_behalf_of` (unchanged). With this, previews always run as the caller, so reading editor/preview S3 as the caller (viewer-scoped `job_helpers`) is unconditionally correct. - Test: the deployed-component e2e now seeds the script with a distinct on_behalf and asserts the component job's `permissioned_as` is the app identity, not the script's. - Also reword the getS3File `configuration` param comment to describe current state only (AGENTS.md comment rule). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(apps): surface 'app' trigger kind in Runs UI; condense provenance comments (review) Addresses codex review nits: - Add `app` to `jobTriggerKinds`, `triggerIconMap` (LayoutDashboard), and `triggerDisplayNamesMap` so app-component jobs (which now carry `trigger_kind = 'app'`) are filterable in Runs and render their trigger info. - Condense the app-origination marker, on-behalf-identity, and provenance-gate comments to state each invariant once in <=4 lines at its relevant site (AGENTS.md comment rule). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5cde2d5b67 |
fix(sessions): sync AI-session preview with workspace edits + stop phantom autosave (WIN-2160) (#10061)
* fix(sessions): sync AI-session editor preview with workspace edits + stop phantom autosave (WIN-2160) Two related draft-sync fixes surfaced by the new AI sessions preview. 1. Session preview went stale after a workspace edit. A session's editor runtime cell (content store + loadedPath) outlives the sessions page: it survives toggling to workspace mode and MRU tab eviction. The shared per-user draft can change while the editor is unmounted — most visibly by editing the same item in the classic workspace editor, or from another device — but on the next mount the load early-returns on the still-set loadedPath and the preview keeps showing the pre-toggle content. Fix: invalidate the cell's loadedPath when SessionEditorTarget unmounts, so the next mount re-fetches the draft as a clean first load. This also sidesteps a Monaco model-reuse race (a force-reload that remounts the editor while the old one is still disposing renders a stale model) and prevents the outbound draft-sync from posting the stale store back (ready() stays false until the reload lands). Applies to all three editor kinds (script, flow, raw app) since they share SessionEditorTarget. 2. Opening a deployed script in the full-page editor autosaved a phantom draft with no user change. The deployed baseline carries a server-derived assets: [] that the editor's draft value never reproduces, so draftValuesEqual never matched baseline, discardIf returned false, and the settle-time write posted a no-op draft. Fix: ignore assets in the draft-vs-baseline comparison (it's derived from content, so it can't mask a real change). Fixes WIN-2160 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sessions): condense teardown-invalidation comment to repo comment-length rule --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ff774c46bf |
feat: add per-workspace job-retention override (#10050)
* feat: add per-workspace job-retention override (EE) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 2ba6a2a75b6fc97858b306b2c98ada481e363c10 This commit updates the EE repository reference after PR #658 was merged in windmill-ee-private. Previous ee-repo-ref: e7fb36acd813cd717bcf05f5aafbf81de271d618 New ee-repo-ref: 2ba6a2a75b6fc97858b306b2c98ada481e363c10 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
04eb7ddd39 |
fix: clearer errors on auto-draft save failure (WIN-2157) (#10053)
* fix: clearer errors on auto-draft save failure (WIN-2157) When an autosave draft save fails, the cloud indicator now surfaces the backend reason on hover (native title tooltip) in addition to the existing click popover, so the cause is discoverable without a click. Backend now returns a clearer, actionable message: - `require_can_write_path` distinguishes a malformed path (unrecognized namespace prefix -> BadRequest) from a genuine permission denial, and the deny message spells out where the user *can* write. - `require_owner_of_path` no longer panics with an out-of-bounds index on a malformed single-segment path (e.g. a bare `u`/`f`); it returns a clear BadRequest instead. Covered by a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: trim narrative comment to invariant in drafts.rs (WIN-2157) Address CI review (AGENTS.md: comments record constraints, not narration, ≤4 lines): keep the malformed-path invariant, drop the motivation tail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: don't let a malformed stored draft 400 the draft listing (WIN-2157) Address CI review (P1): require_can_write_path can now return BadRequest for a malformed path, and list_drafts propagated it — so a single malformed stored draft row (the draft table has no path constraint; legacy/admin-authored rows may be malformed) would make GET /drafts/list return 400. Treat BadRequest like NotAuthorized there: the row is simply not writable. Verified e2e on EE — listing returns 200 with can_write false for the malformed rows. Also trim "unchanged"/"still" drafting-history narration from the regression test comments (P2, AGENTS.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: compress list_drafts comment to 4 lines (WIN-2157) Address CI review P2: keep the constraint (draft table has no path constraint) and the invariant (one malformed row must not 400 the listing) within the AGENTS.md ≤4-line limit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a89b896ce5 |
fix(frontend): mint draft path for new SDK builder items so autosave attaches (#10056)
Fixes WIN-2159 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7d02d9a1e4 |
fix(frontend): keep draft autosave alive after AI-session round-trip (#10052)
* fix(frontend): keep draft autosave alive after AI-session round-trip A UserDraft entry is shared by refcount across the components editing the same draft — notably an AI-session preview and the nav editor on either side of the Workspace<->AI Sessions toggle. The entry's autosave mirror was a $effect.root created inside whichever component first acquired it; when that component (the session preview) unmounted while the returned-to nav editor still held a refcount, the mirror stopped firing even though the entry lived on — silently killing autosave in the workspace editor for scripts, flows and (raw) apps. Move the cell out of the mirror root (so handles survive) and re-home the mirror to each new acquirer, so it is always owned by a mounted component. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(frontend): record mirror-ownership invariant on releaseEntry Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): preserve sync baseline across mirror re-home Addresses a re-home edge case (Codex review): the replacement mirror rearmed the first-write skip, so a draft edit the outgoing mirror had not yet observed (e.g. a session edit still pending at the Workspace<->AI Sessions handoff) was swallowed as the new baseline instead of POSTed, dropping the final change. Persist the serialization baseline on the entry (mirrorBaseline) and, on a re-home, seed the mirror from it without re-arming the skip — so a genuine unobserved change still syncs while an unchanged inherited value still doesn't POST. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): make draft autosave mirror component-independent Replaces the re-home approach (Codex review): re-homing the mirror to the last acquirer assumed LIFO holder lifetimes, which the sessions UI breaks — it keeps multiple warm session previews mounted at once, so two warm previews of one draft share the entry and closing the newer one killed autosave in the surviving older one. Instead create the entry's mirror $effect.root in a microtask, where no component/effect is active, so it is a true top-level root owned by the ENTRY: it survives every holder unmounting and is disposed only at refcount 0. Removes the re-home/baseline bookkeeping entirely. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(frontend): condense mirror-deferral comment per review Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1e192f2d86 |
feat(apps): authorize deployed-app S3 reads on-behalf of the author for logged-in viewers (#10048)
* feat(apps): authorize deployed-app S3 reads on-behalf of the author for logged-in viewers A logged-in user viewing a deployed app now reads S3 files (rich result, table/image/PDF preview, CSV export, download, metadata) the same way an anonymous viewer already does: on-behalf of the app author per the app policy's execution_mode, gated by an app-provenance check — instead of against the viewer's own S3 permissions. This aligns S3 with every other thing an app does (scripts, flows, resources all already run on-behalf of the author) and lets an operator who lacks folder S3 permission still see data rendered inside the app. The raw job_helpers/* S3 API stays viewer-scoped: a viewer who lacks folder permission is still denied there. Only which endpoint the app frontend uses for logged-in deployed viewers changes. Backend: - Add app-scoped, provenance-gated apps_u/* variants for all S3 display ops (download_s3_file already existed; add download_s3_parquet_file_as_csv, load_file_metadata, load_file_preview, load_parquet_preview, load_csv_preview, load_table_count). Each routes through one shared helper (app_s3_on_behalf_and_provenance) that scope-confines an app embed token, resolves the on-behalf identity, and runs the provenance gate ONCE before dispatching to the EE *_internal S3 helpers. - Close the confused-deputy hole in check_if_allowed_to_access_s3_file_from_app: the unconditional Ok() bypass for a logged-in, non-embed session now only applies in viewer execution mode (where the on-behalf identity IS the viewer, so the viewer's own permissions still bound the read downstream). Author-mode reads (anonymous/publisher) always enforce provenance, for anonymous and logged-in viewers alike, so a viewer cannot launder the author's S3 permissions with an arbitrary file_key. Frontend: - Route the deployed-app view through apps_u/* using the app-viewer isEditor signal instead of login state (the old $userStore proxy wrongly sent logged-in deployed viewers to the viewer-scoped job_helpers API). Editor and preview keep viewer identity via job_helpers. execution_mode: viewer remains the escape hatch for per-viewer S3 enforcement. Fixes provenance-gated S3 display for logged-in operators on deployed apps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(backend): document cargo features, restarting the dev backend, and filesystem object storage The dev backend runs `cargo watch --features quickjs` by default, which omits S3, EE, MCP, and non-JS runtimes — feature-gated routes then 404 or return a "requires <feature>" stub at runtime. Add a backend/CLAUDE.md section that: - explains that you must restart the backend with the appropriate features to exercise gated functionality, with the pid/cwd-scoped restart recipe (never pkill target/debug/windmill) and the PORT=$BACKEND_PORT gotcha; - documents what each commonly-toggled feature gate does (private, enterprise, license, parquet, duckdb, language runtimes, mcp, trigger kinds, no_auth) plus common combinations; - documents using the built-in FilesystemStorage large-file storage for dev workspace object storage (hidden from the UI dropdown; set via edit_large_file_storage_config), including the advanced_permissions shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): don't flatten inner query in app-scoped S3 preview routes axum's `Query` uses `serde_urlencoded`, which cannot deserialize the typed (numeric/bool) fields of a `#[serde(flatten)]`-ed struct and 400s on `limit` / `offset` ("invalid type: string, expected u32"). The app-scoped load_csv_preview / load_parquet_preview / load_table_count routes flattened LoadPreviewQuery / LoadCountQuery, so their previews were broken. Restate the fields directly on the outer query structs (with an into_inner() to rebuild the inner query) and extend the CE OSS stub to match. Also bumps ee-repo-ref.txt for the companion EE csv-separator panic fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address CI review — nested DisplayResult routing, byte-range contract, docs, tests - [P1] Thread `appPath` into the nested `DisplayResult`s (render_all children and the expanded-result drawer) so logged-in deployed viewers route nested/expanded S3 tables, images, PDFs, and downloads through `apps_u/*` too, not job_helpers. - [P2] Mark `read_bytes_from`/`read_bytes_length` required on the `apps_u/load_file_preview` route (they are non-optional in LoadFilePreviewQuery), and mirror the full query shape in the CE OSS stub so the byte-range contract is enforced identically on CE and EE. - [P2] Fix the backend retrigger command in backend/CLAUDE.md: cargo watch runs from `backend/`, so `touch README.md` (not `backend/README.md`). - [P2] Trim app_s3_onbehalf.rs comments per AGENTS.md (state the invariant once, no drafting-history narration). - Extend the integration test to cover the table-count, csv-preview (numeric limit/offset deserialization), and file-preview (byte-range required) routes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(apps): tighten S3 provenance-gate comments per AGENTS.md Consolidate the viewer-mode / author-mode rationale to ≤4 lines at each branch of the gate, and drop the repeated explanation from the shared app_s3_on_behalf_and_provenance doc comment (which now just states what the helper does). No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to f292a1040da6a667ce7c22abf63ec0debfdd480f This commit updates the EE repository reference after PR #657 was merged in windmill-ee-private. Previous ee-repo-ref: a582389084eb363997cb5e8053f29220e0d3eaec New ee-repo-ref: f292a1040da6a667ce7c22abf63ec0debfdd480f Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
e668193a93 |
fix(frontend): don't re-seed empty editor on stale ?new_draft after draft exists (#10044)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8343203ec2 |
feat(mcp): add multi-workspace MCP tokens via the gateway endpoint (#10043)
* feat(mcp): add multi-workspace MCP tokens via the gateway endpoint A single MCP token with no bound workspace (workspace_id NULL + mcp scope) now works across every workspace the token owner can access, served through the existing /api/mcp/gateway endpoint. This avoids having to register one MCP server entry per workspace in clients like Claude/Cursor. In multi-workspace mode the runner exposes a synthetic `list_workspaces` tool plus the generic API endpoint tools, each workspace-scoped one gaining a required `workspace_id` argument (mirroring the proxy pattern users built externally). Per-workspace scripts/flows are not enumerated to avoid flooding the tool list — they are run via runScriptByPath/runFlowByPath with an explicit workspace_id. Auth is resolved per tool call: the gateway middleware detects a workspace-less mcp token and marks the request MultiWorkspaceMcp, and the runner resolves a per-workspace ApiAuthed from the raw token via the AuthCache (validating membership; superadmins may act in any workspace). Single-workspace tokens are unchanged. Frontend: the MCP token creation flow gains an "All workspaces" option that produces a workspace-less token and the gateway URL. Fixes WIN-2153 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(mcp): cover multi-workspace endpoint tool transformation Unit tests for endpoint_tool_to_mcp_tool_multi and list_workspaces_tool: workspace-scoped tools gain a required workspace_id arg, global tools are left unchanged, workspace_id is not duplicated, and list_workspaces takes no arguments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mcp): forward script/flow args for runScriptByPath/runFlowByPath These endpoints have an additionalProperties body (no declared properties), so build_request_body previously returned an empty body and dropped every script/flow argument. This was latent for the per-path run endpoints and became load-bearing in multi-workspace mode, where scripts/flows can only be run via runScriptByPath/runFlowByPath — parameterized runs silently lost their arguments. build_request_body now forwards all arguments not consumed by a path/query parameter for pass-through (additionalProperties) bodies, keeping the strict declared-only behavior for endpoints with explicit properties. The runner strips the synthetic workspace_id argument before dispatch so it can't leak into the forwarded body. Reported by Codex review on #10043. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(mcp): note workspace_id requirement in multi-workspace tool descriptions Workspace-scoped tools already gain a required workspace_id parameter (with its own schema description) in multi-workspace mode, but the tool's prose description was unchanged. Append a note so models/clients that read the description text know to pass workspace_id (and to call list_workspaces first). Global tool descriptions are left untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(mcp): trim multi-workspace tool/arg descriptions The workspace_id note repeats across every workspace-scoped tool in each tools/list, so keep it terse: description suffix "Requires `workspace_id`." and arg description "Target workspace id (from list_workspaces)." to avoid spending tokens on repeated boilerplate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mcp): enforce script/flow path scopes for multi-workspace run-by-path In multi-workspace mode runScriptByPath/runFlowByPath are the only way to run scripts/flows, but they were authorized against the endpoint scope only — never the caller's mcp:scripts:/mcp:flows: path scopes. A granular token could run items outside its allowed paths (e.g. mcp:scripts:f/team/* + mcp:endpoints:* running f/other/secret), and a mcp:endpoints:* token could run arbitrary scripts. Now these two endpoints are authorized by the script/flow scope of the requested path (matching single-workspace mode's per-item tools): exposed in list_tools only when the token grants some script/flow (McpScopeConfig::has_any), and at call time the path is checked via is_allowed("script"/"flow", path). Verified e2e: mcp:scripts:f/team/* runs f/team/* but is denied f/other/*; mcp:endpoints:* alone no longer exposes or runs run-by-path. Reported by Codex + Pi review on #10043. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mcp): deny run-by-path for mcp:favorites multi-workspace tokens mcp:favorites sets granular=false, so the previous run-by-path scope check (gated on `granular`) was skipped entirely — a default "Favorites only" all-workspaces token could run any script/flow by naming its path, bypassing the favorites restriction. Favorites are an enumerated set reachable only through per-item tools, not by arbitrary path, so they grant nothing for run-by-path. has_any() now returns true only for mcp:all (not favorites), and the call-time check drops the `granular` gate and relies on is_allowed() directly (already false for favorites, true for mcp:all, pattern-matched for granular). Verified e2e: mcp:favorites no longer exposes or runs run-by-path; mcp:all still runs; granular script scopes still path-enforced. Reported by Codex review on #10043. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3b0781761b |
fix(frontend): show nested restart button for subflows nested in containers (#10042)
* fix(frontend): show nested restart button for subflows nested in containers Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): keep nested-restart flat fallback anchored to the leaf Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1c88242849 |
fix(frontend): show optimistic user message and fork-creation label before beforeSend (#10037)
* fix(frontend): show optimistic user message and fork-creation label before beforeSend In AI chat, `sendRequest` previously set `loading` and pushed the user message only after the `beforeSend()` hook completed. For forked sessions `beforeSend` runs several sequential API calls (materialize session, flush files, create workspace fork, load copilot config) that take seconds, while the composer clears its textarea immediately. The result: the message text vanished into a void with no bubble and no loading indicator until the fork finished. Now the user bubble and loading indicator are shown optimistically before `beforeSend`, with context elements and the snapshot attached afterwards. A general-purpose `loadingLabel` lets any `beforeSend` hook describe its pre-flight work; the session hook sets "Creating workspace fork..." around `commitSessionWorkspace`. If `beforeSend` throws, the optimistic bubble and loading state are rolled back. Fixes WIN-2150 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): make Stop/Escape cancel the send during the beforeSend pre-flight Showing the loading indicator before beforeSend also exposed the Stop button and Escape handler during "Creating workspace fork...", but the abort controller was created after beforeSend, so cancel() had nothing to abort and the request still fired once the pre-flight resolved. Create the abort controller before beforeSend and check `signal.aborted` after it: a Stop/Escape during the pre-flight now rolls back the optimistic turn and skips the request. Factor the rollback into a shared helper reused by the beforeSend-failure and cancel paths, and refresh the now-stale beforeSend doc comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): restore prompt and hand off queued message on pre-flight cancel The pre-flight abort check rolled back the optimistic turn and returned early, skipping the recovery the main cancel path runs. Because the input clears its composer on send, a Stop/Escape during "Creating workspace fork..." lost the typed prompt from both the bubble and the composer, and bypassed the queued-message handoff. Mirror the main "cancelled before usable output" path: restore the prompt to the composer via the same restoreInstructions helper, or auto-send a queued message when one is taking over, and return true so a parent queued-flush doesn't re-queue the cancelled turn. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c5060a1e9a |
fix: scope AI-session flow/script editors to the session workspace (#10025)
* fix: scope flow script-edit drawer to session workspace and fix scroll Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: scope flow schema inference to session workspace Thread an optional workspace through loadSchemaFromPath/loadSchemaFlow/ loadSchemaFromModule/loadFlowModuleState/initFlowState/pickScript/pickFlow and pass the op (session) workspace at fork-context call sites, so a flow opened in an AI session resolves path-referenced scripts/subflows against the session workspace instead of the nav workspace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: scope script editor log panel and git-repo pickers to op workspace LogPanel and the ansible git-repo viewer/picker read the nav workspace directly; pass the script editor's op workspace so past-test results/logs and git-repo resource/file lookups target the session workspace in a fork. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: correct session pipeline trigger-editor workspace comment The comment claimed session activation syncs $workspaceStore; SessionPicker intentionally does not, so trigger create/edit/delete from a fork session's pipeline canvas writes to the nav workspace. Document the known limitation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: forward op workspace to git-repo S3 file browser GitRepoViewer scoped its own calls to the op workspace but rendered the nested S3FilePickerInner without workspace={ws}, so the file list/preview/ metadata still queried the nav workspace with a session-workspace prefix. Addresses Codex review on #10025. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
15f9e9b48f |
perf: skip redundant retry-chain job query for successful top-level scripts (#10035)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
03535691d6 |
fix(frontend): open new script/flow/app in AI session (not-found + friendly tab) (#10028)
* fix(frontend): open new script/flow/app in AI session without "not found" "Open in AI session" on a never-deployed item opened the session preview against the friendly live-edited path (script.path / $pathStore) instead of the URL draft path the editor loads and saves by, so get-by-path 404'd. It also flushed only queued autosaves, so an untouched new item — which never triggered autosave — had no draft row to load at all. Target the URL draft path (userDraftPath / liveEditorDraftStoragePath; raw-app already used appPath), and add UserDraft.forcePersist to materialize a brand-new draft in beforeOpen, gated to never-deployed items where there is no deployed baseline to discard against. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): label a new session preview tab by its friendly name A never-deployed item's preview tab read draft_<uuid> instead of the typed/ auto name. The sessions page can't reactively read a runtime cell's state across reactive roots, so the live editor (SessionEditorTarget, handed the runtime as a prop) now stamps a transient friendlyLabel onto the tab model — which the page does observe — via a pure draftFriendlyLeaf helper. Unifies scripts, flows and raw apps through one path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): address review nits on AI-session open - Flow drawer (FlowEditorDrawer) mounts FlowBuilder with no liveEditorDraftStoragePath, so gating the AI button solely on it hid the session entry point there; fall back to $pathStore (the pre-PR behavior for those deployed-flow drawers) while the main editor still prefers the URL draft path. - Clear a tab's stamped friendlyLabel when it is retargeted, so a draft tab's friendly name no longer lingers after navigating to a plain page. - Trim the repeated persist-hook comments to satisfy the AGENTS.md comment rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> |
||
|
|
5a460dbec6 |
fix: accept bunnative language in AI chat flow step validation (#10030)
* fix: accept bunnative language in AI chat flow step validation Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: regenerate copilot flow schema from openflow spec Run gen_openflow_schema.sh + minifiedOpenflowJson.sh instead of hand-patching. Also syncs three fields the checked-in generated files had drifted from since the last regen (reasoning_effort, reasoning_token_delta streaming event, aiagent tag). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: regenerate system prompts for bunnative openflow schema Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5387076c1c |
fix(frontend): persist per-session preview panel resize width (#10031)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3704d00956 | fix: sync theme into session page preview iframes on toggle (#10018) | ||
|
|
d7a9b46ab9 | fix(sessions): open test pane when enabling debug so the debug UI is visible (#9998) | ||
|
|
9036ac789f |
fix(frontend): name the draft in AI chat test-run confirmation (#10024)
* fix(frontend): name the draft in AI chat test-run confirmation
The confirmation card shown before an AI-chat test run displayed a
static, generic header ("Run script test"). Make it name the target
and clarify it runs the user's draft.
- Tool.confirmationMessage now accepts a function of the parsed args;
shared.ts resolves it before setting the tool status.
- test_run_script/flow/step (global chat) build a dynamic header
naming the script/flow/step, e.g. "Run a test of your draft of X".
- In-editor script/flow test-run tools say "Run a test of your draft".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): fall back to tool name in YOLO tooltip for function messages
The auto-accept ("bypassed in current mode") tooltip rendered
confirmationMessage directly. Now that it can be a function of the call
args, render the tool name instead of the function source there.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): use neutral wording in test-run confirmations
The global test-run tools fall back to deployed content when no draft
exists, so "your draft" could contradict what actually runs. Drop the
draft claim and just name the target: "Run a test of X".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
368fd2d9e4 |
fix: resolve fork family/picker for superadmin visiting a non-member workspace (#10023)
* fix: populate fork base picker for superadmin visiting a non-member workspace Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: resolve fork family for superadmin across sidebar picker and scope header Extract the superadmin-visited-workspace fallback into a shared useForkableWorkspaces composable and apply it to WorkspaceFamilyPicker and WorkspaceScopeHeader so the sidebar fork picker and its fork-count trigger resolve the family for a superadmin viewing a non-member workspace. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: resolve superadmin-visited workspace name in the scope trigger chip The sidebar scope trigger next to the fork picker read $userWorkspaces directly, so a superadmin viewing a non-member workspace saw its raw id instead of the resolved name/family. Thread the folded-in forkable list into WorkspaceScopeTrigger, and trim the now-duplicated per-site rationale comments to a pointer at the composable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c537d45e49 |
fix(frontend): persist forked "Copy of X" script drafts (#10021)
* fix(frontend): persist forked "Copy of X" script drafts
Forking a script ("Copy of X"), hub-forking, or seeding a new draft from
a URL/YAML/JSON import opened the editor with pre-filled content, but the
saved draft never appeared in the scripts list.
The edit route suspends autosave for every `?new_draft=true` load
(`UserDraft.stopSync`) so the seed write doesn't post as the user's first
edit, expecting ScriptBuilder to lift it. ScriptBuilder's `restartSync`
only ran inside `if (script.content == '')`, so a non-empty seed (fork /
hub / import) skipped it and left autosave suspended for the session —
both autosave and explicit Ctrl+S then silently no-op'd, so the draft was
never written and never listed.
Add an `else if` branch for pre-filled `new_draft` seeds that runs the
same stores-gated restart cascade (restart only, no template seeding),
restoring parity with the empty-new-script flow. The empty-seed block is
unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: condense scheduleRestartSync comments to ≤4 lines
Address Codex review P2: trim the helper and new-branch comments to the
core invariant per AGENTS.md's comment-length rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c139eed631 |
fix: session preview tab labels, splitter hover, and diff-drawer sizing (#10008)
* fix: show pending friendly path in new raw app session tab Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: reveal a subtle rounded grabber on the sessions chat/preview splitter on hover Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: cap session diff blocks so each item's card fits the drawer viewport Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: floor flow diff cap at its min height and tidy diff/splitter comments Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |