Simplify session creation: modal → right-drawer quick-start#1801
Simplify session creation: modal → right-drawer quick-start#1801kasiazjc wants to merge 14 commits into
Conversation
Lets a user mark a primary agentic tool for quick-start session creation. Lives in UserPreferences (already a flexible JSON blob), so no DB migration is needed.
Lets a user set/clear their primary tool for quick-start session creation, backed by the new preferences.default_agentic_tool field.
Resolves the tool "Add session" should use with no picker: explicit default_agentic_tool preference, else the user's most-recently-created session's tool, else unresolved (caller falls back to a tile picker).
One mechanism behind both tool-choice entry points: the quick-start empty-state tiles (nothing to replace yet) and the in-drawer "Switch tool" affordance (silently swaps out the current, never-prompted session). Creates a session with the chosen tool, resolves MCP inheritance the same way NewSessionModal already does, remembers the pick as the new quick-start default, and navigates to the result. Exposed via AppActionsContext (onChooseAgenticTool/availableAgents) so it's reachable from inside SessionPanel without prop drilling.
Wires every "Add session" entry point (branch card, board canvas node, event-stream panel) to skip the old blocking modal: when a tool resolves (explicit default or this user's last-used tool), the session is created immediately and the drawer opens directly on it. When nothing resolves (first-ever session for this user), the drawer still opens immediately instead of a modal, showing PendingToolChoicePanel — tiles for each available tool, matching the session drawer's own header/footer chrome so the panel doesn't visibly change shape between "picking a tool" and "using a session". Picking a tile creates the session via the same chooseAgenticTool action and swaps this panel out for the real one. The old NewSessionModal survives as an "Advanced setup..." escape hatch reachable from the empty state, for the rare case someone wants to set title/prompt/MCP/env vars before the session exists.
Replaces the static header title with a click-to-edit field: "Untitled session" placeholder when there's no explicit title or description, click swaps in a borderless Input seeded from the explicit title only, Enter/blur saves via the existing onUpdateSession action, Escape cancels without saving.
Reuses the same chooseAgenticTool action the quick-start empty state uses, with the current session passed as replacingSessionId. Only offered via the "..." menu when the session has zero tasks (never prompted yet) — tool is a per-session SDK choice baked into every task, so it can't safely change once a conversation exists.
Cheap, synchronous heuristic (collapse whitespace, cut at ~60 chars on a word boundary) with no LLM call — tool-agnostic since it only reads Task.full_prompt, already stored regardless of which agentic tool ran it. Wired into the existing task-completion hook in TasksService.patch: fires once, when a session's first task completes successfully with still no explicit title. A user-set title (including one typed while the task was still running) always wins, since by the time the hook runs session.title would already be non-empty.
Tiles render per available agent, picking one calls onChoose with the tool id, and the advanced-setup escape hatch only shows up when a handler is provided.
Also hardens canSwitchTool against a missing session.tasks array (session.tasks is typed as always-present, but defend anyway rather than let a malformed/legacy record crash the whole panel) — the existing SessionPanel test only avoided this because it doesn't wire onChooseAgenticTool, short-circuiting before the access.
Review: quick-start session creation flow (high-effort pass)Verified directly against source (not just diff context) — Blocking
Non-blocking / worth a look
Confirmed non-issue
Cleanup (reuse / simplification, lower priority)
|
…witch-tool TOCTOU Review findings #1 and #2 on PR #1801: "Switch tool disappears after the first task" and agentic_tool immutability were only client-side conventions — nothing on the server stopped a direct sessions.patch from changing agentic_tool on a session with existing tasks, or the switch-tool swap from cascade-deleting a session that gained a task between the picker opening and the tile being clicked (TOCTOU on a multiplayer canvas). SessionsService.patch() now rejects an agentic_tool change on a session that already has tasks, for both single and batch patches. remove() refuses a swap-triggered removal (marked via query._swapReplace) when the target session has gained a task since the swap was initiated, while leaving a normal user-intentional delete of a session with history unaffected.
Client side of review finding #2: chooseAgenticTool now marks the "replace the old session" removal with query._swapReplace so the daemon's TOCTOU guard (SessionsService.remove) can tell it apart from a normal user-intentional delete. Also addresses finding #6: a failed cleanup removal (including the new guard's Conflict) was only console.error'd — the flow continued as if nothing happened, leaving an orphaned session with zero user-facing signal. Now shows a themed error message so the user knows the old session may still be visible on the branch.
…first Review finding #3: auto-title was gated on session.tasks?.length === 1, so a degenerate first prompt (image-only paste, or any text-empty prompt) permanently disabled auto-titling for that session — the heuristic never got another chance. Same for a first task that failed and only a later retry succeeded. The gate is now just "the session still has no title" — fires on any completed task, keeps retrying until a non-empty title is derived or an explicit title is set (which always wins, unchanged from before).
…t the old one Review finding #8: the "Switch tool" modal highlighted the session's current (old) tool tile during the async request instead of the tile the user just clicked — the opposite of PendingToolChoicePanel's choosingTool pattern, which correctly highlights + overlays the clicked tile. There was also no loading indicator at all. switchingTool now tracks the clicked tool id (string | null) instead of a bare boolean, so the grid highlights the right tile and shows the same Spin overlay the quick-start empty-state picker already has.
Fixed the 3 blocking findings, plus one low-risk cleanupAll server-side, covered by new unit/integration tests against a real database ( #1 — #2 — TOCTOU on the "Switch tool" swap. The client now marks the replace-session removal with #3 — Auto-title no longer permanently disabled by a degenerate first prompt. Dropped the #8 (non-blocking, fixed anyway) — "Switch tool" now highlights the tile you just clicked instead of the session's old tool, with the same loading overlay #6 (non-blocking, fixed as part of #2) — a failed swap-cleanup removal (including the new Conflict case) now surfaces a themed error message instead of only Left for later — flagging, not fixing blind
Full lint/typecheck/test suite green across core (2737), daemon (1405, +10 new), and ui (735, +7 new). Rebased on current |
Summary
Starting a session no longer opens a modal you have to fill in and dismiss. "Add session" now opens the session drawer directly, ready to type into.
Why / context
The old flow: click "Add session" → modal → pick a tool → (often) same tool as last time → fill in a title you'll probably rename anyway → submit → then the session drawer opens. That's a form in front of every single session, for a choice that's almost always "same as last time."
This follows the "quick-create" pattern from Linear/Notion (create first, refine inline) and matches how Claude/ChatGPT auto-title new threads. The friction removed compounds — session creation is the single highest-frequency action on the board.
Nothing that needed the modal is gone: MCP/tool config for the rare cases that need it upfront is one click away via "Advanced setup...". Board-zone flows that intentionally require a picker (
show_picker,always_newtriggers) are untouched — this branch makes zero changes toSessionCanvas.tsxor the zone-trigger modal (confirmed by diff), see Validation below for the caveat on live-testing that path.Implementation notes
resolveDefaultAgenticTool: explicitpreferences.default_agentic_tool→ most-recently-created session's tool →undefined(no hardcoded fallback).chooseAgenticToolis the single action behind both tool-choice surfaces (empty-state tiles and the in-drawer "Switch tool" affordance) — creates the session, resolves MCP inheritance the same way the old modal did, remembers the pick as the new quick-start default, and navigates to the result. "Switch tool" passesreplacingSessionIdto swap out a never-prompted session transparently.agentic_toolcan't change mid-session (different SDK per tool), so "Switch tool" is hidden the moment a session has run one task.TasksService; fires once, only if the title is still empty when the first task completes — an explicit title (even one typed mid-task) always wins.default_agentic_toollives in the existingUserPreferencesJSON blob — no migration needed.77800e48, progressive board mount) that was already merged tomainand is just part of this branch's history — not new work in this PR.Validation / test plan
chooseAgenticTool, auto-title heuristic,PendingToolChoicePanel, inline title edit, switch-tool affordance). All green: core 2737, ui 726–734, daemon 1392–1395 passed across multiple runs.main(77800e48), no conflicts, re-verified lint/typecheck/tests clean after rebase.show_pickertrigger flow — not yet confirmed via live click-through. Diff-isolation gives high confidence (this branch touches zero lines inSessionCanvas.tsx,ZoneConfigModal.tsx, or the zone-trigger modal), but I was not able to reliably reproduce the drag-and-drop trigger in the environment used for this pass (Docker-based env unavailable). Remaining live QA, including this flow, to be finished in a follow-up pass.Risks / rollout / rollback
Out of scope / follow-ups
ai_titleevent some CLIs already emit is a nice-to-have follow-up, not required for this to ship.