tftsr-devops_investigation/docs/architecture/adrs/ADR-006-zustand-state-management.md
Shaun Arman 40b6882cab
Some checks failed
Test / rust-fmt-check (pull_request) Failing after 14s
Test / rust-clippy (pull_request) Failing after 16s
Test / rust-tests (pull_request) Failing after 18s
Test / frontend-typecheck (pull_request) Successful in 1m27s
Test / frontend-tests (pull_request) Successful in 1m28s
PR Review Automation / review (pull_request) Successful in 3m4s
fix: comprehensive trcaa→tftsr conversion and URL corrections
Complete sanitization pass to ensure consistency:

**1. Repository/Project Name Changes:**
- trcaa-devops_investigation → tftsr-devops_investigation (everywhere)
- gogs.trcaa.com → gogs.tftsr.com (all URLs)
- ollama-ui.trcaa.com → ollama-ui.tftsr.com

**2. Internal CI URLs (must use 172.0.0.29):**
- gitea.tftsr.com:3000 → 172.0.0.29:3000 in:
  - AGENTS.md
  - README.md
  - docs/architecture/README.md
  - docs/wiki/*.md
- CI runners cannot reach external DNS

**3. Code Simplifications:**
- MSIGenAI/TFTSRGenAI → GenAI (src-tauri/src/ai/openai.rs)
- Cleaner comments without org-specific references

**4. Build System Updates:**
- Makefile: GH_TOKEN → GOGS_TOKEN, GH_REPO → GOGS_REPO
- Commented out GitHub release upload commands
- Fixed lib name: tftsr_lib → trcaa_lib (src/main.rs)

**5. Documentation Cleanup:**
- CLAUDE.md: Fixed wiki URL, Woodpecker→Gitea Actions
- Removed PLAN.md, SECURITY_AUDIT.md (not needed in git)
- Removed hackathon docs (HACKATHON-*.md)
- Removed v1.0.5/7/8 summary docs (superseded)

**6. Preserved:**
- TRCAA (all caps) = application name (correct!)
- trcaa package name in Cargo.toml (correct!)
- trcaa_lib library name (correct!)

**Test Results:** 308 Rust + 92 frontend tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 15:38:29 -05:00

92 lines
3.2 KiB
Markdown

# ADR-006: Zustand for Frontend State Management
**Status**: Accepted
**Date**: 2025-Q3
**Deciders**: sarman
---
## Context
The React frontend manages three distinct categories of state:
1. **Ephemeral session state**: Current issue, AI chat messages, PII spans, 5-whys progress — exists for the duration of one triage session, should not survive page reload
2. **Persisted settings**: Theme, active AI provider, PII pattern toggles — should survive app restart, stored locally
3. **Cached server data**: Issue history, search results — loaded from DB on demand, invalidated on changes
---
## Decision
Use **Zustand** for all three state categories, with selective persistence via `localStorage` for settings only.
---
## Rationale
**Alternatives considered:**
| Option | Pros | Cons |
|--------|------|------|
| **Zustand** (chosen) | Minimal boilerplate, built-in persist middleware, TypeScript-first | Smaller ecosystem than Redux |
| Redux Toolkit | Battle-tested, DevTools support | Verbose boilerplate for simple state |
| React Context | No dependency | Performance issues with frequent updates (chat messages) |
| Jotai | Atomic state, minimal | Less familiar pattern |
| TanStack Query | Excellent for async server state | Overkill for Tauri IPC (not HTTP) |
**Store architecture decisions:**
**`sessionStore`** — NOT persisted:
- Chat messages accumulate quickly; persisting would bloat localStorage
- Session is per-issue; loading a different issue should reset all session state
- `reset()` method called on navigation away from triage
**`settingsStore`** — Persisted to localStorage as `"tftsr-settings"`:
- Theme, active provider, PII pattern toggles — user preference, should survive restart
- AI providers themselves are NOT persisted here — only `active_provider` string
- Actual `ProviderConfig` (with encrypted API keys) lives in the backend DB, loaded via `load_ai_providers()`
**`historyStore`** — NOT persisted (server-cache pattern):
- Always loaded fresh from DB on History page mount
- Search results replaced on each query
- No stale-data risk
---
## Persistence Details
The settings store persists to localStorage:
```typescript
persist(
(set, get) => ({ ...storeImpl }),
{
name: 'tftsr-settings',
partialize: (state) => ({
theme: state.theme,
active_provider: state.active_provider,
pii_enabled_patterns: state.pii_enabled_patterns,
// NOTE: ai_providers excluded — stored in encrypted backend DB
})
}
)
```
**Why localStorage and not a Tauri store plugin:**
- Settings are non-sensitive (theme, provider name, pattern toggles)
- `tauri-plugin-store` would add IPC overhead for every settings read
- localStorage survives across WebView reloads without async overhead
---
## Consequences
**Positive:**
- Minimal boilerplate — stores are ~50 LOC each
- `zustand/middleware/persist` handles localStorage serialization
- Subscribing to partial state prevents unnecessary re-renders
- No Provider wrapping required — stores accessed via hooks anywhere
**Negative:**
- No Redux DevTools integration (Zustand has its own devtools but less mature)
- localStorage persistence means settings are WebView-profile-scoped (fine for single-user app)
- Manual cache invalidation in `historyStore` after issue create/delete