Files
archy/CLAUDE.md
T
DorianandClaude Opus 4.6 bda670d8d9 feat(app): base-aware routing, embedded flag, panel slide animation
- Use import.meta.env.BASE_URL for router history (Archy /aiui/ deployments)
- Capture embedded flag from URL params before router init
- Add panelSlideIn animation with prefers-reduced-motion support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:41:26 +00:00

12 KiB
Raw Blame History

CLAUDE.md — AIUI Project Guide

Project Overview

AIUI is a next-generation AI content surface UI. It's a pnpm monorepo with two packages:

  • @aiui/app — Reference application (Vite + Vue 3 + Tailwind CSS)
  • @aiui/core — Reusable component library

Stack: Vue 3 (Composition API), TypeScript ~5.8 (strict), Vite, Tailwind CSS, Pinia, Vue Router, Turborepo Node: >=20.0.0 | pnpm: >=10.0.0

Quick Reference

pnpm dev            # Run app dev server + Claude proxy
pnpm dev:core       # Watch-build core library
pnpm build          # Build all packages (turbo)
pnpm test           # Run tests (vitest)
pnpm lint           # Lint all packages (eslint)
pnpm typecheck      # Type-check all packages (vue-tsc)
pnpm clean          # Remove dist/ directories

Dev server: http://localhost:5173 | Claude proxy: http://localhost:3141

Core Philosophy

  • Open source only — MIT/Apache-2.0 licensed dependencies only
  • Decentralized-first — Pluggable adapters, no vendor lock-in
  • Bitcoin only — sats/Lightning/Cashu/Fedimint. Never fiat, never altcoins. AIUI is never a wallet — always deep-link to external wallets
  • Privacy-first — E2E encryption (tweetnacl.js), encrypted local storage (AES-256-GCM), no tracking/telemetry
  • Mobile-first, everywhere-perfect — Desktop is an enhancement of the mobile experience
  • Plugin-everything — All integrations go through typed plugin interfaces

Vue 3 Conventions

Always use <script setup lang="ts"> — never Options API.

Script section ordering

Imports → Props (defineProps) → Emits (defineEmits) → Reactive state → Computed → Watchers → Methods → Lifecycle hooks → defineExpose

Naming

Thing Convention Example
Components PascalCase ProjectCard.vue
Composables camelCase, use prefix useTheme.ts
Props (JS) camelCase projectName
Props (template) kebab-case project-name
Boolean props is/has/can/should prefix isVisible, canEdit
Emits (template) kebab-case with colon namespacing project:updated
Stores camelCase, use prefix, Store suffix useSettingsStore

Reactive state rules

  • ref() for primitives, reactive() for objects
  • computed() for derived values — no side effects in computed
  • shallowRef() for large collections/objects not requiring deep reactivity
  • Always use unique IDs for :key — never array index

Props

Always use object-style with type annotations, never array-style:

// Correct
defineProps<{ title: string; count?: number }>()

// Wrong
defineProps(['title', 'count'])

Performance

  • Lazy load with defineAsyncComponent for non-critical components
  • Use onErrorCaptured for error boundaries
  • Always handle loading/error/data states in async operations

File Structure

packages/app/src/
├── components/
│   ├── ui/              # Generic UI components
│   ├── chat/            # Chat interface components
│   ├── content-panel/   # Content panel components
│   ├── renderers/       # Content type renderers
│   └── layout/          # Layout components
├── composables/         # Shared composition functions
├── stores/              # Pinia stores
├── pages/               # Route-level components
├── styles/              # Global CSS, themes, tokens
├── utils/               # Pure utility functions
├── types/               # TypeScript type definitions
├── plugins/             # Plugin system
└── mocks/               # Dev fixtures & mock data

packages/core/src/
├── plugins/             # Plugin system interfaces
└── types/               # Shared TypeScript types

Tailwind & Design System

Glass Morphism (Archy-derived)

This project uses a glass morphism design language. Key utility classes:

Class Purpose
.glass Standard glass: rgba(0,0,0,0.35), blur(18px), white border 0.18 opacity
.glass-strong Stronger blur: blur(24px)
.glass-card Card variant: rgba(0,0,0,0.65), border-radius: 1rem
.glass-button Button: 48px height, rgba(0,0,0,0.6), blur(18px)
.glass-button-sm Compact button variant
.gradient-card Gradient background card

Spacing

4px grid system: 1=4px, 2=8px, 3=12px, 4=16px, etc.

Colors

  • Background: #0a0a0a (near-black)
  • Accent / Bitcoin orange: #F7931A
  • Primary: #606060
  • Text opacity scale: /25 (placeholder) → /40 (muted) → /60 (secondary) → /70 (interactive) → /80 (body) → /90 (emphasis) → /96 (headings) → text-white (active)
  • No separator borders between major sections

Typography

Inter/system-ui for body, Menlo/Monaco for monospace.

Responsive breakpoints (mobile-first)

sm 640px → md 768px → lg 1024px → xl 1280px → 2xl 1536px

Animations

  • animate-fade-up (900ms), animate-fade-up-fast (400ms), animate-fade-in (500ms), animate-scale-in (250ms)
  • Duration: 100ms micro, 200ms fast, 300ms moderate, 500ms normal, 600ms max
  • Easing: ease-out for entrances (90% of animations), ease-in for exits
  • Only animate transform and opacity — avoid animating layout properties
  • Always respect prefers-reduced-motion

Content Surfaces Architecture

Every content renderer supports up to five surfaces:

  1. Chat Preview (~120px max) — inline bubble, identify content at a glance
  2. Chat Play (~200px max) — inline playback with expand button
  3. Panel Preview (unlimited) — full browsing, filtering, sorting
  4. Panel Play — full immersive playback
  5. Panel Edit — full interaction, sends changes back to chat

On mobile, Panel surfaces open as full-screen overlays, not side-by-side.

interface RendererDefinition {
  id: string
  name: string
  contentType: string
  surfaces: SurfaceType[]
  chatPreview?: Component
  chatPlay?: Component
  panelPreview?: Component
  panelPlay?: Component
  panelEdit?: Component
  lazyDependencies?: () => Promise<any>
}

Chat surfaces must have zero lazy dependencies. Panel surfaces may lazy-load heavy libraries.

Plugin System

All integrations are plugins. Plugin types: ai-provider, media-source, messaging, storage, renderer, file-handler, crypto, search, auth, wallet, social-embed, mcp, media.

interface AIUIPlugin {
  id: string
  name: string
  version: string
  type: PluginType
  description?: string
  init(context: PluginContext): Promise<void>
  destroy(): Promise<void>
  isAvailable(): Promise<boolean>
}

Sandboxing: Tier 1 (trusted built-in), Tier 2 (community — sandboxed iframes), Tier 3 (external processes). Community plugins get no direct DOM access.

AI Provider Integration

interface AIProviderAdapter extends AIUIPlugin {
  type: 'ai-provider'
  chat(messages: Message[], options: ChatOptions): AsyncIterable<ChatChunk>
  models(): Promise<Model[]>
  supportsStreaming: boolean
  supportsVision: boolean
  supportsTools: boolean
}

Normalize tool calling across providers (OpenAI tool_calls vs Claude tool_use). Never include API keys in context injection.

Security & Crypto

  • E2E encryption: tweetnacl.js XSalsa20-Poly1305
  • Local storage: Web Crypto API AES-256-GCM + PBKDF2 (100K+ iterations)
  • API keys: encrypted at rest, never in localStorage, never logged, masked in UI (last 4 chars)
  • No eval() or innerHTML with untrusted content
  • Sanitize all user input against XSS
  • HTTPS only, CSP headers in production
  • Dev bypass: VITE_DISABLE_CRYPTO=true (never in production)

Accessibility

WCAG AA minimum compliance:

  • Color contrast: 4.5:1 normal text, 3:1 large/interactive
  • Keyboard: all elements focusable via Tab, visible focus indicators, Escape closes modals
  • Semantic HTML: use <header>, <nav>, <main>, <article>, <aside>, <footer> — not div soup
  • ARIA: aria-label for icon buttons, aria-live="polite" for dynamic updates, sr-only for screen reader text
  • Touch targets: min 44x44px with 8px gaps
  • All images need alt attributes (decorative: alt="")
  • Respect prefers-reduced-motion

Performance Budget

  • Initial load: < 250KB gzipped
  • Core bundle: Vue + Tailwind + Pinia + Router + chat UI (~150KB) + markdown + streaming (~50KB)
  • Everything else: lazy-loaded on demand
  • Virtual scrolling (TanStack Virtual) for chat lists
  • Clean up listeners in onUnmounted, use shallowRef for large data
  • Core Web Vitals: LCP < 2.5s, FID < 100ms, CLS < 0.1
  • Preconnect to API hosts, debounce inputs (100ms)

Mobile UX (iOS HIG-Informed)

Follows Apple iOS Human Interface Guidelines. See .cursor/rules/15-mobile-ux.mdc for full reference.

  • Typography: Body 17px, Footnote 13px, Caption 11px minimum — never smaller than 11px
  • Touch targets: min 44×44px tappable area, 8px gap between targets
  • Side margins: 16px (px-4)
  • Primary actions: Bottom thumb zone
  • Viewport: height: 100dvh with env(safe-area-inset-*) for notched devices
  • Form inputs: min 16px font (prevents iOS zoom), appropriate inputmode
  • Content panels: Full-screen overlay or bottom sheet on mobile, never side-by-side
  • Transitions: 150ms micro, 300ms standard, 400ms modal — ease-out entrances, ease-in exits
  • Sheets: Bottom sheets with close button, don't rely solely on swipe-to-dismiss
  • Support both portrait and landscape

Environment & Dev Mode

Env vars must be prefixed VITE_. Secrets go in .env.local (gitignored). See .env.example for template.

Feature flags via useFeatureFlags(): isDev, isTauri, isMobile, isCryptoEnabled, isMockData

Dev mode enables: mock data, debug panel, verbose logging, disabled encryption, all renderers without lazy loading.

Git Conventions

Commit format

type(scope): description

Types: feat, fix, refactor, style, docs, test, chore, perf Scope: package or area — core, app, chat, renderer-film, plugin-x

Branches

main (production), dev (integration), feat/description, fix/description

Rules

  • One feature per PR
  • All tests pass, TypeScript strict passes, no lint errors
  • No force push to main/dev
  • Never commit .env.local, secrets, or node_modules
  • Squash merge features, tag releases v1.0.0

Archipelago (Archy) Integration

AIUI runs inside an iframe in Archipelago's Chat mode. All communication with the host happens via window.postMessage() through a strict protocol.

Architecture

AIUI (iframe)  ←→  postMessage  ←→  Archy ContextBroker  ←→  Node data

AIUI is quarantined — it never directly accesses Archy's APIs, stores, or node data. The Archy ContextBroker fetches and sanitizes data before passing it to AIUI.

Protocol

Use archyBridge.ts (src/services/archyBridge.ts) for all Archy communication:

import { archyBridge } from '@/services/archyBridge'

// Request context (respects user permissions)
const apps = await archyBridge.requestContext('apps')
if (!apps.permitted) {
  // Show: "Enable 'Installed Apps' access in Archy Settings"
}

// Request an action
await archyBridge.requestAction('open-app', { appId: 'btcpay-server' })

// Listen for theme/permission updates
archyBridge.onPermissionsUpdate((categories) => { ... })
archyBridge.onThemeUpdate((theme) => { ... })

Context categories (user toggles each on/off in Archy Settings):

  • apps — App names, status, health (no credentials)
  • system — CPU, RAM, disk (no paths or IPs)
  • network — Connection status, peer count (no IPs)
  • wallet — Balance, channel count (no keys or seeds)
  • files — File/folder names (no contents)

Critical Rules

  1. NEVER fetch Archy APIs directly — always use archyBridge
  2. NEVER store or log raw user data from context responses
  3. NEVER make HTTP requests to the host machine
  4. Handle permitted: false gracefully — tell users what to enable
  5. Send ready message on mount so Archy knows the iframe loaded
  6. Build must output a static SPA servable from any base path
  7. All AI provider keys are user-provided and stored locally in AIUI only

Build & Deploy

AIUI deploys as a Podman container on the Archy node:

  • Build: pnpm buildpackages/app/dist/
  • Container: nginx:alpine serving the dist
  • Proxied at /aiui/ via Archy's nginx
  • Updates independently of Archy — new container image = new version