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) andglance.md(original Glance spec with fictional config files likesystems.config/infra.configthat 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-leftmust 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 inlinestyle={{ 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
pageSubtitlesentry (currently only BookNest) render a larger 28px title + subtitle line instead, and the top bar grows from 56px → 72px — seeTopBar.tsx'spageSubtitlesmap andApp.tsx'stopBarHeightlookup, 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-6at 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 --noEmitstays clean while the page renders blank with a runtimeSyntaxErroronly 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). SeeTopBar.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 fromapi.listIntegrations()), Bookmarks (total + favorites fromapi.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'stestConnection()result, polled by the existing/api/integrationslist.
Infrastructure (/infrastructure)
- Hero banner at the
App.tsxlayout level (showHeroincludes 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-sidekeyword — 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/heroObjectPositionlookup maps inApp.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/disconnectmessages; supports attaching to an existing tmux session or starting a new one. - Certificate auth (OPKSSH) shells out to the system
sshbinary under a pty rather than using the JS SSH client. - Session logging to
ARCHNEST_SESSION_LOG_DIRis 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 typessh, runs thedockerCLI 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). Seedocs/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 theguacdsidecar 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'sMaxSessionslimit 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 viaapi.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 viaapi.setup(), gated to only work while theuserstable is empty; (2) optional "Connect Your Services" integration onboarding grid (skippable, same integration form as Settings). Routed to based onGET /api/system/setup-status.
Backend Architecture (current, not aspirational)
backend/— Fastify 5 + TypeScript (ESM,tsxdev /tsc -bbuild),better-sqlite3for storage, deployed as its own Docker container alongside the frontend and aguacdsidecar.- 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 — seeROADMAP.md. - Integration credentials split across
integrations(non-secret config) andsecrets(AES-256-GCM-encrypted, keyed byARCHNEST_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/implementingtestConnection()(required) andlistResources()(optional);registry.tsmapsIntegrationType→ 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 thedockerCLI over SSH for the Containers page — injection-safe ref validation), andmetrics/(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), androutes/agents.ts(token-gated push-agent ingest into thedocker_agent_reportstable, read-only). Seedocs/docker-agent-monitoring.md. - Vite dev server proxies
/api→http://localhost:4000; prod routes/apito 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.mdbut not built — it has open decisions pending user sign-off, and must default OFF so it can't lock the live instance.