# ArchNest — Design Decisions & Page Reference > Living reference for visual/UX conventions and what's actually built on each page. > Apply the Global Rules consistently to any new page. The Page Notes section > describes the **real, deployed** implementation — not a mockup. If you change a > page's layout or data source, update its section here in the same PR. > > This file replaces the old `archnest-blueprint.md` (original 6-page mockup > pitch, since deleted — superseded by the real 11-page app below) and > `glance.md` (original Glance spec with fictional config files like > `systems.config`/`infra.config` that were never built — also deleted). Anything > from those files still true (color palette, typography, card style, dropdown > menu shape) has been folded in below. --- ## Global Rules (apply to every page) ### Sidebar - Expanded width: 200px. Collapsed width: 64px (icons only). - User can manually collapse/expand via a toggle button (not just responsive breakpoints). - Main content `margin-left` must match the sidebar width exactly. - Bottom of sidebar: live "System Status" widget — green dot + "All Systems Operational" or "X Issue(s) Detected", driven by polling integration status (not the fictional ping-sweep config described in the old spec). ### Page Title (Top Bar) - Color: gold `#C8A434` — not white. Use inline `style={{ color: '#C8A434' }}` if a Tailwind class doesn't apply. - Font: 18px, bold, uppercase, tracking-wide. - No border on the top bar — it blends into the page background. - Pages with a `pageSubtitles` entry (currently only BookNest) render a larger 28px title + subtitle line instead, and the top bar grows from 56px → 72px — see `TopBar.tsx`'s `pageSubtitles` map and `App.tsx`'s `topBarHeight` lookup, which must be kept in sync or the layout clips/gaps. ### Colors | Role | Value | |------|-------| | Background (page) | `#0D0E10` | | Background (cards) | `#141518` | | Background (sidebar) | `#0A0B0D` | | Border (cards) | `#1E2025` | | Border/accent (hover/active) | `#C8A434` (gold) | | Success | `#2ECC71` | | Warning | `#E67E22` | | Danger | `#E74C3C` | | Text (primary) | `#E8E6E0` | | Text (secondary) | `#7A7D85` | Tailwind v4 `@theme` custom colors (`text-gold`, `bg-card`, etc.) don't always apply reliably — fall back to inline `style={{ color: '#C8A434' }}` when a color isn't rendering, and verify visually after changes. ### Typography - Card titles: 10-11px, uppercase, tracking-[1.5px], secondary color, font-medium. - Large numbers: 24-28px, bold, primary color. - Subtitles/labels: 10-11px, secondary color. - Body text in lists: 13px, primary color. - Timestamps: 11px, secondary color. ### Card Style - Border radius: 12px. Background `#141518`. Border 1px solid `#1E2025`. - Padding: 16-24px depending on density. No shadows (flat design). - Hover: border transitions to gold, 0.2s ease. ### Content Alignment - All rows in a page share the same horizontal padding (`px-6` at the parent container level) so the hero banner, status cards, and content rows line up left/right. ### Animations - Card hover: border → gold, 0.2s ease. - Progress ring / sparkline: animate from 0 on load, ~1s. - Progress bars: fill animation ~0.8s. ### Icons - Lucide React, 14-18px depending on context. Gold for active/accent, secondary color for inactive. Gold glow on active sidebar items: `shadow-[0_0_6px_rgba(200,164,52,0.5)]`. - **Gotcha**: the installed lucide-react version's TypeScript types list some brand/wordmark icons (`Github`, `Gitlab`, `Linkedin`, `Youtube`) that aren't actually exported at runtime — `tsc --noEmit` stays clean while the page renders blank with a runtime `SyntaxError` only visible in the Vite dev log. Verify icon names against the package's actual exports before importing anything brand-flavored. ### No Footer - No page has a fixed footer/status bar. Don't add one unless explicitly requested. ### Target Display - Primary design target: 16-inch / 1920px-wide screen. Don't constrain content width unnecessarily — design should feel spacious. ### User Avatar & Dropdown (TopBar, every page) - 36px circle, gold border + glow, shows uploaded avatar image or initials. - Adjacent: display name + "Administrator" role label, chevron (rotates on open). - Dropdown menu: header (name + email) → **Profile** (`/settings?tab=profile`) → **Appearance** (`/settings?tab=appearance`) → **Security** (`/settings?tab=security`) → **Help & Support** (`/help`) → divider → **Sign Out** (red, danger styling). See `TopBar.tsx`. --- ## Page Notes (what's actually built, page by page) All pages below consume real backend data via `src/lib/api.ts` — **zero mock data anywhere in the app.** Where a section says "real backend data," it means an actual SQL-backed or live-polled endpoint, not a config file or static array. ### Glance (`/`) - Hero banner (image with radial-gradient fade mask) + KPI cards overlapping the bottom edge via negative margin. - 4 status cards: **System Status** (% of integrations reachable), **Infrastructure** (resource count + health breakdown from `api.listResources()`), **Integrations** (connected/total from `api.listIntegrations()`), **Bookmarks** (total + favorites from `api.listBookmarks()`). - Middle row (3 columns): Resource Overview (top 6 resources + status), Recent Activity (5 latest from `api.listEvents(5)`), problem/critical resources widget. - Bottom row: Connected Integrations grid + 4 Shortcuts buttons (to Settings, BookNest, Infrastructure). - No footer. Hides the hero gracefully if the banner image fails to load. - There is **no** `systems.config`/`infra.config`/ping-sweep/fail2ban machinery — that was speculative spec from before the backend existed. Health figures are derived directly from each integration adapter's `testConnection()` result, polled by the existing `/api/integrations` list. ### Infrastructure (`/infrastructure`) - Hero banner at the `App.tsx` layout level (`showHero` includes this route), extending behind the sticky, transparent-on-hero-routes TopBar. - Sub-tabs: **Overview** (only enabled tab) and **Network** (disabled, "Coming soon" — intentional, leave alone unless asked). - 4 status cards: Total Resources, Healthy, Warnings, Critical — from `api.listResources()`. - Middle row: Resource Distribution (donut by integration type) + Node Status (4-col tile grid, one tile per resource — replaced an earlier world-map concept since a map doesn't make sense for a small/single-site setup). - Bottom row: Integration Health list + Recent Activity (`api.listEvents(4)`). - Card backgrounds use a `cardDim` + `cardVignette` (radial-gradient, `closest-side` keyword — a fixed `%` leaves a visible hard edge on straight sides) combo on the middle row only, per explicit visual preference; bottom row stays plain/regular. ### BookNest (`/booknest`) - Hero banner reused at layout level with page-specific tuning (`heroPaddingTop`/`heroObjectPosition` lookup maps in `App.tsx`). - Large 28px title + "Your Digital Library" subtitle (the one page using `pageSubtitles`), header grows to 72px. - Page stats row (Links / Categories / Favorites) + "Delete All" bulk action. - Main column: Quick Access (top 5 categories) + bookmark groups grid (4 cols, full CRUD with hover edit/delete/star). - Right sidebar (spans both grid rows via `gridRow: '1 / span 2'`): Favorites, Recently Added (5 newest), Link Health donut, Category Breakdown donut. - Add/edit bookmark modal supports auto-detected favicons via `guessServiceIconUrl()` or manual icon entry. - All data from `api.listBookmarks()` / `api.listBookmarkCategories()` with real create/update/delete calls — no local-only state. ### Terminal (`/terminal`) - Left sidebar: SSH hosts (integrations of type `ssh`), click to connect. - Tab bar + 1/2/4-pane split layout, each pane an independent xterm instance. - **Sessions persist across in-app navigation**: the xterm instances + WebSockets are owned by `src/lib/TerminalSessionContext.tsx` (mounted above the router), and their DOM nodes are re-parented into the page on mount / moved to a hidden root on unmount rather than disposed. Closing a tab/pane or logging out tears a session down; a full browser reload still drops them. (Self-hosted caps the grid at 4 panes; "as many as fit" is a paid-tier roadmap item.) - Preferences panel (theme: ArchNest Dark/Matrix/Solarized/Midnight Blue, font size 11-16px, font family) — stored in `localStorage` (`archnest-terminal-prefs`), not synced server-side. - WebSocket to `/api/terminal`: `connect`/`input`/`resize`/`list_tmux`/`disconnect` messages; supports attaching to an existing tmux session or starting a new one. - Certificate auth (OPKSSH) shells out to the system `ssh` binary under a pty rather than using the JS SSH client. - Session logging to `ARCHNEST_SESSION_LOG_DIR` is backend-supported but has no dedicated viewer UI yet. ### Tunnels (`/tunnels`) - List + create form for local/remote/dynamic (SOCKS5) SSH tunnels. - Create form fields conditionally show/hide based on mode (no endpoint host/port fields for dynamic mode). - Status color map: stopped `#7A7D85`, connecting `#C8A434`, retrying `#E0A030`, connected `#2ECC71`, error `#E74C3C`. Polls every 3s. - Backed by `api.listTunnels()/createTunnel()/connectTunnel()/disconnectTunnel()/deleteTunnel()`. ### Files (`/files`) - SSH host selector + breadcrumb-navigable SFTP directory browser. - File editor modal: plain textarea, files up to 50MB (anything larger is download-only). Heuristic binary detection (null bytes/control chars) decides base64 vs. text encoding. - Inline directory creation, rename, delete, upload, download. - Host-to-host transfer modal: pick source file + destination host/path, copy-or-move toggle, live progress bar fed by `api.getTransfer(id)` polling. ### Containers (`/containers`) - Host selector spans **three sources**: Docker Engine TCP API (integrations of type `docker`), Docker-over-SSH (integrations of type `ssh`, runs the `docker` CLI on the host), and read-only push **agents** (hosts that POST reports). - **Intra-page tabs**: tab 1 is the container spreadsheet (Name/Image/State/CPU/Memory/Ports/Actions); clicking a container name opens a closeable per-container **detail tab** (overview/state+health/stats/ports/ networks/mounts/env-with-secrets-masked/labels). Detail is richest for agent hosts (full `docker inspect`); docker/ssh sources degrade gracefully. - Per-container state badge (running/paused/exited/dead) with context-aware action buttons (Start/Stop/Restart/Pause/Unpause/Remove) — buttons disable themselves for invalid transitions. **Agent rows are read-only** (no actions). - Live CPU/memory stats: polled for Docker-API running containers; embedded in the report for agent hosts; not available for the SSH list view. - Logs modal (configurable tail) and exec modal (interactive shell) for docker/ssh sources, via `/api/docker/exec` (base64-framed) or `/api/docker-ssh/exec` (plain UTF-8). See `docs/docker-agent-monitoring.md`. ### Remote Desktop (`/remote-desktop`) - Left sidebar: hosts from integrations of type `remote_desktop`. - Main area is a Guacamole canvas tunneled over WebSocket to `/api/guacamole`, proxying to the `guacd` sidecar container (RDP/VNC/Telnet). - Requires `ARCHNEST_GUAC_CRYPT_KEY` — sessions fail without it (the backend warns on startup if it's missing). ### Host Metrics (`/host-metrics`) - Left sidebar: SSH hosts. Selecting one starts a 5-second polling loop against `api.getHostMetrics(integrationId)`, cleared on deselect. - Cards: CPU/Memory/Disk gauges (color thresholds: green <75%, yellow 75-90%, red ≥90%) + Uptime, then Network Interfaces, Processes (top 10), Listening Ports, Firewall Rules (UFW/iptables), Login Activity. - Backed by 10 sequential SSH-exec collectors under `backend/src/ssh/metrics/` (cpu, memory, disk, uptime, network, system, processes, ports, firewall, login-stats) — sequential on purpose, to avoid exceeding OpenSSH's `MaxSessions` limit per host. ### Settings (`/settings`) - Fixed 200px left nav, 7 sections: Profile, Appearance, Security, Integrations, Notifications, Data & Backup, About. URL-deep-linkable via `?tab=` (`useSearchParams`) — use this pattern for any future section. - **Profile**: display name, email, avatar upload (`FileReader.readAsDataURL` → base64 data URL, persisted via `api.updateMe()` — this one *is* a real backend round-trip, unlike the rest of the page below). - **Appearance**: accent color swatches — **local-state only, doesn't persist or apply anywhere** (see Known Stubs in `HANDOFF.md`). - **Security**: password-change form + 2FA toggle — currently placeholder UI pending the Phase 2 auth work in `HANDOFF.md`. - **Integrations**: one card per type (Proxmox/Docker/NetBird/Cloudflare/AWS/ Uptime Kuma/Weather/Remote Desktop/SSH), each with type-specific fields (secrets masked, eye-toggle to reveal, "Test Connection" button). Real CRUD via `api.createIntegration()/updateIntegration()/deleteIntegration()/testIntegration()`. - **Notifications**: toggles — placeholder, no delivery mechanism (see Known Stubs in `HANDOFF.md`). - **Data & Backup**: full JSON export/import of integrations + secrets + bookmarks + tunnels via `backend/src/routes/data.ts` — this is the one endpoint that intentionally round-trips decrypted secrets, by design, for backup portability. - **About**: static version/license/links info. ### Help (`/help`) - Fully static — no backend calls. 11 guide cards (one per real page) with a short description and tips. Update this page whenever a new page ships. ### Login (`/login`) / Enrollment (`/enrollment`) - `Login.tsx`: username/password form → `api.login()` → `AuthContext`. - `Enrollment.tsx`: two-step first-run flow — (1) create the admin account via `api.setup()`, gated to only work while the `users` table is empty; (2) optional "Connect Your Services" integration onboarding grid (skippable, same integration form as Settings). Routed to based on `GET /api/system/setup-status`. --- ## Backend Architecture (current, not aspirational) - `backend/` — Fastify 5 + TypeScript (ESM, `tsx` dev / `tsc -b` build), `better-sqlite3` for storage, deployed as its own Docker container alongside the frontend and a `guacd` sidecar. - Auth: JWT (`@fastify/jwt`) with server-tracked sessions and a multi-user schema (admin/member roles, 10-seat cap). Authentik SSO is deferred to a paid AWS add-on — see `ROADMAP.md`. - Integration credentials split across `integrations` (non-secret config) and `secrets` (AES-256-GCM-encrypted, keyed by `ARCHNEST_SECRET_KEY`) tables — secrets never appear in a generic "list integrations" response by construction, not by remembering to redact a field. - Every integration type has an adapter in `backend/src/integrations/` implementing `testConnection()` (required) and `listResources()` (optional); `registry.ts` maps `IntegrationType` → adapter. All 9 types (proxmox/docker/netbird/cloudflare/aws/uptime_kuma/weather/ssh/remote_desktop) are real, working adapters — none are stubs anymore. - `backend/src/ssh/` is the shared SSH transport layer powering Terminal, Files, Tunnels, Transfers, and Host Metrics: `connect.ts` (jump-host chaining, host-key verification, cert auth), `sftp.ts` (ephemeral SFTP), `transfer.ts` (host-to-host streamed copy/move with progress + cancel), `docker.ts` (runs the `docker` CLI over SSH for the Containers page — injection-safe ref validation), and `metrics/` (the 10 collectors listed above). - Docker container data has three transports: `backend/src/docker/` + `routes/docker.ts` (Engine TCP API), `ssh/docker.ts` + `routes/dockerSsh.ts` (CLI over SSH), and `routes/agents.ts` (token-gated push-agent ingest into the `docker_agent_reports` table, read-only). See `docs/docker-agent-monitoring.md`. - Vite dev server proxies `/api` → `http://localhost:4000`; prod routes `/api` to the backend container via Nginx Proxy Manager. ## Future Integration Notes - AWS/Cloudflare/NetBird/Proxmox/Uptime Kuma adapters are real but currently surface basic resource inventory + health only — deeper cost/pricing/budget data (mentioned in the old blueprint) is not implemented and not currently planned; revisit only if explicitly requested. - **Mesh prerequisite gate** (require a verified NetBird mesh before the app can be configured) is designed in `docs/mesh-prerequisite-gate.md` but not built — it has open decisions pending user sign-off, and must default OFF so it can't lock the live instance.