Files
ForgeLM/.planning/research/STACK.md
T

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

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, saved config)
  • RuntimeConfigDraft - the unsaved user-editable form state that only exists while the settings UI is open

Why:

  • The current bug exists because RuntimeSettingsDrawer.tsx rehydrates local input state from snapshot.config in 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:

  • saveRuntimeConfig
  • startRuntime
  • stopRuntime

After mutation success, invalidate or directly update the runtime/snapshot query.

Why:

  • Query owns live synchronization concerns better than a custom useEffect + setInterval hook.
  • 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.config into defaultValues
  • 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:

  1. Preferred: initialize useForm({ defaultValues, resolver }) when the settings panel opens and call reset(savedConfig) only on open, cancel, or successful save.
  2. If external updates must appear while editing: use values plus resetOptions: { 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.tsx and RuntimeSettingsDrawer.tsx duplicates 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.ts with a query-backed runtime hook.
  • Stop using [open, snapshot] as a blanket form-reset trigger in tauri-app/src/features/runtime/components/RuntimeSettingsDrawer.tsx.
  • Keep RuntimeSnapshot as the saved/live shape from the backend, but introduce a frontend-only RuntimeConfigDraft mapper instead of editing snapshot.config by 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.tsx query-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