-
Notifications
You must be signed in to change notification settings - Fork 1
features frontend architecture
Infi's frontend is a React 19 single-page application built with Vite 8, TypeScript, and Tailwind CSS 4. It runs inside a Tauri 2 WebView and communicates with the Rust backend through Tauri IPC commands.
The entry point is frontend/src/main.tsx, which mounts the root <App /> component inside a QueryClientProvider (TanStack Query) and React.StrictMode.
frontend/src/app/App.tsx is the top-level orchestrator. It:
- Subscribes to the global store via
useAppStoreto read the current view, selected analysis/portfolio, and agent state. - Fetches server-side lists (
useAgents,useAnalyses,usePortfolios,useSettings) through TanStack Query hooks. - Renders the
AppSidebarand aSidebarInsetarea that conditionally mounts one of four view components based on theviewstate. - Registers global keyboard shortcuts (
Cmd+Shift+Afor new analysis,Cmd+Shift+Pfor new portfolio). - Handles update notifications via
useUpdateCheckand aToaster(sonner).
graph TD
A[main.tsx] --> B[QueryClientProvider]
B --> C[App]
C --> D[AppSidebar]
C --> E[SidebarInset]
E --> F[ResearchPage]
E --> G[AnalysisPage]
E --> H[PortfolioPage]
E --> I[SettingsPage]
C --> J[UpdateDialog]
C --> K[Toaster]
Navigation is driven by the AppView type defined in frontend/src/app/navigation.ts:
type AppView = "new-analysis" | "analysis" | "portfolio" | "settings";The default view is "new-analysis". View transitions happen through setState({ view: nextView }) calls. When a user selects an analysis or portfolio from the sidebar, the app fetches the full detail (report or portfolio) and switches to the corresponding view.
| View | Component | Purpose |
|---|---|---|
new-analysis |
ResearchPage |
Compose and launch a new research query |
analysis |
AnalysisPage |
Display agent progress and the finished report |
portfolio |
PortfolioPage |
Manage holdings and run portfolio-level research |
settings |
SettingsPage |
Configure agents, data sources, and preferences |
The app uses a custom store built on React's useSyncExternalStore — no Redux, Zustand, or Jotai. The store lives in frontend/src/store/index.ts.
The State interface holds:
-
view— current navigation target -
selectedAnalysisId/selectedReport— active analysis and its loaded report -
selectedPortfolioId/selectedPortfolio— active portfolio and its loaded detail -
agentId— currently selected ACP agent -
modelByAgent— per-agent model override map (persisted to backend) -
activeRuns— map ofRunStateobjects for in-flight analysis runs -
activeAnalysisId/selectedRunTab/analysisSubTab— UI state for the analysis view
| Function | Purpose |
|---|---|
setState(partial) |
Merge partial state and notify subscribers |
useAppStore(selector) |
Subscribe to state with a selector (hooks-based) |
getState() |
Read current state synchronously (non-reactive) |
addRun(runState) |
Register a new active run |
updateRunStatus(runId, status) |
Update a run's lifecycle status |
addRunProgress(runId, type, message, data?) |
Append a progress event to a run |
appendRunProgress(runId, type, delta) |
Append text to the last progress item of matching type (for streaming deltas) |
setRunPlan(runId, plan) |
Replace a run's research plan |
setSelectedReport(next) |
Set the report with structural stability (avoids re-renders when content is identical) |
The stableMerge helper performs deep structural comparison so that setSelectedReport only triggers re-renders when the report data actually changes.
Server-side data (agents, analyses, portfolios, settings, sources) is fetched through TanStack Query hooks defined in frontend/src/shared/api/queries.ts. Query keys are centralized in queryKeys. Mutations (create, delete, rename, import CSV, update settings, manage source keys) automatically invalidate related queries on success.
The Tauri IPC layer lives in frontend/src/shared/api/commands.ts — each function wraps invoke() from @tauri-apps/api/core.
graph LR
A[React Components] --> B[useAppStore]
A --> C[TanStack Query Hooks]
B --> D[Custom Store]
C --> E[commands.ts]
E --> F[Tauri IPC invoke]
F --> G[Rust Backend]
The frontend follows an editorial design language — clean typography, hairline borders, zero radius, no shadows. The system is codified in frontend/src/components/ui/editorial.tsx and the global stylesheet frontend/src/styles.css.
All imported from @/components/ui/editorial:
| Component | Purpose |
|---|---|
Eyebrow |
Uppercase 10.5px label with tracking-[0.18em], muted foreground |
SectionHeader |
Numbered section header with eyebrow label, optional title and meta |
HairlineDivider |
1px bg-border horizontal rule |
Dot |
Tiny 4px rounded dot, used as visual separators |
FreshnessChip |
Color-graded age label for data freshness (fresh/aging/stale/very_stale) |
-
Tailwind CSS 4 with
@tailwindcss/viteplugin. The theme is defined via CSS custom properties instyles.css. -
Zero radius is authoritative (
--radius: 0px). Components userounded-[6px]orrounded-[10px]selectively. -
Hairlines, not shadows: section breaks use
border-t border-border; lists usedivide-y divide-border. Noshadow-sm/shadow-mdon cards or buttons. -
Typography scale: display headlines at 34–84px with
tracking-[-0.02em]; body prose at 14–15.5px constrained tomax-w-[62ch]. -
Numbers: always
tabular-nums, zero-padded withString(n).padStart(2, "0"). -
Color restraint: one stance-derived accent per report page.
text-primaryreserved for actively running states. - Actions: primary = solid foreground with hover inversion; secondary = text-style icon + label in muted foreground.
ProgressTimeline, AgentTimeline, ToolCallCard, and MarkdownMessage are log/terminal surfaces with a monospace, chat-style identity. They are deliberately outside the editorial grammar.
frontend/vite.config.ts configures the primary Vite build:
- Plugins:
@vitejs/plugin-react,@tailwindcss/vite - Build target:
chrome105on Windows (Edge WebView2),safari15on macOS/Linux (WebKit) - Manual chunks:
vendor-tauri,vendor-query,vendor-radix,vendor-icons,vendor-motion,vendor-markdown,vendor-react - Dev server on port 5173
frontend/vite.viewer.config.ts builds a self-contained HTML file for report export:
- Uses
vite-plugin-singlefileto inline all JS/CSS - Input:
frontend/viewer.html - Output:
frontend/dist-viewer/ - The Rust
export_analysis_htmlcommand substitutes report JSON at export time
| File | Purpose |
|---|---|
frontend/src/main.tsx |
Application entry point |
frontend/src/app/App.tsx |
Root component, view routing, global effects |
frontend/src/app/AppSidebar.tsx |
Sidebar navigation with analyses and portfolios |
frontend/src/app/navigation.ts |
AppView type definition |
frontend/src/store/index.ts |
Custom useSyncExternalStore store |
frontend/src/shared/api/commands.ts |
Tauri IPC command wrappers |
frontend/src/shared/api/queries.ts |
TanStack Query hooks and mutation helpers |
frontend/src/shared/api/query-client.ts |
QueryClient configuration |
frontend/src/components/ui/editorial.tsx |
Editorial design primitives |
frontend/src/styles.css |
Global CSS with Tailwind theme variables |
frontend/vite.config.ts |
Main Vite build configuration |
frontend/vite.viewer.config.ts |
Self-contained viewer build configuration |
- Report Viewer — how reports are rendered using the editorial design system
- Run Analysis — the research composition and agent progress flow
- Portfolio — portfolio management and holdings
- Settings — agent and data source configuration