Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/guardrails.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/security-codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: ["javascript", "python"]
language: ["javascript"]
steps:
- name: Checkout
uses: actions/checkout@v6
Expand Down
98 changes: 60 additions & 38 deletions ARCHITECTURAL_GUARDRAILS.md
Original file line number Diff line number Diff line change
@@ -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 | — |

---

Expand All @@ -43,48 +43,65 @@ 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.

---

## 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`.

Expand All @@ -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.

---
Expand All @@ -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.

---

Expand All @@ -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.

---

Expand All @@ -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_
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function HelpPopover() {
<PopoverContent className="w-96" align="start">
<div className="space-y-4">
<div className="space-y-2">
<h4 className="font-medium leading-none">How It Works</h4>
<h4 className="leading-none font-medium">How It Works</h4>
<div className="text-muted-foreground space-y-2 text-sm">
<p>• Select a goal from your saved goals or track a custom target</p>
<p>• Each dot represents your chosen step amount towards your investment target</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function Tooltip({
<div className="bg-popover border-border w-64 rounded-md border p-3 text-sm shadow-md">
<div className="space-y-3">
<div className="space-y-1">
<h4 className="font-medium leading-none">Step {data.stepIndex + 1}</h4>
<h4 className="leading-none font-medium">Step {data.stepIndex + 1}</h4>
<p className="text-muted-foreground">
Target: {formatCurrency(data.stepAmount, isBalanceHidden)}
</p>
Expand All @@ -135,7 +135,7 @@ function Tooltip({
</div>
</div>
{/* Tooltip arrow */}
<div className="border-t-border absolute left-1/2 top-full h-0 w-0 -translate-x-1/2 transform border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent"></div>
<div className="border-t-border absolute top-full left-1/2 h-0 w-0 -translate-x-1/2 transform border-t-4 border-r-4 border-l-4 border-r-transparent border-l-transparent"></div>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function EmptyPlaceholder({
{icon}
</div>
<h2 className="mt-2 text-xl font-semibold">{title}</h2>
<p className="text-muted-foreground mt-2 text-center text-sm font-normal leading-6">
<p className="text-muted-foreground mt-2 text-center text-sm leading-6 font-normal">
{description}
</p>
</div>
Expand Down Expand Up @@ -138,7 +138,7 @@ export function AccountBreakdown({
{percentage.toFixed(1)}% of total fees
</div>
{/* Tooltip arrow */}
<div className="border-t-border absolute left-1/2 top-full h-0 w-0 -translate-x-1/2 transform border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent"></div>
<div className="border-t-border absolute top-full left-1/2 h-0 w-0 -translate-x-1/2 transform border-t-4 border-r-4 border-l-4 border-r-transparent border-l-transparent"></div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const FeeCategoriesChart = ({
<Card className="overflow-hidden backdrop-blur-sm">
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="text-muted-foreground text-sm font-medium uppercase tracking-wider">
<CardTitle className="text-muted-foreground text-sm font-medium tracking-wider uppercase">
Fee Categories
</CardTitle>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function FeeHistoryChart({
<CardTitle className="text-xl">Fee History</CardTitle>
<CardDescription>{periodDescription}</CardDescription>
</CardHeader>
<CardContent className="flex h-full flex-col px-4 pb-6 pt-0 sm:px-6">
<CardContent className="flex h-full flex-col px-4 pt-0 pb-6 sm:px-6">
{chartData.length === 0 ? (
<EmptyPlaceholder
className="mx-auto flex h-[300px] max-w-[420px] items-center justify-center"
Expand Down Expand Up @@ -127,7 +127,7 @@ export function FeeHistoryChart({
return (
<>
<div
className="border-border bg-(--color-bg) h-2.5 w-2.5 shrink-0 rounded-[2px]"
className="border-border h-2.5 w-2.5 shrink-0 rounded-[2px] bg-(--color-bg)"
style={
{
"--color-bg": entry.color,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function EmptyPlaceholder({
{icon}
</div>
<h2 className="mt-2 text-xl font-semibold">{title}</h2>
<p className="text-muted-foreground mt-2 text-center text-sm font-normal leading-6">
<p className="text-muted-foreground mt-2 text-center text-sm leading-6 font-normal">
{description}
</p>
</div>
Expand Down Expand Up @@ -138,7 +138,7 @@ export function TopFeeSources({ feeAnalytics, currency, isBalanceHidden }: TopFe
{percentage.toFixed(1)}% of total fees
</div>
{/* Tooltip arrow */}
<div className="border-t-border absolute left-1/2 top-full h-0 w-0 -translate-x-1/2 transform border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent"></div>
<div className="border-t-border absolute top-full left-1/2 h-0 w-0 -translate-x-1/2 transform border-t-4 border-r-4 border-l-4 border-r-transparent border-l-transparent"></div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function DistributionCharts({ distribution, currency }: DistributionChart
return (
<>
<div
className="border-border bg-(--color-bg) h-2.5 w-2.5 shrink-0 rounded-[2px]"
className="border-border h-2.5 w-2.5 shrink-0 rounded-[2px] bg-(--color-bg)"
style={
{
"--color-bg": entry.color,
Expand Down Expand Up @@ -181,7 +181,7 @@ export function DistributionCharts({ distribution, currency }: DistributionChart
return (
<>
<div
className="border-border bg-(--color-bg) h-2.5 w-2.5 shrink-0 rounded-[2px]"
className="border-border h-2.5 w-2.5 shrink-0 rounded-[2px] bg-(--color-bg)"
style={
{
"--color-bg": entry.color,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function EquityCurveChart({
return (
<>
<div
className="border-border bg-(--color-bg) h-2.5 w-2.5 shrink-0 rounded-[2px]"
className="border-border h-2.5 w-2.5 shrink-0 rounded-[2px] bg-(--color-bg)"
style={
{
"--color-bg": entry.color,
Expand Down
4 changes: 2 additions & 2 deletions addons/swingfolio-addon/src/pages/dashboard-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export default function DashboardPage({ ctx }: DashboardPageProps) {
<Card
className={`${metrics.totalPL >= 0 ? "border-success/10 bg-success/10" : "border-destructive/10 bg-destructive/10"}`}
>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3 pt-4">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pt-4 pb-3">
<CardTitle className="text-sm font-medium">P/L</CardTitle>
<GainAmount
className="text-xl font-bold sm:text-2xl"
Expand Down Expand Up @@ -332,7 +332,7 @@ export default function DashboardPage({ ctx }: DashboardPageProps) {
{getChartPeriodDisplay(selectedPeriod).description}
</p>
</div>
<div className="bg-secondary text-muted-foreground self-start whitespace-nowrap rounded-full px-2 py-1 text-xs sm:self-auto">
<div className="bg-secondary text-muted-foreground self-start rounded-full px-2 py-1 text-xs whitespace-nowrap sm:self-auto">
{selectedPeriod} → {getChartPeriodDisplay(selectedPeriod).type}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/components/account-selector-mobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const AccountSelectorMobile = forwardRef<HTMLButtonElement, AccountSelect
{!iconOnly && "Add account"}
</Button>
</SheetTrigger>
<SheetContent side="bottom" className="rounded-t-4xl mx-1 h-[80vh] p-0">
<SheetContent side="bottom" className="mx-1 h-[80vh] rounded-t-4xl p-0">
<SheetHeader className="border-border border-b px-6 py-4">
<SheetTitle>Select Account</SheetTitle>
<SheetDescription>Choose an account to add to the comparison</SheetDescription>
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/components/account-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ export const AccountSelector = forwardRef<HTMLButtonElement, AccountSelectorProp
role="combobox"
aria-expanded={open}
className={cn(
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
>
Expand Down
6 changes: 3 additions & 3 deletions apps/frontend/src/components/action-palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -74,7 +74,7 @@ export function ActionPalette({
>
{/* Header - only show if title provided */}
{title && (
<div className="flex items-center justify-between px-5 pb-3 pt-5">
<div className="flex items-center justify-between px-5 pt-5 pb-3">
<h3 className="text-foreground text-lg font-bold">{title}</h3>
<button
onClick={handleClose}
Expand All @@ -97,7 +97,7 @@ export function ActionPalette({
{groups.map((group, groupIndex) => (
<div key={groupIndex}>
{group.title && (
<div className="text-muted-foreground px-2 py-1.5 text-xs font-medium uppercase tracking-wider">
<div className="text-muted-foreground px-2 py-1.5 text-xs font-medium tracking-wider uppercase">
{group.title}
</div>
)}
Expand Down
Loading
Loading