Files
archy/neode-ui
archipelagoandClaude Opus 5 fa6fe32ef9
Demo images / Build & push demo images (push) Failing after 2m18s
feat(wallet): a balance that isn't loaded yet says so, in pixels
An unloaded balance rendered as `0`. Zero is not a loading state — it is
a number, and it is the one number that frightens people. Someone
opening the dashboard while the RPCs were still in flight was told, in
the wallet's own typeface, that their money was gone.

There is no formatting fix for that. The fix is to stop claiming a
figure we do not have, so `null` now means "not known yet" and `0` means
"none", and the two are kept apart end to end: the refs start at null,
a rail becomes a number only when its call actually succeeds, and a
snapshot key that was never written stays unknown instead of becoming a
zero.

In place of the figure, a small dot-matrix scans in the rail's own
colour. It inherits currentColor, so on-chain shimmers orange, Lightning
yellow, Cashu purple, Fedimint blue and Ark teal with no colour table to
keep in sync — and it is sized to the figure it stands in for, so
nothing jumps when the real number lands. It carries role="status" and
names what it is waiting for; a shimmering box with no text is nothing
at all to a screen reader.

Two consequences worth stating. The total is withheld until every rail
that makes it up is known — summing nulls as zero would show a total
*lower* than the rails beneath it, which is worse than showing nothing
because it looks authoritative. And the Ark row stays hidden while its
balance is unknown, since "unknown" must not be read as "> 0" on the
many nodes with no Ark sidecar.

The LND app UI had the same bug in a different shape: its tiles start as
an em-dash, but renderBalances() runs on every poll including before the
first response, and `num(null && …)` is 0 — so the dashes were painted
over with "0 sats" almost immediately. Same treatment, in plain CSS.

Also fixes a stale assertion in AppHeroSection's suite, which has been
red since 9ccc325a changed "Restarting..." to a real ellipsis; and two
test proofs that used a plausible-looking hex string for `C`. The V3
codec never parses that field so it went unnoticed, but the V4 encoder
hands it to the reference implementation, which checks the point is
actually on secp256k1. Real curve points now.

Frontend: 996 tests green. Backend: 1436 green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 08:54:12 -04:00
..
2026-08-14 06:34:47 -04:00
2026-08-14 06:34:47 -04:00

Archipelago Web UI

Vue 3 + TypeScript + Vite + Tailwind CSS + Pinia

The web interface for Archipelago — a self-sovereign Bitcoin Node OS.

Quick Start

cd neode-ui
npm install
npm start

Visit http://localhost:8100 — login with password: password123

This starts:

  • Mock backend on port 5959 (no Docker required)
  • Vite dev server on port 8100 with HMR

Stop with npm stop.

Architecture

neode-ui/
├── src/
│   ├── api/              # RPC client (rpc-client.ts), WebSocket, container client
│   ├── stores/           # Pinia stores (app, container, appLauncher, monitoring)
│   ├── views/            # Page components (Dashboard, Marketplace, Settings, etc.)
│   ├── components/       # Reusable components (SplashScreen, AppSession, etc.)
│   ├── router/           # Vue Router configuration
│   ├── types/            # TypeScript type definitions
│   └── style.css         # Global styles + Tailwind utilities
├── public/assets/        # Static assets (images, fonts, app icons, audio)
├── mock-backend.js       # Mock backend server (simulates Rust backend)
├── docker/               # Docker configs (nginx, entrypoint)
└── vite.config.ts        # Vite config with backend proxy

Dev Modes

The mock backend supports multiple startup modes via VITE_DEV_MODE:

Mode Command Behavior
default npm start Fully set up, login screen
existing VITE_DEV_MODE=existing npm run dev:mock Same as default
setup VITE_DEV_MODE=setup npm run dev:mock First-time password setup flow
onboarding VITE_DEV_MODE=onboarding npm run dev:mock Post-setup onboarding flow
boot npm run dev:boot 25s simulated boot sequence

Mock Backend

The mock backend (mock-backend.js) simulates the full Rust backend for local development:

Pre-installed apps (always visible in My Apps):

  • Bitcoin Core, LND, Electrs, Mempool, FileBrowser, LoraBell, Fedimint

Marketplace: 30+ curated apps with Docker images, install/uninstall simulation

Features simulated:

  • Authentication (login, password change, TOTP 2FA)
  • System metrics (CPU, memory, disk — randomized for realism)
  • Node identity (DID, Nostr pubkey, Tor address)
  • Federation (3 mock nodes with apps, metrics, trust levels)
  • Mesh networking (4 LoRa peers, encrypted messaging, invoices)
  • Peer-to-peer messaging
  • FileBrowser API (mock file system with Music, Documents, Photos, Videos)
  • DWN sync status
  • Transport layer (mesh/LAN/Tor routing)
  • Notifications (5 realistic entries)
  • Claude AI chat proxy (requires ANTHROPIC_API_KEY)

Container runtime: If Docker/Podman is available, the mock backend will run real containers for installed apps. Otherwise, it simulates them.

Demo Deployment (Portainer)

Deploy the demo via Docker Compose for showcasing:

docker compose -f docker-compose.demo.yml build
docker compose -f docker-compose.demo.yml up -d

Or deploy through Portainer Stacks:

  1. Stacks > Add stack > name: archy-demo
  2. Web editor: paste docker-compose.demo.yml contents
  3. Add environment variable: ANTHROPIC_API_KEY (for Claude chat)
  4. Deploy

Access at http://your-host:4848 — password: password123

Development Commands

npm start              # Start mock backend + Vite (recommended)
npm stop               # Stop all servers
npm run dev:mock       # Same as start, without port cleanup
npm run dev:boot       # Boot mode (simulated startup delay)
npm run backend:mock   # Mock backend only
npm run dev            # Vite only (needs backend running separately)
npm run dev:real       # Vite with real Rust backend

npm run build          # Production build (outputs to ../web/dist/neode-ui/)
npm run build:docker   # Build for Docker (no type checking)
npm run type-check     # TypeScript type checking
npm test               # Run tests

Design System

Glass Classes

Class Use
.glass-card Content containers, modals, panels
.glass-button ALL buttons (primary and secondary)
.path-option-card Interactive cards with hover lift
.info-card Status badges, metric displays

Tokens

  • Font: Avenir Next (primary), Montserrat (font-archipelago)
  • Glass: bg: rgba(0,0,0,0.60), blur: 24px, border: rgba(255,255,255,0.22)
  • Accent: #fb923c (Bitcoin orange), #4ade80 (green), #ef4444 (red)
  • Text: rgba(255,255,255,0.9) primary, rgba(255,255,255,0.6) muted

Rules

  • Global CSS classes in style.css only — never inline Tailwind in components
  • .gradient-button is banned — use .glass-button
  • All components use <script setup lang="ts">

API

import { rpcClient } from '@/api/rpc-client'

await rpcClient.login('password')
await rpcClient.startPackage('bitcoin')
const metrics = await rpcClient.getMetrics()

State management via Pinia stores. WebSocket patches applied automatically.

Build Output

  • Dev build: ../web/dist/neode-ui/
  • Docker build: dist/ (deployed to nginx)

License

MIT