From a2aa9657b14cd07a137938bac3be8146bafc1b65 Mon Sep 17 00:00:00 2001 From: Dorian Date: Mon, 9 Mar 2026 17:09:59 +0000 Subject: [PATCH] fix: prevent My Apps crash when installing apps + add filebrowser to demo The My Apps page went blank after installing apps because pkg['static-files'].icon was accessed without optional chaining on dynamically installed packages that lack the static-files property. - Make static-files optional in PackageDataEntry type - Add defensive ?.icon access with fallback in Apps.vue and AppDetails.vue - Add filebrowser to mock backend staticDevApps (enables Cloud page in demo) - Expand portMappings and marketplaceMetadata for all marketplace apps - installPackage now uses staticApp() format for consistent data shape Co-Authored-By: Claude Opus 4.6 --- DEMO-DEPLOY.md | 10 + docker-compose.demo.yml | 6 + image-recipe/build-auto-installer-iso.sh | 160 ++++++--- image-recipe/configs/archipelago.service | 5 +- loop/Old Plans/plan.md | 337 ++++++++++++++++++ neode-ui/mock-backend.js | 165 +++++---- neode-ui/src/App.vue | 6 +- neode-ui/src/api/websocket.ts | 25 +- neode-ui/src/components/CLIPopup.vue | 2 +- neode-ui/src/components/OnlineStatusPill.vue | 2 +- neode-ui/src/components/cloud/FileCard.vue | 10 + .../src/components/cloud/FileCardGrid.vue | 12 +- neode-ui/src/components/cloud/FileGrid.vue | 3 + neode-ui/src/components/cloud/ShareModal.vue | 257 +++++++++++++ neode-ui/src/composables/useMessageToast.ts | 5 + neode-ui/src/stores/app.ts | 1 - neode-ui/src/style.css | 219 ++++++++++++ neode-ui/src/types/api.ts | 2 +- neode-ui/src/views/AppDetails.vue | 4 +- neode-ui/src/views/Apps.vue | 6 +- neode-ui/src/views/CloudFolder.vue | 17 + neode-ui/src/views/Login.vue | 23 +- neode-ui/src/views/OnboardingIntro.vue | 2 +- scripts/first-boot-containers.sh | 62 +++- 24 files changed, 1200 insertions(+), 141 deletions(-) create mode 100644 loop/Old Plans/plan.md create mode 100644 neode-ui/src/components/cloud/ShareModal.vue diff --git a/DEMO-DEPLOY.md b/DEMO-DEPLOY.md index 1b88a53d..d1e2ec37 100644 --- a/DEMO-DEPLOY.md +++ b/DEMO-DEPLOY.md @@ -27,6 +27,16 @@ ports: - "YOUR_PORT:80" ``` +## Chat (Claude AI) + +Set `ANTHROPIC_API_KEY` in the Portainer stack environment to enable real AI chat: + +1. In the stack editor, add under **Environment variables**: + - `ANTHROPIC_API_KEY` = your Anthropic API key (starts with `sk-ant-api...`) +2. Redeploy the stack + +Without this key, chat shows a "not configured" error. The key is passed to the `neode-backend` container which proxies requests to `api.anthropic.com`. + ## Dev Mode `VITE_DEV_MODE=existing` skips setup/onboarding and goes straight to login. For other flows: diff --git a/docker-compose.demo.yml b/docker-compose.demo.yml index bd295cce..2aa60689 100644 --- a/docker-compose.demo.yml +++ b/docker-compose.demo.yml @@ -14,12 +14,18 @@ services: environment: VITE_DEV_MODE: "existing" # Skip setup/onboarding, go straight to login ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} + NODE_OPTIONS: "--dns-result-order=ipv4first" expose: - "5959" dns: - 8.8.8.8 - 1.1.1.1 restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:5959/health"] + interval: 30s + timeout: 10s + retries: 3 neode-web: build: diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index 3d7ca58e..6b464ea5 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -356,10 +356,12 @@ INSTALLER_ISO="$WORK_DIR/installer-iso" rm -rf "$INSTALLER_ISO" mkdir -p "$INSTALLER_ISO" cd "$INSTALLER_ISO" -(7z x -y "$BASE_ISO" 2>/dev/null || 7za x -y "$BASE_ISO" 2>/dev/null || bsdtar -xf "$BASE_ISO" 2>/dev/null) || { +# 7z returns exit code 2 for warnings (symlinks in ISO) — check for key files instead +7z x -y "$BASE_ISO" >/dev/null 2>&1 || 7za x -y "$BASE_ISO" >/dev/null 2>&1 || bsdtar -xf "$BASE_ISO" 2>/dev/null || true +if [ ! -d "$INSTALLER_ISO/live" ] || [ ! -f "$INSTALLER_ISO/live/vmlinuz" ]; then echo " ❌ Failed to extract ISO. Install p7zip-full: sudo apt install p7zip-full" exit 1 -} +fi # ============================================================================= # STEP 3: Add Archipelago components @@ -501,7 +503,8 @@ mkdir -p "$IMAGES_DIR" IMAGES_CAPTURED_FROM_SERVER=0 if [ -n "$DEV_SERVER" ] && [ "$DEV_SERVER" != "localhost" ] && [ "$DEV_SERVER" != "127.0.0.1" ]; then echo " Capturing container images from live server ($DEV_SERVER)..." - CAPTURE_PATTERNS="bitcoin-ui bitcoin-knots lnd lnd-ui electrs-ui filebrowser mempool mempool-electrs tailscale homeassistant btcpayserver nbxplorer postgres nostr-rs-relay strfry alpine-tor fedimintd gatewayd dwn-server" + # Patterns match against `podman images` repository names (not container names) + CAPTURE_PATTERNS="bitcoin-ui bitcoinknots lnd lnd-ui electrs-ui filebrowser mempool backend frontend electrs tailscale homeassistant home-assistant btcpayserver nbxplorer postgres alpine-tor nostr-rs-relay strfry fedimintd gatewayd dwn-server grafana uptime-kuma jellyfin vaultwarden searxng mariadb valkey nginx-alpine portainer" REMOTE_TMP="/tmp/archipelago-image-capture-$$" SAVED_LIST=$(ssh "$DEV_SERVER" "mkdir -p $REMOTE_TMP && for p in $CAPTURE_PATTERNS; do img=\$(sudo podman images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -i \"\$p\" | head -1); [ -n \"\$img\" ] && sudo podman save -o \"$REMOTE_TMP/\$p.tar\" \"\$img\" 2>/dev/null && echo \"\$p\"; done" 2>/dev/null) || true for p in $SAVED_LIST; do @@ -518,26 +521,30 @@ fi # Define images to bundle for fallback (when not from server or missing). Includes filebrowser. # bitcoin-ui and lnd-ui are custom and normally captured from server or built separately. +# Alpha: core Bitcoin/Lightning stack + essential apps. Others pulled on-demand from Marketplace. CONTAINER_IMAGES=" -bitcoinknots/bitcoin:29 bitcoin-knots.tar -lightninglabs/lnd:v0.18.4-beta lnd.tar -ghcr.io/home-assistant/home-assistant:stable homeassistant.tar -btcpayserver/btcpayserver:latest btcpayserver.tar +docker.io/bitcoinknots/bitcoin:latest bitcoin-knots.tar +docker.io/lightninglabs/lnd:v0.18.4-beta lnd.tar +docker.io/homeassistant/home-assistant:2024.1 homeassistant.tar +docker.io/btcpayserver/btcpayserver:1.13.5 btcpayserver.tar docker.io/nicolasdorier/nbxplorer:2.6.0 nbxplorer.tar -docker.io/library/postgres:16 postgres-btcpay.tar -mempool/frontend:latest mempool-frontend.tar -mempool/backend:v2.5.0 mempool-backend.tar -mempool/electrs:latest mempool-electrs.tar -docker.io/mariadb:10.11 mariadb-mempool.tar +docker.io/library/postgres:15-alpine postgres-btcpay.tar +docker.io/mempool/frontend:v2.5.0 mempool-frontend.tar +docker.io/mempool/backend:v2.5.0 mempool-backend.tar +docker.io/mempool/electrs:latest mempool-electrs.tar +docker.io/library/mariadb:10.11 mariadb-mempool.tar docker.io/fedimint/fedimintd:v0.10.0 fedimint.tar docker.io/fedimint/gatewayd:v0.10.0 fedimint-gateway.tar -docker.io/filebrowser/filebrowser:latest filebrowser.tar -scsibug/nostr-rs-relay:latest nostr-rs-relay.tar -hoytech/strfry:latest strfry.tar -tailscale/tailscale:latest tailscale.tar +docker.io/filebrowser/filebrowser:v2.27.0 filebrowser.tar docker.io/andrius/alpine-tor:latest alpine-tor.tar docker.io/library/nginx:alpine nginx-alpine.tar ghcr.io/tbd54566975/dwn-server:main dwn-server.tar +docker.io/grafana/grafana:10.2.0 grafana.tar +docker.io/louislam/uptime-kuma:1 uptime-kuma.tar +docker.io/vaultwarden/server:1.30.0-alpine vaultwarden.tar +docker.io/searxng/searxng:latest searxng.tar +docker.io/portainer/portainer-ce:2.19.4 portainer.tar +docker.io/tailscale/tailscale:stable tailscale.tar " # Pull and save each image (force AMD64 for x86_64 target) only if not already present @@ -722,6 +729,13 @@ FBCSERVICE cp "$WORK_DIR/archipelago-first-boot-containers.service" "$ARCH_DIR/scripts/" fi +# Bundle E2E test script for post-install validation +if [ -f "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" ]; then + cp "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" "$ARCH_DIR/scripts/" + chmod +x "$ARCH_DIR/scripts/run-e2e-tests.sh" + echo " ✅ Bundled E2E test script for post-install validation" +fi + # Bundle docker UI source files for building custom UIs on first boot (fallback if images not captured) DOCKER_UI_DIR="$SCRIPT_DIR/../docker" if [ -d "$DOCKER_UI_DIR" ]; then @@ -992,6 +1006,17 @@ if [ -d "$BOOT_MEDIA/archipelago/container-images" ]; then echo " ✅ Container images staged for first-boot loading" fi +# Initialize backend data directories for seamless first boot +mkdir -p /mnt/target/var/lib/archipelago/tor-config +mkdir -p /mnt/target/var/lib/archipelago/identities +mkdir -p /mnt/target/var/lib/archipelago/lnd + +# Copy E2E test script for post-install validation +if [ -f "$BOOT_MEDIA/archipelago/scripts/run-e2e-tests.sh" ]; then + cp "$BOOT_MEDIA/archipelago/scripts/run-e2e-tests.sh" /mnt/target/opt/archipelago/scripts/ + chmod +x /mnt/target/opt/archipelago/scripts/run-e2e-tests.sh +fi + # Ensure correct ownership (use numeric UID:GID 1000:1000 since we're outside chroot) chown -R 1000:1000 /mnt/target/opt/archipelago 2>/dev/null || true chown -R 1000:1000 /mnt/target/var/lib/archipelago 2>/dev/null || true @@ -1143,6 +1168,8 @@ echo -e "${GREEN}║ Pre-loaded apps (ready to start via Web UI): echo -e "${GREEN}║ • Bitcoin Knots • LND • Home Assistant ║${NC}" echo -e "${GREEN}║ • BTCPay Server • Mempool • Nostr Relays ║${NC}" echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}║ Validate: bash /opt/archipelago/scripts/run-e2e-tests.sh ║${NC}" +echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}" echo "" read -p "Press Enter to reboot..." @@ -1376,37 +1403,88 @@ echo "📦 Step 6: Creating bootable ISO..." OUTPUT_ISO="$OUTPUT_DIR/archipelago-installer-x86_64.iso" -# Get MBR for hybrid boot -ISOHDPFX="" -for path in \ - "/usr/local/share/syslinux/isohdpfx.bin" \ - "/usr/share/syslinux/isohdpfx.bin" \ - "/opt/homebrew/share/syslinux/isohdpfx.bin" \ - "$INSTALLER_ISO/isolinux/isohdpfx.bin"; do - if [ -f "$path" ]; then - ISOHDPFX="$path" +# Extract MBR from original Debian Live ISO (most reliable for hybrid boot) +# This preserves the exact MBR that makes the ISO work as a USB drive in Balena Etcher +echo " Extracting hybrid MBR from original Debian Live ISO..." +ISOHDPFX="$WORK_DIR/isohdpfx.bin" +dd if="$BASE_ISO" bs=1 count=432 of="$ISOHDPFX" 2>/dev/null + +# Verify we got a valid MBR (should be 432 bytes) +ISOHDPFX_SIZE=$(stat -c%s "$ISOHDPFX" 2>/dev/null || stat -f%z "$ISOHDPFX" 2>/dev/null || echo 0) +if [ "$ISOHDPFX_SIZE" -ne 432 ]; then + echo " ⚠️ MBR extraction unexpected size ($ISOHDPFX_SIZE), trying syslinux paths..." + for path in \ + "/usr/lib/ISOLINUX/isohdpfx.bin" \ + "/usr/share/syslinux/isohdpfx.bin" \ + "/usr/local/share/syslinux/isohdpfx.bin"; do + if [ -f "$path" ]; then + ISOHDPFX="$path" + echo " Using $path" + break + fi + done +fi + +# Find the EFI boot image — 7z may extract it to different locations +EFI_IMG="" +for efi_path in \ + "$INSTALLER_ISO/boot/grub/efi.img" \ + "$INSTALLER_ISO/EFI/boot/efi.img" \ + "$INSTALLER_ISO/efi.img"; do + if [ -f "$efi_path" ]; then + EFI_IMG="$efi_path" break fi done -if [ -z "$ISOHDPFX" ]; then - echo " Extracting MBR from isolinux.bin..." - dd if="$INSTALLER_ISO/isolinux/isolinux.bin" of="$WORK_DIR/isohdpfx.bin" bs=432 count=1 2>/dev/null - ISOHDPFX="$WORK_DIR/isohdpfx.bin" +# If no standalone efi.img, check for [BOOT] directory from 7z extraction +if [ -z "$EFI_IMG" ] && [ -d "$INSTALLER_ISO/[BOOT]" ]; then + # 7z extracts El Torito boot images into [BOOT]/ — the EFI image is usually entry 2 + for entry in "$INSTALLER_ISO/[BOOT]/"*; do + # EFI images are typically > 1MB FAT filesystems + if [ -f "$entry" ]; then + entry_size=$(stat -c%s "$entry" 2>/dev/null || stat -f%z "$entry" 2>/dev/null || echo 0) + if [ "$entry_size" -gt 1048576 ]; then + mkdir -p "$INSTALLER_ISO/boot/grub" + cp "$entry" "$INSTALLER_ISO/boot/grub/efi.img" + EFI_IMG="$INSTALLER_ISO/boot/grub/efi.img" + echo " Recovered EFI image from [BOOT] directory" + break + fi + fi + done fi -xorriso -as mkisofs -o "$OUTPUT_ISO" \ - -volid "ARCHIPELAGO" \ - -J -R \ - -isohybrid-mbr "$ISOHDPFX" \ - -c isolinux/boot.cat \ - -b isolinux/isolinux.bin \ - -no-emul-boot -boot-load-size 4 -boot-info-table \ - -eltorito-alt-boot \ - -e boot/grub/efi.img \ - -no-emul-boot \ - -isohybrid-gpt-basdat \ - "$INSTALLER_ISO" +if [ -z "$EFI_IMG" ]; then + echo " ⚠️ No EFI boot image found — ISO will only support Legacy BIOS boot" + xorriso -as mkisofs -o "$OUTPUT_ISO" \ + -volid "ARCHIPELAGO" \ + -iso-level 3 \ + -J -joliet-long -R \ + -isohybrid-mbr "$ISOHDPFX" \ + -c isolinux/boot.cat \ + -b isolinux/isolinux.bin \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -partition_offset 16 \ + "$INSTALLER_ISO" +else + # Make EFI path relative to INSTALLER_ISO for xorriso + EFI_REL="${EFI_IMG#$INSTALLER_ISO/}" + xorriso -as mkisofs -o "$OUTPUT_ISO" \ + -volid "ARCHIPELAGO" \ + -iso-level 3 \ + -J -joliet-long -R \ + -isohybrid-mbr "$ISOHDPFX" \ + -c isolinux/boot.cat \ + -b isolinux/isolinux.bin \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -eltorito-alt-boot \ + -e "$EFI_REL" \ + -no-emul-boot \ + -isohybrid-gpt-basdat \ + -partition_offset 16 \ + "$INSTALLER_ISO" +fi echo "" echo "╔════════════════════════════════════════════════════════════════╗" diff --git a/image-recipe/configs/archipelago.service b/image-recipe/configs/archipelago.service index ed601be2..b800c1e1 100644 --- a/image-recipe/configs/archipelago.service +++ b/image-recipe/configs/archipelago.service @@ -1,13 +1,14 @@ [Unit] Description=Archipelago Backend -After=network-online.target +After=network-online.target archipelago-setup-tor.service Wants=network-online.target [Service] Type=simple -User=archipelago +User=root Environment="ARCHIPELAGO_BIND=0.0.0.0:5678" Environment="ARCHIPELAGO_DEV_MODE=true" +ExecStartPre=/bin/bash -c 'mkdir -p /etc/archipelago && echo "ARCHIPELAGO_HOST_IP=$(hostname -I 2>/dev/null | awk "{print $$1}")" > /etc/archipelago/host-ip.env' ExecStart=/usr/local/bin/archipelago Restart=on-failure RestartSec=5 diff --git a/loop/Old Plans/plan.md b/loop/Old Plans/plan.md new file mode 100644 index 00000000..f058b738 --- /dev/null +++ b/loop/Old Plans/plan.md @@ -0,0 +1,337 @@ +# 2-Year Production Roadmap — Archipelago v1.0 + +**Goal**: Take Archipelago from developer preview to a flawless, mass-market Bitcoin Node OS. Every app installs perfectly, every service runs reliably, every interaction is polished and intuitive — on desktop and mobile. + +**Timeline**: March 2026 → March 2028 (8 quarters) +**Method**: Quarterly phases, each building on the last. Deploy and verify after every task. + +--- + +## Q1 2026 (Mar–May): Foundation Hardening + +### Phase 1A: App Store Reliability — Every App Installs Without Fail + +- [x] **APP-101** — fix(marketplace): audit and fix all 24 marketplace app install flows. For each app in `getCuratedAppList()` in `neode-ui/src/views/Marketplace.vue` (bitcoin-knots, electrs, btcpay-server, lnd, mempool, homeassistant, grafana, searxng, ollama, onlyoffice, penpot, nextcloud, vaultwarden, jellyfin, photoprism, immich, filebrowser, nginx-proxy-manager, portainer, uptime-kuma, tailscale, fedimint, indeedhub), verify each one: (1) marketplace card renders correctly with icon, (2) clicking Install triggers `package.install` RPC, (3) container pulls and creates successfully, (4) container starts on the correct ports per `apps/PORTS.md`, (5) status shows "Running" in My Apps. Fix any broken apps. Deploy with `./scripts/deploy-to-target.sh --live`. Test each app at http://192.168.1.228. + +- [x] **APP-102** — fix(apps): ensure iframe vs new-tab behavior is correct for all apps. In `neode-ui/src/stores/appLauncher.ts`, verify `mustOpenInNewTab()` includes all apps that set `X-Frame-Options: DENY/SAMEORIGIN`. Currently covers BTCPay (23000), Home Assistant (8123), Nextcloud (8085), Immich (2283). Test each running app by clicking "Open" in AppDetails.vue — iframe apps must load inside the overlay, new-tab apps must open in a fresh browser tab. If any app fails to load in iframe, either fix the nginx proxy to strip X-Frame-Options or add it to `mustOpenInNewTab()`. Deploy and verify each app. + +- [x] **APP-103** — fix(apps): verify all PORT_TO_PROXY mappings in appLauncher.ts match nginx config. Cross-reference every entry in `PORT_TO_PROXY` in `neode-ui/src/stores/appLauncher.ts` with the actual nginx location blocks in `image-recipe/configs/nginx-archipelago.conf` and `image-recipe/configs/snippets/archipelago-https-app-proxies.conf`. Any missing nginx proxy blocks must be added. Any port mismatches must be corrected. Deploy nginx config and verify each app loads via its proxy path. + +- [x] **APP-104** — fix(deploy): ensure first-boot-containers.sh creates every marketplace app container. Compare the apps listed in `scripts/first-boot-containers.sh` with `scripts/deploy-to-target.sh`. Any app that deploy creates but first-boot doesn't must be added to first-boot. This ensures fresh ISO installs have all containers ready. + +- [x] **APP-105** — fix(backend): verify get_app_config() handles all 24 apps. In `core/archipelago/src/api/rpc/package.rs`, check `get_app_config()` returns correct ports, volumes, env vars, and custom args for every marketplace app. Any app missing its config will fail to install. Add missing configs. + +- [x] **APP-106** — fix(backend): verify get_app_metadata() for all 24 apps. In `core/archipelago/src/container/docker_packages.rs`, check `get_app_metadata()` returns correct title, description, icon path, and repo URL for every marketplace app. Fix missing or incorrect entries. + +### Phase 1B: App Dependencies — Bitcoin, Lightning, Fedimint Chains + +- [x] **DEP-101** — fix(backend): implement robust dependency checking for all apps. In `core/archipelago/src/api/rpc/package.rs`, ensure dependency checks work: Electrs requires Bitcoin Knots running, LND requires Bitcoin Knots running, BTCPay requires LND running, Mempool requires Bitcoin Knots + Electrs. When installing an app with unmet dependencies, the UI should either auto-install dependencies or show a clear message: "Bitcoin Knots must be installed and running first." Deploy and verify by trying to install Electrs without Bitcoin. + +- [x] **DEP-102** — fix(ui): show dependency status in MarketplaceAppDetails.vue. When viewing an app that has dependencies, show a "Requirements" section listing each dependency with a green checkmark (installed & running), yellow warning (installed but stopped), or red X (not installed). Add an "Install All Requirements" button that queues dependency installations in order. This lives in `neode-ui/src/views/MarketplaceAppDetails.vue`. + +- [x] **DEP-103** — feat(fedimint): integrate Fedimint Guardian + Gateway as paired services. Fedimint currently runs as a single container (fedimintd). Add Fedimint Gateway as a companion service that runs alongside the Guardian. In `scripts/deploy-to-target.sh`, when creating fedimint, also create `fedimint-gateway` container using `fedimint/gatewayd` image. Configure the gateway to auto-connect to the guardian. In the Marketplace, show Fedimint as one app that runs both services. The UI should show both Guardian and Gateway status. + +- [x] **DEP-104** — feat(fedimint): auto-configure Fedimint Gateway to use LND. The Fedimint Gateway needs a Lightning backend. When both LND and Fedimint are installed, auto-configure the gateway to use LND's gRPC endpoint. In `core/archipelago/src/api/rpc/package.rs`, add Fedimint Gateway config that reads LND's tls.cert and admin.macaroon from the LND data volume. The user should only need to open lightning channels — everything else should be automatic. + +- [x] **DEP-105** — feat(ui): lightning channel management interface. Create `neode-ui/src/views/apps/LightningChannels.vue` accessible from the LND app detail page. Show: (1) list of open channels with capacity bars, (2) "Open Channel" button with peer URI input and amount, (3) channel status (pending open/close, active, inactive), (4) total inbound/outbound liquidity summary. Use existing RPC to call LND's REST API through the backend proxy. This is critical for Fedimint Gateway to be useful. + +### Phase 1C: Animation & UI Polish + +- [x] **ANIM-101** — fix(css): audit and improve all transition animations in style.css. In `neode-ui/src/style.css`, review every `transition` property. Standardize: (1) hover lifts use `transform: translateY(-2px)` with `transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1)`, (2) active presses use `translateY(1px)`, (3) color transitions use `transition: color 0.2s ease, background-color 0.2s ease`, (4) modal/overlay entrances use `transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)`. Replace all `transition: all 0.3s ease` with specific properties to avoid animating layout properties. Deploy and verify animations feel smooth. + +- [x] **ANIM-102** — fix(ui): add smooth page transitions between routes. In `neode-ui/src/views/Dashboard.vue`, wrap `` in a `` component with `name="page"`. In `style.css`, add: `.page-enter-active, .page-leave-active { transition: opacity 0.2s ease, transform 0.2s ease; }` `.page-enter-from { opacity: 0; transform: translateY(8px); }` `.page-leave-to { opacity: 0; }`. This gives every page navigation a subtle fade-up entrance. Deploy and verify navigation feels smooth. + +- [x] **ANIM-103** — fix(ui): add staggered entrance animations for card grids. In views that display card grids (Apps.vue, Marketplace.vue, Home.vue, Web5.vue), add staggered entrance animations so cards appear one after another with a 50ms delay. Use CSS `animation-delay` with `nth-child()` or Vue's ``. The effect should be subtle — cards fade in and slide up slightly. Deploy and verify. + +- [x] **ANIM-104** — fix(ui): smooth loading state transitions. In all views that have loading states, ensure the transition from loading to loaded is animated — not an instant swap. Use Vue's `` with `mode="out-in"` around loading/content states. The loading spinner should fade out as the content fades in. Deploy and verify on Apps, Marketplace, Server, and Home views. + +- [x] **ANIM-105** — fix(ui): polish the app launcher overlay animation. In `neode-ui/src/components/AppLauncherOverlay.vue`, ensure the overlay slides up smoothly when opening an app. Add a subtle backdrop blur transition. The iframe should have a loading indicator that fades out when the app loads. The close animation should slide down. Use `will-change: transform` for GPU acceleration. Deploy and verify. + +### Phase 1D: Mobile Responsiveness + +- [x] **MOB-101** — fix(ui): audit and fix all views at 375px (iPhone SE) width. Test every view at 375px: Login, Dashboard sidebar, Home, Apps, Marketplace, AppDetails, MarketplaceAppDetails, Settings, Web5, Cloud, Server, Chat. Fix: horizontal overflow, overlapping elements, text truncation, buttons too small to tap (min 44px touch target), broken grid layouts. Add or fix responsive Tailwind classes in `style.css`. Deploy and verify. + +- [x] **MOB-102** — fix(ui): optimize sidebar navigation for mobile. The Dashboard sidebar should collapse to a bottom tab bar on mobile (< 768px). Show icons only with labels below. The mode switcher should be accessible from Settings on mobile. Ensure the sidebar doesn't overlap content on mobile. Deploy and verify. + +- [x] **MOB-103** — fix(ui): optimize app launcher overlay for mobile. The app iframe launcher should be full-screen on mobile with a sticky top bar (app title + close + open-in-new-tab). On desktop, maintain the current overlay style. Deploy and verify apps are usable on mobile. + +--- + +## Q2 2026 (Jun–Aug): Identity & Onboarding + +### Phase 2A: Multi-Identity System + +- [x] **ID-101** — feat(backend): implement identity manager with multiple DIDs. Create `core/archipelago/src/identity/mod.rs` with: (1) `IdentityManager` struct that stores multiple identities in `/var/lib/archipelago/identity/`, (2) each identity has an Ed25519 keypair, a DID, a display name, and a purpose tag (personal, business, anonymous), (3) the first identity is created during onboarding, (4) RPC endpoints: `identity.list`, `identity.create`, `identity.delete`, `identity.get`, `identity.set-default`. Store identities encrypted using the node's master key. Build on server and deploy. + +- [x] **ID-102** — feat(backend): implement identity signing service. Add `identity.sign` and `identity.verify` RPC endpoints. `identity.sign` takes a DID id and a message, returns a detached Ed25519 signature. `identity.verify` takes a DID, message, and signature, returns boolean. This enables apps to request signatures from the user's chosen identity. Build on server and deploy. + +- [x] **ID-103** — feat(ui): identity management view. Create a new "Identity" section in the Web5 view (replace the hidden `v-if="false"` DID section at line 429 of `Web5.vue`). Show: (1) list of all identities with name, DID (truncated), purpose badge, (2) "Create Identity" button that opens a modal with name + purpose selector, (3) each identity card has Copy DID, Set Default, Delete actions, (4) the default identity shows a star badge. Wire to the backend RPC endpoints from ID-101. Deploy and verify. + +- [x] **ID-104** — feat(ui): identity picker for service connections. Create a reusable `` component that shows a dropdown of the user's identities with their names and truncated DIDs. When a service (like Indeehub) needs a DID, it calls this component to let the user choose which identity to use. The selected identity's DID and signing capability are then passed to the service. + +- [x] **ID-105** — feat(backend): Nostr identity bridge. Each identity can optionally have an associated Nostr keypair (secp256k1). Add `identity.create-nostr-key` RPC that generates a Nostr keypair linked to an identity. Add `identity.nostr-sign` for NIP-01 event signing. This bridges the DID world with Nostr. The user's Nostr pubkey is derivable from their identity. Build on server and deploy. + +- [x] **ID-106** — feat(apps): Indeehub identity integration. When opening Indeehub, pass the user's selected identity DID via the iframe URL or postMessage. Indeehub should recognize the user's sovereign identity without requiring account creation. Implement the postMessage protocol: parent sends `{ type: 'archipelago:identity', did: '...', signature: '...' }`, Indeehub responds with `{ type: 'archipelago:identity:ack' }`. Deploy and verify Indeehub recognizes the user. + +### Phase 2B: Onboarding Flow Polish + +- [x] **ONB-101** — fix(ui): polish onboarding intro animation. In `neode-ui/src/views/OnboardingIntro.vue`, add a cinematic entrance: the Archipelago logo fades in with a subtle scale (0.95 → 1.0), followed by the tagline sliding up, then the "Get Started" button fading in. Total duration: 2 seconds. Use CSS keyframe animations. Deploy and verify. + +- [x] **ONB-102** — fix(ui): improve onboarding DID step UX. In `OnboardingDid.vue`, when the backend generates the DID, show a brief animation of key generation (spinning lock icon → checkmark). Display the DID in a styled card with a copy button. Explain in plain language: "This is your sovereign digital identity. It proves you are you, without any company in the middle." Deploy and verify. + +- [x] **ONB-103** — fix(ui): add identity purpose selection to onboarding. After DID creation in onboarding, add a step where the user names their first identity (default: "Personal") and optionally selects a purpose (Personal, Business, Anonymous). This feeds into the multi-identity system from ID-101. Deploy and verify. + +- [x] **ONB-104** — fix(ui): smooth transition between onboarding steps. Add a horizontal slide transition between onboarding steps — swiping left to advance, right to go back. Use `` with `name="slide"` and direction-aware classes. Deploy and verify the flow feels like swiping through cards. + +--- + +## Q3 2026 (Sep–Nov): Network & Node Discovery + +### Phase 3A: Node Overlay Network + +- [x] **NET-101** — feat(backend): implement node visibility signaling. Create `core/archipelago/src/network/overlay.rs` with: (1) a `NodeVisibility` enum (Hidden, Discoverable, Public), (2) RPC endpoints `network.set-visibility` and `network.get-visibility`, (3) when set to Discoverable, the node publishes a Nostr NIP-33 replaceable event (kind 30078, tag `d:archipelago-node`) with its onion address and public DID, (4) when set to Hidden, the event is deleted. This uses the existing Nostr discovery code in `core/archipelago/src/nostr_discovery.rs`. Build on server and deploy. + +- [x] **NET-102** — feat(backend): implement connection request protocol. Add RPC endpoints: `network.request-connection` (sends a connection request to a peer's onion address over Tor), `network.list-requests` (shows pending incoming requests), `network.accept-request` (adds peer to trusted list), `network.reject-request`. Connection requests are sent as encrypted Nostr DMs (NIP-04) containing the sender's DID and onion address. Build on server and deploy. + +- [x] **NET-103** — feat(ui): node visibility controls in Web5 view. In the Web5 view, add a "Node Visibility" card (replace or augment the existing Connected Nodes section). Show: (1) current visibility status (Hidden/Discoverable/Public), (2) toggle to change visibility, (3) when Discoverable, show the node's onion address, (4) warning: "Making your node discoverable lets other Archipelago users find and connect with you." Wire to NET-101 RPCs. Deploy and verify. + +- [x] **NET-104** — feat(ui): connection request management. In the Web5 view, add a "Connection Requests" tab to the Connected Nodes section. Show: (1) incoming requests with sender DID and timestamp, (2) Accept/Reject buttons, (3) notification badge on the Web5 sidebar icon when requests are pending. Wire to NET-102 RPCs. Deploy and verify. + +- [x] **NET-105** — feat(backend): implement peer health monitoring. Add a background task that periodically (every 5 minutes) checks if connected peers are reachable over Tor. Update peer status in the database. Send WebSocket events when peer status changes. The existing `rpcClient.checkPeerReachable()` in Web5.vue already calls this — ensure the backend implementation is robust with timeouts. Build on server and deploy. + +### Phase 3B: Tor Services Management + +- [x] **TOR-101** — feat(backend): implement Tor hidden service management RPC. Create RPC endpoints: `tor.list-services` (returns all configured hidden services with their .onion addresses), `tor.create-service` (creates a new hidden service for a given local port), `tor.delete-service`, `tor.get-onion-address`. Read from `/var/lib/archipelago/tor/` directory structure. Currently Tor setup is hardcoded in deploy script — make it dynamic. Build on server and deploy. + +- [x] **TOR-102** — feat(ui): Tor services management in Settings or Web5. Add a "Tor Services" section showing: (1) list of all hidden services with their .onion addresses and what app they expose, (2) toggle to enable/disable Tor for each service, (3) "Broadcast my services over Tor" master toggle, (4) copy .onion address button for each service. Wire to TOR-101 RPCs. Deploy and verify. + +- [x] **TOR-103** — fix(deploy): make Tor hidden service creation dynamic. Refactor `scripts/deploy-to-target.sh` Tor section (lines 471-530) to read from a config file (`/var/lib/archipelago/tor/services.json`) instead of hardcoding services. When an app is installed that supports Tor, automatically add a hidden service entry. When uninstalled, remove it. Rebuild torrc from the config file and restart the Tor container. Deploy and verify. + +- [x] **TOR-104** — feat(backend): Tor-based content serving. When a peer accesses your node over Tor, serve only the content you've explicitly made available. Create `core/archipelago/src/network/content_server.rs` with: (1) a list of shared content items (files, streams), (2) access control per item (free, paid via ecash), (3) a lightweight HTTP handler that serves content to authenticated peers. This is the foundation for content streaming. Build on server and deploy. + +--- + +## Q4 2026 (Dec–Feb 2027): Ecash & Content Economy + +### Phase 4A: Ecash Integration + +- [x] **ECASH-101** — feat(backend): implement Cashu ecash wallet. Create `core/archipelago/src/wallet/ecash.rs` with: (1) Cashu wallet client that connects to the local Fedimint mint, (2) RPC endpoints: `wallet.ecash-balance`, `wallet.ecash-mint` (create ecash tokens from Lightning), `wallet.ecash-melt` (redeem ecash to Lightning), `wallet.ecash-send` (create ecash token for peer), `wallet.ecash-receive` (accept ecash token from peer). Use the Cashu protocol for interoperability. Build on server and deploy. + +- [x] **ECASH-102** — feat(ui): ecash wallet in Web5 view. Replace the dummy "Web5 Wallet" card in Web5.vue (lines 221-268) with a real ecash wallet UI. Show: (1) ecash balance in sats, (2) Mint button (Lightning → ecash), (3) Melt button (ecash → Lightning), (4) Send button (generates ecash token string), (5) Receive button (paste ecash token), (6) transaction history. Wire to ECASH-101 RPCs. Deploy and verify. + +- [x] **ECASH-103** — feat(backend): implement pay-per-access content gating. Extend the content server from TOR-104 with ecash payment verification. When content is marked as "paid", the server returns a 402 Payment Required with a Cashu invoice. The requesting peer pays with ecash, receives a receipt token, and includes it in subsequent requests. Implement in `core/archipelago/src/network/content_server.rs`. Build on server and deploy. + +- [x] **ECASH-104** — feat(ui): content pricing controls. In the content sharing UI (to be built in Phase 4B), add pricing controls: (1) free/paid toggle per content item, (2) price in sats input, (3) "Pay what you want" option with minimum, (4) preview: "Peers will pay X sats to access this." Wire to backend content server config. Deploy and verify. + +### Phase 4B: Content Streaming & File Sharing + +- [x] **CONTENT-101** — feat(backend): implement content catalog RPC. Create `core/archipelago/src/network/content_catalog.rs` with: (1) `content.list-mine` — list content I'm sharing, (2) `content.add` — add a file or stream to my catalog, (3) `content.remove` — stop sharing, (4) `content.set-pricing` — free or ecash-gated, (5) `content.set-availability` — available to all peers, specific peers, or nobody. Store catalog in `/var/lib/archipelago/content/catalog.json`. Build on server and deploy. + +- [x] **CONTENT-102** — feat(backend): implement peer content browsing. Add `content.browse-peer` RPC that connects to a peer's onion address over Tor and fetches their content catalog. Returns a list of available items with titles, descriptions, sizes, and prices. The peer's content server (TOR-104) serves the catalog at a well-known endpoint. Build on server and deploy. + +- [x] **CONTENT-103** — feat(backend): implement content streaming protocol. For media files (video, audio), implement chunked streaming over Tor. The requesting node sends a range request, the serving node streams the content chunk by chunk. For paid content, payment is per-chunk (micropayments via ecash). Use HTTP range requests over the Tor hidden service. Build on server and deploy. + +- [x] **CONTENT-104** — feat(ui): content sharing dashboard. Create a "Content" tab in the Web5 view. Show: (1) "My Shared Content" — list of files/streams you're sharing with pricing, (2) "Add Content" button — file picker to add from Cloud/FileBrowser, (3) "Browse Peers" — select a connected peer and browse their catalog, (4) download/stream buttons with payment flow for paid content. Deploy and verify. + +- [x] **CONTENT-105** — feat(ui): content streaming player. When a user clicks to stream video/audio from a peer, open a media player in the app launcher overlay. Show: (1) video/audio player with standard controls, (2) streaming progress indicator, (3) cost tracker (total sats spent on this stream), (4) quality selector if multiple qualities available. Use HTML5 `