Initial ForgeLM import
This commit is contained in:
commit
a392b9dc60
4296 files changed
+1212675
No files matched your search
@@ -0,0 +1,261 @@
|
||||
# Architecture Patterns
|
||||
|
||||
**Domain:** Runtime workspace state flow for existing Tauri + React desktop app
|
||||
**Researched:** 2026-03-28
|
||||
**Overall confidence:** MEDIUM-HIGH
|
||||
|
||||
## Recommended Architecture
|
||||
|
||||
Use a feature-scoped runtime workspace controller with **three explicit state domains** instead of one polling snapshot flowing directly into both status UI and form inputs.
|
||||
|
||||
1. **Live runtime state** - polled, read-only, owned by query state
|
||||
2. **Persisted settings state** - latest saved config from Tauri, read-only except after save success
|
||||
3. **Draft settings state** - local editable form state, owned only by the settings editor
|
||||
|
||||
That separation is the core fix. The current problem comes from `RuntimeSettingsDrawer.tsx` rehydrating local input state from `snapshot.config` on every `snapshot` change while `useRuntimeManager.ts` refreshes the whole `RuntimeSnapshot` every 3 seconds. Polling is correct for live runtime health, but it must not be the owner of editable inputs.
|
||||
|
||||
Recommended shape:
|
||||
|
||||
```text
|
||||
Tauri commands / file persistence / process state
|
||||
|
|
||||
v
|
||||
Runtime query layer (polling live snapshot + persisted config)
|
||||
|
|
||||
+--> Status/summary components (read-only)
|
||||
|
|
||||
+--> Draft controller initializer/reset source
|
||||
|
|
||||
v
|
||||
Editable settings form (local reducer/state)
|
||||
|
|
||||
v
|
||||
Save mutation
|
||||
|
|
||||
v
|
||||
update persisted config -> invalidate/refetch live query
|
||||
```
|
||||
|
||||
## Recommended Component Boundaries
|
||||
|
||||
| Component | Responsibility | Communicates With |
|
||||
|-----------|---------------|-------------------|
|
||||
| `RuntimeWorkspaceProvider` | Feature root; composes live query, command mutations, and draft provider | Tauri API adapter, child panels |
|
||||
| `useRuntimeWorkspaceQuery` | Polls runtime status and loads latest persisted config; exposes `data`, `isFetching`, `refetch` | `api.ts`, query cache |
|
||||
| `useRuntimeCommands` | Starts, stops, saves, rescans; serializes mutations and invalidates/refetches workspace query after success | Tauri commands, query cache |
|
||||
| `RuntimeStatusPanel` | Renders runtime readiness, last error, endpoint, process state, restart-required banners | live query only |
|
||||
| `RuntimeSettingsDraftProvider` | Owns draft settings, dirty fields, reset/cancel, stale-baseline detection | workspace query, settings form |
|
||||
| `RuntimeSettingsForm` | Pure controlled inputs for local/remote settings | draft provider only |
|
||||
| `RuntimeModelLibraryPanel` | Reads managed library, scanned models, search results; writes selections into draft only | aux queries, draft provider |
|
||||
| `RuntimePrimaryActions` | Save, cancel, start, stop, save-and-start logic; decides enablement from live state + draft state | commands, live query, draft provider |
|
||||
|
||||
## Data Model Recommendation
|
||||
|
||||
Do not let the frontend treat one `RuntimeSnapshot` object as the source of truth for everything. Normalize it into these view models inside the runtime feature:
|
||||
|
||||
### 1. Live Snapshot
|
||||
|
||||
Read-only runtime state used for polling-sensitive UI.
|
||||
|
||||
```ts
|
||||
type LiveRuntimeSnapshot = {
|
||||
status: RuntimeStatus;
|
||||
ready: boolean;
|
||||
processRunning: boolean;
|
||||
resolvedUrl: string;
|
||||
binaryPath: string | null;
|
||||
lastError: RuntimeErrorInfo | null;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Persisted Config Baseline
|
||||
|
||||
Latest saved config returned from Tauri. This is the reset target for the form.
|
||||
|
||||
```ts
|
||||
type PersistedRuntimeConfig = RuntimeConfig & {
|
||||
revision: number | string;
|
||||
};
|
||||
```
|
||||
|
||||
If possible, add a monotonic `revision` or `updated_at` field on the Tauri side. Without it, the frontend can still compare deep equality, but a revision is cleaner for stale-draft detection and cancel/reset behavior.
|
||||
|
||||
### 3. Draft Config
|
||||
|
||||
Local editable state that never changes because polling ran.
|
||||
|
||||
```ts
|
||||
type RuntimeDraftState = {
|
||||
values: RuntimeConfig;
|
||||
dirtyFields: Set<string>;
|
||||
initializedFromRevision: number | string | null;
|
||||
saveState: "idle" | "saving" | "error";
|
||||
};
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Polling Flow: live snapshot vs draft settings
|
||||
|
||||
1. `useRuntimeWorkspaceQuery` bootstraps from Tauri and continues polling on an interval.
|
||||
2. Poll responses update **live snapshot** immediately.
|
||||
3. Poll responses update **persisted config baseline** only when the returned config revision changed.
|
||||
4. `RuntimeStatusPanel` rerenders from live snapshot on every poll.
|
||||
5. `RuntimeSettingsDraftProvider` does **not** overwrite draft values during normal polling.
|
||||
6. If the baseline revision changes while the draft is dirty, show a non-destructive "saved settings changed" notice with actions: `Reload saved`, `Keep editing`.
|
||||
|
||||
This matches React guidance: controlled inputs should stay owned by local state, and state should not be repeatedly reset from props/effects unless the user is intentionally switching context.
|
||||
|
||||
### Open / Close Drawer Flow
|
||||
|
||||
1. Opening settings initializes draft from the latest persisted config baseline.
|
||||
2. While the drawer stays open, the draft is the only owner of form fields.
|
||||
3. Closing with cancel discards the draft and clears dirty state.
|
||||
4. Reopening creates a fresh draft from the current persisted baseline.
|
||||
|
||||
If the app keeps the editor mounted, key the draft provider by `baseline.revision` only when the user explicitly resets/cancels. Do not key it by every poll result.
|
||||
|
||||
### Save Flow
|
||||
|
||||
1. User edits draft values locally.
|
||||
2. `saveRuntimeConfig` mutation sends the full draft payload.
|
||||
3. On success, mutation updates the persisted baseline from the response and clears dirty state.
|
||||
4. Then invalidate/refetch the live workspace query.
|
||||
5. Status components pick up any restart-required or readiness change from the fresh live snapshot.
|
||||
|
||||
If the response already contains the updated snapshot/config, update query cache directly first and refetch after only when needed. That follows TanStack Query guidance: mutations own writes; queries are refreshed by invalidation or cache updates.
|
||||
|
||||
### Start / Stop Flow
|
||||
|
||||
1. Start/stop actions never mutate draft state directly.
|
||||
2. `Start` uses the latest persisted config, not unsaved inputs.
|
||||
3. `Save and start` is a composed action: `save draft -> if success and local mode valid -> start runtime -> refetch live snapshot`.
|
||||
4. `Stop` affects live runtime state only, then refetches.
|
||||
|
||||
That prevents ambiguous ownership such as starting from a half-edited form that the backend has not persisted yet.
|
||||
|
||||
### Auxiliary Query Flow
|
||||
|
||||
GPU info, model library contents, scanned folders, and Hugging Face search results should be separate queries from the workspace snapshot.
|
||||
|
||||
- They are not core runtime status.
|
||||
- Their failures should not poison runtime readiness state.
|
||||
- Their data should write into the draft only through explicit user actions like selecting a model or choosing a folder.
|
||||
|
||||
## Patterns to Follow
|
||||
|
||||
### Pattern 1: Server state vs client draft split
|
||||
|
||||
**What:** Treat polling data as server/external state and editable fields as client state.
|
||||
|
||||
**When:** Always, for runtime settings forms that stay open while health/status continues changing.
|
||||
|
||||
**Why:** It removes the current overwrite bug at the ownership boundary rather than patching around it with conditional `useEffect` logic.
|
||||
|
||||
### Pattern 2: Feature-local state, not app-global store
|
||||
|
||||
**What:** Keep runtime workspace state inside a dedicated feature provider or feature hooks instead of introducing a broad app-wide store.
|
||||
|
||||
**When:** Brownfield restructure limited to runtime workspace.
|
||||
|
||||
**Why:** The existing app does not use a global frontend store. This change is a feature-boundary problem, not a whole-app state-management problem.
|
||||
|
||||
### Pattern 3: Query/mutation orchestration for Tauri commands
|
||||
|
||||
**What:** Model polling reads as queries and start/stop/save as mutations, even though transport is `invoke()` instead of HTTP.
|
||||
|
||||
**When:** Any operation that returns asynchronous backend state and needs post-write refresh.
|
||||
|
||||
**Why:** It gives explicit ownership for polling, mutation in-flight state, invalidation, and optimistic UI guards.
|
||||
|
||||
### Pattern 4: Separate read model from write model
|
||||
|
||||
**What:** Prefer a workspace response shaped like:
|
||||
|
||||
```ts
|
||||
type RuntimeWorkspaceResponse = {
|
||||
live: LiveRuntimeSnapshot;
|
||||
persistedConfig: PersistedRuntimeConfig;
|
||||
};
|
||||
```
|
||||
|
||||
**When:** As soon as the team touches the Tauri contract.
|
||||
|
||||
**Why:** Nesting writable config inside the polled runtime snapshot is what makes accidental rehydration easy.
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### Anti-Pattern 1: Rehydrating the form from every snapshot refresh
|
||||
|
||||
**What:** `useEffect(() => setForm(snapshot.config), [snapshot])`
|
||||
|
||||
**Why bad:** Polling becomes an uncontrolled reset mechanism that discards in-progress edits, folder picks, and model selections.
|
||||
|
||||
**Instead:** Initialize draft once per open/reset or once per confirmed baseline revision change.
|
||||
|
||||
### Anti-Pattern 2: One `busy` flag for unrelated operations
|
||||
|
||||
**What:** A single boolean covering save, start, stop, scan, and search.
|
||||
|
||||
**Why bad:** It couples unrelated interactions and makes action enablement ambiguous.
|
||||
|
||||
**Instead:** Track mutation states independently: `isSaving`, `isStarting`, `isStopping`, `isScanning`, `isSearchingHub`.
|
||||
|
||||
### Anti-Pattern 3: Letting runtime actions read unsaved form state implicitly
|
||||
|
||||
**What:** Start/stop logic reaching directly into form inputs.
|
||||
|
||||
**Why bad:** Operators cannot tell whether the runtime is using saved config or draft config.
|
||||
|
||||
**Instead:** Runtime operations use persisted config; composed actions explicitly save first.
|
||||
|
||||
## Scalability Considerations
|
||||
|
||||
| Concern | At current scope | As runtime surface grows |
|
||||
|---------|------------------|---------------------------|
|
||||
| Polling frequency | 3-second polling is acceptable | Move to adaptive polling by state: faster while starting, slower while stopped/remote |
|
||||
| Mutation contention | Manual guarding is sufficient | Add serialized command queue to prevent overlapping start/stop/save races |
|
||||
| Contract clarity | Frontend normalization can bridge current shape | Introduce dedicated workspace DTO with `live` + `persistedConfig` + `revision` |
|
||||
| UI complexity | Feature-local provider is enough | Split drawer into tabs/sections without changing ownership model |
|
||||
|
||||
## Suggested Build Order And Dependency Implications
|
||||
|
||||
1. **Normalize the runtime workspace contract**
|
||||
- Introduce frontend selectors/types for `live snapshot`, `persisted baseline`, and `draft` even if the backend still returns the old combined payload.
|
||||
- Dependency implication: everything else should build on this split, not on raw `RuntimeSnapshot`.
|
||||
|
||||
2. **Extract query/mutation hooks from `useRuntimeManager`**
|
||||
- Create distinct hooks for workspace polling and runtime commands.
|
||||
- Dependency implication: status panel and action bar can migrate first without touching the form internals.
|
||||
|
||||
3. **Add the draft provider and migrate `RuntimeSettingsDrawer` to controlled draft state**
|
||||
- Replace `useEffect` rehydration on every `snapshot` change with explicit `initialize/reset/cancel` semantics.
|
||||
- Dependency implication: this is the critical prerequisite for reliable save/cancel behavior.
|
||||
|
||||
4. **Refactor status and actions into read-only/live consumers**
|
||||
- `RuntimeStatusCard` and any future summary header should read from live query only.
|
||||
- Dependency implication: once status no longer reads draft state, polling can remain aggressive without UX regressions.
|
||||
|
||||
5. **Move auxiliary lookups into separate query slices**
|
||||
- GPU, model library, model scan, and Hub search should become isolated resources.
|
||||
- Dependency implication: failures and loading states become localized instead of blocking save/start controls.
|
||||
|
||||
6. **Optionally add backend config revision support**
|
||||
- Persist and return `revision` or `updated_at` with config reads/writes.
|
||||
- Dependency implication: enables stale-draft warnings and cleaner reset rules, but can follow the frontend split if roadmap pressure is high.
|
||||
|
||||
## Architecture Recommendation For Roadmap
|
||||
|
||||
Phase the work around ownership boundaries, not around visual components:
|
||||
|
||||
1. **State split foundation** - separate live polling state from persisted config and draft state
|
||||
2. **Workspace action orchestration** - isolate save/start/stop/restart-required behavior
|
||||
3. **UI restructuring on top of stable ownership** - merge status and settings into one control center after the state model is safe
|
||||
|
||||
That order matters. If the team starts with UI rearrangement before fixing ownership, the new surface will keep the same overwrite bug under a different layout.
|
||||
|
||||
## Sources
|
||||
|
||||
- Codebase evidence: `tauri-app/src/features/runtime/useRuntimeManager.ts`, `tauri-app/src/features/runtime/components/RuntimeSettingsDrawer.tsx`, `tauri-app/src/features/runtime/components/RuntimeStatusCard.tsx`, `tauri-app/src-tauri/src/runtime/mod.rs`, `tauri-app/src-tauri/src/runtime/state.rs`
|
||||
- React official docs via Context7 (`/reactjs/react.dev`) on controlled inputs, preserving/resetting state with keys, and avoiding unnecessary effect-driven derived state - HIGH confidence
|
||||
- TanStack Query official docs via Context7 (`/tanstack/query/v5.90.3`) on polling with `refetchInterval`, mutation cache updates, and invalidation after writes - HIGH confidence
|
||||
@@ -0,0 +1,102 @@
|
||||
# Feature Landscape
|
||||
|
||||
**Domain:** Runtime workspace UX for local-first AI desktop tools
|
||||
**Researched:** 2026-03-28
|
||||
**Scope:** Runtime management, settings editing, model source selection, and lifecycle/status reliability in a local desktop app
|
||||
|
||||
## Table Stakes
|
||||
|
||||
Features users expect. Missing = product feels unsafe or annoying to operate.
|
||||
|
||||
| Feature | Why Expected | Complexity | Notes |
|
||||
|---------|--------------|------------|-------|
|
||||
| Draft-safe settings editing | Desktop operators expect form edits to remain stable until they explicitly save, cancel, or reset. If polling overwrites a path field or provider choice, trust collapses immediately. | Med | Treat live runtime snapshot and editable draft as separate state models. Background refresh should update live status, not clobber draft inputs. Confidence: HIGH |
|
||||
| Explicit save/cancel/revert flow | Adjacent desktop tools expose clear commit points for configuration changes, especially when edits affect runtime behavior, security, paths, or restart requirements. | Med | Show dirty state, changed fields, and per-field or whole-form reset. Keep save semantics deterministic. Confidence: HIGH |
|
||||
| Unified runtime control center | Users expect one place to see current runtime, active model/provider, health, and lifecycle actions instead of split status and settings surfaces. | Med | Status summary, runtime actions, and current effective config should live together even if advanced settings remain collapsible. Confidence: HIGH |
|
||||
| Clear lifecycle controls with feedback | Start, stop, restart, retry, and health check are baseline for local runtime tools and adjacent desktop dev tools. Users need to know whether an action is pending, succeeded, failed, or requires attention. | Med | Include transitional states, disable conflicting actions in flight, and preserve action history or last result. Confidence: HIGH |
|
||||
| Effective vs draft configuration visibility | Users need to understand what the runtime is currently using versus what is only edited locally and not yet applied. | Med | Use labels such as Live, Draft, Unsaved, Restart required. This directly reduces confusion in brownfield apps with background polling. Confidence: HIGH |
|
||||
| Model source and model selection with validation | Local AI tools commonly let users pick a provider/runtime source and then choose from valid models. Users expect incompatible or missing options to be explained before save. | Med | Cascade validation: source -> runtime availability -> model list -> compatibility warnings. Confidence: MEDIUM |
|
||||
| Health/status surface with actionable diagnostics | Operators expect more than a green/red badge. They need endpoint, process state, selected model, last health check, and error guidance when unhealthy. | Med | Troubleshooting entry points matter because local runtimes fail for common machine-specific reasons. Confidence: HIGH |
|
||||
| Safe path/directory editing for model storage or managed libraries | Desktop tools that manage runtime assets usually expose path selection, current location, and validation because storage layout is a core operator concern. | Med | Folder picker plus existence/permission checks before commit. Never silently revert user-entered paths. Confidence: HIGH |
|
||||
| Restart-required and apply-now signaling | When settings affect runtime startup, networking, auth, or model loading behavior, users expect the UI to say whether a save applies immediately or on next restart. | Low | Show this per setting group, not as hidden tribal knowledge. Confidence: HIGH |
|
||||
| Error recovery without losing work | Failed saves, failed starts, or failed health checks must not erase the user draft. Users expect to fix the problem and retry from the same screen. | Med | Preserve draft across failures and optionally across app refresh/reopen for the current session. Confidence: HIGH |
|
||||
|
||||
## Differentiators
|
||||
|
||||
Features that create competitive advantage for this scope.
|
||||
|
||||
| Feature | Value Proposition | Complexity | Notes |
|
||||
|---------|-------------------|------------|-------|
|
||||
| Side-by-side live snapshot and editable draft | Turns a common failure mode into a trust signal: users can compare what is running with what they are preparing to apply. | Med | Best fit for this project because the current pain is polling overwriting edits. Confidence: HIGH |
|
||||
| Per-setting change impact preview | Before save, explain consequences such as restart needed, model reload required, auth exposure risk, or path rescan. | Med | Makes advanced runtime settings feel safe without adding wizard friction. Confidence: MEDIUM |
|
||||
| Runtime/source switching with preserved drafts per source | Users exploring local vs remote runtimes often lose partial config when switching providers. Preserving draft state per source reduces setup frustration. | High | Useful when multiple runtime backends or model sources are supported. Confidence: MEDIUM |
|
||||
| Inline diagnostics and guided repair actions | Instead of just reporting Unhealthy, offer actions like open logs, retry probe, reveal models folder, reset to last saved, or test connection. | Med | Adjacent tools win on time-to-recovery, not just status visibility. Confidence: HIGH |
|
||||
| Optimistic model browser with compatibility hints | Show available models, installed/downloaded state, source provenance, and whether the current runtime can serve them before commit. | High | Especially valuable if local and remote sources coexist. Confidence: MEDIUM |
|
||||
| Session-safe draft persistence and recovery | If the app reloads or the runtime poll stream reconnects, the user can restore unsaved edits intentionally rather than losing them. | High | Strong differentiator because settings trust is the product constraint called out in `PROJECT.md`. Confidence: MEDIUM |
|
||||
| Explainable automation for model loading/runtime lifecycle | Features like auto-start, JIT loading, auto-unload, or health polling become safer when users can see why the app took an action and how to override it. | Med | Borrow the best part of LM Studio and Docker Desktop: power features with visible state and controls. Confidence: MEDIUM |
|
||||
|
||||
## Anti-Features
|
||||
|
||||
Features to explicitly NOT build.
|
||||
|
||||
| Anti-Feature | Why Avoid | What to Do Instead |
|
||||
|--------------|-----------|-------------------|
|
||||
| Silent form rehydration from background polling | This is the core trust-destroying behavior in the current product. Users read it as data loss. | Keep live runtime snapshot separate from draft state and ask before replacing a dirty draft. |
|
||||
| Implicit auto-save for high-impact runtime settings | Auto-saving ports, auth, paths, or provider changes creates surprise restarts and hard-to-debug state. | Use explicit save/cancel, with optional narrow autosave only for low-risk preferences later if ever needed. |
|
||||
| Split-brain control surfaces | Duplicated controls across status card and settings drawer create uncertainty about which surface is authoritative. | Make one workspace the source of truth and let secondary surfaces deep-link into it. |
|
||||
| Overloading the workspace with chat redesign work | Chat is validated and out of scope; broadening here dilutes the runtime reliability goal. | Touch chat only where active runtime/model context must be surfaced. |
|
||||
| Hidden config mutations on source switch | Auto-clearing fields or swapping model selections without explanation feels like a bug. | Preserve drafts per source or require explicit confirmation before destructive changes. |
|
||||
| Advanced settings without safety copy | Security, network, and path settings are easy to misuse when labels are terse. | Add impact text, validation, and restart/apply semantics directly in the UI. |
|
||||
|
||||
## Feature Dependencies
|
||||
|
||||
```text
|
||||
Unified runtime control center -> Clear lifecycle controls with feedback
|
||||
Unified runtime control center -> Effective vs draft configuration visibility
|
||||
Effective vs draft configuration visibility -> Draft-safe settings editing
|
||||
Draft-safe settings editing -> Explicit save/cancel/revert flow
|
||||
Draft-safe settings editing -> Error recovery without losing work
|
||||
Model source and model selection with validation -> Optimistic model browser with compatibility hints
|
||||
Clear lifecycle controls with feedback -> Health/status surface with actionable diagnostics
|
||||
Safe path/directory editing -> Restart-required and apply-now signaling
|
||||
Explicit save/cancel/revert flow -> Per-setting change impact preview
|
||||
Draft-safe settings editing -> Session-safe draft persistence and recovery
|
||||
Health/status surface with actionable diagnostics -> Inline diagnostics and guided repair actions
|
||||
```
|
||||
|
||||
## MVP Recommendation
|
||||
|
||||
Prioritize:
|
||||
1. Draft-safe settings editing
|
||||
2. Explicit save/cancel/revert flow
|
||||
3. Unified runtime control center
|
||||
4. Clear lifecycle controls with feedback
|
||||
5. Effective vs draft configuration visibility
|
||||
6. Model source and model selection with validation
|
||||
|
||||
One early differentiator worth pulling into MVP if capacity allows:
|
||||
7. Side-by-side live snapshot and editable draft
|
||||
|
||||
Defer:
|
||||
- Runtime/source switching with preserved drafts per source: valuable, but only after one-runtime draft safety is solid
|
||||
- Session-safe draft persistence and recovery: strong trust feature, but second-order after fixing in-session overwrite behavior
|
||||
- Optimistic model browser with compatibility hints: defer until core save/apply semantics are reliable
|
||||
|
||||
## Sources
|
||||
|
||||
### Authoritative / official docs
|
||||
- Docker Desktop settings and maintenance docs: https://docs.docker.com/desktop/settings-and-maintenance/settings (used for expectations around desktop settings, lifecycle, restart/apply semantics, notifications)
|
||||
- LM Studio server settings docs: https://lmstudio.ai/docs/developer/core/server/settings (used for expectations around runtime/network/auth/model-loading controls)
|
||||
- LM Studio headless/runtime service docs: https://lmstudio.ai/docs/developer/core/headless (used for expectations around start-on-login, auto server restore, JIT model loading)
|
||||
- VS Code settings docs: https://code.visualstudio.com/docs/configure/settings (used for expectations around settings scopes, direct editing, modified state, and user/workspace separation)
|
||||
- VS Code default settings reference: https://code.visualstudio.com/docs/reference/default-settings (used to confirm settings editor + JSON are both first-class)
|
||||
- Ollama troubleshooting docs: https://docs.ollama.com/troubleshooting (used for expectations around logs, diagnostics, runtime failure recovery, and model/config locations)
|
||||
|
||||
### Ecosystem / discovery sources
|
||||
- Exa ecosystem search on local AI desktop tools and adjacent desktop developer tools, 2026 queries covering LM Studio, Ollama clients, AnythingLLM, Open WebUI, Docker Desktop, and unsaved-settings reliability patterns
|
||||
|
||||
## Confidence Notes
|
||||
|
||||
- HIGH confidence: explicit save/cancel, separate live vs draft state, unified control center, lifecycle feedback, diagnostics, and non-destructive failure handling are strongly supported by official desktop-tool patterns and the project's stated constraints.
|
||||
- MEDIUM confidence: preserved drafts per source, compatibility-hint browsers, and session draft recovery are opinionated differentiators derived from ecosystem patterns and UX pain points rather than a single canonical doc.
|
||||
- LOW confidence: none of the core recommendations rely only on unverified single-source claims.
|
||||
@@ -0,0 +1,164 @@
|
||||
# Domain Pitfalls
|
||||
|
||||
**Domain:** Brownfield runtime/settings workspace UX for a local-first desktop app
|
||||
**Researched:** 2026-03-28
|
||||
|
||||
## Critical Pitfalls
|
||||
|
||||
### Pitfall 1: Polling overwrites dirty draft state
|
||||
**What goes wrong:** A live runtime snapshot is treated as the same thing as the editable form, so background refresh rehydrates in-progress fields like models folder, selected model, API URL, or start policy.
|
||||
**Why it happens:** Brownfield code usually starts with "load snapshot into local state" and later adds polling. In this repo, `useRuntimeManager` polls every 3 seconds in `tauri-app/src/features/runtime/useRuntimeManager.ts:33`, while `RuntimeSettingsDrawer` resets local form state whenever `snapshot` changes in `tauri-app/src/features/runtime/components/RuntimeSettingsDrawer.tsx:76`.
|
||||
**Consequences:** Users lose folder-picker selections, selected models jump, save writes unexpected values, and trust in the settings surface collapses.
|
||||
**Warning signs:**
|
||||
- Editing a field and seeing it snap back after a status refresh
|
||||
- Folder picker or model dropdown changing without explicit user action
|
||||
- Bugs phrased as "save worked but UI changed before I clicked save"
|
||||
- `useEffect` blocks that copy server/runtime props into form state whenever snapshot props change
|
||||
**Prevention:**
|
||||
- Separate `liveSnapshot`, `draft`, and `savedBaseline` as distinct states
|
||||
- Freeze draft reinitialization while form is dirty; only rebase on explicit save/cancel/reopen
|
||||
- Show live runtime changes as non-destructive notices instead of mutating draft fields
|
||||
- Add tests that simulate polling during edits and assert draft preservation
|
||||
**Phase should address it:** Workspace state-model phase, before any UI reorganization or control consolidation
|
||||
**Detection:** Repro test: open settings, edit `models_dir`, trigger polling/refresh, verify draft stays untouched.
|
||||
|
||||
### Pitfall 2: Event actions are coupled to render-sync effects instead of explicit user intents
|
||||
**What goes wrong:** Save/start/stop behavior is inferred from snapshot changes or effect chains instead of being modeled as explicit commands with predictable sequencing.
|
||||
**Why it happens:** Brownfield React screens often grow from separate cards/drawers and then unify later. React's current guidance explicitly warns against using Effects for event-specific logic because it creates stale or duplicated actions.
|
||||
**Consequences:** Double start attempts, stop-before-save confusion, operations firing after unrelated refreshes, and hard-to-debug race conditions during "save and start" flows.
|
||||
**Warning signs:**
|
||||
- One button both mutates draft state and infers an operational action from subsequent render state
|
||||
- Bugs that only appear under slow I/O or repeated clicks
|
||||
- Operational state described as "sometimes it starts twice" or "clicked stop but save still ran"
|
||||
- Shared busy flags masking which operation is in flight
|
||||
**Prevention:**
|
||||
- Model `save`, `start`, `stop`, and `saveThenStart` as explicit commands with one operation state machine
|
||||
- Keep event logic in action handlers, not in snapshot-sync effects
|
||||
- Add idempotency/operation guards so a second click cannot enqueue conflicting work
|
||||
- Track operation-specific status (`saving`, `starting`, `stopping`) instead of one generic `busy`
|
||||
**Phase should address it:** Runtime control orchestration phase
|
||||
**Detection:** Integration tests for rapid repeated clicks, slow save + start, and stop while save is pending.
|
||||
|
||||
### Pitfall 3: Async side effects write back after the user has already moved on
|
||||
**What goes wrong:** Model scans, GPU queries, Hugging Face searches, and folder pickers resolve later and overwrite newer UI intent.
|
||||
**Why it happens:** These tasks are launched from the settings drawer and update local state independently. In `RuntimeSettingsDrawer`, `scanRuntimeModels`, `queryRuntimeGpu`, `getModelLibraryState`, and search flows all update drawer state after async completion.
|
||||
**Consequences:** Wrong model gets auto-selected, stale scan results replace newer folder choices, GPU recommendations overwrite manual values, and users save a configuration they did not mean to create.
|
||||
**Warning signs:**
|
||||
- Changing folders quickly causes the first scan result to win
|
||||
- Search results from an old query appear after entering a new one
|
||||
- "Use recommended" values reappear after a user typed something else
|
||||
- No request tokens, abort logic, or latest-request guards around async setters
|
||||
**Prevention:**
|
||||
- Guard async responses with request IDs or abort/cancel semantics
|
||||
- Apply async results only if they still match the current draft context
|
||||
- Never auto-apply a recommendation after the field becomes dirty
|
||||
- Record whether a value is user-authored vs system-suggested before mutating it
|
||||
**Phase should address it:** Async data-source hardening phase for model discovery and device/runtime metadata
|
||||
**Detection:** Tests that change folders/modes mid-request and assert stale responses are ignored.
|
||||
|
||||
### Pitfall 4: One surface mixes draft validation, runtime health, and operational errors with no ownership boundaries
|
||||
**What goes wrong:** Users see one error banner, but it is unclear whether the issue is an unsaved field problem, a runtime health failure, a file-dialog issue, or a failed start/stop command.
|
||||
**Why it happens:** Brownfield runtime screens often merge settings and operations without redesigning the message model. This repo already carries both `localError` and `snapshot.last_error` into the same banner area in `tauri-app/src/features/runtime/components/RuntimeSettingsDrawer.tsx:614`.
|
||||
**Consequences:** Users retry the wrong action, overwrite a recoverable draft, or think save failed when only health revalidation failed.
|
||||
**Warning signs:**
|
||||
- A single banner renders validation, transport, and runtime-health problems interchangeably
|
||||
- Support tickets include "I don't know what failed"
|
||||
- Start/stop buttons remain visible but their failure cause is ambiguous
|
||||
**Prevention:**
|
||||
- Split error ownership into validation, draft-save, runtime-operation, and background-health channels
|
||||
- Keep background degradation visible without blocking draft editing
|
||||
- Attach remediation copy to each error type (`fix field`, `retry save`, `restart runtime`, `recheck health`)
|
||||
- Log structured error sources so QA can verify the right message appears for each failure class
|
||||
**Phase should address it:** Error-handling and operator feedback phase
|
||||
**Detection:** UX acceptance tests that inject each failure type independently and verify distinct copy and CTA behavior.
|
||||
|
||||
## Moderate Pitfalls
|
||||
|
||||
### Pitfall 5: Hidden-mode fields leak across local/remote switching
|
||||
**What goes wrong:** Switching between local and remote modes either drops meaningful local draft state or silently carries stale values that should be isolated.
|
||||
**Why it happens:** Brownfield forms often reuse one object for multiple conceptual modes. `createRuntimeConfigPatch` conditionally preserves some snapshot values based on `form.mode` in `tauri-app/src/features/runtime/runtime-settings-form.ts:76`, which is safer than wiping everything, but still requires explicit draft semantics to avoid confusion.
|
||||
**Consequences:** Users think a local model choice was saved when it was only temporarily hidden, or remote credentials and local runtime state appear partially applied.
|
||||
**Warning signs:**
|
||||
- Toggling local/remote changes the primary CTA wording but preserves unexplained hidden values
|
||||
- Save behavior differs depending on prior mode history
|
||||
- Bugs mention "switching back brought old values back" or "my local setup disappeared"
|
||||
**Prevention:**
|
||||
- Maintain mode-specific draft slices with a shared shell state
|
||||
- Show when hidden values are preserved vs discarded
|
||||
- Require explicit save to commit a mode switch and its dependent fields
|
||||
- Add tests for local -> remote -> local round trips without data loss or accidental carryover
|
||||
**Phase should address it:** Settings information architecture phase
|
||||
**Detection:** Round-trip tests across mode switches with unsaved changes.
|
||||
|
||||
### Pitfall 6: Polling ignores editing context and transient operation states
|
||||
**What goes wrong:** Background refresh keeps firing during `starting`, `stopping`, picker dialogs, and active editing, producing UI jitter and misleading status transitions.
|
||||
**Why it happens:** A fixed interval poller is simple to add in brownfield code, but runtime workspaces need context-aware refresh rules.
|
||||
**Consequences:** Status flashes between stale and optimistic values, controls disable unexpectedly, and users mistrust whether the runtime is actually ready.
|
||||
**Warning signs:**
|
||||
- Same poll interval regardless of `starting`, `ready`, `stopping`, or drawer-open state
|
||||
- Snapshot refresh racing against optimistic local status updates
|
||||
- Intermittent test failures tied to timers
|
||||
**Prevention:**
|
||||
- Make refresh policy state-aware: pause or downshift polling while editing, during modal OS dialogs, and while a command is in flight
|
||||
- Prefer explicit revalidation on operation completion over constant polling during transitions
|
||||
- Distinguish optimistic local operation state from confirmed backend snapshot state
|
||||
**Phase should address it:** Runtime observability and refresh-policy phase
|
||||
**Detection:** Timer-based tests covering idle, editing, starting, stopping, and degraded states.
|
||||
|
||||
### Pitfall 7: Unsaved-change boundaries stop at Save/Cancel and ignore close/navigation paths
|
||||
**What goes wrong:** Closing the drawer, switching surfaces, or triggering a mode change drops dirty edits without warning.
|
||||
**Why it happens:** Brownfield teams add explicit Save buttons first, then forget every other exit path.
|
||||
**Consequences:** Data loss reports continue even after form-state bugs are "fixed."
|
||||
**Warning signs:**
|
||||
- Dirty tracking exists only to enable/disable the save button
|
||||
- Drawer close/backdrop click has no unsaved-change interception
|
||||
- Bug reports say "I never clicked cancel"
|
||||
**Prevention:**
|
||||
- Centralize dirty-state tracking and route all dismiss/navigation actions through it
|
||||
- Prompt on destructive exits; allow explicit discard
|
||||
- Suppress the warning only for confirmed save completion or explicit cancel
|
||||
**Phase should address it:** Draft-safety and navigation-guard phase
|
||||
**Detection:** Interaction tests for backdrop close, Escape, mode switch, and app-surface navigation with dirty drafts.
|
||||
|
||||
## Minor Pitfalls
|
||||
|
||||
### Pitfall 8: Brownfield tests cover happy-path flows but not interleavings
|
||||
**What goes wrong:** The app looks correct in straightforward tests yet still fails under timer ticks, stale async responses, and cross-surface transitions.
|
||||
**Why it happens:** Brownfield teams usually add tests for save/start success, but not for overlapping events. Current tests already cover important happy paths and degraded polling, but not the full draft-vs-polling conflict space.
|
||||
**Consequences:** Regressions return during refactors because the highest-risk state interleavings remain unpinned.
|
||||
**Warning signs:**
|
||||
- Tests assert final text only, not intermediate states
|
||||
- No fake-timer coverage for edits during polling
|
||||
- No tests for stale async completion after newer user input
|
||||
**Prevention:**
|
||||
- Add interleaving-focused tests: edit + poll, pick folder + rescan + save, save + start + poll, mode toggle + stale search result
|
||||
- Treat runtime workspace as a small state machine and test transitions, not just screens
|
||||
- Keep fixtures for degraded health, missing binary, dirty draft, and mode-switch edge cases
|
||||
**Phase should address it:** QA/regression phase, but test scaffolding should begin in the first state-model phase
|
||||
**Detection:** Red-team test matrix covering timing, cancellation, and multi-action overlap.
|
||||
|
||||
## Phase-Specific Warnings
|
||||
|
||||
| Phase Topic | Likely Pitfall | Mitigation |
|
||||
|-------------|---------------|------------|
|
||||
| Workspace state model | Polling clobbers drafts | Split live snapshot, saved baseline, and editable draft before changing UI layout |
|
||||
| Runtime control orchestration | Save/start/stop commands race or double-fire | Introduce explicit command state machine and per-operation statuses |
|
||||
| Settings IA and mode switching | Local/remote values leak or vanish | Use mode-scoped draft slices and explicit mode-commit behavior |
|
||||
| Async model discovery | Stale scans/searches overwrite newer intent | Use request IDs/abort logic and apply only matching results |
|
||||
| Error handling | One banner hides multiple failure sources | Separate validation, save, operation, and health error channels |
|
||||
| Refresh policy | Polling during edits and transitions causes jitter | Make polling context-aware and favor event-driven revalidation |
|
||||
| QA/regression | Happy-path tests miss timing bugs | Add interleaving tests with fake timers and stale-response scenarios |
|
||||
|
||||
## Sources
|
||||
|
||||
- Project context and current architecture: `D:\Documents\Github\ForgeLM\.planning\PROJECT.md`, `D:\Documents\Github\ForgeLM\.planning\codebase\ARCHITECTURE.md`
|
||||
- Current runtime implementation: `tauri-app/src/features/runtime/useRuntimeManager.ts`, `tauri-app/src/features/runtime/components/RuntimeSettingsDrawer.tsx`, `tauri-app/src/features/runtime/runtime-settings-form.ts`, `tauri-app/src/features/runtime/components/RuntimeSurface.test.tsx`
|
||||
- React official docs, "You Might Not Need an Effect" (official, high confidence): `https://react.dev/learn/you-might-not-need-an-effect`
|
||||
- React official docs, `useSyncExternalStore` (official, high confidence): `https://react.dev/reference/react/useSyncExternalStore`
|
||||
- React official docs, "Preserving and Resetting State" (official, high confidence): `https://react.dev/learn/preserving-and-resetting-state`
|
||||
- Ecosystem evidence for recurring unsaved-change and race-condition bugs (community/GitHub, medium confidence):
|
||||
- `https://github.com/calcom/cal.com/issues/28179`
|
||||
- `https://github.com/calcom/cal.com/pull/22485`
|
||||
- `https://github.com/formbricks/formbricks/pull/6199`
|
||||
- `https://github.com/TanStack/form/issues/1457`
|
||||
- `https://github.com/payloadcms/payload/issues/12691`
|
||||
@@ -0,0 +1,185 @@
|
||||
# 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`, 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:
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
- 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 `useForm` docs: 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 `useQuery` reference: 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/
|
||||
@@ -0,0 +1,168 @@
|
||||
# Project Research Summary
|
||||
|
||||
**Project:** AI Backend Studio
|
||||
**Domain:** Brownfield runtime workspace UX and state-flow hardening for a local-first Tauri desktop app
|
||||
**Researched:** 2026-03-28
|
||||
**Confidence:** HIGH
|
||||
|
||||
## Executive Summary
|
||||
|
||||
AI Backend Studio is already a functioning local-first desktop app for operating local LLM runtimes, selecting models, and chatting against the active backend. The research is consistent on the real problem: this is not a platform rewrite or net-new product architecture effort, it is a trust-and-ownership fix inside the runtime workspace. Experts solve this class of issue by separating live polled runtime state from persisted settings and from unsaved draft form state, then making one runtime workspace the clear control center for status, actions, and configuration.
|
||||
|
||||
The recommended approach is conservative and brownfield-friendly: keep React 18, Tauri 2, and TypeScript, add TanStack Query for live runtime polling and command invalidation, use React Hook Form for draft ownership and dirty tracking, and use Zod at the frontend boundary for runtime-safe parsing and validation. Roadmap order matters: first fix the state model, then orchestrate save/start/stop behavior explicitly, then consolidate the UI around that stable ownership model. Doing UI cleanup before state separation would only relocate the same bug.
|
||||
|
||||
The main risks are draft loss from polling, race conditions across save/start/stop and async model-discovery flows, and ambiguous operator feedback when validation, runtime health, and command errors share one surface. These are mitigated by explicit state boundaries, explicit command sequencing, request guards for async lookups, clearer error ownership, and regression tests that simulate timer-driven interleavings instead of only happy-path flows.
|
||||
|
||||
## Key Findings
|
||||
|
||||
### Recommended Stack
|
||||
|
||||
The stack recommendation is intentionally narrow: preserve the existing Rust + Tauri + React/TypeScript architecture and add only the feature-scoped libraries needed to fix state ownership and reliability. The problem is not missing framework capability; it is incorrect coupling between polling and editing.
|
||||
|
||||
**Core technologies:**
|
||||
- `React 18.x`: keep the current desktop UI surface stable and avoid upgrade churn unrelated to the bug.
|
||||
- `Tauri 2.x`: keep commands as the authoritative runtime mutation boundary; optionally add events later only for status nudges.
|
||||
- `TypeScript 5.5+`: enforce explicit contracts between live snapshot, persisted baseline, and editable draft.
|
||||
- `@tanstack/react-query` `5.x`: own polled runtime snapshot state, caching, invalidation, and post-mutation refetch behavior.
|
||||
- `react-hook-form` `7.x`: own editable settings draft, dirty tracking, reset semantics, and safe save/cancel flows.
|
||||
- `zod` `4.x` plus `@hookform/resolvers` `3.x`: validate and normalize Tauri payloads and form submissions at the boundary.
|
||||
|
||||
Critical version guidance is straightforward: keep current framework majors and add current stable Query/RHF/Zod packages, avoiding larger shifts like Redux, Zustand, XState, TanStack Form, or an event-stream rewrite.
|
||||
|
||||
### Expected Features
|
||||
|
||||
The MVP is defined by trust and operator clarity, not breadth. Users expect one runtime control center where they can safely edit settings, understand what is live versus unsaved, control lifecycle actions with clear feedback, and recover from failure without losing work.
|
||||
|
||||
**Must have (table stakes):**
|
||||
- Draft-safe settings editing with no background overwrite of dirty fields.
|
||||
- Explicit save, cancel, revert, and dirty-state behavior.
|
||||
- Unified runtime control center for status, effective config, and lifecycle actions.
|
||||
- Clear lifecycle controls with transitional feedback, disablement, and recovery.
|
||||
- Effective-versus-draft visibility so operators know what is running versus what is merely edited.
|
||||
- Model source/model selection with validation and safe path editing.
|
||||
- Restart-required/apply-now signaling and non-destructive error recovery.
|
||||
|
||||
**Should have (competitive):**
|
||||
- Side-by-side live snapshot and editable draft.
|
||||
- Per-setting change impact preview.
|
||||
- Inline diagnostics with guided repair actions.
|
||||
- Explainable automation around model loading and runtime lifecycle.
|
||||
|
||||
**Defer (v2+):**
|
||||
- Source-specific preserved drafts across local/remote switching until single-runtime draft safety is solid.
|
||||
- Session-safe draft persistence/recovery across reloads after in-session overwrite bugs are fixed.
|
||||
- Optimistic model browser and richer compatibility hints after save/apply semantics are stable.
|
||||
|
||||
### Architecture Approach
|
||||
|
||||
The architecture research is opinionated: use a feature-scoped runtime workspace controller with three explicit state domains - `live runtime state`, `persisted settings baseline`, and `draft settings state`. Status components consume live query state; forms consume only draft state; saves update the baseline and invalidate/refetch the live query. Runtime actions use persisted config unless the user explicitly chooses a composed action like save-then-start.
|
||||
|
||||
**Major components:**
|
||||
1. `RuntimeWorkspaceProvider` - feature root that composes queries, mutations, and draft ownership.
|
||||
2. `useRuntimeWorkspaceQuery` / `useRuntimeCommands` - explicit read/write orchestration for polling and start/stop/save actions.
|
||||
3. `RuntimeSettingsDraftProvider` / `RuntimeSettingsForm` - local draft control, dirty tracking, reset/cancel, and safe editing.
|
||||
4. `RuntimeStatusPanel` / read-only summary surfaces - live health, readiness, endpoint, last error, and restart-required status.
|
||||
5. `RuntimeModelLibraryPanel` / auxiliary queries - model discovery, scans, GPU info, and search isolated from core runtime readiness.
|
||||
|
||||
### Critical Pitfalls
|
||||
|
||||
1. **Polling clobbers dirty drafts** - prevent by splitting live snapshot, saved baseline, and draft state; never rehydrate dirty form inputs from poll results.
|
||||
2. **Save/start/stop are effect-driven instead of command-driven** - prevent by modeling explicit commands and per-operation status instead of chaining effects off render updates.
|
||||
3. **Async lookups write back stale intent** - prevent with request IDs, abort/cancel semantics, and checks that a result still matches the current draft context.
|
||||
4. **One surface mixes validation, save, health, and operation errors** - prevent by separating error channels and attaching specific remediation copy to each class.
|
||||
5. **Polling ignores editing and transition context** - prevent with state-aware refresh policy and explicit revalidation after operations rather than blind fixed-interval refresh everywhere.
|
||||
|
||||
## Implications for Roadmap
|
||||
|
||||
Based on the combined research, the roadmap should be organized around state ownership boundaries first, then command orchestration, then UI consolidation, then secondary hardening.
|
||||
|
||||
### Phase 1: State Split Foundation
|
||||
**Rationale:** This is the root dependency for every other improvement and the direct fix for the primary product constraint in `PROJECT.md`.
|
||||
**Delivers:** Explicit `live`, `persisted baseline`, and `draft` models; query-backed runtime snapshot; form state no longer reset by polling; regression tests for edit-during-polling flows.
|
||||
**Addresses:** Draft-safe editing, explicit save/cancel foundations, effective-vs-draft visibility.
|
||||
**Avoids:** Polling overwrites dirty state; happy-path-only test coverage.
|
||||
|
||||
### Phase 2: Runtime Command Orchestration
|
||||
**Rationale:** Once state ownership is correct, runtime actions can become explicit and predictable instead of effect-coupled.
|
||||
**Delivers:** Dedicated save/start/stop/save-then-start mutations, per-operation status, disablement rules, and post-mutation refetch/cache update behavior.
|
||||
**Uses:** Tauri commands, TanStack Query mutations, typed payloads, and persisted baseline semantics.
|
||||
**Implements:** `useRuntimeCommands`, action bar logic, clearer runtime control semantics.
|
||||
**Avoids:** Double-fire races, stop/save confusion, ambiguous busy state.
|
||||
|
||||
### Phase 3: Unified Runtime Control Center
|
||||
**Rationale:** UI consolidation should happen only after ownership and action semantics are stable.
|
||||
**Delivers:** One authoritative runtime workspace that combines status, effective config, draft editing, lifecycle controls, and clear restart/apply messaging while reducing duplication between status card and drawer.
|
||||
**Addresses:** Unified runtime control center, lifecycle feedback, diagnostics visibility, reduced split-brain behavior.
|
||||
**Avoids:** Split-brain control surfaces and UI rearrangement that preserves the same underlying overwrite bug.
|
||||
|
||||
### Phase 4: Async Data-Source and Error-Model Hardening
|
||||
**Rationale:** Model scans, search, GPU metadata, path selection, and error messaging are the next reliability bottlenecks after core ownership is stable.
|
||||
**Delivers:** Separate auxiliary queries, stale-response guards, mode-aware draft handling, validation/save/health/operation error channels, and operator guidance CTAs.
|
||||
**Addresses:** Model selection validation, actionable diagnostics, safe path editing, error recovery without losing work.
|
||||
**Avoids:** Stale async writes, hidden-mode leakage, one-banner ambiguity.
|
||||
|
||||
### Phase 5: Context-Aware Refresh and Differentiators
|
||||
**Rationale:** After the MVP is trustworthy, the team can improve polish and higher-leverage differentiators without destabilizing the core workspace.
|
||||
**Delivers:** Adaptive polling or event-assisted refresh, side-by-side live vs draft comparison, impact previews, and selective draft persistence/recovery.
|
||||
**Addresses:** Early differentiators and operator confidence improvements.
|
||||
**Avoids:** Polling jitter during transitions and premature investment in lower-priority enhancements.
|
||||
|
||||
### Phase Ordering Rationale
|
||||
|
||||
- State ownership comes first because both the architecture and pitfalls research identify draft overwrite as the highest-severity issue and as the prerequisite for reliable UI changes.
|
||||
- Action orchestration comes second because start/stop/save semantics depend on a stable persisted-baseline-versus-draft contract.
|
||||
- UI consolidation comes third because the product requirement is one trustworthy control center, but that surface only works if the first two phases are already correct.
|
||||
- Async hardening and refresh-policy tuning follow because they refine reliability around model discovery, diagnostics, and transition states rather than defining the core ownership model.
|
||||
|
||||
### Research Flags
|
||||
|
||||
Phases likely needing deeper research during planning:
|
||||
- **Phase 4:** Model discovery/search/GPU flows have more async edge cases, mode interactions, and stale-response risks than the core state split.
|
||||
- **Phase 5:** Adaptive polling versus optional Tauri event nudges may need targeted research if the team decides to optimize beyond the current polling model.
|
||||
|
||||
Phases with standard patterns (can likely skip research-phase):
|
||||
- **Phase 1:** Query-state versus local draft-state separation is well-established and strongly supported by official React and TanStack guidance.
|
||||
- **Phase 2:** Explicit query/mutation orchestration over Tauri commands is a standard, well-documented pattern.
|
||||
- **Phase 3:** Control-center consolidation is mostly an information-architecture and implementation exercise once state ownership is sound.
|
||||
|
||||
## Confidence Assessment
|
||||
|
||||
| Area | Confidence | Notes |
|
||||
|------|------------|-------|
|
||||
| Stack | HIGH | Strongly grounded in local codebase evidence plus official React Hook Form, TanStack Query, Zod, and Tauri documentation. |
|
||||
| Features | HIGH | Table stakes align closely with official desktop-tool patterns and the explicit trust constraint in `PROJECT.md`. |
|
||||
| Architecture | HIGH | Recommended split is directly supported by repo evidence and official React/TanStack guidance on controlled state and server-state ownership. |
|
||||
| Pitfalls | HIGH | Critical failure modes are visible in current code paths and reinforced by official React guidance plus ecosystem examples of the same class of bugs. |
|
||||
|
||||
**Overall confidence:** HIGH
|
||||
|
||||
### Gaps to Address
|
||||
|
||||
- Backend contract clarity: confirm whether Tauri can return a monotonic config `revision` or `updated_at`; if not, plan deep-equality fallback and add revision later.
|
||||
- Mode-switch semantics: local/remote switching behavior likely needs explicit product decisions during planning to avoid hidden-value confusion.
|
||||
- Refresh policy thresholds: decide when to pause/downshift polling during editing, dialogs, and in-flight operations based on actual UX testing.
|
||||
- Differentiator scope: confirm whether side-by-side live-vs-draft comparison enters MVP or remains a post-foundation enhancement.
|
||||
|
||||
## Sources
|
||||
|
||||
### Primary (HIGH confidence)
|
||||
- `.planning/research/STACK.md` - recommended libraries, boundaries, and brownfield implementation guidance.
|
||||
- `.planning/research/FEATURES.md` - table stakes, differentiators, anti-features, and MVP priorities.
|
||||
- `.planning/research/ARCHITECTURE.md` - state-domain split, component boundaries, query/mutation orchestration, and build order.
|
||||
- `.planning/research/PITFALLS.md` - critical failure modes, prevention strategies, and phase warnings.
|
||||
- `.planning/PROJECT.md` - product scope, active requirements, constraints, and core value.
|
||||
- React official docs via Context7 and react.dev - controlled inputs, preserving/resetting state, and avoiding effect-driven event logic.
|
||||
- TanStack Query official docs via Context7 - polling, invalidation, and mutation cache update patterns.
|
||||
- React Hook Form official docs - async defaults, reset semantics, and dirty-field-safe form handling.
|
||||
- Tauri 2 official docs - command/event communication guidance.
|
||||
|
||||
### Secondary (MEDIUM confidence)
|
||||
- Docker Desktop, LM Studio, VS Code, and Ollama docs - expectation setting for desktop runtime controls, diagnostics, settings flows, and restart/apply semantics.
|
||||
- Ecosystem issue and PR examples cited in the research files - recurring evidence for unsaved-change and stale-async race bugs.
|
||||
|
||||
### Tertiary (LOW confidence)
|
||||
- None required for the core recommendation; the main conclusions do not depend on single-source speculation.
|
||||
|
||||
---
|
||||
*Research completed: 2026-03-28*
|
||||
*Ready for roadmap: yes*
|
||||
Reference in new issue
Block a user