feat: complete Phase 1 foundation hardening + three-mode UI design doc

Phase 1a — Gradient Removal:
- Replaced all gradient-button/gradient-card with glass-button/path-option-card
- Removed banned gradient CSS classes

Phase 1b — Security Hardening:
- SecretsManager: AES-256-GCM encryption (core/security)
- electrs_status: credentials from env vars instead of hardcoded
- port_manager: RwLock proper error handling (no unwrap)
- Pinned all 11 :latest manifest images to specific versions
- parmanode converter: pinned inferred image versions

Phase 1c — Code Quality:
- Split rpc.rs (1795 lines) into 6 handler modules (auth, node, container, package, peers)
- Removed sideload code (UI, store, RPC client, 3 doc files)
- Fixed body background flash on logout/refresh
- Replaced 30 TypeScript `any` types with proper types
- Deleted HelloWorld.vue, removed TODO comments
- Added set -euo pipefail to all shell scripts
- Made deploy script verbose with timestamps and elapsed time

Also adds:
- CLAUDE.md project guide
- docs/three-mode-ui-design.md — design spec for Easy/Pro/Chat UI modes
- OnlineStatusPill component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 05:23:42 +00:00
co-authored by Claude Opus 4.6
parent 62d6c13764
commit 486fc39249
58 changed files with 1902 additions and 2286 deletions
+5 -1
View File
@@ -27,7 +27,11 @@ const FOCUSABLE_SELECTOR = [
function getFocusableElements(container: Document | HTMLElement = document): HTMLElement[] {
return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(
(el) => !el.hasAttribute('disabled') && el.offsetParent !== null
(el) =>
!el.hasAttribute('disabled') &&
el.offsetParent !== null &&
!el.hasAttribute('data-controller-ignore') &&
!el.closest('[data-controller-ignore]')
)
}
+1 -1
View File
@@ -15,7 +15,7 @@ function getContext(): AudioContext | null {
function ensureContext(): AudioContext | null {
if (audioContext) return audioContext
try {
const Ctx = window.AudioContext || (window as any).webkitAudioContext
const Ctx = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext
if (!Ctx) return null
audioContext = new Ctx()
return audioContext
+28 -14
View File
@@ -1,24 +1,39 @@
import { ref } from 'vue'
export interface MarketplaceAppInfo {
id: string
title: string
version: string
icon: string
category: string
description: string | { short: string; long: string }
author: string
source: string
manifestUrl: string
url: string
repoUrl: string
s9pkUrl: string
}
// Simple in-memory store for the current marketplace app
const currentMarketplaceApp = ref<any>(null)
const currentMarketplaceApp = ref<MarketplaceAppInfo | null>(null)
export function useMarketplaceApp() {
function setCurrentApp(app: any) {
function setCurrentApp(app: Partial<MarketplaceAppInfo> & { id: string }) {
// Create a clean, serializable copy
currentMarketplaceApp.value = {
id: app.id,
title: app.title,
version: app.version,
icon: app.icon,
category: app.category,
description: app.description,
author: app.author,
source: app.source,
manifestUrl: app.manifestUrl || app.s9pkUrl || app.url,
url: app.url || app.s9pkUrl || app.manifestUrl,
repoUrl: app.repoUrl,
s9pkUrl: app.s9pkUrl
title: app.title ?? '',
version: app.version ?? '',
icon: app.icon ?? '',
category: app.category ?? '',
description: app.description ?? '',
author: app.author ?? '',
source: app.source ?? '',
manifestUrl: app.manifestUrl || app.s9pkUrl || app.url || '',
url: app.url || app.s9pkUrl || app.manifestUrl || '',
repoUrl: app.repoUrl ?? '',
s9pkUrl: app.s9pkUrl ?? '',
}
}
@@ -36,4 +51,3 @@ export function useMarketplaceApp() {
clearCurrentApp
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ let audioContext: AudioContext | null = null
function getContext(): AudioContext | null {
if (audioContext) return audioContext
try {
audioContext = new (window.AudioContext || (window as any).webkitAudioContext)()
audioContext = new (window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext)()
return audioContext
} catch {
return null