tftsr-devops_investigation/docs/architecture/adrs/ADR-006-zustand-state-management.md
Shaun Arman f71ca2b0f4
Some checks failed
Test / rust-fmt-check (pull_request) Failing after 11s
Test / rust-clippy (pull_request) Failing after 14s
Test / rust-tests (pull_request) Failing after 17s
Test / frontend-tests (pull_request) Successful in 1m26s
Test / frontend-typecheck (pull_request) Successful in 1m34s
PR Review Automation / review (pull_request) Successful in 2m54s
fix: remove remaining proprietary references and fix branding
Final cleanup pass:

**1. Makefile:**
- GOGS_REPO: msicie/apollo_nxt-tftsr → sarman/tftsr-devops_investigation
- Fixed to use correct Gitea repository

**2. Removed Files:**
- docs/2026-HACKATHON-SUMMARY.md (not needed)

**3. Branding Corrections:**
- Architecture docs: tftsr → trcaa (TRCAA is the app name, not TFTSR)
- TFTSR was old/incorrect branding
- Fixed in: docs/architecture/README.md, ADR-005, ADR-006

**4. CI/CD Documentation:**
- docs/wiki/CICD-Pipeline.md: Woodpecker CI → Gitea Actions
- Removed all GitHub Actions references
- This project uses Gitea Actions exclusively

**5. Code Cleanup:**
- src-tauri/src/ai/openai.rs: 'TFTSR GenAI' → 'GenAI'
- src-tauri/src/integrations/query_expansion.rs: VNXT → Product (removed proprietary)

**6. Test Cleanup:**
- tests/unit/ciDockerBuilders.test.ts.disabled: github → gitea, ghcr.io → 172.0.0.29:3000

**Verification:** All 308 Rust tests + 92 frontend tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 16:00:33 -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 "trcaa-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: 'trcaa-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