13 KiB
13 KiB
Technology Stack
Project: AI Backend Studio - runtime workspace UX and settings reliability Researched: 2026-03-28 Scope: Brownfield runtime workspace/settings flow inside the existing Tauri + React + TypeScript desktop app
Recommended Stack
Core Framework
| Technology | Version | Purpose | Why | Confidence |
|---|---|---|---|---|
| React | 18.x (keep current) | Existing desktop UI runtime surface | The app already uses React 18 in tauri-app/package.json. This milestone is a state-boundary problem, not a framework problem, so upgrading React is unnecessary churn. |
HIGH |
| Tauri | 2.x (keep current) | Desktop shell, commands, file dialogs, runtime bridge | The current app already uses Tauri 2. The official Tauri 2 guidance supports both invoke commands and frontend event listeners. For this milestone, commands plus scoped polling are the lowest-risk fit; events can be added only for start/stop/status nudges later. |
HIGH |
| TypeScript | 5.5+ (keep current) | Typed runtime snapshot, config draft, and mutation contracts | The bug is fundamentally about mixing live server state with editable client state. Strong, explicit types across RuntimeSnapshot, RuntimeConfigDraft, and mutation payloads reduce accidental overwrite paths. |
HIGH |
State and Form Layer
| Technology | Version | Purpose | Why | Confidence |
|---|---|---|---|---|
@tanstack/react-query |
5.x | Own live runtime snapshot polling, cache, refetch policy, and save/start/stop mutations | In 2025, TanStack Query is the standard React choice for server-state synchronization. Its model matches this exact problem: runtime snapshot is server state, while unsaved settings are client draft state. Use it to poll RuntimeSnapshot and invalidate on save/start/stop instead of hand-rolled setInterval state wiring in tauri-app/src/features/runtime/useRuntimeManager.ts. |
HIGH |
react-hook-form |
7.x | Own editable settings draft and dirty tracking inside the settings surface | React Hook Form is the pragmatic brownfield default for settings forms because it minimizes rerenders, supports async defaults, and supports reset / values with keepDirtyValues. That makes it a strong fit for "refresh live state without clobbering dirty inputs". |
HIGH |
zod |
4.x | Runtime-safe config schema for draft validation and payload parsing | Zod gives one schema for parsing persisted config/snapshot payloads and validating form submissions, which is valuable in a Tauri app where Rust commands return external data into the frontend. Use it at the boundary, not everywhere. | HIGH |
@hookform/resolvers |
3.x | Connect Zod schema to React Hook Form | This is the standard bridge for schema-backed React Hook Form validation and keeps validation logic out of ad hoc component effects. | HIGH |
Tauri Communication Pattern
| Technology | Version | Purpose | Why | Confidence |
|---|---|---|---|---|
Tauri commands via invoke |
Existing | Fetch snapshot, save config, start runtime, stop runtime | Commands already exist and are the right brownfield default for request/response actions. Keep them as the authoritative mutation boundary. | HIGH |
Tauri frontend events via @tauri-apps/api/event |
Existing optional capability | Optional push updates for status transitions only | Tauri events are suitable for small status messages, but the docs explicitly note they are not meant for low-latency or high-throughput streaming. That makes them useful as a later enhancement for process state transitions, but not necessary to solve the draft-overwrite bug right now. | MEDIUM |
Brownfield Implementation Approach
1. Split live snapshot state from draft settings state
Use two explicit models in the frontend:
RuntimeSnapshotQuery- the live polled backend state (status,ready,process_running,resolved_url,last_error, savedconfig)RuntimeConfigDraft- the unsaved user-editable form state that only exists while the settings UI is open
Why:
- The current bug exists because
RuntimeSettingsDrawer.tsxrehydrates local input state fromsnapshot.configin an effect keyed on[open, snapshot], so any poll can rewrite draft inputs. - Server state and form draft state have different lifecycles and should not share the same source of truth.
2. Move polling into TanStack Query and stop mirroring it into form state
Recommended query shape:
useQuery({
queryKey: ["runtime", "snapshot"],
queryFn: getRuntimeSnapshot,
refetchInterval: 3000,
refetchOnWindowFocus: false,
staleTime: 1000,
})
Use mutations for:
saveRuntimeConfigstartRuntimestopRuntime
After mutation success, invalidate or directly update the runtime/snapshot query.
Why:
- Query owns live synchronization concerns better than a custom
useEffect+setIntervalhook. - Per-query refetch controls let the runtime snapshot keep polling without coupling that refresh cycle to the settings draft.
- This is a local, focused dependency addition, not an architectural rewrite.
3. Initialize the settings form once per editing session
Recommended pattern with React Hook Form:
- On drawer open, map
snapshot.configintodefaultValues - While the form is pristine, allow controlled resets from fresher saved config
- Once the form is dirty, do not overwrite dirty fields from polling
- On explicit
Cancel, reset from the latest saved snapshot - On successful
Save, reset the form to the newly saved config
Two safe brownfield patterns:
- Preferred: initialize
useForm({ defaultValues, resolver })when the settings panel opens and callreset(savedConfig)only on open, cancel, or successful save. - If external updates must appear while editing: use
valuesplusresetOptions: { keepDirtyValues: true }so untouched fields can refresh without clobbering edited ones.
Why:
- This matches React Hook Form's documented async/default/reset model.
- It solves the exact overwrite failure without inventing a custom dirty-field reconciliation system first.
4. Keep runtime status visible outside the form, but move actionable runtime management into Runtime Settings
Recommended UI/state split:
- Keep a lightweight runtime summary card in the workspace for at-a-glance status
- Move authoritative runtime controls and editable configuration into one runtime settings surface
- Treat the summary card as read-mostly and query-backed
- Treat the settings surface as draft-backed and mutation-backed
Why:
- The current split between
RuntimeStatusCard.tsxandRuntimeSettingsDrawer.tsxduplicates control responsibility. - A thin summary + authoritative settings surface is the common brownfield cleanup because it reduces duplicate actions without removing useful status visibility.
5. Add schema parsing at the frontend boundary, not a full new domain layer
Use Zod for:
- parsing the runtime snapshot returned from Tauri before it enters UI state
- validating the settings payload before save
- normalizing fields like URL trimming, nullable paths, and numeric ranges in one place
Do not build a large new frontend domain framework for this milestone.
Why:
- This app already has working runtime commands and types.
- The brownfield win is better contracts and form discipline, not a new architecture stack.
Supporting Libraries
| Library | Version | Purpose | When to Use | Confidence |
|---|---|---|---|---|
@tanstack/react-query-devtools |
5.x | Debug query cache and refetch behavior in development | Dev-only while stabilizing polling/mutation interactions; not required for production. | MEDIUM |
immer |
10.x | Optional helper for reducer-based draft transforms | Only if the form keeps some nested local helper state outside React Hook Form and updates become noisy. Skip if not needed. | LOW |
What NOT to Introduce
| Category | Avoid | Why Not |
|---|---|---|
| Global client store | Redux / Zustand for this milestone | The problem is not app-wide client state explosion; it is confusion between server snapshot state and local draft state in one feature. TanStack Query + form-local state is smaller and more standard here. |
| Workflow/state machine framework | XState | The runtime lifecycle is already implemented in Rust/Tauri. Adding a frontend state machine now increases modeling overhead without directly solving the overwrite bug. |
| New form stack | TanStack Form / Formik / custom reducer form rewrite | React Hook Form is the lower-risk brownfield option with mature dirty tracking and async reset behavior. Switching to a less-established or heavier form stack adds migration cost with little benefit. |
| Real-time transport rewrite | Replacing polling with channels/events everywhere | Tauri events/channels are useful, but this bug is caused by how polling results are applied, not by polling itself. Fix the state boundary first; consider event-assisted refresh later if UX still needs faster transitions. |
| Design system rewrite | shadcn/ui, MUI, or CSS framework migration | The milestone is reliability and IA cleanup, not component-system replacement. Keep the existing CSS and components unless a very small extraction helps. |
Alternatives Considered
| Category | Recommended | Alternative | Why Not |
|---|---|---|---|
| Live server state | TanStack Query 5 | Keep custom polling in useRuntimeManager |
The current custom hook already caused accidental coupling between refetches and form resets. Query gives better separation, cache semantics, and mutation invalidation. |
| Draft form state | React Hook Form 7 | Component-local useState for every field |
The current drawer already shows the maintenance cost of hand-managed field state and effect-based rehydration. RHF adds dirty tracking and controlled reset semantics with less code. |
| Validation | Zod 4 | TypeScript types only | TypeScript does not validate runtime payloads from Tauri commands, local storage, or persisted config. This boundary benefits from runtime parsing. |
| Live updates | Polling + optional event nudges | Tauri event-first rewrite | Event-first can help later, but brownfield reliability improves fastest by separating snapshot and draft state while keeping the current command model. |
Installation
# Core additions
npm install @tanstack/react-query react-hook-form zod @hookform/resolvers
# Optional dev tooling
npm install -D @tanstack/react-query-devtools
Implementation Notes for This Repo
- Replace the hand-rolled polling lifecycle in
tauri-app/src/features/runtime/useRuntimeManager.tswith a query-backed runtime hook. - Stop using
[open, snapshot]as a blanket form-reset trigger intauri-app/src/features/runtime/components/RuntimeSettingsDrawer.tsx. - Keep
RuntimeSnapshotas the saved/live shape from the backend, but introduce a frontend-onlyRuntimeConfigDraftmapper instead of editingsnapshot.configby implication. - Keep save/start/stop in the existing Tauri command layer in
tauri-app/src/features/runtime/api.ts; do not move business logic into the React components. - Keep
RuntimeStatusCard.tsxquery-backed and read-mostly; move the authoritative editing and runtime-management flow into the runtime settings surface.
Recommendation Summary
- Recommended default stack: existing React 18 + Tauri 2 + TypeScript 5.5, plus
@tanstack/react-query,react-hook-form,zod, and@hookform/resolvers - Recommended implementation: poll live runtime snapshot through Query, edit config through a separate RHF draft, and only reconcile on open/save/cancel or through
keepDirtyValues - Recommended scope discipline: do not introduce a global store, state machine framework, design system rewrite, or event-stream rewrite for this milestone
Sources
- Local project context:
.planning/PROJECT.md - Local architecture:
.planning/codebase/ARCHITECTURE.md - Local stack:
.planning/codebase/STACK.md - Current runtime polling implementation:
tauri-app/src/features/runtime/useRuntimeManager.ts - Current settings reset behavior:
tauri-app/src/features/runtime/components/RuntimeSettingsDrawer.tsx - Current runtime API boundary:
tauri-app/src/features/runtime/api.ts - React Hook Form
useFormdocs: https://react-hook-form.com/docs/useform - React Hook Form FAQ / async defaults and reactive values: https://react-hook-form.com/faqs
- TanStack Query React
useQueryreference: https://tanstack.com/query/latest/docs/framework/react/reference/useQuery - TanStack Query window focus refetching guide: https://tanstack.com/query/latest/docs/framework/react/guides/window-focus-refetching
- TanStack Query guidance on server state vs client state: Context7
/tanstack/query - Zod docs: https://zod.dev/
- Tauri 2 frontend/backed communication docs: https://v2.tauri.app/develop/calling-frontend/