chore: remove duplicated archy skills and redundant hooks

Wrong archy-specific skills (harden, refactor, test, ux-review, lint,
add-app, pwa-icon-cache-fix) removed — these referenced Archipelago
infrastructure irrelevant to botfights. Redundant hooks (block-risky-bash,
protect-files, post-deploy-check, post-push-progress) removed since global
hooks provide superset protection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-15 12:34:16 +00:00
co-authored by Claude Opus 4.6
parent ebf6667f8f
commit 52752a92bf
12 changed files with 1 additions and 738 deletions
-76
View File
@@ -1,76 +0,0 @@
#!/usr/bin/env bash
# PreToolUse Bash guard: block dangerous shell commands.
# Denies: rm -rf, git reset --hard, git push -f, git clean -fd, chmod -R 777,
# fork bombs, block device overwrites, mkfs, building Rust on macOS for Linux.
set -euo pipefail
INPUT=$(cat)
CMD=$(python3 -c "
import json, sys
try:
data = json.loads(sys.stdin.read())
print(data.get('tool_input', {}).get('command', ''))
except: pass
" <<< "$INPUT")
BASE="${CLAUDE_PROJECT_DIR:-}"
[[ -z "$BASE" ]] && BASE=$(python3 -c "
import json, sys
try:
data = json.loads(sys.stdin.read())
print(data.get('cwd', ''))
except: pass
" <<< "$INPUT")
[[ -z "$BASE" ]] && BASE="$(pwd)"
# Normalize: collapse whitespace, strip leading/trailing
CMD_NORM=$(echo "$CMD" | tr -s '[:space:]' ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
deny() {
local reason="$1"
python3 -c "
import json
print(json.dumps({
'hookSpecificOutput': {
'hookEventName': 'PreToolUse',
'permissionDecision': 'deny',
'permissionDecisionReason': '$reason'
}
}))
"
exit 0
}
# Dangerous patterns
case "$CMD_NORM" in
*"rm -rf"*|*"rm -fr"*|*"rm -f -r"*|*"rm -r -f"*) deny "Destructive rm -rf blocked by security hook" ;;
*"git reset --hard"*) deny "git reset --hard would lose uncommitted work" ;;
*"git push --force"*|*"git push -f"*|*"git push -f "*) deny "git push --force would rewrite history" ;;
*"git clean -fd"*|*"git clean -f -d"*) deny "git clean -fd deletes untracked files" ;;
*"chmod -R 777"*|*"chmod -R 0777"*) deny "chmod -R 777 is a security risk" ;;
*":(){ :"*"};:"*) deny "Fork bomb pattern blocked" ;;
*"> /dev/sd"*|*">/dev/sd"*) deny "Block device overwrite blocked" ;;
*"mkfs "*|*"mkfs."*) deny "Disk format command blocked" ;;
esac
# Block building Rust locally on macOS (should always build on dev server)
if [[ "$(uname)" == "Darwin" ]]; then
if echo "$CMD_NORM" | grep -qE '^\s*cargo\s+build'; then
# Allow if it's clearly an SSH command (building on remote)
if ! echo "$CMD_NORM" | grep -qE 'ssh|sshpass'; then
deny "NEVER build Rust on macOS — use ./scripts/deploy-to-target.sh --live or build on dev server via SSH"
fi
fi
fi
# Check for path traversal escaping project root
if [[ -n "$BASE" ]] && [[ -d "$BASE" ]]; then
if echo "$CMD_NORM" | grep -qE '\.\./|/\.\.'; then
if echo "$CMD_NORM" | grep -qE '(rm|mv|cp|cat|chmod|chown)\s+.*\.\.'; then
if echo "$CMD_NORM" | grep -qE '\brm\b.*\.\.'; then
deny "Path traversal with rm blocked"
fi
fi
fi
fi
exit 0
-43
View File
@@ -1,43 +0,0 @@
#!/usr/bin/env bash
# PostToolUse Bash hook: detect deploy commands and remind to test.
# Triggers after deploy-to-target.sh runs.
set -euo pipefail
INPUT=$(cat)
CMD=$(python3 -c "
import json, sys
try:
data = json.loads(sys.stdin.read())
print(data.get('tool_input', {}).get('command', ''))
except: pass
" <<< "$INPUT")
# Only trigger on deploy commands or git push
if ! echo "$CMD" | grep -qE 'deploy-to-target|git\s+push'; then
exit 0
fi
TIMESTAMP=$(date '+%Y-%m-%d %H:%M')
python3 -c "
import json
message = '''Deploy detected at $TIMESTAMP.
Post-deploy checklist:
1. Test the web UI at http://192.168.1.228
2. Verify modified apps load correctly
3. Check backend logs: sudo journalctl -u archipelago -n 20
4. Check nginx: sudo tail -f /var/log/nginx/error.log
5. If building ISO, sync system configs to image-recipe/configs/
6. Update CHANGELOG.md if this is a notable change'''
output = {
'hookSpecificOutput': {
'hookEventName': 'PostToolUse',
'deployReminder': message
}
}
print(json.dumps(output))
"
-75
View File
@@ -1,75 +0,0 @@
#!/usr/bin/env bash
# PostToolUse Bash hook: detect git push/commit and prompt Claude to update PROGRESS.md.
# Returns structured feedback with recent commits so Claude can write a session log entry.
# Uses python3 instead of jq for JSON (guaranteed on macOS).
set -euo pipefail
INPUT=$(cat)
# Extract command from JSON using python3
CMD=$(python3 -c "
import json, sys
try:
data = json.loads(sys.stdin.read())
print(data.get('tool_input', {}).get('command', ''))
except: pass
" <<< "$INPUT")
# Only trigger on git push or git commit commands
if ! echo "$CMD" | grep -qE '\bgit\s+(push|commit)\b'; then
exit 0
fi
# Gather context for the progress update
BASE="${CLAUDE_PROJECT_DIR:-$(pwd)}"
BRANCH=$(git -C "$BASE" branch --show-current 2>/dev/null || echo "unknown")
PROGRESS_FILE="$BASE/PROGRESS.md"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M')
# Get recent commits (branch vs main, or last 10)
if git -C "$BASE" rev-parse --verify main &>/dev/null; then
COMMITS=$(git -C "$BASE" log --oneline main..HEAD 2>/dev/null | head -15)
if [ -z "$COMMITS" ]; then
COMMITS=$(git -C "$BASE" log --oneline -10 2>/dev/null)
fi
else
COMMITS=$(git -C "$BASE" log --oneline -10 2>/dev/null)
fi
# Get changed files in recent commits
CHANGED_FILES=$(git -C "$BASE" diff --name-only main..HEAD 2>/dev/null | head -20 || \
git -C "$BASE" diff --name-only HEAD~5..HEAD 2>/dev/null | head -20 || \
echo "unknown")
# Build the feedback message and output as JSON using python3
python3 -c "
import json, sys
message = '''Progress Update Needed
A git push/commit was detected on branch \`$BRANCH\` at $TIMESTAMP.
Recent commits:
\`\`\`
$COMMITS
\`\`\`
Changed files:
\`\`\`
$CHANGED_FILES
\`\`\`
Please update PROGRESS.md:
1. Add a session log entry under '## Session Log' with format: ### $TIMESTAMP$BRANCH
2. Summarize what was accomplished (2-4 bullet points based on the commits above)
3. Update any roadmap checkboxes if tasks were completed
4. Commit the PROGRESS.md update'''
output = {
'hookSpecificOutput': {
'hookEventName': 'PostToolUse',
'progressUpdate': message
}
}
print(json.dumps(output))
"
-82
View File
@@ -1,82 +0,0 @@
#!/usr/bin/env bash
# PreToolUse Edit|Write guard: block edits outside project and to protected paths.
# Denies: paths outside project, .git/, .env*, lockfiles, node_modules/, deploy-config.sh
set -euo pipefail
INPUT=$(cat)
FILE_PATH=$(python3 -c "
import json, sys
try:
data = json.loads(sys.stdin.read())
print(data.get('tool_input', {}).get('file_path', ''))
except: pass
" <<< "$INPUT")
BASE="${CLAUDE_PROJECT_DIR:-}"
[[ -z "$BASE" ]] && BASE=$(python3 -c "
import json, sys
try:
data = json.loads(sys.stdin.read())
print(data.get('cwd', ''))
except: pass
" <<< "$INPUT")
[[ -z "$BASE" ]] && BASE="$(pwd)"
# Resolve to absolute path
if [[ -z "$FILE_PATH" ]]; then
exit 0
fi
ABS_BASE=$(cd "$BASE" 2>/dev/null && pwd) || true
[[ -z "$ABS_BASE" ]] && ABS_BASE=$(python3 -c "import os,sys; print(os.path.abspath(os.path.normpath(sys.argv[1])))" "$BASE" 2>/dev/null) || true
[[ -z "$ABS_BASE" ]] && ABS_BASE="$BASE"
[[ "$ABS_BASE" != */ ]] && ABS_BASE="${ABS_BASE}/"
if [[ "$FILE_PATH" != /* ]]; then
ABS_PATH="$ABS_BASE${FILE_PATH#./}"
else
ABS_PATH="$FILE_PATH"
fi
ABS_PATH=$(python3 -c "import os,sys; print(os.path.abspath(os.path.normpath(sys.argv[1])))" "$ABS_PATH" 2>/dev/null) || true
[[ -z "$ABS_PATH" ]] && ABS_PATH="$ABS_BASE${FILE_PATH#./}"
deny() {
local reason="$1"
echo "Blocked: $ABS_PATH$reason" >&2
python3 -c "
import json
print(json.dumps({
'hookSpecificOutput': {
'hookEventName': 'PreToolUse',
'permissionDecision': 'deny',
'permissionDecisionReason': '$reason'
}
}))
"
exit 0
}
# Protected patterns
PROTECTED_PATTERNS=(
".git/"
".env"
".env.local"
"node_modules/"
"package-lock.json"
"scripts/deploy-config.sh"
)
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$ABS_PATH" == *"$pattern"* ]] || [[ "$ABS_PATH" == *"/$pattern" ]]; then
deny "Edit blocked: path matches protected pattern ($pattern)"
fi
done
# .env.*.local
if [[ "$ABS_PATH" =~ \.env\..*\.local$ ]]; then
deny "Edit blocked: .env.*.local files contain secrets"
fi
# Ensure path is under project root
if [[ "$ABS_PATH" != "$ABS_BASE"* ]] && [[ "$ABS_PATH" != "$BASE"* ]]; then
deny "Edit blocked: path is outside project directory"
fi
exit 0
+1 -20
View File
@@ -1,25 +1,6 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-risky-bash.sh"
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
],
"PreToolUse": [],
"PostToolUse": []
}
}
-49
View File
@@ -1,49 +0,0 @@
---
name: add-app
description: Step-by-step guide for adding a new containerized app to Archipelago
disable-model-invocation: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
argument-hint: "[app-name]"
---
Add a new containerized app ($ARGUMENTS) to Archipelago.
## Steps
### 1. Create the manifest
Create `apps/{app-id}/manifest.yml` following the spec in `docs/app-manifest-spec.md`:
- `app.id` (kebab-case), `app.name`, `app.version` (SemVer)
- `container.image` (pinned version, **NEVER** `latest`)
- `security`: readonly_root, dropped capabilities, non-root UID > 1000
- `health_check`, `dependencies`
### 2. Add app icon
Place icon at `neode-ui/public/assets/img/app-icons/{app-id}.{png|webp|svg}`
### 3. Create status UI (if no native web UI)
For apps without their own web interface, create a UI container in `docker/{app-id}-ui/` following the patterns in `.cursor/rules/APP-UI-STANDARDS.md`.
Reference implementations:
- Bitcoin UI: `docker/bitcoin-ui/`
- LND UI: `docker/lnd-ui/`
### 4. Update backend
- Add port mapping in `core/archipelago/src/container/docker_packages.rs`
- Add env vars in `get_app_config()` in `core/archipelago/src/api/rpc.rs`
### 5. Deploy and test
- Deploy: `./scripts/deploy-to-target.sh --live`
- Install from marketplace UI at http://192.168.1.228
- Verify it launches and auto-connects to dependencies
- Check logs: `sudo podman logs {container-name}`
### 6. Security review
- Verify readonly root, dropped caps, non-root user
- Check network isolation
- No hardcoded secrets
-49
View File
@@ -1,49 +0,0 @@
---
name: harden
description: Security hardening review and fixes for Archipelago code and infrastructure
disable-model-invocation: true
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
argument-hint: "[area: backend|frontend|containers|scripts|all]"
---
Perform a security hardening pass on $ARGUMENTS (default: all).
## Backend Hardening (Rust)
- [ ] No hardcoded credentials — check for Base64-encoded auth strings, passwords in source
- [ ] Secrets use `core/security/secrets_manager.rs` — verify encryption is implemented (not plaintext)
- [ ] All RPC endpoints validate inputs before processing
- [ ] No `unwrap()` on user-supplied data — handle errors gracefully
- [ ] Rate limiting on auth endpoints (login, password change)
- [ ] Session tokens have proper expiry and rotation
- [ ] File permissions: keys at 0o600, dirs at 0o700
- [ ] Tracing never logs secrets, passwords, keys, or tokens
## Frontend Hardening (Vue/TypeScript)
- [ ] No secrets in source (API keys, passwords, tokens)
- [ ] No `eval()` or `innerHTML` with untrusted content
- [ ] XSS prevention — sanitize all user inputs
- [ ] CSRF protection on state-changing requests
- [ ] Credentials use `credentials: 'include'` not localStorage tokens
- [ ] No sensitive data in console.log statements
## Container Hardening
- [ ] All manifests: `readonly_root: true` (unless documented exception)
- [ ] All manifests: capabilities dropped, only required ones added
- [ ] All manifests: non-root user (UID > 1000)
- [ ] All manifests: `no-new-privileges: true`
- [ ] All images pinned to specific versions (no `:latest`)
- [ ] Network isolation — no `host` network unless required and documented
- [ ] AppArmor profiles defined and enforced
## Script Hardening
- [ ] All scripts use `set -euo pipefail`
- [ ] No hardcoded passwords (use deploy-config.sh or env vars)
- [ ] SSH uses proper key-based auth where possible
- [ ] No `chmod 777` or overly permissive permissions
- [ ] Temp files use `mktemp` not predictable paths
Report all findings with file paths and line numbers. Fix issues directly where safe to do so. Flag anything that needs discussion.
-52
View File
@@ -1,52 +0,0 @@
---
name: lint
description: Run all linters and type checks for the Archipelago project
allowed-tools: Bash, Read, Grep
argument-hint: "[backend|frontend|all]"
---
Run linters and type-checks for $ARGUMENTS (default: all).
## Frontend Linting
```bash
cd neode-ui
# Type check
npm run type-check 2>&1
# Check for any `any` types (should be zero)
grep -rn ': any' src/ --include='*.ts' --include='*.vue' | grep -v node_modules | grep -v '.d.ts'
# Check for inline Tailwind violations (long class strings)
grep -rn 'class="[^"]\{100,\}"' src/ --include='*.vue'
# Check for TODO/FIXME
grep -rn 'TODO\|FIXME' src/ --include='*.ts' --include='*.vue'
# Check for console.log (should be cleaned before production)
grep -rn 'console\.\(log\|warn\|error\)' src/ --include='*.ts' --include='*.vue' | wc -l
```
## Backend Linting (on dev server)
```bash
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 \
'source ~/.cargo/env && cd ~/archy/core && cargo clippy --all-targets --all-features 2>&1 && cargo fmt --all -- --check 2>&1'
```
## Script Linting
```bash
# Check for scripts missing set -e
for f in scripts/*.sh; do
if ! head -5 "$f" | grep -q 'set -e'; then
echo "MISSING set -e: $f"
fi
done
# Check for hardcoded IPs (should use variables)
grep -rn '192\.168\.1\.' scripts/ --include='*.sh' | grep -v deploy-config
```
Report all issues found with severity (critical/warning/info).
-102
View File
@@ -1,102 +0,0 @@
---
name: pwa-icon-cache-fix
description: Use when the user reports a PWA icon not updating, stale PWA icon, wrong icon after install, or any PWA caching issue. Also applies when changing PWA icons in a Vite + vite-plugin-pwa project.
version: 2.0.0
---
# PWA Icon Cache Fix
## Problem
PWA icons are cached at FOUR independent layers:
1. **Service worker cache** (Workbox precache)
2. **Browser HTTP cache**
3. **Browser manifest resources** (Chromium stores resized icons in its profile data, keyed by a permanent extension ID tied to the origin — NEVER re-fetched even after uninstall/reinstall)
4. **macOS .app bundle** (`.icns` file baked into the `.app` in `~/Applications/`)
Query string cache busting (`?v=2`) and uninstall/reinstall do NOT fix this. Chromium reuses the same extension ID for the same origin, so it keeps the old cached icons.
## Fix Steps
### 1. Verify icon files on disk and server are correct
```bash
# Visual check
Read packages/app/public/pwa-192x192.png
Read packages/app/public/pwa-512x512.png
# Hash match check
curl -s http://localhost:5173/pwa-192x192.png | md5
md5 -q packages/app/public/pwa-192x192.png
```
### 2. Find the PWA's Chromium extension ID
Read the installed `.app` bundle's `Info.plist` to get the `CrAppModeShortcutID`:
```bash
plutil -p "~/Applications/Brave Browser Apps.localized/AIUI.app/Contents/Info.plist" | grep CrAppModeShortcutID
```
This returns an ID like `idemibpphagihbobmgmaojhjfidlfpdl`.
### 3. Overwrite the cached icons in browser profile
Chromium stores resized icons at:
`~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Web Applications/Manifest Resources/{ID}/Icons/`
Overwrite every size using `sips`:
```bash
ICON_DIR="~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Web Applications/Manifest Resources/{ID}/Icons"
SRC="packages/app/public/pwa-512x512.png"
for size in 32 48 64 96 128 192 256 512; do
sips -z $size $size "$SRC" --out "${ICON_DIR}/${size}.png"
done
```
### 4. Rebuild the macOS .icns in the .app bundle
```bash
ICONSET="/tmp/aiui.iconset"
mkdir -p "$ICONSET"
SRC="packages/app/public/pwa-512x512.png"
sips -z 16 16 "$SRC" --out "$ICONSET/icon_16x16.png"
sips -z 32 32 "$SRC" --out "$ICONSET/icon_16x16@2x.png"
sips -z 32 32 "$SRC" --out "$ICONSET/icon_32x32.png"
sips -z 64 64 "$SRC" --out "$ICONSET/icon_32x32@2x.png"
sips -z 128 128 "$SRC" --out "$ICONSET/icon_128x128.png"
sips -z 256 256 "$SRC" --out "$ICONSET/icon_128x128@2x.png"
sips -z 256 256 "$SRC" --out "$ICONSET/icon_256x256.png"
sips -z 512 512 "$SRC" --out "$ICONSET/icon_256x256@2x.png"
sips -z 512 512 "$SRC" --out "$ICONSET/icon_512x512.png"
cp "$SRC" "$ICONSET/icon_512x512@2x.png"
iconutil -c icns "$ICONSET" -o "~/Applications/Brave Browser Apps.localized/AIUI.app/Contents/Resources/app.icns"
```
### 5. Flush macOS icon cache
```bash
touch "~/Applications/Brave Browser Apps.localized/AIUI.app"
killall Finder
killall Dock
```
### 6. Bump PWA_CACHE_VERSION in main.ts
Increment the `PWA_CACHE_VERSION` constant — this nukes all SW caches on next page load for web-layer caching.
### 7. Delete stale build artifacts
Remove old `dist/` and `dev-dist/` SW/manifest files.
## Browser-Specific Paths
- **Brave**: `~/Library/Application Support/BraveSoftware/Brave-Browser/Default/Web Applications/`
- **Chrome**: `~/Library/Application Support/Google/Chrome/Default/Web Applications/`
- **PWA apps (Brave)**: `~/Applications/Brave Browser Apps.localized/`
- **PWA apps (Chrome)**: `~/Applications/Chrome Apps.localized/`
## Key Insight
Chromium assigns a permanent extension ID per origin (e.g., `localhost:5173`). This ID persists across uninstall/reinstall. The icon cache in `Manifest Resources/{ID}/Icons/` is populated ONCE and never refreshed from the manifest. The only fix is to overwrite the files directly on disk.
-41
View File
@@ -1,41 +0,0 @@
---
name: refactor
description: Refactor code for quality, maintainability, and adherence to project standards
disable-model-invocation: true
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
argument-hint: "[file-or-area]"
---
Refactor the specified code ($ARGUMENTS) following Archipelago coding standards.
## Checklist
### Rust Backend
- [ ] No `unwrap()` or `expect()` — use `?` operator with context
- [ ] Replace `#[allow(dead_code)]` — either use it or remove it
- [ ] Functions under 50 lines, single responsibility
- [ ] Custom error types per module with `thiserror`
- [ ] `tracing` for logging — no `println!` or secrets in logs
- [ ] Split files over 500 lines into focused modules
- [ ] Run `cargo clippy --all-targets --all-features` mentally and fix issues
### Vue Frontend
- [ ] Extract ALL inline Tailwind to global classes in `neode-ui/src/style.css`
- [ ] Use semantic class names: `.glass-card`, `.info-card`, `.glass-button`, `.path-option-card`
- [ ] Replace ALL `.gradient-button` with `.glass-button` (gradient buttons are BANNED)
- [ ] Replace ALL `.gradient-card` / `.gradient-card-dark` with `.glass-card` or `.path-option-card`
- [ ] Settings.vue is the gold standard — all screens should match its patterns
- [ ] Replace `any` types with proper interfaces or `unknown`
- [ ] Ensure `<script setup lang="ts">` on all components
- [ ] Remove dead code (unused imports, components like HelloWorld.vue)
- [ ] Remove all `TODO`/`FIXME` — fix now or create GitHub issues
- [ ] Consolidate `console.log` calls to use a logging utility
- [ ] Split views over 800 LOC into sub-components
### General
- [ ] No hardcoded paths (`/Users/dorian/...`)
- [ ] No hardcoded credentials — use env vars or secrets manager
- [ ] Comment WHY not WHAT
- [ ] Remove commented-out code entirely
After refactoring, verify the code still compiles/type-checks. For frontend: `cd neode-ui && npm run type-check`. Do NOT deploy — leave that to `/deploy`.
-59
View File
@@ -1,59 +0,0 @@
---
name: test
description: Run tests or create test coverage for Archipelago
disable-model-invocation: true
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
argument-hint: "[area: backend|frontend|all] or [specific-file]"
---
Run or create tests for $ARGUMENTS.
## Backend Testing (Rust)
### Run existing tests
```bash
# On dev server (never build Rust on macOS)
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228 \
'source ~/.cargo/env && cd ~/archy/core && cargo test --all-features 2>&1'
```
### Creating new tests
- Place unit tests in the same file with `#[cfg(test)]` module
- Place integration tests in `core/{crate}/tests/`
- Use `#[tokio::test]` for async tests
- Mock external dependencies (filesystem, network, Podman)
- Test error cases, not just happy paths
- Aim for >80% coverage on core logic
### Priority areas needing tests
1. RPC endpoint handlers (core/archipelago/src/api/)
2. Manifest parsing (core/container/src/manifest.rs)
3. Dependency resolver (core/container/src/dependency_resolver.rs)
4. Auth flows (core/archipelago/src/auth.rs)
5. Secrets manager (core/security/src/secrets_manager.rs)
6. Port allocation (core/container/src/port_manager.rs)
## Frontend Testing (Vue/TypeScript)
### Setup (if not already configured)
Ensure vitest is configured in `neode-ui/`:
```bash
cd neode-ui && npm run test 2>&1 || echo "No test script configured"
```
### Creating new tests
- Use Vitest + @vue/test-utils
- Place tests in `neode-ui/src/__tests__/` or co-located `*.test.ts`
- Test stores (Pinia) with `createTestingPinia()`
- Test API clients with mocked fetch
- Test component rendering and interactions
- Test routing guards
### Priority areas needing tests
1. Pinia stores (app.ts, container.ts, appLauncher.ts)
2. RPC client (api/rpc-client.ts) — error handling, retry logic
3. WebSocket client (api/websocket.ts) — reconnection
4. Router guards — auth flow, session timeout
5. Key components — ContainerStatus, SpotlightSearch
Report test results and any new tests created.
-90
View File
@@ -1,90 +0,0 @@
---
name: ux-review
description: Review UI components against Archipelago glassmorphism design standards and UX conventions
disable-model-invocation: true
allowed-tools: Read, Glob, Grep, Edit, Write
argument-hint: "[component-or-view-name]"
---
Review the UI of $ARGUMENTS against Archipelago's glassmorphism design system and UX standards.
## Design System Compliance
### Glass Classes (must use global classes from style.css)
- [ ] Section containers use `.path-option-card cursor-default px-6 py-6` (Settings-style sections)
- [ ] Content containers/modals use `.glass-card`
- [ ] Interactive selectable cards use `.path-option-card` (with hover)
- [ ] Status displays use `.info-card` (no hover effects)
- [ ] ALL buttons use `.glass-button` — NEVER `.gradient-button` (BANNED)
- [ ] Large primary actions use `.path-action-button`
- [ ] Info sub-cards use `bg-black/20 rounded-xl border border-white/10`
- [ ] Info rows use `bg-white/5 rounded-lg` pattern
- [ ] Action buttons in info sections use `.info-card-button`
### BANNED — Flag These as Violations
- [ ] No `.gradient-button` anywhere (replace with `.glass-button`)
- [ ] No `.gradient-card` / `.gradient-card-dark` (replace with `.glass-card` or `.path-option-card`)
### NO Inline Tailwind
- [ ] Check for long `class="..."` strings with layout/color utilities
- [ ] Extract to semantic classes in `neode-ui/src/style.css`
- [ ] Name classes semantically: `.app-card`, `.status-badge`, `.nav-item`
### Color Compliance
- [ ] Primary text: `text-white/90` (not `text-white` or arbitrary opacity)
- [ ] Muted text: `text-white/60` to `text-white/70`
- [ ] Backgrounds: `rgba(0,0,0,0.60)` with `backdrop-filter: blur(24px)`
- [ ] Borders: `rgba(255,255,255,0.18)` standard
- [ ] Status colors: green=#4ade80, red=#ef4444, yellow=#facc15, blue=#3b82f6, orange=#fb923c
### Typography
- [ ] Font: Avenir Next (body), Montserrat (headings via `font-archipelago`)
- [ ] H1: text-3xl font-bold, H2: text-2xl font-semibold, H3: text-xl font-semibold
- [ ] Body: text-base, Small: text-sm, Labels: text-xs
### Interaction States
- [ ] Hover: `translateY(-2px)` lift + background brighten + enhanced shadow
- [ ] Active: `translateY(1px)` press
- [ ] Selected: brighter background + glow shadow + enhanced gradient border
- [ ] Disabled: reduced opacity (~50%), no pointer events
- [ ] Loading: spinner SVG + descriptive text, button disabled
- [ ] Focus-visible: soft blue glow `rgba(120, 180, 255, 0.2)`
### Transitions
- [ ] Standard: `all 0.3s ease`
- [ ] All interactive elements have transitions (no jarring state changes)
- [ ] Respect `prefers-reduced-motion`
### Spacing
- [ ] 4px grid system (p-1=4px, p-2=8px, p-3=12px, p-4=16px)
- [ ] 16px default padding on cards
- [ ] Consistent gap values between grid items
### Responsive
- [ ] Mobile: single column, reduced padding, touch targets >= 44x44px
- [ ] Tablet (md:): two columns
- [ ] Desktop (lg:): three columns, full effects
### Accessibility
- [ ] Semantic HTML (`<button>`, `<nav>`, `<main>`, not div soup)
- [ ] ARIA labels on icon-only buttons
- [ ] Keyboard navigable (Tab order, Enter to activate, Esc to close)
- [ ] Color contrast WCAG AA (4.5:1 normal text, 3:1 large)
- [ ] Images have alt text (decorative: `alt=""`)
### Icons
- [ ] Stroke-based SVGs, stroke-width 2.5 default
- [ ] Color: `text-white/85` default, `text-white` on hover
- [ ] Drop-shadow filter applied on interactive icons
- [ ] Size: w-5 h-5 standard, w-4 h-4 small
## Service UI Review (if reviewing docker/*-ui/)
- [ ] Uses `.glass-card` for main sections
- [ ] Uses `.info-card` for status (no hover)
- [ ] Uses `.info-card-button` for actions (with hover)
- [ ] Uses `bg-white/5` for info rows
- [ ] Header: logo + title + description + status
- [ ] Background image loads correctly
- [ ] Mobile responsive
Report violations with file paths and specific fixes.