- System stats (homeStatus.refresh), update status and cloud storage usage are now every-entry, TTL-gated useCachedResource entries (10s/300s/30s), hosted in Home.vue rather than inside the homeStatus Pinia store — a store's own defineStore(id, setup) runs in a bare effectScope where onActivated() silently no-ops (same finding as 02-05's Mesh.vue) - Wallet is the deliberate exception (T-02-13): a new home.wallet-status resource wraps the existing loadWeb5Status() composite fetch and revalidates UNCONDITIONALLY on every activation rather than TTL-gated, keeps the prior figure rendered throughout, and persist:false (never written to sessionStorage). hydrateWalletSnapshot()'s separate localStorage path is untouched. - Read Web5.vue's two existing resources (web5.networking-profits, web5.lnd-info) and did NOT share either key: profits is an unrelated dataset, and lnd-info's own default persist:true (Web5.vue out of this plan's file scope) would leak balance data via its own independent refresh cycle regardless of what Home declares, and Home's wallet fetch is a strictly broader 7-call composite (not the same single-call dataset) - dedup:true added to all 12 underlying rpcClient calls (Home.vue's wallet composite + checkUpdateStatus, homeStatus.ts's 5 status calls) - RefreshIndicator wired next to the Home header, bound to the wallet resource's loadState - The websocket wallet-push path and hydrateWalletSnapshot's pre-network paint are both left exactly as 02-04 placed them Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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:
- Stacks > Add stack > name:
archy-demo - Web editor: paste
docker-compose.demo.ymlcontents - Add environment variable:
ANTHROPIC_API_KEY(for Claude chat) - 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.cssonly — never inline Tailwind in components .gradient-buttonis 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) - Production deploy: via
scripts/deploy-to-target.sh --live
License
MIT