dev_arc_aws/design-decisions.md
Samuel James d8286ac42b
docs: realign design docs with deployed app, consolidate, rewrite README (#24)
* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Add JSON-converted bookmark import file for Archnest data import

Converts homarr-bookmarks.md into the format expected by /api/data/import.

* Auto-populate bookmark icons via favicon service in import JSON

Each bookmark now points to Google's favicon endpoint for its domain
instead of having no icon at all.

* docs: realign design docs with the deployed app, consolidate, rewrite README

README.md was badly stale (listed Terminal as "pending/on hold" and only
5 pages, when all 11 pages are built and live). Rewrote it as a detailed,
accurate map of the architecture, every page, every backend route,
every integration adapter, and the SSH subsystem, written explicitly for
this repo's actual audience (the owner + future AI sessions, never the
public).

Deleted archnest-blueprint.md and glance.md: both were pre-backend mockup
specs describing fictional config files (systems.config, infra.config,
fail2ban-driven security scoring) and placeholder data that never matched
the real implementation, and conflicted with the deployed app's actual
page count/nav/data sources. Their still-true content (color palette,
dropdown menu shape, card styling) was folded into design-decisions.md.

Rewrote design-decisions.md's "Page-Specific Notes" into a full "Page
Notes" section covering all 11 pages plus Login/Enrollment (previously
only 4 pages had notes, and those didn't reflect later changes like
Files/Tunnels/Containers/Host Metrics/Remote Desktop shipping). Each
section now states the real data source per page so it can't drift from
the code silently again.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-20 10:22:04 -04:00

14 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

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 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.
  • 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)

  • Docker host selector (integrations of type docker) + container list.
  • 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 (e.g. can't pause a stopped container).
  • Live CPU/memory stats polled only for running containers.
  • Logs modal (configurable tail count) and an exec modal (interactive shell via WebSocket to /api/docker/exec).

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: single-user-schema JWT (@fastify/jwt) today — see HANDOFF.md for the multi-user/SSO roadmap (Phases 2-4, not yet built).
  • 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), and metrics/ (the 10 collectors listed above).
  • 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.