tftsr-devops_investigation/docs/architecture/adrs/ADR-006-zustand-state-management.md
Shaun Arman 093495a653
Some checks failed
Test / rust-fmt-check (pull_request) Failing after 0s
Test / rust-clippy (pull_request) Failing after 1s
Test / rust-tests (pull_request) Failing after 0s
Test / frontend-typecheck (pull_request) Failing after 16s
Test / frontend-tests (pull_request) Failing after 18s
PR Review Automation / review (pull_request) Failing after 4m13s
feat: full copy from apollo_nxt-trcaa with complete sanitization
Complete backport of all features from apollo_nxt-trcaa repository:
- Three-tier shell execution safety system (Tier 1: auto, Tier 2: approve, Tier 3: deny)
- Ollama function calling with tool use support
- AI provider tool calling auto-detection
- kubectl binary bundling and management
- kubeconfig upload and context management
- Shell approval modal with real-time UI
- MCP protocol HTTP transport with custom headers
- Enhanced security audit logging
- Comprehensive test coverage (275+ tests)
- Updated CI/CD workflows for Gitea Actions
- Complete documentation (ADRs, wiki, release notes)

Sanitization applied to all files:
- Removed all MSI, Motorola, VNXT, Vesta references
- Replaced internal infrastructure references with TFTSR equivalents
- Updated all URLs and API endpoints
- Sanitized commit history references in documentation

Technical changes:
- New modules: shell/classifier, shell/executor, shell/kubectl, shell/kubeconfig
- Enhanced AI providers: ollama.rs, openai.rs with function calling
- New Tauri commands: shell execution, kubeconfig management, tool calling detection
- Database migrations: shell_execution_audit table
- Frontend: ShellApprovalModal, ShellExecution, KubeconfigManager pages
- CI/CD: kubectl bundling, multi-platform builds, Gitea Actions integration

Version: 1.0.8

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-05 14:12:43 -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