feat: Archipelago demo stack (lightweight)

This commit is contained in:
Dorian
2026-03-17 02:14:04 +00:00
commit 6b15143b8a
534 changed files with 75115 additions and 0 deletions

11
neode-ui/.env.example Normal file
View File

@@ -0,0 +1,11 @@
# Frontend Configuration
# Copy this to .env and adjust as needed
# Backend API URL
VITE_BACKEND_URL=http://localhost:5959
# API base path
VITE_API_BASE=/rpc/v1
# Development mode
VITE_DEV_MODE=true

29
neode-ui/.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Backup and temporary video files
**/*-backup-*.mp4
**/*-1.47mb.mp4
**/bg-*.mp4

3
neode-ui/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

View File

@@ -0,0 +1,133 @@
# ATOB Quick Start Guide
## 🚀 See ATOB in Action (30 seconds)
### Step 1: Start the Dev Server
```bash
cd /Users/tx1138/Code/Neode/neode-ui
npm run dev
```
### Step 2: Login
- Open: http://localhost:8100
- Password: `password123`
### Step 3: View ATOB
- Click "Apps" in the sidebar
- See ATOB pre-installed and running
- Click the **"Launch"** button (gradient blue)
- ATOB web app opens in new tab!
## 📦 What's Pre-Installed
ATOB comes pre-loaded in the mock backend with:
- ✅ Status: Running
- ✅ Version: 0.1.0
- ✅ Icon: Blue ATOB logo
- ✅ Launch button: Opens https://app.atobitcoin.io
- ✅ Start/Stop/Restart: Full controls
## 🎯 Features to Test
### In Apps List
1. See ATOB card with icon
2. Status badge shows "running" (green)
3. Launch button appears (gradient)
4. Click Launch → opens web app
### In App Details
1. Click on ATOB card
2. See full description
3. Big Launch button at top
4. Action buttons below
5. Back to Apps link
### Actions Available
- **Launch**: Opens ATOB web app
- **Stop**: Simulates stopping (mock)
- **Restart**: Simulates restart (mock)
- Click card → View details
## 🎨 Visual Elements
```
┌─────────────────────────────────────────────┐
│ Apps │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ [ATOB Icon] A to B Bitcoin │ │
│ │ Tools and services │ │
│ │ [running] v0.1.0 │ │
│ │ │ │
│ │ [Launch] [Stop] │ │ ← Launch button!
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ [Bitcoin Icon] Bitcoin Core │ │
│ │ ... │ │
└─────────────────────────────────────────────┘
```
## 📋 Mock Data Structure
ATOB is defined in `neode-ui/mock-backend.js`:
```javascript
'atob': {
title: 'A to B Bitcoin',
version: '0.1.0',
status: 'running', // ← Shows as running
state: 'installed', // ← Already installed
manifest: {
id: 'atob',
interfaces: {
main: {
ui: true, // ← Makes Launch button appear
}
}
}
}
```
## 🔧 Troubleshooting
### Launch Button Not Showing?
- Check app state is 'running'
- Verify manifest has `interfaces.main.ui: true`
- Restart dev server
### ATOB Not in Apps List?
- Check mock-backend.js has atob entry
- Verify WebSocket connection
- Check browser console for errors
### Launch Opens Wrong URL?
- Check Apps.vue launchApp() function
- Should open: https://app.atobitcoin.io
## 🎁 Bonus: Production Package
A complete s9pk package is also available at:
`~/atob-package/atob.s9pk` (23MB)
Install on real Neode server:
```bash
start-cli package.install ~/atob-package/atob.s9pk
```
## 📖 More Info
- **Full Integration Guide**: `/Users/tx1138/Code/Neode/ATOB_INTEGRATION.md`
- **S9PK Installation**: `~/atob-package/INSTALLATION.md`
- **Marketplace Guide**: `neode-ui/MARKETPLACE_SETUP.md`
---
## ✨ That's It!
ATOB is fully integrated and ready to use in both:
1. **Development** (mock backend - works now!)
2. **Production** (s9pk package - ready to deploy!)
**Enjoy launching ATOB from your Neode server!** 🚀

View File

@@ -0,0 +1,310 @@
# 🌐 Community Marketplace Integration
The Neode UI now includes a **Community Marketplace** tab that connects to the real Start9 app ecosystem!
## Features
### 📑 Two-Tab Interface
**Local Apps Tab:**
- Your custom local apps (k484, atob, amin)
- Sideload packages from URLs
- Quick install for development apps
**Community Marketplace Tab:**
- **Real Start9 packages** from the community registry
- Search functionality
- Bitcoin Core, Lightning Network, BTCPay, Nextcloud, and more!
- GitHub repository links
- Author information
### 🔍 Search & Filter
Search across:
- App names
- Descriptions
- Package IDs
- Author names
### 🎨 Beautiful UI
- Glass-morphism design (matching Neode aesthetic)
- Responsive grid layout
- Loading states
- Error handling with retry
- Install progress indicators
## How It Works
### 1. **Fetches Real Data**
Connects to Start9's community registry:
```
https://registry.start9.com/api/v1/packages
```
### 2. **Smart Multi-Level Fallback System**
If the Start9 registry is unavailable, the system automatically tries multiple sources:
**Level 1:** Start9 Registry API (primary)
```
https://registry.start9.com/api/v1/packages
```
**Level 2:** GitHub API (dynamic)
```
https://api.github.com/users/Start9Labs/repos
```
- Fetches all `-startos` repositories from Start9Labs
- Dynamically builds app list from actual packages
- Shows real-time ecosystem
**Level 3:** Curated App List (ultimate fallback)
Shows 20+ popular Start9 ecosystem apps including:
**Bitcoin & Lightning:**
- Bitcoin Core, Bitcoin Knots
- Core Lightning (CLN), LND
- BTCPay Server
- Ride The Lightning, ThunderHub
- Electrs, Mempool Explorer
- Specter Desktop
**Communication & Social:**
- Synapse (Matrix)
- Nostr Relay
- CUPS Messenger
**Productivity & Storage:**
- Nextcloud
- Vaultwarden (password manager)
- File Browser
**Media:**
- Jellyfin (media server)
- PhotoPrism (AI photos)
- Immich (photo backup)
**Smart Home:**
- Home Assistant
**You'll see a blue info banner:** "📚 Community Apps: Showing X Start9 ecosystem applications."
### 3. **Installation Flow**
When you click "Install" on a community app:
1. Calls `package.install` RPC method with the `.s9pk` URL
2. Backend downloads and extracts the package
3. Polls for installation completion
4. Redirects to Apps page when done
## Usage
### Access the Marketplace
1. Navigate to **Dashboard > Marketplace**
2. Click **"Community Marketplace"** tab
3. Browse or search for apps
4. Click **"Install"** on any app with a manifest URL
### Search for Apps
Type in the search bar:
- "bitcoin" - finds Bitcoin Core
- "lightning" - finds Lightning Network
- "payment" - finds BTCPay Server
- etc.
### Install Community Apps
Apps with green "Install" buttons:
- ✅ Have downloadable `.s9pk` packages
- ✅ Can be installed directly
Apps with "Not Available" buttons:
- ⚠️ Don't have packages yet
- 🔗 Can view GitHub repo for more info
## Technical Details
### API Integration
```typescript
// Fetches community packages
const response = await fetch('https://registry.start9.com/api/v1/packages')
const data = await response.json()
// Transforms to our format
communityApps.value = Object.entries(data).map(([id, pkg]) => ({
id,
title: pkg.title || id,
version: latestVersion,
description: pkg.description,
icon: pkg.icon,
author: pkg.author,
manifestUrl: pkg.manifest,
repoUrl: pkg.repository
}))
```
### Installation
```typescript
// Installs community app
await rpcClient.call({
method: 'package.install',
params: {
id: app.id,
url: app.manifestUrl, // .s9pk URL from registry
version: app.version
}
})
```
## App Card Features
Each community app card shows:
- **Icon** - App logo (with fallback to Neode logo)
- **Title** - App name
- **Version** - Latest available version
- **Author** - Package maintainer
- **Description** - Truncated to 3 lines
- **Install Button** - If manifest available
- **GitHub Link** - Repository access
## States
### Loading
```
🔄 Loading Start9 Community Marketplace...
```
### Error
```
❌ Failed to load marketplace
[Error message]
[Retry Button]
```
### Empty Search
```
No apps found matching "[query]"
```
## Backend Requirements
The mock backend needs to support:
1. **package.install** - Install from `.s9pk` URL
2. **Docker** - Extract and run containers
3. **Networking** - Download from registry URLs
## Future Enhancements
- [ ] **Categories** - Filter by Bitcoin, Storage, Communication, etc.
- [ ] **Ratings** - Show community ratings
- [ ] **Dependencies** - Show required apps
- [ ] **Updates** - Notify when app updates available
- [ ] **Reviews** - User reviews and comments
- [ ] **Screenshots** - App previews
- [ ] **Detailed Views** - Full app pages with more info
## Development
### Test the Feature
1. Start dev server:
```bash
npm start
```
2. Navigate to Marketplace
3. Switch to "Community Marketplace" tab
4. Should see apps load (or fallback list)
### Mock Data (Development Fallback)
If registry is unavailable, shows:
- Bitcoin Core v27.0.0
- Core Lightning v24.02.2
- BTCPay Server v1.13.1
- Nextcloud v29.0.0
### Adding More Mock Apps
Edit `loadCommunityMarketplace()` function in `Marketplace.vue`:
```typescript
communityApps.value = [
// ... existing apps ...
{
id: 'fedimint',
title: 'Fedimint',
version: '0.3.0',
description: 'Federated Chaumian e-cash',
icon: '/assets/img/fedimint.png',
author: 'Fedimint Developers',
manifestUrl: 'https://example.com/fedimint.s9pk',
repoUrl: 'https://github.com/fedimint/fedimint'
}
]
```
## Troubleshooting
### Registry Not Loading
**Problem**: Community tab shows error
**Solutions**:
1. Check internet connection
2. Verify registry URL is accessible
3. Check browser console for CORS errors
4. Fallback mock data will still show
### Apps Won't Install
**Problem**: Install button doesn't work
**Check**:
1. App has `manifestUrl` (not null)
2. Backend is running
3. Docker is available (for real installs)
4. Check backend logs for errors
### Search Not Working
**Problem**: Search doesn't filter apps
**Check**:
1. Type in search box
2. Should filter in real-time
3. Search is case-insensitive
4. Searches title, description, ID, author
## Benefits
**Real Apps** - Access actual Start9 packages
**Easy Discovery** - Browse full ecosystem
**One-Click Install** - Direct installation from registry
**Stay Updated** - See latest versions
**Community Driven** - Access community-maintained apps
**Transparent** - GitHub links for every app
## Security Note
Apps from the community marketplace are:
- Open source (GitHub repos linked)
- Community maintained
- Same packages used by StartOS
- Verified by signature (in production)
Always review an app's repository before installing!
---
🎉 **You can now browse and install real Start9 community apps directly from Neode!**

342
neode-ui/COMPARISON.md Normal file
View File

@@ -0,0 +1,342 @@
# Angular vs Vue 3 - Side by Side Comparison
## Your Question: "Is there a better way?"
**YES! You were right to question it.** Here's why the Vue rewrite solves your problems:
## The Problems You Had
### ❌ Angular Issues
1. **"Disappearing interfaces"** - Components randomly vanishing on route changes
2. **"Routing problems"** - Navigation breaking, routes not loading
3. **"Untable and hard to work with"** - Complex module system, slow builds
4. **"Seems a bit shit"** - Your words, but accurate! 😅
### ✅ Vue Solutions
1. **Stable routing** - Vue Router is simpler and more predictable
2. **Components don't vanish** - Reactive system is more reliable
3. **Fast & easy** - Vite HMR is instant, code is cleaner
4. **Actually enjoyable** - Modern DX that doesn't fight you
## Technical Comparison
### Routing
**Angular (Complex & Brittle):**
```typescript
// app-routing.module.ts
const routes: Routes = [
{
path: '',
redirectTo: '/login',
pathMatch: 'full'
},
{
path: 'login',
loadChildren: () => import('./pages/login/login.module').then(m => m.LoginPageModule)
},
// Module imports, lazy loading, guards in separate files...
]
// app.component.ts - Complex splash logic causing routing issues
this.router.events
.pipe(filter(e => e instanceof NavigationEnd))
.subscribe((e: any) => {
// Lots of state management that can break routes
})
```
**Vue (Clean & Simple):**
```typescript
// router/index.ts
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', redirect: '/login' },
{ path: '/login', component: () => import('../views/Login.vue') },
// Done. No modules, no complexity.
]
})
// Auth guard
router.beforeEach((to, from, next) => {
const isPublic = to.meta.public
if (!isPublic && !store.isAuthenticated) {
next('/login')
} else {
next()
}
})
```
### State Management
**Angular (RxJS Spaghetti):**
```typescript
// Observables everywhere
this.authService.isVerified$
.pipe(
filter(verified => verified),
take(1),
)
.subscribe(() => {
this.subscriptions.add((this.patchData as any).subscribe?.() ?? new Subscription())
this.subscriptions.add((this.patchMonitor as any).subscribe?.() ?? new Subscription())
// Easy to miss unsubscribe, causes memory leaks
})
```
**Vue (Simple & Reactive):**
```typescript
// Pinia store
const isAuthenticated = ref(false)
const serverInfo = computed(() => data.value?.['server-info'])
// No subscriptions to manage!
async function login(password: string) {
await rpcClient.login(password)
isAuthenticated.value = true
await connectWebSocket()
}
```
### Components
**Angular (Verbose):**
```typescript
import { Component, inject, OnDestroy } from '@angular/core'
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'
import { filter, take } from 'rxjs/operators'
import { combineLatest, map, startWith, Subscription } from 'rxjs'
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent implements OnDestroy {
private readonly subscriptions = new Subscription()
constructor(
private readonly titleService: Title,
private readonly patchData: PatchDataService,
// ... 10 more injected services
) {}
ngOnDestroy() {
this.subscriptions.unsubscribe()
}
}
```
**Vue (Concise):**
```vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/stores/app'
const router = useRouter()
const store = useAppStore()
const serverName = computed(() => store.serverName)
async function logout() {
await store.logout()
router.push('/login')
}
</script>
```
### Styling (Glass Cards)
**Angular (Fighting Ionic):**
```scss
// Have to override Ionic parts
ion-menu.left-menu::part(container) {
background: rgba(0, 0, 0, 0.35) !important;
backdrop-filter: blur(18px);
}
:host ::ng-deep ion-item.service-card {
--background: rgba(0, 0, 0, 0.35) !important;
// Fighting specificity wars
}
```
**Vue (Tailwind Utility Classes):**
```vue
<div class="glass-card p-6">
<!-- That's it. No !important, no ::ng-deep -->
</div>
```
## Build Performance
### Angular CLI (Slow)
```bash
$ npm run start:ui
⠙ Building...
[Build takes 45-60 seconds]
⠙ Recompiling...
[HMR takes 5-10 seconds per change]
```
### Vite (Fast)
```bash
$ npm run dev
✓ ready in 344 ms
[HMR updates in < 50ms]
```
**~100x faster development loop!**
## Bundle Size
| Framework | Size | Gzipped |
|-----------|------|---------|
| Angular UI | ~850 KB | ~250 KB |
| Vue UI | ~150 KB | ~50 KB |
**5x smaller bundle!**
## Code Comparison - Same Feature
### Login Page (Angular)
```
app/pages/login/
├── login.page.ts (150 lines)
├── login.page.html (80 lines)
├── login.page.scss (72 lines)
├── login.module.ts (40 lines)
└── login-routing.module.ts (15 lines)
```
**Total: 357 lines across 5 files**
### Login Page (Vue)
```
views/Login.vue (120 lines)
```
**Total: 120 lines in 1 file**
**3x less code!**
## The Backend Connection
### Both Work the Same!
**Angular:**
```typescript
this.http.httpRequest<T>({
method: 'POST',
url: '/rpc/v1',
body: { method, params },
})
```
**Vue:**
```typescript
fetch('/rpc/v1', {
method: 'POST',
body: JSON.stringify({ method, params }),
})
```
**Same API, same WebSocket, same everything.** The backend doesn't care!
## Migration Path
You have **two options**:
### Option 1: Use Vue UI (Recommended)
```bash
cd neode-ui
npm run dev # Develop in Vue
npm run build # Build to ../web/dist/neode-ui/
```
Update Rust to serve from `neode-ui` build directory.
**Pros:**
- Clean slate, no baggage
- Fast development
- Modern patterns
- Easier to maintain
**Cons:**
- Need to recreate any Angular features you haven't ported yet
### Option 2: Keep Angular
```bash
cd web
npm run start:ui # Continue with Angular
```
**Pros:**
- No migration needed
- All features intact
**Cons:**
- Still have routing issues
- Still slow
- Still complex
## Recommendation
**Switch to Vue.** Here's why:
1. **You already questioned the Angular approach** - Trust your instincts!
2. **Routing issues are gone** - Clean Vue Router
3. **Development is faster** - Vite HMR is instant
4. **Code is simpler** - Less boilerplate, easier to understand
5. **All your UI is recreated** - Glassmorphism, splash, everything
6. **Backend works the same** - No changes needed on Rust side
## Next Steps
1. **Test the Vue UI:**
```bash
cd /Users/tx1138/Code/Neode/neode-ui
npm run dev
```
2. **Compare the experience:**
- Open http://localhost:8100
- Navigate around
- Notice how stable it is
- Make a change and see instant HMR
3. **Decide:**
- If you like it → migrate remaining features
- If you don't → keep Angular (but you'll like it!)
## Final Thoughts
You asked: **"Is there a better way?"**
**Answer: Yes, and you're looking at it.** 🎉
The Vue + Tailwind approach is:
- Simpler
- Faster
- More stable
- Easier to maintain
- More enjoyable to work with
Your "untable and hard to work with" Angular feeling was valid. This fixes it.
**Ready to try it?**
```bash
cd neode-ui && npm run dev
```

227
neode-ui/DEV-SCRIPTS.md Normal file
View File

@@ -0,0 +1,227 @@
# 🚀 Neode Development Scripts
Quick reference for starting and stopping the Neode development environment.
## Quick Start
### Start Everything (Recommended)
```bash
npm start
# or
./start-dev.sh
```
This will:
- ✅ Check and clean up any processes on ports 5959, 8100-8102
- ✅ Start Docker Desktop if it's not running (waits up to 60 seconds)
- ✅ Start the mock backend (port 5959)
- ✅ Start Vite dev server (port 8100)
- ✅ Display status with color-coded output
**Access the app:**
- **Frontend**: http://localhost:8100
- **Backend RPC**: http://localhost:5959/rpc/v1
- **WebSocket**: ws://localhost:5959/ws/db
**Login credentials:**
- Password: `password123`
### Stop Everything
```bash
npm stop
# or
./stop-dev.sh
```
This will cleanly shut down:
- Mock backend server
- Vite dev server
- All related processes
---
## Individual Commands
### Run Mock Backend Only
```bash
npm run backend:mock
```
### Run Vite Only
```bash
npm run dev
```
### Run Both (without cleanup)
```bash
npm run dev:mock
```
### Run with Real Rust Backend
```bash
# Terminal 1: Start Rust backend
cd ../core
cargo run --release
# Terminal 2: Start Vite
npm run dev:real
```
---
## Troubleshooting
### Port Already in Use
If you see port conflicts, run:
```bash
npm stop
```
Then start again:
```bash
npm start
```
### Kill All Node Processes (Nuclear Option)
```bash
pkill -9 node
```
### Check What's Running on a Port
```bash
# Check port 5959
lsof -i :5959
# Check port 8100
lsof -i :8100
```
### View Logs
If running in background, logs are in:
```bash
tail -f /tmp/neode-dev.log
```
---
## Features
### Mock Backend
- **Docker Optional** - Apps run for real if Docker is available, otherwise simulated
- **Auto-Detection** - Automatically detects Docker daemon and adapts
- **Fixed Port Allocation** - atob:8102, k484:8103, amin:8104
- **WebSocket Support** - Real-time updates
- **Pre-loaded Apps** - Bitcoin and Lightning already "installed"
📖 **See [DOCKER-APPS.md](./DOCKER-APPS.md) for running real Docker containers**
### Available Test Apps
- `atob` - A to B Bitcoin (simulated)
- `k484` - K484 POS/Admin (simulated)
- `amin` - Admin interface (simulated)
All installations are simulated - they don't actually download or run Docker containers.
---
## Development Workflow
1. **Start servers:**
```bash
npm start
```
2. **Open browser:**
```
http://localhost:8100
```
3. **Login:**
```
password123
```
4. **Make changes** - Vite HMR will reload instantly
5. **Stop servers when done:**
```bash
npm stop
```
---
## Build Commands
### Development Build
```bash
npm run build
```
### Docker Build (no type checking)
```bash
npm run build:docker
```
### Type Check Only
```bash
npm run type-check
```
### Preview Production Build
```bash
npm run preview
```
---
## Script Details
### start-dev.sh
- Checks all required ports (5959, 8100-8102)
- Kills any existing processes on those ports
- Verifies node_modules are installed
- Starts both servers with concurrently
- Handles Ctrl+C gracefully
- Color-coded output for easy reading
### stop-dev.sh
- Finds all Neode-related processes
- Kills by port (5959, 8100-8102)
- Kills by process name (mock-backend, vite, concurrently)
- Confirms each shutdown with status messages
- Color-coded output
---
## Tips
- Always use `npm start` for the cleanest experience
- Run `npm stop` before switching branches if there are backend changes
- Vite will try alternate ports (8101, 8102) if 8100 is busy
- Mock backend simulates 1.5s installation delay for realism
---
## Known Issues
### Node.js Version Warning
```
You are using Node.js 20.18.2. Vite requires Node.js version 20.19+ or 22.12+.
```
**To fix:**
```bash
# Using nvm (recommended)
nvm install 22
nvm use 22
# Or upgrade directly
brew upgrade node
```
The warning is non-fatal - Vite still works, but upgrading is recommended.
---
Happy coding! 🎨⚡

256
neode-ui/DOCKER-APPS.md Normal file
View File

@@ -0,0 +1,256 @@
# 🐳 Running Real Apps with Docker
The mock backend can run **actual** Docker containers for your apps, not just simulate them!
## Current Status
Check the banner when starting the mock backend:
```
🐳 Available (apps will run for real!) ← Docker is running, apps will work!
⚠️ Not available (simulated mode) ← Docker off, apps simulated only
```
## Setup for Real Apps
### 1. Start Docker Desktop
Make sure Docker Desktop is running:
```bash
# Check if Docker daemon is running
docker ps
# If you see: "Cannot connect to the Docker daemon..."
# → Open Docker Desktop application
```
### 2. Build Your App Docker Images
For each app you want to run, you need a Docker image built with the exact name and version:
**For k484:**
```bash
# Assuming you have k484-package/ directory with a Dockerfile
cd ~/k484-package
docker build -t k484:0.1.0 .
```
**For atob:**
```bash
# Assuming you have atob-package/ directory with a Dockerfile
cd ~/atob-package
docker build -t atob:0.1.0 .
```
**For amin:**
```bash
cd ~/amin-package
docker build -t amin:0.1.0 .
```
### 3. Restart the Dev Server
```bash
npm stop
npm start
```
You should now see: `🐳 Available (apps will run for real!)`
### 4. Install Apps in the UI
Visit http://localhost:8100, go to Marketplace, and install apps.
You'll see logs like:
```
[Package] 📦 Installing k484...
[Package] 🐳 Docker available, attempting to run container...
[Package] 🐳 Docker container running on port 8103
[Package] ✅ k484 installed and RUNNING at http://localhost:8103
```
### 5. Launch Apps
Click "Launch" on any installed app - it will open at:
- **atob**: http://localhost:8102
- **k484**: http://localhost:8103
- **amin**: http://localhost:8104
---
## Port Mapping
The mock backend uses fixed ports for known apps:
| App | Port | Container Name |
|-----|------|----------------|
| atob | 8102 | atob-test |
| k484 | 8103 | k484-test |
| amin | 8104 | amin-test |
| Other apps | 8105+ | {id}-test |
---
## Docker Image Requirements
Each app needs a Docker image with:
- **Name**: `{app-id}:0.1.0`
- **Port**: Expose port 80 inside container
- **Format**: Standard web app serving HTTP
Example Dockerfile:
```dockerfile
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
---
## Troubleshooting
### "Docker image not found"
**Problem**: App installs but logs show:
```
[Package] Docker image k484:0.1.0 not found, using simulation mode
```
**Solution**: Build the Docker image first:
```bash
cd ~/k484-package
docker build -t k484:0.1.0 .
```
### "Port is already allocated"
**Problem**:
```
Bind for 0.0.0.0:8103 failed: port is already allocated
```
**Solution**: Stop the existing container:
```bash
docker stop k484-test
docker rm k484-test
```
Or use the UI to uninstall the app first, which will clean up the container.
### "Cannot connect to Docker daemon"
**Problem**:
```
⚠️ Not available (simulated mode)
```
**Solution**:
1. Open Docker Desktop
2. Wait for it to fully start (whale icon in menu bar should be steady)
3. Restart dev server: `npm stop && npm start`
### App appears installed but Launch doesn't work
**Simulated mode**: The app is only simulated in the database, no real container is running.
**Check the logs** when installing to see if Docker was used:
```
# Docker mode (good):
[Package] 🐳 Docker container running on port 8103
[Package] ✅ k484 installed and RUNNING at http://localhost:8103
# Simulated mode (no container):
[Package] Docker not available, using simulation mode
[Package] ✅ k484 installed (simulated - no Docker container)
```
---
## Checking Running Containers
```bash
# List all running containers
docker ps
# Check specific container
docker ps --filter name=k484-test
# View container logs
docker logs k484-test
# Stop a container
docker stop k484-test
# Remove a container
docker rm k484-test
```
---
## Benefits of Docker Mode
**Real apps** - Actually test your applications
**Full functionality** - All features work (not just UI)
**Integration testing** - Test API calls, WebSocket, etc.
**Realistic development** - Matches production environment
## Benefits of Simulated Mode
**No Docker required** - Lightweight development
**Fast startup** - No containers to build/start
**UI testing** - Perfect for frontend-only work
**Lower resource usage** - No Docker overhead
---
## Auto-Detection
The mock backend automatically:
1. Checks if Docker daemon is running
2. Checks if image exists for the app
3. Tries to start container if possible
4. Falls back to simulation if Docker unavailable
This means you can develop with or without Docker, and the system adapts automatically!
---
## Uninstalling Apps
When you uninstall an app through the UI:
- **Docker mode**: Stops and removes the container
- **Simulated mode**: Just removes from database
Both clean up properly - no manual cleanup needed!
---
## Quick Reference
```bash
# Start Docker Desktop first
open -a Docker
# Build images (one-time setup)
cd ~/k484-package && docker build -t k484:0.1.0 .
cd ~/atob-package && docker build -t atob:0.1.0 .
# Start dev servers
cd neode-ui
npm start
# Should see: 🐳 Available (apps will run for real!)
# Install apps via UI at http://localhost:8100
# Apps will actually run at their ports!
# Stop everything when done
npm stop
```
---
Happy coding! 🚀🐳

View File

@@ -0,0 +1,25 @@
FROM node:22-alpine
WORKDIR /app
# Install runtime dependencies
RUN apk add --no-cache wget curl docker-cli
# Copy package files
COPY neode-ui/package*.json ./
# Install Node dependencies (need all deps for mock backend)
RUN npm install
# Copy application code
COPY neode-ui/ ./
# Expose port
EXPOSE 5959
# Health check
HEALTHCHECK --interval=30s --timeout=15s --retries=5 --start-period=180s \
CMD wget --quiet --tries=1 --spider http://localhost:5959/health || exit 1
# Start the mock backend with error handling
CMD ["sh", "-c", "node mock-backend.js 2>&1 || (echo 'ERROR: Backend failed to start'; cat /app/package.json; ls -la /app; sleep infinity)"]

45
neode-ui/Dockerfile.web Normal file
View File

@@ -0,0 +1,45 @@
FROM node:22-alpine AS builder
WORKDIR /app
# Copy package files
COPY neode-ui/package*.json ./
# Install all dependencies (including dev)
RUN npm install
# Copy source code
COPY neode-ui/ ./
# Clean up backup files and large unused assets before build
RUN find public/assets -name "*backup*" -type f -delete || true && \
find public/assets -name "*1.47mb*" -type f -delete || true && \
find public/assets -name "bg-*.mp4" -type f -delete || true
# Build the Vue app (skip type checking, just build)
ENV DOCKER_BUILD=true
ENV NODE_ENV=production
# Use npm script which handles build better
RUN npm run build:docker || (echo "Build failed! Listing files:" && ls -la && echo "Checking vite config:" && cat vite.config.ts && exit 1)
# Production stage
FROM nginx:alpine
# Copy built files to nginx
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy AIUI pre-built dist
COPY demo/aiui/ /usr/share/nginx/html/aiui/
# Copy nginx config template and entrypoint
COPY neode-ui/docker/nginx-demo.conf /etc/nginx/nginx.conf.template
COPY neode-ui/docker/docker-entrypoint.sh /docker-entrypoint-custom.sh
RUN chmod +x /docker-entrypoint-custom.sh
# Expose port
EXPOSE 80
# Substitute ANTHROPIC_API_KEY at runtime, then start nginx
ENTRYPOINT ["/docker-entrypoint-custom.sh"]

316
neode-ui/ERROR_FIXES.md Normal file
View File

@@ -0,0 +1,316 @@
# Error Fixes - WebSocket & Marketplace Issues
## Issues Fixed
### ✅ 1. WebSocket Patch Error
**Error**: `Cannot read properties of undefined (reading 'length')`
**Root Cause**: The `applyDataPatch` function was trying to read the `length` property of an undefined or null `patch` array.
**Fix Applied** (`src/api/websocket.ts`):
```typescript
export function applyDataPatch<T>(data: T, patch: PatchOperation[]): T {
// ✅ NEW: Validate patch is an array before applying
if (!Array.isArray(patch) || patch.length === 0) {
console.warn('Invalid or empty patch received, returning original data')
return data
}
// ✅ NEW: Wrap in try/catch for safety
try {
const result = applyPatch(data, patch as any, false, false)
return result.newDocument as T
} catch (error) {
console.error('Failed to apply patch:', error, 'Patch:', patch)
return data // Return original data on error
}
}
```
**Also Fixed** (`src/stores/app.ts`):
```typescript
// ✅ Added null checks and error handling
wsClient.subscribe((update) => {
if (data.value && update?.patch) { // Added update?.patch check
try {
data.value = applyDataPatch(data.value, update.patch)
} catch (err) {
console.error('Failed to apply WebSocket patch:', err)
// Gracefully continue - app still works without real-time updates
}
}
})
```
**Result**:
- No more crashes from malformed WebSocket messages
- App continues working even if patches fail
- Better logging for debugging
---
### ✅ 2. Marketplace API Error
**Error**: `Method not found: marketplace.get`
**Root Causes**:
1. Backend not running
2. Not authenticated
3. Method requires authentication
**Fixes Applied** (`src/views/Marketplace.vue`):
```typescript
async function loadMarketplace() {
// ✅ NEW: Check authentication first
if (!store.isAuthenticated) {
error.value = 'Please login first to access the marketplace'
loading.value = false
return
}
try {
const data = await store.getMarketplace(selectedMarketplace.value)
// ... rest of logic
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to load marketplace'
// ✅ NEW: Provide specific, actionable error messages
if (errorMessage.includes('Method not found')) {
error.value = 'Backend marketplace API not available. Ensure Neode backend is running and up to date.'
} else if (errorMessage.includes('authenticated') || errorMessage.includes('401')) {
error.value = 'Authentication required. Please login first.'
} else if (errorMessage.includes('Network') || errorMessage.includes('fetch')) {
error.value = 'Cannot connect to backend. Ensure Neode backend is running on port 5959.'
} else {
error.value = errorMessage
}
}
}
```
**Result**:
- Clear, actionable error messages
- Authentication check before API calls
- Better user experience
---
## What You Need to Do
### Immediate Next Steps
**1. Ensure Backend is Running**
The marketplace requires a running Neode backend. You have two options:
#### Option A: Start the Backend (Recommended)
```bash
# Build and run the backend
cd /Users/tx1138/Code/Neode/core
cargo build --release
./target/release/startos
# Should see: "Neode backend listening on :5959"
```
#### Option B: Use Mock Mode (Development Only)
See `TROUBLESHOOTING.md` for how to enable mock mode for UI development without backend.
**2. Login First**
Before accessing the marketplace:
```bash
# Start UI
cd /Users/tx1138/Code/Neode/neode-ui
npm run dev
# Visit: http://localhost:8100
# Navigate to Login
# Enter credentials
# Then access Marketplace
```
**3. Test the Fixes**
```bash
# With backend running and authenticated:
# Navigate to Marketplace
# Should see: Loading → Apps OR Clear error message (not a crash)
```
---
## Testing the Fixes
### Test WebSocket Error Fix
1. **Start the UI**: `npm run dev`
2. **Open DevTools Console**
3. **Login** (to trigger WebSocket connection)
4. **Look for**:
- ✅ Should see: "WebSocket connected"
- ✅ Should NOT crash on malformed patches
- ✅ May see warnings: "Invalid or empty patch received" (this is OK)
### Test Marketplace Error Fix
1. **Without Backend**:
- Navigate to Marketplace
- Should see: "Backend marketplace API not available..." (clear message)
- No crashes or undefined errors
2. **Without Login**:
- Navigate to Marketplace
- Should see: "Please login first..." (clear message)
3. **With Backend & Login**:
- Navigate to Marketplace
- Should see: Loading → Apps list OR specific error
---
## File Changes Summary
### Modified Files
1. **`src/api/websocket.ts`**
- Added validation for patch array
- Added try/catch for safety
- Better error logging
2. **`src/stores/app.ts`**
- Added null checks for WebSocket updates
- Added try/catch in subscription handler
- Removed premature `isConnected` reset on logout
3. **`src/views/Marketplace.vue`**
- Added authentication check
- Added specific error messages
- Better error categorization
### New Documentation
4. **`TROUBLESHOOTING.md`**
- Common issues and solutions
- Mock mode setup
- Debugging tips
- Backend setup guide
5. **`ERROR_FIXES.md`** (this file)
- Summary of fixes
- Testing procedures
- Next steps
---
## Architecture Notes
### Why These Errors Happened
1. **WebSocket Error**:
- Backend sends JSON Patch operations over WebSocket
- If patch format is unexpected or empty, code crashed
- Now: Validates format and handles errors gracefully
2. **Marketplace Error**:
- RPC method `marketplace.get` exists in backend
- But requires running backend + authentication
- Now: Checks auth first, provides clear error messages
### How It Works Now
```
User navigates to Marketplace
Check isAuthenticated ✅
Call store.getMarketplace(url)
RPC Client → POST /rpc/v1
Backend: marketplace.get
Return apps OR error with clear message ✅
Display apps OR show actionable error ✅
```
---
## Verification Checklist
Run through this checklist to verify fixes:
- [ ] UI starts without errors: `npm run dev`
- [ ] Login works (with or without backend)
- [ ] WebSocket connects (if backend available)
- [ ] WebSocket errors don't crash app
- [ ] Marketplace shows clear error when not logged in
- [ ] Marketplace shows clear error when backend unavailable
- [ ] Marketplace loads apps (when backend running + logged in)
- [ ] No console errors about "Cannot read properties of undefined"
- [ ] No crashes when navigating between pages
---
## Still Getting Errors?
### Check Backend Status
```bash
# Is backend running?
lsof -i :5959
# Test backend directly
curl -X POST http://localhost:5959/rpc/v1 \
-H "Content-Type: application/json" \
-d '{"method":"echo","params":{"message":"hello"}}'
# Should return: {"result":"hello"}
```
### Check UI Status
```bash
# Is UI running?
curl http://localhost:8100
# Check console for errors
# Open browser DevTools → Console tab
```
### Enable Debug Logging
Add to `src/api/rpc-client.ts`:
```typescript
async call<T>(options: RPCOptions): Promise<T> {
console.log('🔵 RPC:', options.method, options.params)
// ... rest of method
console.log('🟢 Result:', data.result)
}
```
---
## Summary
### What Was Fixed
✅ WebSocket no longer crashes on bad patches
✅ Marketplace shows clear, actionable errors
✅ Better authentication checks
✅ Comprehensive error handling
### What You Should See Now
✅ No crashes or undefined errors
✅ Clear error messages with next steps
✅ App continues working even if WebSocket fails
✅ Marketplace works when backend is available
### Next Steps
1. Start backend: `cargo run --release`
2. Start UI: `npm run dev`
3. Login at `http://localhost:8100`
4. Test marketplace functionality
**See `TROUBLESHOOTING.md` for detailed debugging help!**

196
neode-ui/LOGO_USAGE.md Normal file
View File

@@ -0,0 +1,196 @@
# Neode Logo Usage Guide
## Logo File
**Primary Logo**: `/assets/img/logo-neode.png`
- Format: PNG with transparency
- Size: 454x454 pixels
- Type: Square icon/badge style
## Usage Throughout App
### 1. **Splash Screen**
- **Size**: Medium-large (300px max width)
- **Position**: Centered on screen
- **Timing**: Appears after alien intro and welcome message
- **Style**: Drop shadow for depth
```vue
<img
src="/assets/img/logo-neode.png"
class="w-[min(50vw,300px)] max-w-[80vw]"
/>
```
---
### 2. **Login Page**
- **Size**: 96px (24 in Tailwind = 96px)
- **Position**: **Half in, half out of glass card** (original design)
- **Style**: Absolute positioned at `-top-12` (48px up from card top)
- **Effect**: Logo appears to "float" above the card
```vue
<div class="glass-card p-8 pt-20 relative">
<div class="absolute -top-12 left-1/2 -translate-x-1/2 z-10">
<img
src="/assets/img/logo-neode.png"
class="w-24 h-24"
/>
</div>
</div>
```
**Visual Effect:**
```
┌─────┐
│ │ ← Half of logo above card
╔══│═════│══╗
║ │ │ ║
║ └─────┘ ║ ← Half of logo inside card
║ ║
║ Content ║
╚═══════════╝
```
---
### 3. **Onboarding Intro**
- **Size**: 128px (32 in Tailwind)
- **Position**: **Half in, half out of glass card**
- **Style**: Same floating effect as login
```vue
<div class="glass-card p-12 pt-20 relative">
<div class="absolute -top-16 left-1/2 -translate-x-1/2">
<img
src="/assets/img/logo-neode.png"
class="w-32 h-32"
/>
</div>
</div>
```
---
### 4. **Onboarding Options**
- **Size**: 128px (32 in Tailwind)
- **Position**: Centered above heading (not in card)
- **Style**: Standard centered logo
```vue
<img
src="/assets/img/logo-neode.png"
class="w-32 h-32 mx-auto mb-8"
/>
```
---
### 5. **Dashboard Sidebar**
- **Size**: 64px (16 in Tailwind)
- **Position**: Top of sidebar, inline with server name
- **Style**: Compact for sidebar
```vue
<div class="flex items-center gap-3">
<img src="/assets/img/logo-neode.png" class="w-16 h-16" />
<div>
<h2>Server Name</h2>
<p>v0.0.0</p>
</div>
</div>
```
---
### 6. **Browser Tab (Favicon)**
- **File**: Same logo used as favicon
- **Platforms**: Standard favicon + Apple touch icon
```html
<link rel="icon" type="image/png" href="/assets/img/logo-neode.png" />
<link rel="apple-touch-icon" href="/assets/img/logo-neode.png" />
```
---
## Size Reference
| Location | Tailwind Class | Actual Size | Purpose |
|----------|---------------|-------------|---------|
| Splash | `w-[min(50vw,300px)]` | Up to 300px | Large reveal |
| Onboarding Intro | `w-32 h-32` | 128px | Prominent |
| Onboarding Options | `w-32 h-32` | 128px | Header |
| Login | `w-24 h-24` | 96px | Floating |
| Sidebar | `w-16 h-16` | 64px | Compact |
---
## The "Half In, Half Out" Effect
This is the signature Neode design pattern for modals/cards:
### CSS Pattern:
```vue
<div class="glass-card pt-20 relative"> <!-- Extra top padding -->
<div class="absolute -top-12 left-1/2 -translate-x-1/2 z-10">
<img src="/assets/img/logo-neode.png" class="w-24 h-24" />
</div>
<!-- Content -->
</div>
```
### Key Properties:
- **Parent card**: `relative` positioning, `pt-20` (extra top padding for logo space)
- **Logo container**: `absolute` positioning
- **Vertical**: `-top-12` (moves logo up by 48px, half of 96px logo height)
- **Horizontal**: `left-1/2 -translate-x-1/2` (perfect centering)
- **Z-index**: `z-10` (appears above card)
### Math:
- Logo height: 96px
- Pushed up: `-48px` (half the height)
- Result: **Top 48px outside card, bottom 48px inside card**
---
## Don't Use These (Deprecated)
`/assets/img/logo-large.svg` - Old text-based logo
`/assets/img/icon.png` - Generic icon
`/assets/img/neode-logo.png` - Duplicate, use `logo-neode.png`
**Always use**: `/assets/img/logo-neode.png`
---
## Design Philosophy
The logo represents:
- **Sovereignty**: Bold, centered presence
- **Elegance**: Clean design that works with glassmorphism
- **Trust**: Consistent across all touchpoints
The "floating" effect on cards creates visual hierarchy and draws attention to the brand while maintaining the clean, modern aesthetic.
---
## Responsive Behavior
### Mobile (< 768px):
- Logo sizes scale down proportionally
- Floating effect maintained
- Touch-friendly sizing
### Tablet (768px - 1024px):
- Standard sizes
- Full effects
### Desktop (> 1024px):
- Largest sizes for impact
- Maximum visual effect
---
**Remember**: The logo is your brand identity. Use it consistently! 🎨

310
neode-ui/ONBOARDING_FLOW.md Normal file
View File

@@ -0,0 +1,310 @@
# Neode Onboarding Flow
## Complete User Journey (Vue 3)
### 1. **Splash Screen** (First Visit Only)
**Duration**: ~23 seconds (skippable)
#### Sequence:
1. **Alien Terminal Intro** (0-16s)
- Line 1: "Initializing Neode OS..." (typing animation)
- Line 2: "Connecting to distributed network..."
- Line 3: "Loading sovereignty protocols..."
- Line 4: "System ready."
- Green `$` prompts, white text
- Skip button in bottom right
2. **Welcome Message** (16-19s)
- "Welcome to Neode" with typing animation
- Fades in after terminal lines complete
3. **Neode Logo** (19-23s)
- Large "NEODE" SVG logo
- Background image fades in
- Smooth transition
#### Local Storage:
- Sets: `neode_intro_seen = '1'`
- Next visit: Skip splash entirely
---
### 2. **Onboarding Intro**
**Route**: `/onboarding/intro`
#### Content:
- **Neode Logo** at top (large SVG)
- **Heading**: "Welcome to Neode"
- **Subheading**: "Your personal server for a sovereign digital life"
- **Features**:
- 🔒 Self-Sovereign: Own your data and applications completely
- ⚡ Powerful: Run any service with one click
- 🛡️ Private: Tor-first architecture for maximum privacy
- **Button**: "Get Started →"
#### Action:
- Navigates to `/onboarding/options`
---
### 3. **Onboarding Options**
**Route**: `/onboarding/options`
#### Content:
- **Neode Logo** at top
- **Heading**: "Choose Your Setup"
- **Subheading**: "How would you like to get started?"
#### Three Glass Cards:
1. **Fresh Start**
- Icon: Plus symbol
- Description: Set up a new server from scratch
2. **Restore Backup**
- Icon: Upload symbol
- Description: Restore from a previous backup
3. **Connect Existing**
- Icon: Link symbol
- Description: Connect to an existing Neode server
#### Selection:
- Cards have hover effects
- Selected card: Brighter, glowing border
- **Button**: "Continue →" (enabled when option selected)
#### Action:
- Sets: `neode_onboarding_complete = '1'`
- Navigates to `/login`
---
### 4. **Login Page**
**Route**: `/login`
#### Content:
- **Neode Logo** floating above card
- **Glass Card** with:
- Title: "Welcome to Neode"
- Password input field
- Login button
- "Forgot password?" link
#### Auth Flow:
- Submit → Pinia store `login()` action
- Success → Navigate to `/dashboard`
- Error → Show error message in red glass banner
---
### 5. **Dashboard**
**Route**: `/dashboard`
#### Layout:
- **Sidebar** (glass):
- Neode logo at top
- Server name + version
- Navigation menu (Home, Apps, Marketplace, Server, Settings)
- Logout button at bottom
- **Main Content**:
- Dynamic based on route (Home, Apps, etc.)
- Connection status banner (if offline)
- Glass cards throughout
---
## Flow Diagram
```
┌─────────────────┐
│ First Visit? │
└────────┬────────┘
┌────▼────┐
│ Yes │──────┐
└─────────┘ │
│ │
┌────▼────────────▼────┐
│ Splash Screen │ (23s, skippable)
│ - Alien Intro │
│ - Welcome Message │
│ - Neode Logo │
└──────────┬───────────┘
[Sets: neode_intro_seen]
┌──────────▼───────────┐
│ Onboarding Intro │
│ - Logo │
│ - Features │
│ - Get Started │
└──────────┬───────────┘
┌──────────▼───────────┐
│ Onboarding Options │
│ - Fresh Start │
│ - Restore │
│ - Connect │
└──────────┬───────────┘
[Sets: neode_onboarding_complete]
┌──────────▼───────────┐
│ Login Page │
│ - Password Input │
└──────────┬───────────┘
[Authenticate]
┌──────────▼───────────┐
│ Dashboard │
│ - Sidebar + Content │
└──────────────────────┘
```
---
## Returning User Flow
### Second Visit Onwards:
```
Open App
├─ neode_intro_seen? YES
├─ neode_onboarding_complete? YES
└──> Login Page (direct)
```
- **No splash screen**
- **No onboarding**
- Goes straight to `/login`
---
## Local Storage Keys
| Key | Value | Set By | Effect |
|-----|-------|--------|--------|
| `neode_intro_seen` | `'1'` | SplashScreen.vue | Skip splash on return |
| `neode_onboarding_complete` | `'1'` | OnboardingOptions.vue | Skip onboarding on return |
---
## Branding Consistency
### Neode Logo Usage
**SVG Logo** (`/assets/img/logo-large.svg`):
- ✅ Splash screen (large, centered)
- ✅ Onboarding intro (medium, top)
- ✅ Onboarding options (medium, top)
- ✅ Login page (floating above card)
- ✅ Dashboard sidebar (small, top left)
**Icon** (`/assets/img/icon.png`):
- ✅ Browser favicon
- ✅ Apple touch icon
### No Start9 Branding
All Start9 references removed. Pure Neode branding throughout.
---
## Design Consistency
### Glassmorphism
Every screen uses:
- Glass cards with `backdrop-filter: blur(18px)`
- Black background with transparency
- White borders with 18% opacity
- Drop shadows for depth
### Colors
- Background: `rgba(0, 0, 0, 0.35)`
- Text: White with opacity (96%, 80%, 70%)
- Accents: Green `#00ff41` (terminal prompts)
- Borders: `rgba(255, 255, 255, 0.18)`
### Typography
- Primary: Avenir Next
- Mono: Courier New (terminal/splash)
- Size scale: 4px grid system
---
## Testing the Flow
### Test as New User:
```bash
# Clear storage
localStorage.clear()
# Reload
location.reload()
```
**Expected**:
1. Splash → Alien intro → Welcome → Logo
2. Onboarding intro → Features
3. Onboarding options → Select option
4. Login → Enter password
5. Dashboard → Home screen
### Test as Returning User:
```bash
# Storage should have:
localStorage.getItem('neode_intro_seen') // '1'
localStorage.getItem('neode_onboarding_complete') // '1'
# Reload
location.reload()
```
**Expected**:
1. Login (direct, no splash/onboarding)
2. Dashboard → Home screen
---
## Skip Behaviors
### Skip Splash
- Button: "Skip Intro" (bottom right)
- Effect: Jumps to logo display
- Still navigates to onboarding intro
### Skip Onboarding
User can navigate directly to `/login` if they know the URL.
---
## Future Enhancements
- [ ] Different flows for each setup option (Fresh/Restore/Connect)
- [ ] Progress indicators during setup
- [ ] Animated transitions between onboarding steps
- [ ] Video/GIF demos on feature cards
- [ ] Personalization (server name input during onboarding)
- [ ] Setup wizard for advanced users
---
## Key Files
| File | Purpose |
|------|---------|
| `src/App.vue` | Manages splash display, handles completion |
| `src/components/SplashScreen.vue` | Alien intro, animations, skip button |
| `src/views/OnboardingIntro.vue` | Welcome screen, feature highlights |
| `src/views/OnboardingOptions.vue` | Setup method selection |
| `src/views/Login.vue` | Authentication |
| `src/views/Dashboard.vue` | Main app layout |
| `src/router/index.ts` | Route definitions, auth guards |
---
**Complete, cohesive, and beautiful!** 🎨⚡

View File

@@ -0,0 +1,135 @@
# Quick Video Optimization Guide
## Current Status
- **Video File**: `public/assets/video/video-intro.mp4`
- **Current Size**: ~18MB
- **Target**: Optimize to ~1MB for fast web loading
## Step 1: Install FFmpeg (if not already installed)
```bash
brew install ffmpeg
```
## Step 2: Run Optimization Script (1MB Target)
Once FFmpeg is installed, run:
```bash
cd neode-ui
./optimize-video-1mb.sh
```
This will:
- ✅ Create a backup of your original video
- ✅ Optimize using H.264 with CRF 30 (good quality, smaller file)
- ✅ Scale to 1280x720 (HD resolution)
- ✅ Reduce frame rate to 30fps
- ✅ Compress audio to 64kbps
- ✅ Add faststart flag for web streaming
- ✅ Target ~1MB file size
- ✅ Replace original with optimized version
## Expected Results
- **Original**: ~18MB
- **Optimized**: ~1-2MB (90-95% reduction)
- **Quality**: Good quality (suitable for background video)
- **Loading**: 10-20x faster
## Manual Command (Alternative - 1MB Target)
If you prefer to run manually:
```bash
cd neode-ui/public/assets/video
# Backup original
cp video-intro.mp4 video-intro-backup.mp4
# Optimize for ~1MB (CRF 30, 1280x720, 30fps)
ffmpeg -i video-intro.mp4 \
-c:v libx264 \
-preset slow \
-crf 30 \
-profile:v high \
-level 4.0 \
-pix_fmt yuv420p \
-vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" \
-r 30 \
-c:a aac \
-b:a 64k \
-ar 44100 \
-movflags +faststart \
-threads 0 \
video-intro-optimized.mp4
# Replace original
mv video-intro-optimized.mp4 video-intro.mp4
```
### If Still Too Large, Use More Aggressive Settings:
```bash
# Even smaller (~500KB-1MB) - CRF 32, 854x480
ffmpeg -i video-intro.mp4 \
-c:v libx264 \
-preset slow \
-crf 32 \
-profile:v high \
-level 4.0 \
-pix_fmt yuv420p \
-vf "scale=854:480:force_original_aspect_ratio=decrease,pad=854:480:(ow-iw)/2:(oh-ih)/2" \
-r 24 \
-c:a aac \
-b:a 48k \
-ar 44100 \
-movflags +faststart \
video-intro-small.mp4
```
## After Optimization
1. **Update cache version** in code:
- `SplashScreen.vue`: Change `?v=3` to `?v=4`
- `OnboardingWrapper.vue`: Change `?v=3` to `?v=4`
2. **Test the video**:
- Verify smooth playback
- Check looping works correctly
- Test on mobile devices
## Quality Settings Explained (1MB Target)
- **CRF 30**: Good quality (recommended for 1MB target)
- Good visual quality suitable for background video
- ~1-2MB file size
- Best balance for web
- **CRF 28**: Better quality
- Higher quality, ~2-3MB file size
- Use if 1MB is too aggressive
- **CRF 32**: Smaller file
- Lower quality but still acceptable
- ~500KB-1MB file size
- Use if you need to hit 1MB exactly
**Recommendation**: Start with CRF 30, adjust based on results.
## Troubleshooting
### Script fails
- Ensure FFmpeg is installed: `brew install ffmpeg`
- Check file permissions: `chmod +x optimize-video.sh`
- Verify video file exists: `ls -lh public/assets/video/video-intro.mp4`
### Quality not good enough
- Use CRF 15 instead of 18 (larger file, better quality)
- Use `preset veryslow` instead of `slow` (slower encoding, better compression)
### File still too large
- Use CRF 20-23 (smaller file, slight quality trade-off)
- Reduce resolution if video is 4K: add `-vf "scale=1920:1080"`

View File

@@ -0,0 +1,181 @@
# 🚀 Quick Start Guide - Neode UI Development
## ✅ Problem Solved!
The **HTTP 500 error** you were seeing was because:
1. The backend server wasn't running on port 5959
2. Your Vue UI was trying to proxy requests to a non-existent backend
**Solution:** I've created a mock backend server that simulates the StartOS API.
---
## 🎯 How to Run (Two Options)
### Option 1: Run Everything Together (Recommended)
```bash
cd /Users/tx1138/Code/Neode/neode-ui
npm run dev:mock
```
This starts:
- ✅ Mock backend on http://localhost:5959
- ✅ Vite dev server on http://localhost:8100
### Option 2: Run Separately
**Terminal 1 - Start Mock Backend:**
```bash
cd /Users/tx1138/Code/Neode/neode-ui
npm run backend:mock
```
**Terminal 2 - Start UI:**
```bash
cd /Users/tx1138/Code/Neode/neode-ui
npm run dev
```
---
## 🔑 Login Credentials
To login to the UI, use:
- **Password:** `password123`
---
## ✅ What's Working Now
The mock backend provides responses for:
-`auth.login` - Login with password
-`auth.logout` - Logout
-`server.echo` - Echo test
-`server.time` - Current time
-`server.metrics` - System metrics
-`package.*` - Package operations (install, start, stop, etc.)
- ✅ WebSocket at `/ws/db` - Real-time updates
---
## 🎨 Current Setup
```
┌──────────────────┐
│ Your Browser │
│ localhost:8100 │
│ │
│ Vue 3 UI │
└────────┬─────────┘
│ proxy
┌──────────────────┐
│ Vite Server │
│ localhost:8100 │
└────────┬─────────┘
│ forward
┌──────────────────┐
│ Mock Backend │
│ localhost:5959 │
│ │
│ (Simulates │
│ StartOS API) │
└──────────────────┘
```
---
## 🔧 Troubleshooting
### Still seeing HTTP 500?
1. **Check if backend is running:**
```bash
lsof -ti:5959
```
If nothing returns, the backend isn't running.
2. **Start the backend manually:**
```bash
cd /Users/tx1138/Code/Neode/neode-ui
node mock-backend.js
```
3. **Check for errors:**
Look for console output in the terminal where you started the backend.
### Port already in use?
```bash
# Kill process on port 5959
lsof -ti:5959 | xargs kill -9
# Kill process on port 8100
lsof -ti:8100 | xargs kill -9
```
### Login not working?
1. Make sure you're using password: `password123`
2. Check browser console (F12) for specific errors
3. Verify backend is responding:
```bash
curl -X POST http://localhost:5959/rpc/v1 \
-H "Content-Type: application/json" \
-d '{"method":"server.echo","params":{"message":"test"}}'
```
Should return: `{"result":"test"}`
---
## 📝 Next Steps
1. **Try logging in** with password `password123`
2. **Explore the UI** - all API calls will return mock data
3. **Add new features** - the mock backend will respond appropriately
4. **Test real backend** - when ready, update `vite.config.ts` to point to your actual StartOS instance
---
## 🌐 Connecting to Real Backend (Later)
When you have a real StartOS instance running, update `neode-ui/vite.config.ts`:
```typescript
server: {
proxy: {
'/rpc': {
target: 'http://YOUR_STARTOS_IP', // Change this
changeOrigin: true,
},
// ...
},
}
```
---
## 📚 More Documentation
- See `README-MOCK-BACKEND.md` for detailed mock backend documentation
- See `README.md` for general project information
- See `QUICK_START.md` for Vue + Vite specifics
---
## ✨ Happy Coding!
Your development environment is now ready. The HTTP 500 error should be gone, and you can login with `password123`.
If you have any issues, check:
1. Both servers are running (ports 5959 and 8100)
2. No firewall blocking the ports
3. Browser console for specific errors
**Current Status:**
- ✅ Mock backend running on port 5959
- ✅ Vite dev server should be running on port 8100
- ✅ Login ready with password: `password123`

175
neode-ui/QUICK_START.md Normal file
View File

@@ -0,0 +1,175 @@
# 🚀 Quick Start Guide
## What Was Built
A **complete Vue 3 + Vite + Tailwind rewrite** of your Neode UI that:
**Recreates ALL your UI work:**
- Glassmorphism design system
- Alien-style splash screen with typing animations
- Login page with glass cards
- Onboarding flow (intro + options)
- Dashboard with glass sidebar
- Apps list with service cards
- Connection status handling
**Fixes routing issues:**
- Clean Vue Router (no more disappearing components!)
- Predictable navigation
- Proper auth guards
**Connects to your backend:**
- RPC client (same endpoints as Angular)
- WebSocket for real-time updates
- Pinia store (replaces PatchDB pattern)
## Start Developing
```bash
cd /Users/tx1138/Code/Neode/neode-ui
npm run dev
```
Visit: **http://localhost:8100**
## Test the App
Since you're in mock mode, you can test:
1. **Splash Screen** - Should show the alien intro on first visit
2. **Skip Button** - Click to skip the intro
3. **Onboarding** - After splash, you'll see the onboarding flow
4. **Login** - Glass card with password input
5. **Dashboard** - Glass sidebar with navigation
6. **Apps** - List view with glass cards
## Connect to Real Backend
When you're ready to connect to the actual backend:
1. **Update proxy in `vite.config.ts`** if backend isn't on port 5959
2. **Remove mock logic** - The RPC client is ready to go!
3. **Test login** - Use your actual password
## File Structure
```
neode-ui/
├── src/
│ ├── api/
│ │ ├── rpc-client.ts # RPC methods (login, packages, etc.)
│ │ └── websocket.ts # WebSocket connection & patches
│ ├── stores/
│ │ └── app.ts # Pinia store (global state)
│ ├── views/
│ │ ├── Login.vue # Login page
│ │ ├── OnboardingIntro.vue # Onboarding welcome
│ │ ├── OnboardingOptions.vue # Setup options
│ │ ├── Dashboard.vue # Main layout with sidebar
│ │ ├── Home.vue # Dashboard home
│ │ ├── Apps.vue # Apps list
│ │ └── ... # Other pages
│ ├── components/
│ │ └── SplashScreen.vue # Alien intro animation
│ ├── router/
│ │ └── index.ts # Routes & auth guard
│ ├── types/
│ │ └── api.ts # TypeScript types
│ └── style.css # Tailwind + glassmorphism
├── public/assets/img/ # Your images & logo
└── vite.config.ts # Vite config with proxy
```
## Development Commands
```bash
# Start dev server (hot reload)
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
# Type check
npm run type-check
```
## Key Differences from Angular
| Angular | Vue 3 |
|---------|-------|
| Modules + Services | Composables + Stores |
| RxJS Observables | Reactive refs/computed |
| Ionic Components | Native HTML + Tailwind |
| Complex routing | Simple Vue Router |
| PatchDB service | Pinia store |
| Slow CLI | Fast Vite |
## Next Steps
1. **Test the UI** - Run `npm run dev` and explore
2. **Compare routing** - Notice how stable the navigation is
3. **Check glassmorphism** - Your design is intact!
4. **Connect backend** - Update vite.config.ts proxy if needed
5. **Iterate** - Add features without Angular complexity
## Why This Is Better
### Routing Fixed ✅
- No more disappearing components
- Clean navigation with Vue Router
- Predictable route guards
### Faster Development ⚡
- Vite HMR is instant (vs Angular's slow recompile)
- Simpler component structure
- Less boilerplate
### Easier to Maintain 🛠️
- Smaller bundle size
- Modern patterns (Composition API)
- Better TypeScript integration
### Same Features 🎨
- All your glassmorphism styling
- Splash screen with animations
- Login, onboarding, apps list
- WebSocket state sync
## Troubleshooting
**Server won't start?**
```bash
# Kill any process on port 8100
lsof -ti:8100 | xargs kill -9
npm run dev
```
**Assets missing?**
```bash
# Copy from Angular project
cp -r ../web/projects/shared/assets/img/* public/assets/img/
```
**Backend connection fails?**
- Check backend is running
- Update proxy in `vite.config.ts`
- Check browser console for CORS errors
**TypeScript errors?**
- Check `src/types/api.ts` matches your backend
- Run `npm run type-check`
## Success! 🎉
You now have a **modern, stable, fast** UI that's easier to work with than Angular. The routing issues are gone, development is faster, and you can iterate quickly.
**Ready to test?**
```bash
npm run dev
```
Then open http://localhost:8100 and see your beautiful glass UI in action!

View File

@@ -0,0 +1,198 @@
# Mock Backend for Neode UI Development
This directory includes a mock backend server that simulates the StartOS backend API, allowing you to develop the UI without needing a full StartOS instance.
## Quick Start
### Option 1: Run Both Mock Backend + UI Together
```bash
npm install
npm run dev:mock
```
This will start:
- Mock backend on `http://localhost:5959`
- Vite dev server on `http://localhost:8100`
### Option 2: Run Separately
**Terminal 1 - Mock Backend:**
```bash
npm run backend:mock
```
**Terminal 2 - UI:**
```bash
npm run dev
```
## Mock Credentials
Use these credentials to login:
- **Password:** `password123`
## What Works
The mock backend provides fake responses for:
### Authentication
-`auth.login` - Login with password
-`auth.logout` - Logout and clear session
### Server Operations
-`server.echo` - Echo test
-`server.time` - Current time and uptime
-`server.metrics` - System metrics (CPU, memory, disk)
-`server.update` - Trigger update (fake)
-`server.restart` - Restart server (fake)
-`server.shutdown` - Shutdown server (fake)
### Package Management
-`package.install` - Install package (fake)
-`package.uninstall` - Uninstall package (fake)
-`package.start` - Start package (fake)
-`package.stop` - Stop package (fake)
-`package.restart` - Restart package (fake)
### Real-time Updates
- ✅ WebSocket connection at `/ws/db`
- ✅ Sends initial data on connection
- ✅ Sends periodic JSON Patch updates
## Mock Data
The mock backend provides:
- Server info (name, version, status)
- 2 mock packages (Bitcoin Core, Lightning Network)
- UI preferences and theme settings
- Fake system metrics
## Connecting to Real Backend
If you have access to a real StartOS instance, update `vite.config.ts`:
```typescript
server: {
port: 8100,
proxy: {
'/rpc': {
target: 'http://YOUR_STARTOS_IP', // Change this
changeOrigin: true,
},
// ... other proxies
},
}
```
## Architecture
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ Vue 3 UI │────────▶│ Vite Proxy │────────▶│ Mock Backend │
│ localhost:8100 │ │ (transparent) │ │ localhost:5959 │
│ │◀────────│ │◀────────│ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
```
1. UI makes requests to `/rpc/v1` and `/ws/db`
2. Vite dev server proxies them to `localhost:5959`
3. Mock backend responds with fake data
4. UI receives responses as if from a real backend
## Troubleshooting
### Port 5959 Already in Use
```bash
# Find and kill the process
lsof -ti:5959 | xargs kill -9
```
### Mock Backend Won't Start
Make sure dependencies are installed:
```bash
npm install
```
### WebSocket Connection Failed
Check that:
1. Mock backend is running (`npm run backend:mock`)
2. Browser console shows WebSocket connection to `ws://localhost:5959/ws/db`
3. No firewall blocking port 5959
## Development Tips
### Adding New RPC Methods
Edit `mock-backend.js` and add a new case in the switch statement:
```javascript
case 'your.new.method': {
return res.json({
result: {
// Your mock response
}
})
}
```
### Modifying Mock Data
Edit the `mockData` object in `mock-backend.js`:
```javascript
const mockData = {
'server-info': {
name: 'Your Server Name', // Customize here
version: '0.3.5',
// ...
},
// ...
}
```
### Testing WebSocket Updates
The mock backend sends random updates every 5 seconds. To customize:
```javascript
const interval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'patch',
patch: [
// Your JSON Patch operations
],
}))
}
}, 5000) // Change interval here
```
## Production Build
When building for production, make sure you're pointing to a real backend:
```bash
# Build the UI
npm run build
# Output goes to ../web/dist/neode-ui/
```
The mock backend is only for development and should never be used in production!
## Next Steps
- [ ] Connect to a real StartOS instance for full testing
- [ ] Test all UI flows with mock data
- [ ] Implement missing RPC methods as needed
- [ ] Add more realistic mock data
## Support
If you encounter issues:
1. Check that Node.js v18+ is installed
2. Verify all dependencies are installed (`npm install`)
3. Check both terminal outputs for errors
4. Review browser console for network errors

225
neode-ui/README.md Normal file
View File

@@ -0,0 +1,225 @@
# Neode UI - Vue 3 Edition
A modern, clean Vue 3 + Vite + Tailwind rewrite of the Neode OS interface.
## 🎯 Why This Rewrite?
The original Angular interface had routing issues, disappearing components, and was difficult to maintain. This Vue 3 rewrite provides:
-**Clean routing** - Vue Router is simpler and more predictable than Angular router
-**Modern tooling** - Vite is 10x faster than Angular CLI
-**Better DX** - TypeScript + Vue 3 Composition API + Tailwind = rapid development
-**Same glassmorphism design** - All your beautiful UI styling recreated
-**Same features** - Splash screen, login, onboarding, apps list, etc.
-**Backend agnostic** - Connects to the same Rust backend via RPC/WebSocket
## 🏗️ Architecture
```
neode-ui/
├── src/
│ ├── api/ # RPC client & WebSocket handler
│ ├── stores/ # Pinia state management (replaces PatchDB)
│ ├── views/ # Page components
│ ├── components/ # Reusable components (SplashScreen, etc.)
│ ├── router/ # Vue Router configuration
│ ├── types/ # TypeScript types (ported from Angular)
│ └── style.css # Global styles + Tailwind
├── public/assets/ # Static assets (images, fonts, icons)
└── vite.config.ts # Vite config with proxy to backend
```
## 🚀 Getting Started
### Prerequisites
- Node.js 20.19+ or 22.12+ (20.18.2 works but shows warning)
### Quick Start (Recommended)
```bash
cd neode-ui
npm install
npm start
```
Visit **http://localhost:8100** and login with password: `password123`
This starts both:
- ✅ Mock backend (port 5959) - no Docker required!
- ✅ Vite dev server (port 8100) with instant HMR
**Stop servers:**
```bash
npm stop
```
📖 **See [DEV-SCRIPTS.md](./DEV-SCRIPTS.md) for detailed documentation**
### Development Options
```bash
# Start everything (mock backend + Vite)
npm start
# Stop everything
npm stop
# Run mock backend only
npm run backend:mock
# Run Vite only (requires backend running separately)
npm run dev
# Run with real Rust backend (requires core/ to be running)
npm run dev:real
```
### Build for Production
```bash
npm run build
```
Outputs to `../web/dist/neode-ui/`
## 🎨 Design System
### Glassmorphism Classes
```html
<!-- Glass card -->
<div class="glass-card p-6">Content</div>
<!-- Glass button -->
<button class="glass-button px-4 py-3">Click me</button>
<!-- Manual glass styling -->
<div class="bg-glass-dark backdrop-blur-glass border border-glass-border shadow-glass">
Custom glass element
</div>
```
### Spacing (4px grid system)
- `p-4` = 16px padding
- `m-6` = 24px margin
- `gap-4` = 16px gap
### Colors
- `text-white/80` = 80% white opacity
- `bg-glass-dark` = rgba(0, 0, 0, 0.35)
- `border-glass-border` = rgba(255, 255, 255, 0.18)
## 🔌 API Connection
### RPC Calls
```typescript
import { rpcClient } from '@/api/rpc-client'
// Login
await rpcClient.login('password')
// Start a package
await rpcClient.startPackage('bitcoin')
// Get metrics
const metrics = await rpcClient.getMetrics()
```
### State Management (Pinia)
```typescript
import { useAppStore } from '@/stores/app'
const store = useAppStore()
// Access reactive state
const packages = computed(() => store.packages)
const isAuthenticated = computed(() => store.isAuthenticated)
// Call actions
await store.login(password)
await store.installPackage('nextcloud', marketplaceUrl, '1.0.0')
```
### WebSocket Updates
The store automatically subscribes to WebSocket updates and applies JSON patches to the state. No manual setup required!
## 📝 Routes
| Route | Component | Description |
|-------|-----------|-------------|
| `/` | Redirect | Redirects to /login |
| `/login` | Login | Login page with glass styling |
| `/onboarding/intro` | OnboardingIntro | Welcome screen |
| `/onboarding/options` | OnboardingOptions | Setup options |
| `/dashboard` | Dashboard | Main layout with sidebar |
| `/dashboard/apps` | Apps | Apps list with glass cards |
| `/dashboard/apps/:id` | AppDetails | App details page |
| `/dashboard/marketplace` | Marketplace | Browse apps |
| `/dashboard/server` | Server | Server settings |
| `/dashboard/settings` | Settings | UI settings |
## ✨ Features Recreated
- [x] Alien-style terminal splash screen with typing animation
- [x] Skip intro button
- [x] Login page with glass card and fade-up animation
- [x] Onboarding intro with feature highlights
- [x] Onboarding options with selectable glass cards
- [x] Dashboard layout with glass sidebar
- [x] Apps list with status badges and quick actions
- [x] Connection status banner
- [x] Offline detection
- [x] WebSocket state synchronization
- [x] RPC authentication
- [x] Responsive design
## 🐛 Debugging
### Common Issues
**Assets not loading?**
```bash
# Ensure assets are copied
cp -r ../web/projects/shared/assets/img/* public/assets/img/
```
**Backend connection refused?**
- Check backend is running on port 5959
- Update proxy in `vite.config.ts` if using different port
**TypeScript errors?**
```bash
npm run type-check
```
## 📦 Deployment
The Vue app can be served by the Rust backend (replace the Angular build):
1. Build: `npm run build` (outputs to `../web/dist/neode-ui/`)
2. Update Rust to serve from this directory
3. Restart backend
## 🔮 Future Enhancements
- [ ] Dark/light theme toggle
- [ ] App configuration UI
- [ ] Marketplace browsing & search
- [ ] Server metrics charts
- [ ] Backup/restore UI
- [ ] Notification system
- [ ] Multi-language support
## 🤝 Contributing
This is a clean rewrite - feel free to add features without the baggage of the old Angular codebase!
## 📄 License
Same as Neode OS

67
neode-ui/REFRESH_STEPS.md Normal file
View File

@@ -0,0 +1,67 @@
# How to See ATOB Changes
The mock backend has ATOB data, but your browser needs to refresh properly.
## Quick Fix (Try these in order)
### 1. Hard Refresh the Browser
```
Mac: Cmd + Shift + R
Windows/Linux: Ctrl + Shift + R
```
### 2. Clear Site Data and Refresh
In Chrome/Brave:
1. Open DevTools (F12)
2. Right-click the refresh button
3. Select "Empty Cache and Hard Reload"
### 3. Logout and Login Again
1. Click logout in the UI
2. Login again with: `password123`
3. Navigate to Apps
### 4. Restart Everything
```bash
# Stop the current dev server (Ctrl+C)
cd /Users/tx1138/Code/Neode/neode-ui
npm run dev:mock
```
Then visit: http://localhost:8100
## What to Expect
After refresh, you should see in the Apps page:
- **Bitcoin Core** (running)
- **Core Lightning** (stopped)
- **A to B Bitcoin** (running) ← NEW!
The ATOB card will have:
- Blue ATOB icon
- "A to B Bitcoin" title
- "running" green badge
- **"Launch"** button (gradient blue)
- Stop button
## Verification
Check browser console (F12):
```javascript
// Should show the WebSocket data includes atob
```
Check Network tab:
- WebSocket connection to `ws://localhost:5959/ws/db`
- Should receive data with package-data containing atob
## Still Not Working?
Check if the WebSocket sent the data:
1. Open DevTools (F12)
2. Go to Network tab
3. Filter by "WS" (WebSocket)
4. Click on the WebSocket connection
5. Look at Messages
6. You should see initial data with atob in package-data

429
neode-ui/TROUBLESHOOTING.md Normal file
View File

@@ -0,0 +1,429 @@
# Neode UI Troubleshooting
## Common Issues and Solutions
### 1. "Method not found: marketplace.get"
**Cause**: The backend isn't running or doesn't have the marketplace API enabled.
**Solutions**:
#### Option A: Start the Backend
```bash
# Ensure the Neode backend is running
cd /Users/tx1138/Code/Neode
# Start your backend service (adjust command as needed)
```
#### Option B: Check Backend Status
```bash
# Test if backend is accessible
curl -X POST http://localhost:5959/rpc/v1 \
-H "Content-Type: application/json" \
-d '{"method":"echo","params":{"message":"test"}}'
# Should return: {"result":"test"}
```
#### Option C: Use Mock Mode (Development Only)
Enable mock mode in `src/views/Marketplace.vue` by uncommenting the mock data section (see below).
---
### 2. WebSocket Error: "Cannot read properties of undefined (reading 'length')"
**Cause**: WebSocket patch data is malformed or empty.
**Fixed**: This is now handled gracefully. The app will log a warning but continue working.
**What Changed**:
- Added validation in `src/api/websocket.ts``applyDataPatch()`
- Added try/catch in `src/stores/app.ts``connectWebSocket()`
If you still see this error:
1. Check browser console for details
2. Verify backend is sending correct patch format
3. The app will continue working without real-time updates
---
### 3. "Please login first to access the marketplace"
**Cause**: You're not authenticated yet.
**Solution**:
1. Navigate to `/login`
2. Enter your password
3. Return to marketplace
**Check Auth Status**:
```javascript
// In browser console
const store = useAppStore()
console.log('Authenticated:', store.isAuthenticated)
```
---
### 4. "Cannot connect to backend"
**Cause**: Backend isn't running or proxy isn't configured.
**Solutions**:
#### Check Vite Proxy
Verify `vite.config.ts` has:
```typescript
proxy: {
'/rpc/v1': 'http://localhost:5959',
'/ws/db': {
target: 'ws://localhost:5959',
ws: true
}
}
```
#### Test Backend Connection
```bash
# Check if backend is listening
lsof -i :5959
# Or use netstat
netstat -an | grep 5959
```
#### Start Backend Manually
```bash
cd /Users/tx1138/Code/Neode/core
cargo run --release --bin startos
```
---
## Development Without Backend
### Enable Mock Mode
If you want to develop the UI without a running backend, you can enable mock mode:
**Edit `src/views/Marketplace.vue`**:
```typescript
async function loadMarketplace() {
loading.value = true
error.value = null
apps.value = []
// MOCK MODE - Comment out in production
const MOCK_MODE = true // Set to false when backend is available
if (MOCK_MODE) {
// Simulate loading delay
await new Promise(resolve => setTimeout(resolve, 1000))
apps.value = [
{
id: 'bitcoin',
title: 'Bitcoin Core',
description: 'A full Bitcoin node implementation. Store, validate, and relay blocks and transactions.',
version: '25.0.0',
icon: '/assets/img/bitcoin.png',
},
{
id: 'lightning',
title: 'Lightning Network',
description: 'Lightning Network implementation for fast, low-cost Bitcoin payments.',
version: '0.17.0',
icon: '/assets/img/lightning.png',
},
{
id: 'nextcloud',
title: 'Nextcloud',
description: 'Self-hosted file sync and sharing platform.',
version: '27.1.0',
icon: '/assets/img/nextcloud.png',
},
]
loading.value = false
return
}
// END MOCK MODE
// Real implementation continues...
try {
// ...
}
}
```
---
## Debugging Tips
### Enable Verbose Logging
**Add to `src/api/rpc-client.ts`**:
```typescript
async call<T>(options: RPCOptions): Promise<T> {
console.log('🔵 RPC Request:', options) // Add this
const response = await fetch(this.baseUrl, {
// ...
})
const data = await response.json()
console.log('🟢 RPC Response:', data) // Add this
return data.result as T
}
```
### Check WebSocket Status
In browser console:
```javascript
// Check WebSocket connection
const store = useAppStore()
console.log('Connected:', store.isConnected)
console.log('Data:', store.data)
```
### Monitor Network Traffic
1. Open Chrome DevTools (F12)
2. Go to Network tab
3. Filter by "WS" for WebSocket
4. Filter by "XHR" for RPC calls
5. Inspect request/response payloads
---
## Backend Build Issues
### Build Rust Backend
```bash
cd /Users/tx1138/Code/Neode/core
cargo build --release
# Binary will be at:
# target/release/startos
```
### Run Backend for Development
```bash
# Run with debug logging
RUST_LOG=debug ./target/release/startos
# Or use cargo run
RUST_LOG=debug cargo run --release
```
---
## Port Conflicts
### Check What's Using Port 5959
```bash
# macOS
lsof -i :5959
# Kill process if needed
kill -9 <PID>
```
### Check What's Using Port 8100 (Vite)
```bash
lsof -i :8100
kill -9 <PID>
```
---
## Authentication Issues
### Clear Session Cookies
In browser DevTools:
1. Application tab → Cookies
2. Delete all cookies for `localhost:8100`
3. Refresh page and login again
### Check Auth Cookie
In browser console:
```javascript
// Check if auth cookie exists
document.cookie
```
---
## WebSocket Connection Fails
### Common Causes
1. **Backend not running**: Start backend on port 5959
2. **CORS issues**: Vite proxy should handle this
3. **Port mismatch**: Check backend is listening on 5959
### Debug WebSocket
**Add to `src/api/websocket.ts`**:
```typescript
this.ws.onopen = () => {
console.log('✅ WebSocket connected')
this.reconnectAttempts = 0
resolve()
}
this.ws.onerror = (error) => {
console.error('❌ WebSocket error:', error)
reject(error)
}
this.ws.onmessage = (event) => {
console.log('📨 WebSocket message:', event.data) // Add this
try {
const update = JSON.parse(event.data)
this.callbacks.forEach((callback) => callback(update))
} catch (error) {
console.error('Failed to parse message:', error)
}
}
```
---
## Build Errors
### TypeScript Errors
```bash
# Type check without building
npm run type-check
# Fix common issues
npm install
rm -rf node_modules package-lock.json
npm install
```
### Vite Build Fails
```bash
# Clear cache and rebuild
rm -rf dist node_modules/.vite
npm run build
```
---
## Performance Issues
### Slow Hot Reload
1. **Check file watchers**:
```bash
# macOS - increase file watcher limit
ulimit -n 4096
```
2. **Clear Vite cache**:
```bash
rm -rf node_modules/.vite
```
### High Memory Usage
- Close other tabs/applications
- Restart Vite dev server
- Check for memory leaks in DevTools → Memory
---
## Still Having Issues?
### Collect Debug Information
1. **Browser Console Logs**:
- Open DevTools → Console
- Copy all errors
2. **Network Tab**:
- Check failed requests
- Copy request/response details
3. **Backend Logs**:
```bash
# If using systemd
journalctl -u startos -f
# If running manually
# Check terminal output
```
4. **System Info**:
```bash
node --version
npm --version
cargo --version
```
### Clean Restart
```bash
# Stop everything
# Kill Vite dev server (Ctrl+C)
# Kill backend process
# Clean rebuild
cd /Users/tx1138/Code/Neode/neode-ui
rm -rf dist node_modules/.vite
npm install
npm run dev
# In another terminal, start backend
cd /Users/tx1138/Code/Neode/core
cargo run --release
```
---
## Quick Reference
### Start Development
```bash
# Terminal 1: UI
cd /Users/tx1138/Code/Neode/neode-ui
npm run dev
# Terminal 2: Backend (if available)
cd /Users/tx1138/Code/Neode/core
cargo run --release
```
### Check Service Status
```bash
# Check if UI is running
curl http://localhost:8100
# Check if backend is running
curl http://localhost:5959/rpc/v1 -X POST \
-H "Content-Type: application/json" \
-d '{"method":"echo","params":{"message":"test"}}'
```
### Reset Everything
```bash
# Clean all state
rm -rf neode-ui/dist neode-ui/node_modules/.vite
cd neode-ui && npm install && npm run dev
```

View File

@@ -0,0 +1,199 @@
# Video Compression Guide for Web
## Current Video Status
- **File**: `public/assets/video/video-intro.mp4`
- **Size**: ~1MB (optimized - 95%+ reduction from original 36MB)
- **Usage**: Background video for Welcome Noderunner screen and onboarding/login
- **Format**: MP4 (H.264 compatible)
- **Last Updated**: December 26, 2025 (16:35)
- **Optimization**: CRF 32, 1280x720, 30fps (web-optimized for fast loading)
## Recommended Compression
### Target File Size
- **Ideal**: 2-5MB for web
- **Maximum**: 8MB (still acceptable but slower loading)
- **Current**: 16MB (too large, needs compression)
### Recommended Settings
#### Using FFmpeg (Recommended)
```bash
# Install FFmpeg (if not installed)
# macOS: brew install ffmpeg
# Linux: sudo apt install ffmpeg
# Windows: Download from https://ffmpeg.org/download.html
# Compress video with H.264 codec (best browser compatibility)
ffmpeg -i video-intro.mp4 \
-c:v libx264 \
-preset slow \
-crf 28 \
-c:a aac \
-b:a 128k \
-movflags +faststart \
-vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" \
video-intro-compressed.mp4
# For even smaller file (more aggressive compression)
ffmpeg -i video-intro.mp4 \
-c:v libx264 \
-preset slow \
-crf 32 \
-c:a aac \
-b:a 96k \
-movflags +faststart \
-vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" \
video-intro-compressed-small.mp4
```
#### Using HandBrake (GUI Tool)
1. Download HandBrake: https://handbrake.fr/
2. Open your video file
3. Preset: **Web/Google Gmail Large 3 Minutes 720p30**
4. Adjust settings:
- **Video Codec**: H.264 (x264)
- **Framerate**: Same as source (or 30fps)
- **Quality**: RF 28-32 (higher = smaller file, lower quality)
- **Resolution**: 1920x1080 or 1280x720
- **Audio**: AAC, 128kbps or 96kbps
5. Check "Web Optimized" checkbox
6. Start encoding
#### Using Online Tools
- **CloudConvert**: https://cloudconvert.com/mp4-compressor
- **FreeConvert**: https://www.freeconvert.com/video-compressor
- **Clideo**: https://clideo.com/compress-video
## FFmpeg Parameter Explanation
- `-c:v libx264`: Use H.264 video codec (best browser support)
- `-preset slow`: Better compression (slower encoding, smaller file)
- `-crf 28`: Quality setting (18-28 = high quality, 28-32 = medium, 32+ = lower)
- `-c:a aac`: Audio codec
- `-b:a 128k`: Audio bitrate (96k-128k is fine for background music)
- `-movflags +faststart`: Enables progressive download (starts playing before fully downloaded)
- `-vf scale=...`: Resize video (1920x1080 or 1280x720 recommended)
- `pad=...`: Add black bars if aspect ratio doesn't match
## Recommended Settings by Use Case
### High Quality (5-8MB)
```bash
ffmpeg -i video-intro.mp4 \
-c:v libx264 -preset slow -crf 24 \
-c:a aac -b:a 128k \
-movflags +faststart \
-vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" \
video-intro-hq.mp4
```
### Balanced (2-4MB) - **RECOMMENDED**
```bash
ffmpeg -i video-intro.mp4 \
-c:v libx264 -preset slow -crf 28 \
-c:a aac -b:a 96k \
-movflags +faststart \
-vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" \
video-intro-balanced.mp4
```
### Small File (1-2MB)
```bash
ffmpeg -i video-intro.mp4 \
-c:v libx264 -preset slow -crf 32 \
-c:a aac -b:a 64k \
-movflags +faststart \
-vf "scale=854:480:force_original_aspect_ratio=decrease,pad=854:480:(ow-iw)/2:(oh-ih)/2" \
video-intro-small.mp4
```
## Additional Optimizations
### 1. Trim Video Length
If the video is longer than needed, trim it:
```bash
# Trim to first 30 seconds
ffmpeg -i video-intro.mp4 -t 30 -c copy video-intro-trimmed.mp4
```
### 2. Reduce Frame Rate
If original is 60fps, reduce to 30fps:
```bash
ffmpeg -i video-intro.mp4 -r 30 -c:v libx264 -crf 28 video-intro-30fps.mp4
```
### 3. Create Multiple Formats (Optional)
For better browser support, create WebM version:
```bash
# WebM version (often smaller, but less browser support)
ffmpeg -i video-intro.mp4 \
-c:v libvpx-vp9 -crf 30 -b:v 0 \
-c:a libopus -b:a 96k \
video-intro.webm
```
Then update HTML:
```html
<video>
<source src="/assets/video/video-intro.webm" type="video/webm">
<source src="/assets/video/video-intro.mp4" type="video/mp4">
</video>
```
## Testing Compression
After compression, test:
1. **File size**: Should be 2-5MB
2. **Visual quality**: Check on different screen sizes
3. **Loading speed**: Test on slow connections
4. **Playback**: Ensure smooth playback and looping
## Quick Command (Copy-Paste Ready)
```bash
# Navigate to video directory
cd neode-ui/public/assets/video
# Backup original
cp video-intro.mp4 video-intro-original.mp4
# Compress (balanced quality, ~2-4MB)
ffmpeg -i video-intro.mp4 \
-c:v libx264 -preset slow -crf 28 \
-c:a aac -b:a 96k \
-movflags +faststart \
-vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" \
video-intro-compressed.mp4
# Replace original (after testing)
mv video-intro-compressed.mp4 video-intro.mp4
```
## Browser Compatibility
- **MP4 (H.264)**: Works in all modern browsers
- **WebM (VP9)**: Works in Chrome, Firefox, Edge (not Safari)
- **Recommendation**: Use MP4 for maximum compatibility
## Performance Tips
1. **Preload**: Add `preload="metadata"` to video tag (loads only metadata, not full video)
2. **Poster Image**: Add `poster="/assets/img/bg-intro.jpg"` for instant display while loading
3. **Lazy Load**: Consider loading video only when user reaches that screen
4. **CDN**: Host video on CDN for faster delivery
## Current Implementation
The video is currently set to:
- `autoplay`: Starts automatically
- `loop`: Repeats continuously
- `muted`: Required for autoplay in most browsers
- `playsinline`: Prevents fullscreen on mobile
These settings are optimal for background video use.

View File

@@ -0,0 +1,226 @@
# Video Optimization Guide - Quality Preservation
## Current Video
- **File**: `public/assets/video/video-intro.mp4`
- **Size**: ~36MB
- **Goal**: Optimize for web without losing quality
## Quick Optimization
Run the optimization script:
```bash
cd neode-ui
./optimize-video.sh
```
This will:
1. Create a backup of the original video
2. Optimize using H.264 with CRF 18 (near-lossless)
3. Add faststart flag for web streaming
4. Replace the original with the optimized version
## Manual Optimization
If you prefer to optimize manually:
### Option 1: Near-Lossless (Recommended)
```bash
cd neode-ui/public/assets/video
# Backup original
cp video-intro.mp4 video-intro-backup.mp4
# Optimize with CRF 18 (near-lossless, best quality)
ffmpeg -i video-intro.mp4 \
-c:v libx264 \
-preset slow \
-crf 18 \
-profile:v high \
-level 4.0 \
-pix_fmt yuv420p \
-c:a aac \
-b:a 128k \
-ar 48000 \
-movflags +faststart \
-threads 0 \
video-intro-optimized.mp4
# Replace original
mv video-intro-optimized.mp4 video-intro.mp4
```
**Expected result**: 15-25MB (40-60% reduction) with visually identical quality
### Option 2: Lossless (Maximum Quality)
```bash
ffmpeg -i video-intro.mp4 \
-c:v libx264 \
-preset veryslow \
-crf 15 \
-profile:v high \
-level 4.0 \
-pix_fmt yuv420p \
-c:a aac \
-b:a 192k \
-ar 48000 \
-movflags +faststart \
video-intro-lossless.mp4
```
**Expected result**: 20-30MB (20-40% reduction) with imperceptible quality loss
### Option 3: H.265/HEVC (Better Compression, Modern Browsers)
```bash
ffmpeg -i video-intro.mp4 \
-c:v libx265 \
-preset slow \
-crf 20 \
-pix_fmt yuv420p \
-c:a aac \
-b:a 128k \
-movflags +faststart \
video-intro-hevc.mp4
```
**Note**: H.265 provides better compression but has limited browser support (Safari, Edge, Chrome on Android). Use H.264 for maximum compatibility.
## Parameter Explanation
### CRF (Constant Rate Factor)
- **CRF 15**: Visually lossless (largest file)
- **CRF 18**: Near-lossless (recommended, good balance)
- **CRF 20**: High quality (smaller file, still excellent)
- **CRF 23**: Good quality (noticeable but acceptable)
- **Lower = Better Quality, Larger File**
### Preset
- **veryslow**: Best compression, slowest encoding
- **slow**: Best balance (recommended)
- **medium**: Faster encoding, larger file
- **fast**: Quick encoding, largest file
### Faststart Flag
- `-movflags +faststart`: Moves metadata to beginning of file
- Enables progressive download (video starts playing before fully downloaded)
- **Essential for web video**
### Profile & Level
- `-profile:v high`: Enables advanced H.264 features
- `-level 4.0`: Maximum compatibility with modern devices
- Ensures video plays on all browsers/devices
## Quality Comparison
| CRF | Quality | File Size | Use Case |
|-----|---------|-----------|----------|
| 15 | Lossless | ~30MB | Maximum quality needed |
| 18 | Near-lossless | ~20MB | **Recommended** - Best balance |
| 20 | High | ~15MB | Good quality, smaller file |
| 23 | Good | ~10MB | Acceptable quality |
## Expected Results
With CRF 18 (recommended):
- **Original**: 36MB
- **Optimized**: ~18-22MB (40-50% reduction)
- **Quality**: Visually identical to original
- **Loading**: 2-3x faster on slower connections
## Testing
After optimization:
1. **Visual comparison**: Compare original and optimized side-by-side
2. **File size**: Check reduction percentage
3. **Loading speed**: Test on slow connection (Chrome DevTools → Network → Throttling)
4. **Playback**: Verify smooth playback and looping
## Browser Compatibility
- **H.264 (MP4)**: Works in all modern browsers ✅
- **H.265 (HEVC)**: Safari, Edge, Chrome Android ✅ (Chrome Desktop ❌)
- **VP9 (WebM)**: Chrome, Firefox, Edge ✅ (Safari ❌)
**Recommendation**: Use H.264 for maximum compatibility.
## Advanced: Two-Pass Encoding
For even better quality at same file size:
```bash
# First pass
ffmpeg -i video-intro.mp4 \
-c:v libx264 \
-preset slow \
-crf 18 \
-profile:v high \
-level 4.0 \
-pix_fmt yuv420p \
-pass 1 \
-an \
-f null \
/dev/null
# Second pass
ffmpeg -i video-intro.mp4 \
-c:v libx264 \
-preset slow \
-crf 18 \
-profile:v high \
-level 4.0 \
-pix_fmt yuv420p \
-c:a aac \
-b:a 128k \
-ar 48000 \
-movflags +faststart \
-pass 2 \
video-intro-optimized.mp4
```
## Troubleshooting
### "FFmpeg not found"
```bash
# macOS
brew install ffmpeg
# Linux
sudo apt install ffmpeg
# Windows
# Download from https://ffmpeg.org/download.html
```
### "Permission denied"
```bash
chmod +x optimize-video.sh
```
### Video looks pixelated
- Use lower CRF (15-18 instead of 20+)
- Use slower preset (slow instead of medium)
### File size still too large
- Use CRF 20-23 (slight quality trade-off)
- Reduce resolution if video is 4K (scale to 1920x1080)
- Reduce frame rate if 60fps (reduce to 30fps)
## After Optimization
1. **Update cache-busting version** in code:
- `SplashScreen.vue`: Change `?v=3` to `?v=4`
- `OnboardingWrapper.vue`: Change `?v=3` to `?v=4`
2. **Test the video**:
- Check playback smoothness
- Verify looping works
- Test on mobile devices
3. **Update documentation**:
- Update `VIDEO_COMPRESSION_GUIDE.md` with new file size

View File

@@ -0,0 +1 @@
if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' })

126
neode-ui/dev-dist/sw.js Normal file
View File

@@ -0,0 +1,126 @@
/**
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// If the loader is already loaded, just stop.
if (!self.define) {
let registry = {};
// Used for `eval` and `importScripts` where we can't get script URL by other means.
// In both cases, it's safe to use a global var because those functions are synchronous.
let nextDefineUri;
const singleRequire = (uri, parentUri) => {
uri = new URL(uri + ".js", parentUri).href;
return registry[uri] || (
new Promise(resolve => {
if ("document" in self) {
const script = document.createElement("script");
script.src = uri;
script.onload = resolve;
document.head.appendChild(script);
} else {
nextDefineUri = uri;
importScripts(uri);
resolve();
}
})
.then(() => {
let promise = registry[uri];
if (!promise) {
throw new Error(`Module ${uri} didnt register its module`);
}
return promise;
})
);
};
self.define = (depsNames, factory) => {
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
if (registry[uri]) {
// Module is already loading or loaded.
return;
}
let exports = {};
const require = depUri => singleRequire(depUri, uri);
const specialDeps = {
module: { uri },
exports,
require
};
registry[uri] = Promise.all(depsNames.map(
depName => specialDeps[depName] || require(depName)
)).then(deps => {
factory(...deps);
return exports;
});
};
}
define(['./workbox-21a80088'], (function (workbox) { 'use strict';
self.skipWaiting();
workbox.clientsClaim();
/**
* The precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
workbox.precacheAndRoute([{
"url": "registerSW.js",
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.mbd8vi24k24"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
allowlist: [/^\/$/],
denylist: [/^\/app\//, /^\/rpc\//, /^\/ws/, /^\/aiui\//]
}));
workbox.registerRoute(/^https:\/\/fonts\.googleapis\.com\/.*/i, new workbox.CacheFirst({
"cacheName": "google-fonts-cache",
plugins: [new workbox.ExpirationPlugin({
maxEntries: 10,
maxAgeSeconds: 31536000
}), new workbox.CacheableResponsePlugin({
statuses: [0, 200]
})]
}), 'GET');
workbox.registerRoute(/^https:\/\/fonts\.gstatic\.com\/.*/i, new workbox.CacheFirst({
"cacheName": "gstatic-fonts-cache",
plugins: [new workbox.ExpirationPlugin({
maxEntries: 10,
maxAgeSeconds: 31536000
}), new workbox.CacheableResponsePlugin({
statuses: [0, 200]
})]
}), 'GET');
workbox.registerRoute(/\/rpc\/v1\/.*/i, new workbox.NetworkFirst({
"cacheName": "api-cache",
"networkTimeoutSeconds": 10,
plugins: [new workbox.ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 300
})]
}), 'GET');
workbox.registerRoute(/\/assets\/.*/i, new workbox.CacheFirst({
"cacheName": "assets-cache-v2",
plugins: [new workbox.ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 2592000
})]
}), 'GET');
}));

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A to B Bitcoin - Neode</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
overflow: hidden;
}
.container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
background: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.header h1 {
color: white;
font-size: 20px;
font-weight: 600;
}
.badge {
background: rgba(34, 197, 94, 0.2);
border: 1px solid rgba(34, 197, 94, 0.4);
color: #86efac;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
iframe {
flex: 1;
border: none;
background: white;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: white;
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🅰️➡️🅱️ A to B Bitcoin</h1>
<div class="badge">Running on Neode</div>
</div>
<iframe
id="atobFrame"
src="https://app.atobitcoin.io"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
</body>
</html>

View File

@@ -0,0 +1,33 @@
server {
listen 80;
server_name atob;
root /usr/share/nginx/html;
index index.html;
# Enable gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Proxy to actual ATOB if needed (or serve local iframe)
location /api {
proxy_pass https://app.atobitcoin.io;
proxy_set_header Host app.atobitcoin.io;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

View File

@@ -0,0 +1,11 @@
#!/bin/sh
# Copy nginx config template
cp /etc/nginx/nginx.conf.template /etc/nginx/nginx.conf
# Ensure client_max_body_size 0 is present (unlimited uploads)
# This is a safety net in case the config template was cached without the directive
if ! grep -q 'client_max_body_size' /etc/nginx/nginx.conf; then
sed -i 's/http {/http {\n client_max_body_size 0;/' /etc/nginx/nginx.conf
fi
exec nginx -g 'daemon off;'

View File

@@ -0,0 +1,129 @@
worker_processes 1;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 768;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# Allow large uploads globally (filebrowser, etc.)
client_max_body_size 0;
server {
listen 80 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html index.htm;
# Proxy API requests to backend
location /rpc/v1 {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Proxy WebSocket connections
location /ws {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
# Proxy public assets from backend
location /public {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Proxy REST API requests
location /rest {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Proxy FileBrowser API to mock backend (demo mode)
location /app/filebrowser/ {
client_max_body_size 10G;
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_request_buffering off;
}
# Serve AIUI SPA
location /aiui/ {
alias /usr/share/nginx/html/aiui/;
try_files $uri $uri/ /aiui/index.html;
location ~* /aiui/assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# Proxy AIUI API requests (web-search, etc.) to backend
location /api/ {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Proxy Ollama (local AI) requests to backend
location /aiui/api/ollama/ {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
proxy_set_header Connection "";
}
# Proxy Claude API requests to backend (which handles API key + streaming)
location /aiui/api/claude/ {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
proxy_set_header Connection "";
}
# Serve static files
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}

View File

@@ -0,0 +1,77 @@
worker_processes 1;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 768;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
server {
listen 80 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html index.htm;
# Proxy API requests to backend
location /rpc/v1 {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Proxy WebSocket connections
location /ws {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
# Proxy public assets from backend
location /public {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Proxy REST API requests
location /rest {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Serve static files
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

View File

@@ -0,0 +1,4 @@
{
"status": "passed",
"failedTests": []
}

View File

@@ -0,0 +1,134 @@
import { test, type Page } from '@playwright/test'
const SCREENSHOT_DIR = './e2e/screenshots'
const PASSWORD = 'password123'
/** Set localStorage values to skip splash screen and onboarding */
async function skipSplashAndOnboarding(page: Page) {
await page.goto('/login')
await page.evaluate(() => {
localStorage.setItem('neode_intro_seen', '1')
localStorage.setItem('neode_onboarding_complete', '1')
})
}
async function login(page: Page) {
await skipSplashAndOnboarding(page)
await page.goto('/login')
await page.waitForLoadState('networkidle')
// Wait for the password input to appear (server health check may delay it)
const passwordInput = page.locator('input[type="password"]').first()
await passwordInput.waitFor({ timeout: 15_000 })
await passwordInput.fill(PASSWORD)
// Click the login/submit button
const submitBtn = page
.locator(
'button:has-text("Login"), button:has-text("Unlock"), button:has-text("Continue"), button[type="submit"]',
)
.first()
await submitBtn.click()
// Wait for navigation to dashboard
await page.waitForURL('**/dashboard**', { timeout: 15_000 })
await page.waitForLoadState('networkidle')
// Wait for home page content to confirm dashboard is loaded
await page.locator('text=Welcome Noderunner').waitFor({ timeout: 10_000 })
await page.waitForTimeout(1500)
}
/** Navigate to a dashboard child route via sidebar link click */
async function navigateTo(page: Page, path: string, waitForText: string) {
// Use in-page navigation to avoid full SPA reload
await page.evaluate((p) => {
window.history.pushState({}, '', p)
window.dispatchEvent(new PopStateEvent('popstate'))
}, path)
// Wait for the page-specific content to appear
await page.locator(`text=${waitForText}`).first().waitFor({ timeout: 10_000 })
// Let content settle after route change
await page.waitForTimeout(800)
}
async function screenshot(page: Page, name: string) {
// Wait for any animations to settle
await page.waitForTimeout(1000)
await page.screenshot({
path: `${SCREENSHOT_DIR}/${name}.png`,
fullPage: true,
})
}
test.describe('Visual Regression — Public Pages', () => {
test('login page', async ({ page }) => {
await skipSplashAndOnboarding(page)
await page.goto('/login')
await page.waitForLoadState('networkidle')
// Wait for server health check and form to become active
await page.locator('input[type="password"]').first().waitFor({ timeout: 15_000 })
await page.waitForTimeout(1000)
await screenshot(page, '01-login')
})
})
test.describe('Visual Regression — Dashboard Pages', () => {
test.beforeEach(async ({ page }) => {
await login(page)
})
test('home / dashboard', async ({ page }) => {
// Already on home after login
await screenshot(page, '02-dashboard-home')
})
test('apps list', async ({ page }) => {
await navigateTo(page, '/dashboard/apps', 'My Apps')
await screenshot(page, '03-apps-list')
})
test('marketplace', async ({ page }) => {
await navigateTo(page, '/dashboard/marketplace', 'App Store')
await screenshot(page, '04-marketplace')
})
test('cloud storage', async ({ page }) => {
await navigateTo(page, '/dashboard/cloud', 'Cloud')
await screenshot(page, '05-cloud')
})
test('server', async ({ page }) => {
await navigateTo(page, '/dashboard/server', 'Network')
await screenshot(page, '06-server')
})
test('web5', async ({ page }) => {
await navigateTo(page, '/dashboard/web5', 'Web5')
await screenshot(page, '07-web5')
})
test('settings', async ({ page }) => {
await navigateTo(page, '/dashboard/settings', 'Settings')
await screenshot(page, '08-settings')
})
test('chat', async ({ page }) => {
await navigateTo(page, '/dashboard/chat', 'AI Assistant')
await screenshot(page, '09-chat')
})
test('federation', async ({ page }) => {
await navigateTo(page, '/dashboard/server/federation', 'Federation')
await screenshot(page, '10-federation')
})
test('credentials', async ({ page }) => {
await navigateTo(page, '/dashboard/web5/credentials', 'Credentials')
await screenshot(page, '11-credentials')
})
test('system update', async ({ page }) => {
await navigateTo(page, '/dashboard/settings/update', 'System Update')
await screenshot(page, '12-system-update')
})
})

43
neode-ui/fix-k484-nginx.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Quick fix for k484 nginx SPA routing
# Run this after installing k484 if /admin route doesn't work
echo "🔧 Fixing k484 nginx configuration for SPA routing..."
if ! /usr/local/bin/docker ps --filter name=k484-test --format "{{.Names}}" | grep -q k484-test; then
echo "❌ k484-test container is not running"
echo " Install k484 first through the Neode UI"
exit 1
fi
# Update nginx config for SPA routing
/usr/local/bin/docker exec k484-test sh -c 'cat > /etc/nginx/conf.d/default.conf << "EOF"
server {
listen 80;
listen [::]:80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
EOF
'
# Fix logo permissions
/usr/local/bin/docker exec k484-test chmod 644 /usr/share/nginx/html/k484-logo.png 2>/dev/null || true
# Restart nginx
/usr/local/bin/docker restart k484-test > /dev/null
echo "✅ k484 nginx config fixed!"
echo " Try http://localhost:8103/admin now"

30
neode-ui/index.html Normal file
View File

@@ -0,0 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/assets/icon/favico-black-v2.svg" />
<link rel="icon" href="/favicon-v2.ico" sizes="48x48" />
<link rel="icon" type="image/png" sizes="64x64" href="/assets/icon/pwa-64x64-v2.png" />
<link rel="icon" type="image/png" sizes="192x192" href="/assets/icon/pwa-192x192-v2.png" />
<link rel="icon" type="image/png" sizes="512x512" href="/assets/icon/pwa-512x512-v2.png" />
<link rel="apple-touch-icon" href="/assets/icon/apple-touch-icon-180x180-v2.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icon/apple-touch-icon-180x180-v2.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/assets/icon/pwa-192x192-v2.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/assets/icon/pwa-512x512-v2.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" />
<meta name="description" content="Archipelago - Your sovereign personal server" />
<meta name="theme-color" content="#000000" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Archipelago" />
<meta name="application-name" content="Archipelago" />
<meta name="msapplication-TileColor" content="#000000" />
<meta name="msapplication-TileImage" content="/assets/icon/pwa-192x192-v2.png" />
<title>Archipelago OS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

187
neode-ui/loop/loop.sh Executable file
View File

@@ -0,0 +1,187 @@
#!/usr/bin/env sh
# Headless loop script for overnight Claude Code automation.
# Rate-limit aware: detects limits, sleeps until reset, and retries automatically.
set -u
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
PROMPT_FILE="${PROMPT_FILE:-$PROJECT_DIR/loop/prompt.md}"
LOG_FILE="${LOG_FILE:-$PROJECT_DIR/loop/loop.log}"
ITERATION_COUNT="${ITERATION_COUNT:-10}"
ITERATION_DELAY="${ITERATION_DELAY:-30}"
CLAUDE_BIN="${CLAUDE_BIN:-claude}"
RATE_LIMIT_WAIT="${RATE_LIMIT_WAIT:-3600}"
MAX_RATE_LIMIT_RETRIES="${MAX_RATE_LIMIT_RETRIES:-5}"
CLAUDE_EXIT=0
cd "$PROJECT_DIR"
log() {
echo "$1" | tee -a "$LOG_FILE"
}
banner() {
log ""
log "================================================================"
log " $1"
log " $(date '+%Y-%m-%d %H:%M:%S')"
log "================================================================"
log ""
}
section() {
log ""
log "----------------------------------------"
log " $1"
log "----------------------------------------"
log ""
}
plan_has_tasks() {
grep -q '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null
}
remaining_tasks() {
grep -c '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null || echo "0"
}
next_task() {
grep -m1 '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null | sed 's/^- \[ \] //' || echo "(none)"
}
check_rate_limit() {
[ "${CLAUDE_EXIT:-0}" -eq 0 ] && return 1
tail -50 "$LOG_FILE" 2>/dev/null | grep -v "^Rate limit detected" | grep -v "^Sleeping" | grep -v "^=" | grep -v "^-" | grep -qi \
-e "rate.limit" \
-e "too.many.requests" \
-e "429" \
-e "quota.exceeded" \
-e "usage.limit" \
-e "limit.reached" 2>/dev/null
}
banner "NEODE-UI OVERNIGHT AUTOMATION STARTED"
log " Project: $PROJECT_DIR"
log " Prompt: $PROMPT_FILE"
log " Autonomous: ${CLAUDE_AUTONOMOUS:-0}"
log " Iterations: $ITERATION_COUNT (${ITERATION_DELAY}s between each)"
log " Rate limit: wait ${RATE_LIMIT_WAIT}s, retry up to ${MAX_RATE_LIMIT_RETRIES}x"
log " Tasks left: $(remaining_tasks)"
log " Next task: $(next_task)"
log ""
i=1
rate_limit_retries=0
while [ "$i" -le "$ITERATION_COUNT" ]; do
if ! plan_has_tasks; then
banner "ALL TASKS COMPLETE"
log " No remaining tasks in plan.md. Stopping."
break
fi
section "ITERATION $i/$ITERATION_COUNT"
log " Tasks remaining: $(remaining_tasks)"
log " Next task: $(next_task)"
log ""
export CLAUDE_PROJECT_DIR="$PROJECT_DIR"
export CLAUDE_AUTONOMOUS="${CLAUDE_AUTONOMOUS:-1}"
if [ -f "$PROMPT_FILE" ]; then
log " Starting Claude session..."
log ""
"$CLAUDE_BIN" -p --dangerously-skip-permissions \
< "$PROMPT_FILE" 2>&1 | tee -a "$LOG_FILE"
CLAUDE_EXIT=$?
log ""
log " Claude exited with code: $CLAUDE_EXIT"
else
log " ERROR: $PROMPT_FILE not found"
exit 1
fi
if check_rate_limit; then
rate_limit_retries=$((rate_limit_retries + 1))
if [ "$rate_limit_retries" -ge "$MAX_RATE_LIMIT_RETRIES" ]; then
section "RATE LIMITED — SCHEDULING LAUNCHD RETRY"
log " Hit rate limit $rate_limit_retries times. Creating launchd job to retry later."
PLIST_LABEL="com.neode-ui.overnight-retry"
PLIST_PATH="$HOME/Library/LaunchAgents/${PLIST_LABEL}.plist"
RETRY_TIME=$(date -v+${RATE_LIMIT_WAIT}S '+%H:%M' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M')
RETRY_HOUR=$(echo "$RETRY_TIME" | cut -d: -f1)
RETRY_MIN=$(echo "$RETRY_TIME" | cut -d: -f2)
cat > "$PLIST_PATH" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${PLIST_LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>cd ${PROJECT_DIR} && caffeinate -i ./loop/loop.sh >> ${LOG_FILE} 2>&1; launchctl unload ${PLIST_PATH}; rm -f ${PLIST_PATH}</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>${RETRY_HOUR}</integer>
<key>Minute</key>
<integer>${RETRY_MIN}</integer>
</dict>
<key>EnvironmentVariables</key>
<dict>
<key>CLAUDE_AUTONOMOUS</key>
<string>1</string>
<key>CLAUDE_PROJECT_DIR</key>
<string>${PROJECT_DIR}</string>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin</string>
</dict>
<key>StandardOutPath</key>
<string>${LOG_FILE}</string>
<key>StandardErrorPath</key>
<string>${LOG_FILE}</string>
</dict>
</plist>
PLIST
launchctl load "$PLIST_PATH" 2>/dev/null || true
log " Scheduled retry at ~${RETRY_TIME}"
log " Plist: $PLIST_PATH (auto-removes after running)"
exit 0
fi
section "RATE LIMITED — WAITING"
log " Attempt $rate_limit_retries/$MAX_RATE_LIMIT_RETRIES"
log " Sleeping ${RATE_LIMIT_WAIT}s until $(date -v+${RATE_LIMIT_WAIT}S '+%H:%M:%S' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M:%S')..."
sleep "$RATE_LIMIT_WAIT"
if ! plan_has_tasks; then
banner "ALL TASKS COMPLETE (during rate limit wait)"
break
fi
log " Retrying..."
continue
fi
rate_limit_retries=0
section "ITERATION $i COMPLETE"
log " Tasks remaining: $(remaining_tasks)"
log " Next task: $(next_task)"
i=$((i + 1))
if [ "$i" -le "$ITERATION_COUNT" ] && [ "$ITERATION_DELAY" -gt 0 ]; then
log " Pausing ${ITERATION_DELAY}s before next iteration..."
sleep "$ITERATION_DELAY"
fi
done
banner "LOOP FINISHED"
log " Completed $((i - 1)) iterations"
log " Tasks remaining: $(remaining_tasks)"
log ""

3
neode-ui/loop/plan.md Normal file
View File

@@ -0,0 +1,3 @@
# Overnight Plan -- neode-ui
> Tasks will be generated during setup.

38
neode-ui/loop/prepare.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env sh
# Pre-run script: verify repo state and create overnight branch.
set -eu
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
cd "$PROJECT_DIR"
DATE=$(date '+%Y-%m-%d')
BRANCH="overnight/${DATE}"
echo "=== neode-ui overnight pre-run check @ $(date '+%Y-%m-%dT%H:%M:%S') ==="
# 1. Check git status is clean
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "Error: Working tree not clean. Commit or stash changes first." >&2
git status --short >&2
exit 1
fi
# 2. Check we're not already on an overnight branch
current=$(git branch --show-current 2>/dev/null || true)
if [ -n "$current" ] && [ "$current" = "$BRANCH" ]; then
echo "Already on $BRANCH. Ready to run." >&2
exit 0
fi
# 3. Create date-stamped branch
if git rev-parse --verify "$BRANCH" >/dev/null 2>&1; then
echo "Branch $BRANCH already exists. Checkout or use a different date." >&2
exit 1
fi
git checkout -b "$BRANCH"
echo "Created branch $BRANCH"
echo ""
echo "Reminder: Push before starting overnight run: git push -u origin $BRANCH"
echo "Then run: caffeinate -i ./loop/loop.sh"
echo "=== Ready ==="

26
neode-ui/loop/prompt.md Normal file
View File

@@ -0,0 +1,26 @@
You are working through an overnight automation plan for the neode-ui project. Read these files first:
1. `loop/plan.md` -- Your task checklist (mark items `- [x]` as you complete them)
2. `CLAUDE.md` -- Project conventions, architecture, and coding standards
## Working Process
For each task in `loop/plan.md`:
1. Find the first unchecked `- [ ]` item
2. Read the task description carefully
3. Read the relevant source files before making changes
4. Implement following CLAUDE.md conventions
5. Run any test/build commands specified in the task
6. Fix all errors before continuing
7. Commit with conventional format: `type: description`
8. Mark it done `- [x]` in `loop/plan.md`
9. Move to the next unchecked task immediately
## Rules
- Never skip a testing gate -- if tests fail, fix before moving on
- If a task is proving difficult, make at least 10 genuine attempts before moving on
- Always read source files before editing them
- Do not stop until all tasks are checked or you are rate limited
- Commit after each completed task

2146
neode-ui/mock-backend.js Executable file

File diff suppressed because it is too large Load Diff

135
neode-ui/optimize-video-1mb.sh Executable file
View File

@@ -0,0 +1,135 @@
#!/bin/bash
# Video Optimization Script for Web - 1MB Target
# Optimizes video-intro.mp4 to ~1MB for fast web loading
set -e
VIDEO_DIR="public/assets/video"
INPUT_FILE="${VIDEO_DIR}/video-intro.mp4"
OUTPUT_FILE="${VIDEO_DIR}/video-intro-optimized.mp4"
BACKUP_FILE="${VIDEO_DIR}/video-intro-backup-$(date +%Y%m%d-%H%M%S).mp4"
echo "🎬 Video Optimization Script - 1MB Target"
echo "=========================================="
echo ""
# Check if FFmpeg is installed
if ! command -v ffmpeg &> /dev/null; then
echo "❌ FFmpeg is not installed."
echo ""
echo "Install it with:"
echo " macOS: brew install ffmpeg"
echo " Linux: sudo apt install ffmpeg"
echo " Windows: Download from https://ffmpeg.org/download.html"
exit 1
fi
# Check if input file exists
if [ ! -f "$INPUT_FILE" ]; then
echo "❌ Input file not found: $INPUT_FILE"
exit 1
fi
echo "📹 Input file: $INPUT_FILE"
INPUT_SIZE=$(du -h "$INPUT_FILE" | cut -f1)
echo " Size: $INPUT_SIZE"
echo ""
# Get video info
echo "📊 Analyzing video..."
DURATION=$(ffprobe -v quiet -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$INPUT_FILE" 2>/dev/null || echo "unknown")
RESOLUTION=$(ffprobe -v quiet -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "$INPUT_FILE" 2>/dev/null || echo "unknown")
FPS=$(ffprobe -v quiet -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 "$INPUT_FILE" 2>/dev/null | awk -F'/' '{print $1/$2}' | head -1 || echo "unknown")
echo " Duration: ${DURATION}s"
echo " Resolution: ${RESOLUTION}"
echo " Frame rate: ${FPS}fps"
echo ""
# Create backup
echo "💾 Creating backup..."
cp "$INPUT_FILE" "$BACKUP_FILE"
echo " Backup saved to: $BACKUP_FILE"
echo ""
# Optimize video for 1MB target
echo "⚙️ Optimizing video for ~1MB target..."
echo " Resolution: 1280x720 (HD)"
echo " Frame rate: 30fps"
echo " CRF: 30 (good quality, smaller file)"
echo " Audio: 64kbps (background music quality)"
echo ""
ffmpeg -i "$INPUT_FILE" \
-c:v libx264 \
-preset slow \
-crf 30 \
-profile:v high \
-level 4.0 \
-pix_fmt yuv420p \
-vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" \
-r 30 \
-c:a aac \
-b:a 64k \
-ar 44100 \
-movflags +faststart \
-threads 0 \
-y \
"$OUTPUT_FILE" 2>&1 | grep -E "(Duration|Stream|frame|size|time)" || true
if [ $? -eq 0 ] && [ -f "$OUTPUT_FILE" ]; then
echo ""
echo "✅ Optimization complete!"
echo ""
OUTPUT_SIZE=$(du -h "$OUTPUT_FILE" | cut -f1)
OUTPUT_BYTES=$(stat -f%z "$OUTPUT_FILE" 2>/dev/null || stat -c%s "$OUTPUT_FILE" 2>/dev/null)
if [ -n "$OUTPUT_BYTES" ]; then
OUTPUT_MB=$(echo "scale=2; $OUTPUT_BYTES / 1024 / 1024" | bc 2>/dev/null || echo "unknown")
echo "📊 Results:"
echo " Original size: $INPUT_SIZE"
echo " Optimized size: $OUTPUT_SIZE (~${OUTPUT_MB}MB)"
# Calculate compression ratio
ORIGINAL_BYTES=$(stat -f%z "$INPUT_FILE" 2>/dev/null || stat -c%s "$INPUT_FILE" 2>/dev/null)
if [ -n "$ORIGINAL_BYTES" ] && [ -n "$OUTPUT_BYTES" ]; then
RATIO=$(echo "scale=1; ($ORIGINAL_BYTES - $OUTPUT_BYTES) * 100 / $ORIGINAL_BYTES" | bc)
echo " Size reduction: ${RATIO}%"
# Check if target achieved
TARGET_BYTES=1048576 # 1MB in bytes
if [ "$OUTPUT_BYTES" -gt "$TARGET_BYTES" ]; then
EXCESS_MB=$(echo "scale=2; ($OUTPUT_BYTES - $TARGET_BYTES) / 1024 / 1024" | bc)
echo ""
echo "⚠️ File size is ${EXCESS_MB}MB over 1MB target"
echo " Current: ~${OUTPUT_MB}MB"
echo ""
echo " Options to reduce further:"
echo " - Use CRF 32 (slightly lower quality, smaller file)"
echo " - Reduce resolution to 854x480"
echo " - Reduce frame rate to 24fps"
else
echo ""
echo "✅ Target achieved! File is under 1MB"
fi
fi
fi
echo ""
echo "🔄 Replacing original file..."
mv "$OUTPUT_FILE" "$INPUT_FILE"
echo " ✅ Original file replaced with optimized version"
echo ""
echo "💡 To restore backup:"
echo " mv \"$BACKUP_FILE\" \"$INPUT_FILE\""
else
echo ""
echo "❌ Optimization failed. Original file preserved."
rm -f "$OUTPUT_FILE"
exit 1
fi
echo ""
echo "✨ Done! Video optimized for web (~1MB target)."

126
neode-ui/optimize-video.sh Executable file
View File

@@ -0,0 +1,126 @@
#!/bin/bash
# Video Optimization Script for Web
# Optimizes video-intro.mp4 for web use while preserving quality
set -e
VIDEO_DIR="public/assets/video"
INPUT_FILE="${VIDEO_DIR}/video-intro.mp4"
OUTPUT_FILE="${VIDEO_DIR}/video-intro-optimized.mp4"
BACKUP_FILE="${VIDEO_DIR}/video-intro-backup-$(date +%Y%m%d-%H%M%S).mp4"
echo "🎬 Video Optimization Script"
echo "============================"
echo ""
# Check if FFmpeg is installed
if ! command -v ffmpeg &> /dev/null; then
echo "❌ FFmpeg is not installed."
echo ""
echo "Install it with:"
echo " macOS: brew install ffmpeg"
echo " Linux: sudo apt install ffmpeg"
echo " Windows: Download from https://ffmpeg.org/download.html"
exit 1
fi
# Check if input file exists
if [ ! -f "$INPUT_FILE" ]; then
echo "❌ Input file not found: $INPUT_FILE"
exit 1
fi
echo "📹 Input file: $INPUT_FILE"
INPUT_SIZE=$(du -h "$INPUT_FILE" | cut -f1)
echo " Size: $INPUT_SIZE"
echo ""
# Get video info
echo "📊 Analyzing video..."
DURATION=$(ffprobe -v quiet -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$INPUT_FILE" 2>/dev/null || echo "unknown")
RESOLUTION=$(ffprobe -v quiet -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "$INPUT_FILE" 2>/dev/null || echo "unknown")
echo " Duration: ${DURATION}s"
echo " Resolution: ${RESOLUTION}"
echo ""
# Create backup
echo "💾 Creating backup..."
cp "$INPUT_FILE" "$BACKUP_FILE"
echo " Backup saved to: $BACKUP_FILE"
echo ""
# Optimize video for web (target ~1MB)
echo "⚙️ Optimizing video for web (target ~1MB)..."
echo " Using H.264 with optimized settings"
echo " Preset: slow (best compression efficiency)"
echo " Resolution: 1280x720 (HD, good quality)"
echo " Frame rate: 30fps (smooth playback)"
echo ""
ffmpeg -i "$INPUT_FILE" \
-c:v libx264 \
-preset slow \
-crf 28 \
-profile:v high \
-level 4.0 \
-pix_fmt yuv420p \
-vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" \
-r 30 \
-c:a aac \
-b:a 64k \
-ar 44100 \
-movflags +faststart \
-threads 0 \
-y \
"$OUTPUT_FILE" 2>&1 | grep -E "(Duration|Stream|frame|size|time)" || true
if [ $? -eq 0 ]; then
echo ""
echo "✅ Optimization complete!"
echo ""
OUTPUT_SIZE=$(du -h "$OUTPUT_FILE" | cut -f1)
OUTPUT_BYTES=$(stat -f%z "$OUTPUT_FILE" 2>/dev/null || stat -c%s "$OUTPUT_FILE" 2>/dev/null)
OUTPUT_MB=$(echo "scale=2; $OUTPUT_BYTES / 1024 / 1024" | bc 2>/dev/null || echo "unknown")
echo "📊 Results:"
echo " Original size: $INPUT_SIZE"
echo " Optimized size: $OUTPUT_SIZE (~${OUTPUT_MB}MB)"
# Calculate compression ratio
ORIGINAL_BYTES=$(stat -f%z "$INPUT_FILE" 2>/dev/null || stat -c%s "$INPUT_FILE" 2>/dev/null)
if [ -n "$ORIGINAL_BYTES" ] && [ -n "$OUTPUT_BYTES" ]; then
RATIO=$(echo "scale=1; ($ORIGINAL_BYTES - $OUTPUT_BYTES) * 100 / $ORIGINAL_BYTES" | bc)
echo " Size reduction: ${RATIO}%"
# Check if target achieved
TARGET_BYTES=1048576 # 1MB in bytes
if [ "$OUTPUT_BYTES" -gt "$TARGET_BYTES" ]; then
EXCESS=$(echo "scale=1; ($OUTPUT_BYTES - $TARGET_BYTES) / 1024 / 1024" | bc)
echo ""
echo "⚠️ File size is ${EXCESS}MB over 1MB target"
echo " Consider using CRF 30-32 for smaller file size"
else
echo ""
echo "✅ Target achieved! File is under 1MB"
fi
fi
echo ""
echo "🔄 Replacing original file..."
mv "$OUTPUT_FILE" "$INPUT_FILE"
echo " ✅ Original file replaced with optimized version"
echo ""
echo "💡 To restore backup:"
echo " mv \"$BACKUP_FILE\" \"$INPUT_FILE\""
else
echo ""
echo "❌ Optimization failed. Original file preserved."
rm -f "$OUTPUT_FILE"
exit 1
fi
echo ""
echo "✨ Done! Video optimized for web (~1MB target)."

12521
neode-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

61
neode-ui/package.json Normal file
View File

@@ -0,0 +1,61 @@
{
"name": "neode-ui",
"private": true,
"version": "1.1.0",
"type": "module",
"scripts": {
"start": "./start-dev.sh",
"stop": "./stop-dev.sh",
"test": "vitest run",
"test:watch": "vitest",
"dev": "vite",
"dev:mock": "concurrently \"node mock-backend.js\" \"VITE_AIUI_URL=http://localhost:5173 vite\" \"cd ../../AIUI && pnpm dev 2>/dev/null || echo '[AIUI] Not found at ../../AIUI — chat will show placeholder'\"",
"dev:real": "echo 'Start backend: cd ../core && cargo run --release' && vite",
"backend:mock": "node mock-backend.js",
"backend:real": "cd ../core && cargo run --release",
"build": "vue-tsc -b && vite build",
"build:docker": "vite build",
"build:production": "NODE_ENV=production vue-tsc -b && vite build --mode production",
"preview": "vite preview",
"type-check": "vue-tsc --noEmit",
"prebuild": "cp ../../loop-start.mp3 public/assets/audio/ 2>/dev/null || true",
"generate-pwa-icons": "pwa-assets-generator --preset minimal-2023 public/assets/icon/favico-black.svg && cp public/assets/icon/favicon.ico public/favicon.ico",
"generate-welcome-speech": "node scripts/generate-welcome-speech.js"
},
"dependencies": {
"d3": "^7.9.0",
"fast-json-patch": "^3.1.1",
"fuse.js": "^7.1.0",
"pinia": "^3.0.4",
"qrcode": "^1.5.4",
"vue": "^3.5.24",
"vue-i18n": "^11.3.0",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@playwright/test": "^1.58.2",
"@types/d3": "^7.4.3",
"@types/node": "^24.10.0",
"@types/qrcode": "^1.5.6",
"@vite-pwa/assets-generator": "^1.0.2",
"@vitejs/plugin-vue": "^6.0.1",
"@vitest/coverage-v8": "^3.2.4",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.22",
"concurrently": "^9.1.2",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dockerode": "^4.0.9",
"express": "^4.21.2",
"jsdom": "^25.0.1",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.18",
"typescript": "~5.9.3",
"vite": "^7.2.2",
"vite-plugin-pwa": "^1.2.0",
"vitest": "^3.1.1",
"vue-tsc": "^3.1.3",
"ws": "^8.18.0"
}
}

View File

@@ -0,0 +1,23 @@
import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './e2e',
outputDir: './e2e/test-results',
timeout: 60_000,
expect: {
timeout: 10_000,
},
use: {
baseURL: 'http://192.168.1.228',
viewport: { width: 1440, height: 900 },
screenshot: 'only-on-failure',
trace: 'off',
ignoreHTTPSErrors: true,
},
projects: [
{
name: 'chromium',
use: { browserName: 'chromium' },
},
],
})

View File

@@ -0,0 +1,7 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -0,0 +1,84 @@
# Replace Intro & Dashboard Backgrounds
To change the intro splash and dashboard tab backgrounds **without touching any code**, overwrite these files with your own assets. Use the exact names and locations below.
**Location:** All images go in `neode-ui/public/assets/img/`
**Format:** JPG recommended. Portrait or landscape; they use `background-size: cover` and `center center`.
---
## Intro Background
| Filename | Used for |
|----------|----------|
| **`bg-intro.jpg`** | Intro splash (alien typing + video poster + fallback), Dashboard default |
---
## Intro Video
| Filename | Where | Used for |
|----------|-------|----------|
| **`video-intro.mp4`** | `neode-ui/public/assets/video/` | Welcome Noderunner + logo, onboarding, login |
**Format:** MP4 (H.264). Keep under ~5MB for web. See `VIDEO_COMPRESSION_GUIDE.md` for optimization.
---
---
## Dashboard Tab Backgrounds
| Filename | Tab |
|----------|-----|
| **`bg-home.jpg`** | Home |
| **`bg-web5.jpg`** | Web5 |
| **`bg-network.jpg`** | Server / Network |
| **`bg-settings.jpg`** | Settings |
| **`bg-myapps.jpg`** | My Apps |
| **`bg-appstore.jpg`** | App Store / Marketplace |
| **`bg-cloud.jpg`** | Cloud |
| **`bg-intro.jpg`** | Default (also intro) |
| **`bg-intro-3.jpg`** | Alternate layer during transitions |
---
## Intro Flow Backgrounds (onboarding)
| Filename | Used for |
|----------|----------|
| **`bg-intro-1.jpg`** | Onboarding done, login |
| **`bg-intro-2.jpg`** | Onboarding verify |
| **`bg-intro-3.jpg`** | Onboarding path, dashboard transition layer |
| **`bg-intro-4.jpg`** | Onboarding options |
| **`bg-intro-5.jpg`** | Onboarding did |
| **`bg-intro-6.jpg`** | Onboarding backup |
---
## Quick Reference
| Asset | Full path |
|-------|-----------|
| Intro image | `neode-ui/public/assets/img/bg-intro.jpg` |
| Intro video | `neode-ui/public/assets/video/video-intro.mp4` |
| Home | `neode-ui/public/assets/img/bg-home.jpg` |
| Web5 | `neode-ui/public/assets/img/bg-web5.jpg` |
| Network | `neode-ui/public/assets/img/bg-network.jpg` |
| Settings | `neode-ui/public/assets/img/bg-settings.jpg` |
| My Apps | `neode-ui/public/assets/img/bg-myapps.jpg` |
| App Store | `neode-ui/public/assets/img/bg-appstore.jpg` |
| Cloud | `neode-ui/public/assets/img/bg-cloud.jpg` |
| Default | `neode-ui/public/assets/img/bg-intro.jpg` |
| Transition | `neode-ui/public/assets/img/bg-intro-3.jpg` |
| Intro 16 | `neode-ui/public/assets/img/bg-intro-1.jpg``bg-intro-6.jpg` |
---
## Steps to Replace
1. Put your images in `neode-ui/public/assets/img/` with the exact filenames above.
2. Put your video in `neode-ui/public/assets/video/video-intro.mp4`.
3. Run `npm run build` (or deploy) so the new assets are included.
No code changes required.

View File

@@ -0,0 +1,23 @@
# Welcome Noderunner Speech
The intro plays a sci-fi female voice saying "Welcome Noderunner" as the text types in.
## Generate the audio (ElevenLabs)
1. Get a free API key at [elevenlabs.io](https://elevenlabs.io) (free tier: 10k chars/month)
2. Run:
```bash
cd neode-ui
ELEVENLABS_API_KEY=your_key npm run generate-welcome-speech
```
3. Commit `welcome-noderunner.mp3` to the repo
## Custom sci-fi voice
Browse [ElevenLabs Voice Library](https://elevenlabs.io/voice-library) and search for "sci-fi", "AI", "robot", or "character". Copy the voice ID from the URL or voice settings, then:
```bash
ELEVENLABS_API_KEY=your_key ELEVENLABS_VOICE_ID=voice_id npm run generate-welcome-speech
```
Recommended: "The Digital Oracle", "The Friendly AI Assistant", or similar character voices from the Synthetic/Character categories.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,93 @@
Copyright 2011 The Montserrat Project Authors (https://github.com/JulietaUla/Montserrat)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Some files were not shown because too many files have changed in this diff Show More