fix the seven blockers from the UX review - #40
Merged
3 commits merged intoAug 30, 2026
Merged
Conversation
Fixes, from a read of the whole tool surface: - snap_to_beats returned `timeline()` where every other read uses `working_timeline()`, so inside a task it handed back the user's untouched cut next to a `cuts_aligned` count of moves that were not in it. - timeline_summary promised "any per-track gaps" and returned none. It now reports them, head gap included — a hole is black picture, which an agent never sees for itself. - download_speech_model filled the cache but never selected the model, so naming one and then analyzing still transcribed with the old one. Add set_speech_model, the write side of transcription_status. - core_err mapped everything to internal_error; a stale id or an out-of-range value is the caller's mistake, and invalid_params is what tells a model to fix its arguments instead of giving up. - Clamp the sizes a model picks out of a schema description (waveform buckets, frame widths), the way skim_asset already does. Optimizations: - Build the tool router once. #[tool_handler]'s default router expression is re-evaluated by call_tool, list_tools and get_tool, so every request rebuilt all 85 routes — ~250us of release-build work per tool call for an identical result. - platform_check resolved the cut summary twice (two timeline deserializes, two asset queries) for one answer.
- Release the project lock before emitting `project-changed`. The event makes the GUI re-fetch and that re-fetch takes the same lock, so the notify belonged outside. An `edit()` helper does the whole shape once and takes the lock/mutate/notify boilerplate out of 61 tools. - export now reports progress and can be cancelled. A render runs for minutes; an agent could neither tell a slow export from a hung one nor abandon settings it already knew were wrong. Both ride the protocol — the client's `progressToken` and the cancellation token rmcp trips on `notifications/cancelled` — and a cancelled render deletes its half-written file from inside the blocking job, since cancelling the request can drop the handler future. - Add import_asset, so an agent can load media rather than only rearranging what it was handed. It deliberately does not stage: a file on disk is not an edit to the user's cut. - set_mask no longer swallows a failed timeline read. `.ok()` made a read that failed indistinguishable from "this clip has no mask yet", and silently reset every field the caller did not name. - set_speech_model emits `speech-model-changed`, which the webview listens for. The GUI reads transcription status only at launch, so a model an agent picked stayed invisible in the picker until restart.
Each one was the UI saying or doing something that wasn't true. - The Inspector was gated on a clip being selected, so its Text overlays section — which belongs to the timeline, not to any clip — was unreachable until you clicked one. It is now always mounted (it already had an empty state) and toggled from the toolbar, so closing it is also how you get the width back. - The timeline's transition badge read `crossfade ? 'Crossfade' : 'Dip to black'`, mislabelling nine of eleven kinds. `transitionLabel()` already existed, unused. - "Drop media to start" now does: `+page.svelte` listens for Tauri's drag-drop event, filters by the same extension list the picker uses and runs the same import. - The export dialog opened on Web 1080p, so a 9:16 project opened its export already landscape and the readiness panel warned about the frame the user had just chosen. It now opens on the project frame. - The agent panel showed a green "live" dot whether or not anything was connected. A streamable-HTTP client holds no connection between calls, so `agent_status` reports when an agent last spoke to the endpoint (stamped in `lock_agent` and in `get_info`) and the panel judges from the age: connected / away / nothing yet. - Import ran a full analysis per asset with no way out — a model download and then minutes of inference each. Analysis is now cancellable end to end: `CancelFn` beside the `ProgressFn`, checked between steps, polled about once a second inside the ffmpeg whisper run, and per chunk during the model download (keeping the `.part` file so a retry resumes). The status bar names the step, says what is queued behind it, and stops it. - `editor.error` was recorded and never rendered, so a `.kerf` that would not open opened as silence. It is a dismissible banner now, and `editor.loading` shows in the status bar.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Each of these was the UI saying or doing something that wasn't true.
ui.inspectorOpencrossfade ? 'Crossfade' : 'Dip to black'— 9 of 11 kinds mislabelledtransitionLabel(), which already existed unusedonDragDropEvent→isMediaPathfilter (same extensions the picker uses) →editor.importPaths, plus a drop overlayinitialExport()opens on the project frame — the preset that renders it when one matches, else the default preset with its resolution clearedagent_statusreports when an agent last spoke to the endpoint; the panel judges from the ageeditor.error/editor.loadingset and never renderedNotes for review
Agent presence (#5). A streamable-HTTP client holds no connection between calls, so there is no socket to report as open.
mcp::LAST_AGENT_ACTIVITYis stamped inlock_agent— the one choke point every agent-side project access goes through — and inget_info, sinceinitializeis the moment an agent is known to be there. The panel pollsagent_statusevery 5s and shows connected / away / nothing yet.Cancellation (#6) is real but not instantaneous.
CancelFnsits beside theProgressFn:-stats_period 1), which is the step that actually runs for minutes — the child is killed;.partfile so a retry resumes rather than re-fetching 148 MB.A cancelled pass caches nothing: a half-analyzed asset would read as analyzed and its missing transcript as "no speech". The Stop tooltip says exactly what it does.
API change.
Transcriber::transcribeis now a 3-arg trait method.analyze_with_progress/analyze_asset_media_with_progresskeep their signatures and delegate with a no-op cancel, so no existing caller moved.Checks
cargo test --workspace --no-default-features297 pass ·cargo clippyclean ·cargo fmt --checkclean ·cargo checkbuilds with the ffmpeg default features and with--features whisper·bun run check0 errors ·bun test66 pass.The two MCP commits underneath (
53ad658,d5389bb) were already unpushed onmainand come along with the branch.🤖 Generated with Claude Code
https://claude.ai/code/session_01X1y7fErQC6TsYqCw8mXnFv