Files

73 lines
6.0 KiB
Markdown

# Phase 2 Research: Lifecycle Command Safety
**Date:** 2026-03-29
**Source:** Manual orchestration fallback in current session (subagent spawning unavailable in this environment)
## Goal to Plan For
Phase 2 must make runtime lifecycle actions trustworthy after Phase 1's draft ownership work. The user should be able to:
- start, stop, and retry the managed local runtime with clear in-progress and completion feedback (`CTRL-01`)
- understand whether a draft change applies immediately, requires restart, or should be saved before an apply action (`CTRL-02`)
- avoid conflicting lifecycle actions while another lifecycle action is already running (`CTRL-03`)
## Current Implementation Findings
### Frontend runtime manager
- `tauri-app/src/features/runtime/useRuntimeManager.ts` owns one coarse `busy` boolean for all save and lifecycle actions.
- `startLocal()` and `stopLocal()` optimistically set `status` to `starting` or `stopping`, but there is no explicit command metadata describing which action is in flight, what completed, or what the user should do next.
- Polling continues every 3000 ms and already preserves dirty draft values from Phase 1, so Phase 2 should build on that state model instead of bypassing it.
### Runtime status and settings UI
- `tauri-app/src/features/runtime/components/RuntimeStatusCard.tsx` exposes a single primary action with generic busy copy (`Working...`). It does not tell the user whether the current action is a start, stop, retry, or blocked transition.
- `tauri-app/src/features/runtime/components/RuntimeSettingsDrawer.tsx` already has a combined `Save and start local API` / `Stop local API` CTA and blocks saves for invalid or pending draft fields.
- The drawer already recognizes one restart-related backend signal: it exits early when a save returns `last_error.code === "restart_required"`, but there is no explicit impact preview before save and no dedicated restart/apply guidance after save.
### Backend runtime commands
- `tauri-app/src-tauri/src/runtime/mod.rs` already prevents restart-impacting config changes while the local process is running by returning `restart_required` from `save_runtime_config_command()` when `connection_mode`, `model_path`, `addr`, `gpu_layers`, or `ctx_size` change.
- `start_runtime_inner()` returns the current snapshot if a child process already exists, but it does not record a structured "busy/conflict" result.
- `stop_runtime()` sets status to `Stopping`, kills the child, then returns `Stopped`, but it does not distinguish repeated stop requests, remote-mode no-ops, or user-facing completion messages.
- `tauri-app/src-tauri/src/runtime/state.rs` already revalidates health and degrades a running process when `/health` fails, which means lifecycle feedback should continue to respect `ready`, `degraded`, and `error` states rather than flattening everything into `busy`.
### Existing tests and patterns
- `tauri-app/src/features/runtime/components/RuntimeSurface.test.tsx` already covers start, stop, save-and-start, degraded polling, and draft safety. It is the best end-to-end harness for Phase 2 regressions.
- Phase 1 plan files under `.planning/phases/01-hf-model-discovery/` define the house style for executable plans: frontmatter with `requirements` and `must_haves`, then XML tasks with mandatory `read_first` and `acceptance_criteria` blocks.
## Gaps to Close in Phase 2
1. `busy` is too generic. The UI needs action-specific state such as `start`, `stop`, `save`, `save_and_start`, and `retry` so labels and disabled states are predictable.
2. Apply semantics are implicit. The user can trigger save or save-and-start flows, but the UI does not explicitly explain which fields require restart and when a saved change is live versus pending restart.
3. Conflict prevention is mostly optimistic. Frontend controls disable while `busy` is true, but backend commands do not provide a strong structured guard for duplicate or conflicting lifecycle requests.
4. Completion behavior is underspecified. The user sees status changes, but there is no stable command-result summary for success/failure/retry guidance after a lifecycle action finishes.
## Planning Implications
Phase 2 should be split into three execution plans:
1. **Backend lifecycle command contract** - add explicit command guard behavior and command-result semantics at the Tauri runtime boundary.
2. **Frontend lifecycle/apply state model** - teach the runtime manager and utilities how to classify draft impact, track the active command, and preserve actionable completion feedback.
3. **User-facing lifecycle surfaces** - update the status card, settings drawer, and regression tests so the user sees the correct action copy, restart/apply guidance, and blocked states.
## Concrete Decisions for the Plans
- Treat these fields as restart-impacting because the backend already does: `connection_mode`, `model_path`, `addr`, `gpu_layers`, `ctx_size`.
- Keep Phase 1 draft-preservation behavior intact; Phase 2 must layer lifecycle semantics on top of the current draft/baseline/live split rather than replacing it.
- Use `RuntimeSurface.test.tsx` for integrated lifecycle behavior and Rust runtime tests for backend guard behavior.
- Keep brownfield compatibility: do not remove existing `startRuntime`, `stopRuntime`, `saveRuntimeConfig`, or `RuntimeSnapshot` entry points; extend them with additional metadata instead.
## Risks
- If the frontend invents apply semantics that do not match backend restart rules, the workspace will feel inconsistent.
- If backend guards remain implicit, racey double-clicks or overlapping drawer/status-card actions can still produce confusing no-op results.
- If completion feedback only lives in transient button labels, users will lose the reason for a blocked or completed action after the next poll.
## Recommended Verification Focus
- Rust tests around duplicate/conflicting start/stop/save requests
- `RuntimeSurface.test.tsx` coverage for action-specific busy labels, blocked conflicting actions, restart-required guidance, and retry/error completion flows
- Existing Phase 1 draft safety assertions still passing after lifecycle changes