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

3.2 KiB

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:

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