diff --git a/.github/workflows/guardrails.yml b/.github/workflows/guardrails.yml index e6b903d1a..5cf95179b 100644 --- a/.github/workflows/guardrails.yml +++ b/.github/workflows/guardrails.yml @@ -26,15 +26,13 @@ jobs: run: | # List of patterns to block BLOCKED_FILES=( - "AGENTS.md" - "CLAUDE.md" "SYSTEM_PROMPT.md" "agent-instructions.md" "prompt.md" ".ai-notes" ".agent" ) - + for pattern in "${BLOCKED_FILES[@]}"; do if find . -name "$pattern" -type f 2>/dev/null | grep -q .; then echo "❌ Found blocked file: $pattern" @@ -55,7 +53,7 @@ jobs: "openhands" "opencode" ) - + for pattern in "${SENSITIVE_PATTERNS[@]}"; do if grep -r "$pattern" . --include="*.ts" --include="*.js" --include="*.py" --include="*.md" 2>/dev/null | grep -v "node_modules" | grep -v ".git" | grep -q .; then echo "⚠️ Found reference: $pattern" diff --git a/.github/workflows/security-codeql.yml b/.github/workflows/security-codeql.yml index 01232a69c..2d8db3f65 100644 --- a/.github/workflows/security-codeql.yml +++ b/.github/workflows/security-codeql.yml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - language: ["javascript", "python"] + language: ["javascript"] steps: - name: Checkout uses: actions/checkout@v6 diff --git a/ARCHITECTURAL_GUARDRAILS.md b/ARCHITECTURAL_GUARDRAILS.md index 99311f02d..ac156479c 100644 --- a/ARCHITECTURAL_GUARDRAILS.md +++ b/ARCHITECTURAL_GUARDRAILS.md @@ -1,25 +1,25 @@ # Architectural Guardrails — Folio -> **Purpose**: Define non-negotiable architectural constraints for the Folio codebase. -> These guardrails protect the system's integrity as it evolves. +> **Purpose**: Define non-negotiable architectural constraints for the Folio +> codebase. These guardrails protect the system's integrity as it evolves. --- ## Tech Stack -| Layer | Technology | Version | -|-------|-----------|---------| -| **Desktop Shell** | Tauri 2.x | `@tauri-apps/cli ^2.10.0` | -| **Backend (Rust)** | Rust 2021 Edition | Workspace: `apps/tauri`, `apps/server`, `crates/*` | -| **Frontend** | React + TypeScript | `^5.9.3` | -| **Build Tool** | Vite | — | -| **Styling** | Tailwind CSS | — | -| **State Management** | Zustand | — | -| **Charts** | Recharts | — | -| **Database** | SQLite (via Diesel 2.2) | Bundled | -| **Package Manager** | pnpm (monorepo) | Workspaces | -| **Testing** | Vitest + Playwright (E2E) | — | -| **Linting** | ESLint 9 + Prettier | — | +| Layer | Technology | Version | +| -------------------- | ------------------------- | -------------------------------------------------- | +| **Desktop Shell** | Tauri 2.x | `@tauri-apps/cli ^2.10.0` | +| **Backend (Rust)** | Rust 2021 Edition | Workspace: `apps/tauri`, `apps/server`, `crates/*` | +| **Frontend** | React + TypeScript | `^5.9.3` | +| **Build Tool** | Vite | — | +| **Styling** | Tailwind CSS | — | +| **State Management** | Zustand | — | +| **Charts** | Recharts | — | +| **Database** | SQLite (via Diesel 2.2) | Bundled | +| **Package Manager** | pnpm (monorepo) | Workspaces | +| **Testing** | Vitest + Playwright (E2E) | — | +| **Linting** | ESLint 9 + Prettier | — | --- @@ -43,10 +43,13 @@ apps/ 1. **No `unsafe` code** — `unsafe_code = "forbid"` in workspace lints. 2. **Clippy warnings are errors** — `all = "warn"` in workspace lints. -3. **Diesel for ORM** — All database access goes through Diesel, never raw SQL except migrations. +3. **Diesel for ORM** — All database access goes through Diesel, never raw SQL + except migrations. 4. **SQLite only** — No external database dependencies. Data is local-first. -5. **Error handling** — Use `thiserror` for library errors, `anyhow` for application errors. -6. **Async runtime** — Tokio is the only async runtime. No mixing with other runtimes. +5. **Error handling** — Use `thiserror` for library errors, `anyhow` for + application errors. +6. **Async runtime** — Tokio is the only async runtime. No mixing with other + runtimes. 7. **HTTP client** — `reqwest` with `rustls-tls` only. No native TLS. --- @@ -54,37 +57,51 @@ apps/ ## Frontend Guardrails 1. **TypeScript strict mode** — No `any` types. Use proper type definitions. -2. **Component architecture** — Feature-based organization under `apps/frontend/src/features/`. -3. **State management** — Zustand for global state. No Redux, no Context for data state. -4. **Data fetching** — TanStack Query (React Query) for server state. No manual fetch calls. -5. **Styling** — Tailwind CSS utility classes only. No CSS modules, no styled-components. +2. **Component architecture** — Feature-based organization under + `apps/frontend/src/features/`. +3. **State management** — Zustand for global state. No Redux, no Context for + data state. +4. **Data fetching** — TanStack Query (React Query) for server state. No manual + fetch calls. +5. **Styling** — Tailwind CSS utility classes only. No CSS modules, no + styled-components. 6. **No direct DOM manipulation** — Use React refs when absolutely necessary. --- ## Tauri-Specific Guardrails -1. **IPC boundaries** — Frontend communicates with Rust only through Tauri commands (`#[tauri::command]`). -2. **Capabilities model** — Use Tauri v2 capabilities for permission scoping. No blanket permissions. -3. **No direct filesystem access from frontend** — All file I/O goes through Rust commands. -4. **Secrets in Rust only** — API keys, encryption keys, and credentials never touch the frontend. +1. **IPC boundaries** — Frontend communicates with Rust only through Tauri + commands (`#[tauri::command]`). +2. **Capabilities model** — Use Tauri v2 capabilities for permission scoping. No + blanket permissions. +3. **No direct filesystem access from frontend** — All file I/O goes through + Rust commands. +4. **Secrets in Rust only** — API keys, encryption keys, and credentials never + touch the frontend. --- ## Data & Security Guardrails -1. **Local-first architecture** — All user data stored locally in SQLite. No cloud storage of financial data. -2. **Encryption at rest** — Sensitive data encrypted using `chacha20poly1305` + `x25519-dalek`. -3. **No analytics or tracking** — No third-party analytics, crash reporting, or telemetry. -4. **Secret scanning** — Pre-commit hooks scan for API keys, tokens, and credentials. -5. **Environment variables** — All configuration via `.env` files. Never commit `.env`. +1. **Local-first architecture** — All user data stored locally in SQLite. No + cloud storage of financial data. +2. **Encryption at rest** — Sensitive data encrypted using `chacha20poly1305` + + `x25519-dalek`. +3. **No analytics or tracking** — No third-party analytics, crash reporting, or + telemetry. +4. **Secret scanning** — Pre-commit hooks scan for API keys, tokens, and + credentials. +5. **Environment variables** — All configuration via `.env` files. Never commit + `.env`. --- ## Monorepo Guardrails 1. **pnpm workspaces** — All packages managed through `pnpm-workspace.yaml`. -2. **Internal crate paths** — Rust crates referenced via `path = "crates/*"` in `Cargo.toml`. +2. **Internal crate paths** — Rust crates referenced via `path = "crates/*"` in + `Cargo.toml`. 3. **Shared TypeScript config** — `tsconfig.base.json` extended by all packages. 4. **Linting consistency** — ESLint config shared via `eslint.base.config.js`. @@ -94,7 +111,8 @@ apps/ 1. **Unit tests** — Co-located with source files (`*.test.ts`, `*_test.rs`). 2. **E2E tests** — Playwright for full application flows in `e2e/`. -3. **No test-only dependencies in production** — Dev dependencies only in `devDependencies`. +3. **No test-only dependencies in production** — Dev dependencies only in + `devDependencies`. 4. **Coverage threshold** — Maintain minimum 80% coverage on critical paths. --- @@ -103,8 +121,10 @@ apps/ 1. **Bundle size budget** — Frontend bundle must not exceed 500KB gzipped. 2. **Database queries** — All queries must use indexes. No N+1 queries. -3. **Market data caching** — Market data cached locally, never fetched synchronously on UI thread. -4. **Lazy loading** — Features loaded on demand. No eager loading of non-critical modules. +3. **Market data caching** — Market data cached locally, never fetched + synchronously on UI thread. +4. **Lazy loading** — Features loaded on demand. No eager loading of + non-critical modules. --- @@ -117,8 +137,10 @@ See [ADR Template](docs/adr/TEMPLATE.md) for format. ## Diagrams -Architecture diagrams are maintained in `docs/architecture/diagrams/` using Mermaid syntax. -See [System Context Diagram](docs/architecture/diagrams/system-context.mmd) for the C4 overview. +Architecture diagrams are maintained in `docs/architecture/diagrams/` using +Mermaid syntax. See +[System Context Diagram](docs/architecture/diagrams/system-context.mmd) for the +C4 overview. --- @@ -133,4 +155,4 @@ These guardrails are enforced through: --- -*Last updated: April 2026 | Version: 3.0.0* +_Last updated: April 2026 | Version: 3.0.0_ diff --git a/addons/goal-progress-tracker/src/components/help-popover.tsx b/addons/goal-progress-tracker/src/components/help-popover.tsx index 7c448d13d..9ee6ef3ed 100644 --- a/addons/goal-progress-tracker/src/components/help-popover.tsx +++ b/addons/goal-progress-tracker/src/components/help-popover.tsx @@ -13,7 +13,7 @@ function HelpPopover() {
-

How It Works

+

How It Works

• Select a goal from your saved goals or track a custom target

• Each dot represents your chosen step amount towards your investment target

diff --git a/addons/goal-progress-tracker/src/components/investment-calendar.tsx b/addons/goal-progress-tracker/src/components/investment-calendar.tsx index c7351a399..858d798fa 100644 --- a/addons/goal-progress-tracker/src/components/investment-calendar.tsx +++ b/addons/goal-progress-tracker/src/components/investment-calendar.tsx @@ -116,7 +116,7 @@ function Tooltip({
-

Step {data.stepIndex + 1}

+

Step {data.stepIndex + 1}

Target: {formatCurrency(data.stepAmount, isBalanceHidden)}

@@ -135,7 +135,7 @@ function Tooltip({
{/* Tooltip arrow */} -
+
); diff --git a/addons/investment-fees-tracker/src/components/account-breakdown.tsx b/addons/investment-fees-tracker/src/components/account-breakdown.tsx index aab86ddb3..7240e9a28 100644 --- a/addons/investment-fees-tracker/src/components/account-breakdown.tsx +++ b/addons/investment-fees-tracker/src/components/account-breakdown.tsx @@ -23,7 +23,7 @@ function EmptyPlaceholder({ {icon}

{title}

-

+

{description}

@@ -138,7 +138,7 @@ export function AccountBreakdown({ {percentage.toFixed(1)}% of total fees {/* Tooltip arrow */} -
+
diff --git a/addons/investment-fees-tracker/src/components/fee-categories-chart.tsx b/addons/investment-fees-tracker/src/components/fee-categories-chart.tsx index dbafa56b6..cc207fe50 100644 --- a/addons/investment-fees-tracker/src/components/fee-categories-chart.tsx +++ b/addons/investment-fees-tracker/src/components/fee-categories-chart.tsx @@ -73,7 +73,7 @@ export const FeeCategoriesChart = ({
- + Fee Categories
diff --git a/addons/investment-fees-tracker/src/components/fee-history-chart.tsx b/addons/investment-fees-tracker/src/components/fee-history-chart.tsx index 72c30b18a..5a4f92b26 100644 --- a/addons/investment-fees-tracker/src/components/fee-history-chart.tsx +++ b/addons/investment-fees-tracker/src/components/fee-history-chart.tsx @@ -74,7 +74,7 @@ export function FeeHistoryChart({ Fee History {periodDescription}
- + {chartData.length === 0 ? (

{title}

-

+

{description}

@@ -138,7 +138,7 @@ export function TopFeeSources({ feeAnalytics, currency, isBalanceHidden }: TopFe {percentage.toFixed(1)}% of total fees {/* Tooltip arrow */} -
+
diff --git a/addons/swingfolio-addon/src/components/distribution-charts.tsx b/addons/swingfolio-addon/src/components/distribution-charts.tsx index 9b5f37415..955a18e0b 100644 --- a/addons/swingfolio-addon/src/components/distribution-charts.tsx +++ b/addons/swingfolio-addon/src/components/distribution-charts.tsx @@ -100,7 +100,7 @@ export function DistributionCharts({ distribution, currency }: DistributionChart return ( <>
= 0 ? "border-success/10 bg-success/10" : "border-destructive/10 bg-destructive/10"}`} > - + P/L
-
+
{selectedPeriod} → {getChartPeriodDisplay(selectedPeriod).type}
diff --git a/apps/frontend/src/components/account-selector-mobile.tsx b/apps/frontend/src/components/account-selector-mobile.tsx index ed1345249..024942201 100644 --- a/apps/frontend/src/components/account-selector-mobile.tsx +++ b/apps/frontend/src/components/account-selector-mobile.tsx @@ -148,7 +148,7 @@ export const AccountSelectorMobile = forwardRef - + Select Account Choose an account to add to the comparison diff --git a/apps/frontend/src/components/account-selector.tsx b/apps/frontend/src/components/account-selector.tsx index 02016aba3..fb58c78a0 100644 --- a/apps/frontend/src/components/account-selector.tsx +++ b/apps/frontend/src/components/account-selector.tsx @@ -351,7 +351,7 @@ export const AccountSelector = forwardRef diff --git a/apps/frontend/src/components/action-palette.tsx b/apps/frontend/src/components/action-palette.tsx index e704326ce..b9c88bced 100644 --- a/apps/frontend/src/components/action-palette.tsx +++ b/apps/frontend/src/components/action-palette.tsx @@ -65,7 +65,7 @@ export function ActionPalette({ side={side} sideOffset={8} className={cn( - "w-auto min-w-[260px] max-w-[320px] p-0", + "w-auto max-w-[320px] min-w-[260px] p-0", "rounded-2xl", "border-border/50 border dark:border-white/10", "bg-card backdrop-blur-xl", @@ -74,7 +74,7 @@ export function ActionPalette({ > {/* Header - only show if title provided */} {title && ( -
+

{title}

diff --git a/apps/frontend/src/components/app-launcher.tsx b/apps/frontend/src/components/app-launcher.tsx index d245292c6..2be25af62 100644 --- a/apps/frontend/src/components/app-launcher.tsx +++ b/apps/frontend/src/components/app-launcher.tsx @@ -749,7 +749,7 @@ export function AppLauncher() { Quick launch @@ -758,7 +758,7 @@ export function AppLauncher() { className={cn( "flex flex-1 flex-col bg-transparent", "[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-4 [&_[cmdk-group-heading]]:font-medium", - "[&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2", + "[&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0", "[&_[cmdk-input-wrapper]]:px-5 [&_[cmdk-input]]:h-14 [&_[cmdk-input]]:text-base", "[&_[cmdk-item]]:px-4 [&_[cmdk-item]]:py-4 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5", "[&_[data-cmdk-input-wrapper]_svg]:h-5 [&_[data-cmdk-input-wrapper]_svg]:w-5", diff --git a/apps/frontend/src/components/benchmark-symbol-selector-mobile.tsx b/apps/frontend/src/components/benchmark-symbol-selector-mobile.tsx index b2e7bf7d6..d3a25a47a 100644 --- a/apps/frontend/src/components/benchmark-symbol-selector-mobile.tsx +++ b/apps/frontend/src/components/benchmark-symbol-selector-mobile.tsx @@ -187,7 +187,7 @@ export function BenchmarkSymbolSelectorMobile({ {!iconOnly && "Add Benchmark"} - + Select Benchmark Choose a benchmark or search for any symbol @@ -197,13 +197,13 @@ export function BenchmarkSymbolSelectorMobile({ {/* Search Input */}
- + setSearchQuery(e.target.value)} - className="bg-background border-input ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring h-10 w-full rounded-md border px-3 py-2 pl-9 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2" + className="bg-background border-input ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring h-10 w-full rounded-md border px-3 py-2 pl-9 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none" />
diff --git a/apps/frontend/src/components/classification/classification-sheet.tsx b/apps/frontend/src/components/classification/classification-sheet.tsx index 4b791f4cc..2e362a283 100644 --- a/apps/frontend/src/components/classification/classification-sheet.tsx +++ b/apps/frontend/src/components/classification/classification-sheet.tsx @@ -78,7 +78,7 @@ export function ClassificationSheet({
-
+
{/* Loading State */} {isLoading && } diff --git a/apps/frontend/src/components/classification/multi-select-taxonomy.tsx b/apps/frontend/src/components/classification/multi-select-taxonomy.tsx index 69c620e79..22bf50dc9 100644 --- a/apps/frontend/src/components/classification/multi-select-taxonomy.tsx +++ b/apps/frontend/src/components/classification/multi-select-taxonomy.tsx @@ -284,7 +284,7 @@ export function MultiSelectTaxonomy({ className="h-8 pr-6 text-sm" placeholder="100" /> - + %
diff --git a/apps/frontend/src/components/font-selector.tsx b/apps/frontend/src/components/font-selector.tsx index 8f83f9235..590b3576a 100644 --- a/apps/frontend/src/components/font-selector.tsx +++ b/apps/frontend/src/components/font-selector.tsx @@ -44,7 +44,7 @@ export function FontSelector({ value, onChange, className }: FontSelectorProps)
{font.label}
{value === font.value && ( -
+
)} ))} diff --git a/apps/frontend/src/components/liquid-glass.tsx b/apps/frontend/src/components/liquid-glass.tsx index 1a32be097..5fdbfcf53 100644 --- a/apps/frontend/src/components/liquid-glass.tsx +++ b/apps/frontend/src/components/liquid-glass.tsx @@ -53,7 +53,7 @@ export function LiquidGlass({ style={{ ...style }} >
{children}
-
+
); } diff --git a/apps/frontend/src/components/mobile-actions-menu.tsx b/apps/frontend/src/components/mobile-actions-menu.tsx index 92f59886d..e207a4470 100644 --- a/apps/frontend/src/components/mobile-actions-menu.tsx +++ b/apps/frontend/src/components/mobile-actions-menu.tsx @@ -38,7 +38,7 @@ export function MobileActionsMenu({ - + {title} {description} @@ -53,7 +53,7 @@ export function MobileActionsMenu({ action.onClick(); onOpenChange(false); }} - className="hover:bg-accent active:bg-accent/80 focus:ring-primary flex items-center gap-4 rounded-lg border p-4 text-left transition-colors focus:outline-none focus:ring-2" + className="hover:bg-accent active:bg-accent/80 focus:ring-primary flex items-center gap-4 rounded-lg border p-4 text-left transition-colors focus:ring-2 focus:outline-none" >
diff --git a/apps/frontend/src/components/mobile-loading-indicator.tsx b/apps/frontend/src/components/mobile-loading-indicator.tsx index c367da5e0..8c1e79a7a 100644 --- a/apps/frontend/src/components/mobile-loading-indicator.tsx +++ b/apps/frontend/src/components/mobile-loading-indicator.tsx @@ -8,7 +8,7 @@ export function MobileLoadingIndicator() { } return ( -
+
{/* Smooth indeterminate progress strip */}
onViewChange(view.value)} className={cn( "relative flex cursor-pointer items-center gap-2 rounded-full px-3 py-1.5 text-sm font-medium transition-colors duration-200", - "focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2", + "focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none", isActive ? "text-foreground" : "text-muted-foreground hover:text-foreground/80", )} aria-current={isActive ? "page" : undefined} @@ -102,7 +102,7 @@ function MobileNavigation({ onClick={() => onViewChange(item.value)} className={cn( "relative flex cursor-pointer items-center justify-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-medium transition-colors duration-200", - "focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2", + "focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none", isActive ? "text-foreground" : "text-muted-foreground", )} aria-label={item.label} @@ -229,7 +229,7 @@ export function SwipablePage({ /* Desktop: Navigation at top center + content below */
{/* Header with Navigation and Actions */} -
+
{title &&

{title}

} @@ -200,7 +200,7 @@ export function SwipableRoutesPage({ - + {showCustomAssetForm ? ( <> {/* Custom Asset Form Header */} @@ -291,13 +291,13 @@ export const SymbolSelectorMobile = forwardRef
- + setSearchQuery(e.target.value)} - className="bg-background border-input ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring h-14 w-full rounded-md border px-4 py-3 pl-12 text-base focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2" + className="bg-background border-input ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring h-14 w-full rounded-md border px-4 py-3 pl-12 text-base focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none" autoFocus />
diff --git a/apps/frontend/src/components/ticker-search.tsx b/apps/frontend/src/components/ticker-search.tsx index 3cea90d9e..1ab6c1365 100644 --- a/apps/frontend/src/components/ticker-search.tsx +++ b/apps/frontend/src/components/ticker-search.tsx @@ -514,7 +514,7 @@ const TickerSearchInput = forwardRef( diff --git a/apps/frontend/src/components/update-dialog.tsx b/apps/frontend/src/components/update-dialog.tsx index 723edd6e2..42cb63bdd 100644 --- a/apps/frontend/src/components/update-dialog.tsx +++ b/apps/frontend/src/components/update-dialog.tsx @@ -117,10 +117,10 @@ export function UpdateDialog() { {/* Scrollable content area */}
{/* Header */} -
+
diff --git a/apps/frontend/src/features/wealthfolio-connect/components/connect-empty-state.tsx b/apps/frontend/src/features/wealthfolio-connect/components/connect-empty-state.tsx index 383eff4cd..81ee23046 100644 --- a/apps/frontend/src/features/wealthfolio-connect/components/connect-empty-state.tsx +++ b/apps/frontend/src/features/wealthfolio-connect/components/connect-empty-state.tsx @@ -83,7 +83,7 @@ export function ConnectEmptyState() { {/* CTA */}