dev_arc_aws/design-decisions.md
Samuel James 4ab0b2fff6
All checks were successful
CI / validate (push) Successful in 49s
Document theme palettes + organize assets (#4)
2026-06-24 16:27:33 +00:00

19 KiB

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

Dark Mode (Default — Shipped)

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

Light Mode (Planned — Palette Documented)

Role Value
Background (page) #F7F6F3 (warm off-white)
Background (cards) #FFFFFF (pure white)
Background (sidebar) #FAF9F7 (very light warm gray)
Border (cards) #E8E5DF (soft warm border)
Border/accent (hover/active) #C8A434 (gold — same as dark)
Success #2ECC71
Warning #E67E22
Danger #E74C3C
Text (primary) #1A1A1A (near-black)
Text (secondary) #6B6560 (warm gray)

Hero banners per mode:

  • Dark: assets/themes/archnest-default/archnest-default-dark.png — dark sci-fi cityscape with neon arches
  • Light: assets/themes/archnest-default/archnest-default-light.png — luminous white/gold cityscape, bright sky

Geometric/card backgrounds per mode:

  • Dark: textured black slate with angular gold-lit geometric cuts (top-left and bottom-right diagonal slashes with warm gold edge lighting). Dominant colors: near-black slate #1A1815, charcoal #0F0E0C, gold edge glow #C8A434#8B6914.
  • Light: cream marble with matching diagonal geometric cuts and warm gold edge lighting. Dominant colors: warm cream #F0EDE6, gold highlights #C8A434.

Forest Theme

A second theme with dark and light modes. Same structural layout as the default but with a different visual identity — mountainous alien landscapes with amber/gold point lights and a massive planet in the sky.

Forest — Dark Mode

Role Value Notes
Page background #080806 Near-black with warm brown undertone
Card background #121210 Dark charcoal-brown
Sidebar background #0A0A08 Deepest surface
Border #1E1C18 Warm dark border
Accent #D4A850 Warm amber/gold (slightly warmer than default)
Success #2ECC71
Warning #E67E22
Danger #E74C3C
Text primary #E8E4DC Warm off-white
Text secondary #7A7568 Warm gray-brown

Hero banner: dark alien mountain landscape with massive planet, amber point lights on a grid floor, warm gold highlights on peaks. Deep blacks with scattered amber/gold sparks. Asset: assets/themes/forest/forest-dark.png

Forest — Light Mode

Role Value Notes
Page background #F5F2ED Warm ivory
Card background #FFFFFF Pure white
Sidebar background #FAF8F4 Lightest warm tone
Border #E5E0D8 Soft warm border
Accent #D4A850 Same amber/gold as dark mode
Success #2ECC71
Warning #E67E22
Danger #E74C3C
Text primary #1A1810 Warm near-black
Text secondary #6B6558 Warm brown-gray

Hero banner: luminous white/ivory mountain landscape with massive planet, golden sparkle points on a marble-like floor, peaks dusted in white with gold vein highlights. Ethereal, bright, airy. Asset: assets/themes/forest/forest-light.png

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 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.tszero 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 /apihttp://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.