feat: initialize AIUI monorepo with project rules and core types

Foundation for the next-generation AI content surface UI:
- 16 Cursor rules files covering philosophy, Vue conventions, design system,
  content surfaces, plugin system, AI integration, renderers, security,
  Bitcoin-only policy, dev/prod modes, accessibility, performance, animation,
  mobile UX, and git workflow
- pnpm workspaces + Turborepo monorepo (@aiui/core, @aiui/app)
- Vue 3 + Vite + TypeScript + Tailwind CSS 4
- Core type system: plugins, renderers, messages, content blocks
- Plugin registry with renderer registration
- 50 mock film fixtures with search/filter utilities
- App shell with chat page layout
- Environment config templates

Made-with: Cursor
This commit is contained in:
Dorian
2026-03-02 14:15:39 +00:00
commit c28e6dd811
44 changed files with 5384 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
---
description: Core development philosophy for AIUI - the foundational rules that govern all code and design decisions
globs: "**/*"
alwaysApply: true
---
# Master Philosophy
## Mission
Build the next-generation AI content surface UI — a paradigm where AI responses are rendered as rich, interactive content, not plain text. Delivered as a reusable component library (@aiui/core) and a reference application (AIUI App).
## Philosophical Pillars
### 1. Open Source Only
Every dependency must be OSS (MIT, Apache-2.0, GPL-compatible). No proprietary SDKs, no vendor-locked services. Before adding any dependency, verify its license.
### 2. Decentralized-First
No hard dependency on any centralized service. AI backends, messaging protocols, storage, search — all connect through pluggable adapter interfaces. Users choose their own providers.
### 3. Bitcoin Only
Bitcoin is the only monetary unit. On-chain, Lightning, ecash (Cashu, Fedimint/Fedi). No fiat payment rails, no altcoins, no stablecoins — anywhere in the UI or codebase. AIUI is never a wallet and never handles funds directly. See `10-bitcoin-only.mdc` for full rules.
### 4. Cryptography for Everything Sensitive
E2E encryption for messages, encrypted local storage, proper key management. Privacy is not a feature — it is a requirement.
### 5. Mobile-First, Everywhere-Perfect
Every component works flawlessly on mobile, tablet, and desktop. Mobile is the foundation, not an afterthought. Touch targets, viewport management, and safe areas are first-class citizens.
### 6. Consistency is Sacred
Mobile and desktop versions show identical content and functionality unless explicitly designed otherwise. Design tokens ensure visual consistency across all breakpoints.
### 7. Theme-First Architecture
Theming is a core architectural decision from day one. Themes are CSS-based with reactive state management. Dark mode and light mode are equals.
### 8. Utility-First, Component-Second
Tailwind CSS utilities in templates for maximum flexibility. Component classes only for truly reusable patterns. Extract components when you repeat, not before.
### 9. Performance as a Feature
Initial load < 250KB gzipped. Lazy load everything that isn't immediately visible. CSS transforms for GPU acceleration. SVG over raster images. Code splitting by default.
### 10. Plugin-Everything
Every external integration connects through a typed plugin interface. AI providers, media sources, messaging protocols, wallets, social embeds — all pluggable.
### 11. Accessibility is Not Optional
WCAG AA compliance minimum. Keyboard navigation everywhere. Screen reader friendly. Color contrast tested and validated.
### 12. MCP-Native
First-class Model Context Protocol support for AI tool interoperability.
## Anti-Patterns to Avoid
- Desktop-first thinking
- Hardcoded values (use design tokens)
- Premature abstraction (build three times before abstracting)
- Magic numbers without comments
- Invisible state (user should always know what's happening)
- Handling funds or private keys
- Loading third-party tracking scripts
- Proprietary dependencies
## The Ultimate Goal
When someone uses AIUI, they should think: "This feels incredibly polished", "Everything just works", "My data is safe", "I control my own setup."
When a developer reads the code: "This is well organized", "I understand exactly what's happening", "Adding a new renderer is straightforward."
+84
View File
@@ -0,0 +1,84 @@
---
description: Vue 3 Composition API conventions and best practices for AIUI
globs: "**/*.vue,**/*.ts"
alwaysApply: false
---
# Vue 3 Conventions
## Composition API with `<script setup>`
Always use `<script setup lang="ts">`. Never use Options API.
## Component Organization Order
1. Imports — external, then internal
2. Props — with TypeScript-style validation
3. Emits — explicitly defined
4. State (refs and reactive)
5. Computed — derived values, always pure
6. Watchers — side effects only
7. Methods — business logic
8. Lifecycle hooks — ordered by execution
9. Expose — public API (if needed)
## File Organization
```
src/
components/
ui/ # Primitives (Button, Card, Badge, Input)
chat/ # Chat window, message list, input
content-panel/ # Side panel for surfaced content
renderers/ # Content type renderers
layout/ # Shell, split-pane, responsive containers
composables/ # Shared composition functions (useTheme, useMedia, useCrypto)
stores/ # Pinia stores
plugins/ # Plugin system
types/ # Shared TypeScript types
styles/ # Global CSS, themes, design tokens
utils/ # Pure utility functions
```
## Naming Conventions
- Components: PascalCase (`ProjectCard.vue`)
- Composables: camelCase, prefixed with "use" (`useTheme.ts`)
- Props: camelCase in JS, kebab-case in templates
- Boolean props: prefix with `is`, `has`, `can`, `should`
- Handler props: prefix with `on` (`onClick`, `onClose`)
- Emits: explicit, kebab-case in templates (`project:updated`)
## Props — Always Validate
```typescript
defineProps({
title: { type: String, required: true },
count: { type: Number, default: 0 },
status: {
type: String as PropType<'pending' | 'active' | 'complete'>,
default: 'pending'
}
})
```
Never use array-style props: `defineProps(['title', 'count'])`
## Reactive State
- `ref` for primitives and single values
- `reactive` for objects with multiple properties
- `computed` for derived state (never side effects in computed)
- `shallowRef` for large objects that change at top level only
## Templates — Keep Clean
Move complex logic to computed properties or methods. No inline logic in templates. Use `v-if` for infrequent toggles, `v-show` for frequent ones.
## Composables
- One responsibility per composable
- Return only what's needed
- Handle cleanup in `onUnmounted`
- Make composables testable
## Performance
- Lazy load heavy components: `defineAsyncComponent(() => import(...))`
- Use `shallowRef` for large lists
- Use `:key` with unique identifiers, never index
- Avoid reactive objects in templates (create in script)
## Error Handling
Use `onErrorCaptured` for component-level error boundaries. Always handle async errors with try/catch/finally pattern (loading, error, data states).
+78
View File
@@ -0,0 +1,78 @@
---
description: Tailwind CSS utility-first styling conventions for AIUI
globs: "**/*.vue,**/*.css,**/*.ts"
alwaysApply: false
---
# Tailwind CSS Styling
## Utility-First
Use Tailwind utilities directly in templates. Extract to component classes only when a pattern repeats 3+ times.
## 4px Spacing Grid
All spacing follows a 4px base grid:
- 1 = 4px, 2 = 8px, 3 = 12px, 4 = 16px, 5 = 20px, 6 = 24px, 8 = 32px, 12 = 48px, 16 = 64px
## Typography Scale
```
text-xs = 12px (metadata, timestamps)
text-sm = 14px (body text, buttons)
text-base = 16px (default body, inputs)
text-lg = 18px (subtitles)
text-xl = 20px (card titles)
text-2xl = 24px (section headings)
text-3xl = 30px (page headings)
text-4xl = 36px (hero headings)
```
Font weights: `font-normal` (body), `font-medium` (emphasis), `font-semibold` (headings/buttons), `font-bold` (strong emphasis).
## Border Radius
- `rounded-md` (6px) — inputs
- `rounded-lg` (8px) — buttons, standard cards
- `rounded-2xl` (16px) — modern cards, modals
- `rounded-full` — pills, avatars
## Shadows
- `shadow-sm` — subtle elevation
- `shadow-md` — standard card
- `shadow-lg` — modal, dropdown
- `shadow-xl` — hero card
## Glass Morphism
For cards over complex backgrounds:
```html
<div class="bg-white/10 backdrop-blur-sm border border-white/10 rounded-2xl">
```
Never use glass morphism on body text containers or form inputs.
## Responsive — Mobile First
Always write base styles for mobile, enhance with breakpoints:
```html
<div class="text-base md:text-lg lg:text-xl p-4 md:p-6 lg:p-8">
```
Breakpoints: `sm` (640px), `md` (768px), `lg` (1024px), `xl` (1280px), `2xl` (1536px).
## Dark Mode
Use CSS custom properties for theme switching. Both Tailwind `dark:` and custom theme classes supported:
```html
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
```
## Gradients for Hierarchy
Use gradients strategically for emphasis, not decoration:
```html
<h2 class="bg-gradient-to-r from-white to-gray-400 bg-clip-text text-transparent">
```
## Hover States
All interactive elements need immediate visual feedback (< 100ms):
```html
<button class="transition-colors duration-200 hover:bg-primary-dark">
```
## Focus States
Always visible focus for accessibility:
```html
<button class="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2">
```
+74
View File
@@ -0,0 +1,74 @@
---
description: Design system foundations - colors, typography, spacing, component patterns
globs: "**/*.vue,**/*.css,**/*.ts"
alwaysApply: false
---
# Design System
## Design Tokens
### Color Palette
Semantic color tokens defined by purpose, not appearance:
- `primary` — main brand actions (#606060 / configurable)
- `accent` — highlight, Bitcoin orange (#F7931A)
- `success` — positive states (#10B981)
- `error` — negative states (#EF4444)
- `warning` — caution states (#F59E0B)
- `info` — informational (#3B82F6)
- Neutrals: gray-50 through gray-900
### Typography
- Display font: Geometric sans-serif (Space Grotesk, Inter Display)
- Body font: Clean sans-serif (Inter, system-ui)
- Mono font: Menlo, Monaco, Courier New
- Use single font family with weight variations for simplicity
### Spacing
All spacing on 4px grid. Common patterns:
- Component padding: `p-4` (16px) to `p-6` (24px)
- Section spacing: `py-12` (48px) to `py-16` (64px)
- Element gaps: `gap-4` (16px) default
### Shadows & Glows
```css
shadow-soft: 0 2px 8px rgba(0, 0, 0, 0.08)
shadow-card: 0 4px 16px rgba(0, 0, 0, 0.1)
shadow-elevated: 0 8px 32px rgba(0, 0, 0, 0.15)
```
## Component Patterns
### Buttons
- Primary: solid background, white text, `rounded-lg`, `font-semibold`
- Secondary: border only, primary text, fill on hover
- Ghost: text only, subtle background on hover
- Icon: `p-2 rounded-md`, minimum 44x44px touch target
- Sizes: sm (`px-4 py-2`), md (`px-6 py-3`), lg (`px-8 py-4`)
### Cards
- Background: white/dark with `rounded-2xl` (16px radius)
- Padding: `p-6`
- Shadow: `shadow-card` default, `shadow-elevated` on hover
- Interactive cards get `hover:shadow-lg transition-shadow duration-300 cursor-pointer`
### Form Elements
- Inputs: `min-h-[44px]` touch target, `text-base` (prevents iOS zoom), `rounded-lg`
- Labels: `text-sm font-semibold text-gray-700 mb-2`
- Error text: `text-sm text-error mt-1`
- Focus: `focus:border-primary focus:ring-2 focus:ring-primary/50`
### Badges
- Status badges: `inline-flex items-center px-3 py-1 rounded-full text-sm font-medium`
- Color variants: success (green-100/green-800), warning (yellow-100/yellow-800), error (red-100/red-800)
### Icons
- SVG preferred, using Lucide Icons
- Sizes: xs (16px), sm (20px), md (24px), lg (32px), xl (48px)
- Always use `currentColor` for stroke/fill
- Icon-only buttons must have `aria-label`
## Layout Patterns
- Standard container: `max-w-7xl mx-auto px-4 md:px-6`
- Reading container: `max-w-3xl mx-auto px-4 md:px-6`
- Sidebar + content: `grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-6`
@@ -0,0 +1,91 @@
---
description: Component architecture principles - composition, patterns, and structure
globs: "**/*.vue,**/*.ts"
alwaysApply: false
---
# Component Architecture
## Core Philosophy: Composition Over Configuration
Build complex UIs from simple, focused components that compose well together.
- Single Responsibility: each component does one thing well
- Use slots instead of complex prop APIs
- Provide sensible defaults
- Clear TypeScript interfaces for props
- Keep component state local and minimal
## Anti-Patterns
- God components that do everything
- Prop drilling through many layers (use provide/inject or Pinia)
- Hard-coded values instead of props
- Component logic mixed with layout
- Tight coupling between components
## Compound Component Pattern
Components that work together as a cohesive unit:
```vue
<Card>
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
<Card.Footer>Actions</Card.Footer>
</Card>
```
## Container/Presenter Pattern
Separate logic from presentation:
- Container: handles data fetching, state, side effects
- Presenter: pure rendering, receives data via props, emits events
## Slot Pattern (Vue)
Use named slots for flexible content injection:
```vue
<template>
<div class="section">
<slot name="title" />
<slot name="content" />
<slot name="actions" />
</div>
</template>
```
## Prop Interface Design
```typescript
interface BaseComponentProps {
class?: string
testId?: string
}
interface ButtonProps extends BaseComponentProps {
variant?: 'primary' | 'secondary' | 'ghost'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
loading?: boolean
}
```
## Component File Template
```
1. Imports (external, then internal)
2. Types/Interfaces
3. Constants
4. Main component (props, emits, state, computed, methods, lifecycle)
5. Sub-components (if any)
```
## Error Boundaries
Every major section should have error boundary handling via `onErrorCaptured`. Show fallback UI, never a blank screen.
## Responsive Components
Use CSS-based responsive (`hidden md:block`) over JS-based (`useMediaQuery`) when possible. JS-based only when behavior changes (not just visibility).
## Component Checklist
Before shipping any component:
- [ ] TypeScript interface defined
- [ ] Sensible default props
- [ ] Loading and error states handled
- [ ] ARIA attributes added
- [ ] Keyboard navigation works
- [ ] Responsive behavior tested
- [ ] Dark mode styling works
- [ ] Touch interactions verified on mobile
+82
View File
@@ -0,0 +1,82 @@
---
description: The five content surfaces that define how content is rendered in AIUI
globs: "**/renderers/**,**/chat/**,**/content-panel/**"
alwaysApply: false
---
# Content Surfaces
AIUI has five distinct surfaces where content can appear. Every renderer must define how it behaves in each applicable surface.
## Surface 1: Chat Preview
- Location: inline in chat message bubble
- Max height: ~120px
- Purpose: identify content at a glance (thumbnail, title, brief metadata)
- Always tappable/clickable to expand to Panel Preview or Panel Play
- Lightweight rendering only — no heavy libraries loaded
- Examples: film poster thumbnail strip, file icon with name, code snippet (first 5 lines), image thumbnail
## Surface 2: Chat Play
- Location: inline in chat message bubble
- Max height: ~200px
- Purpose: inline playback without leaving the chat
- Must not disrupt chat scrolling
- Has an "expand" button to open in Panel Play
- Examples: voice note waveform with play button, short video player, audio player, small interactive widget
## Surface 3: Panel Preview
- Location: content panel (beside chat on desktop, overlay on mobile)
- No height limit (scrollable within panel)
- Purpose: full browsing/exploration experience
- Supports: filtering, sorting, searching, pagination
- Click items to go to Panel Play or Panel Edit
- Examples: film grid (tiled, filterable), image gallery, search results list, document preview, file tree
## Surface 4: Panel Play
- Location: content panel
- Purpose: full immersive media playback
- Examples: full video player with controls, audio with spectrum visualization, slideshow, trailer playback
## Surface 5: Panel Edit/Interactive
- Location: content panel
- Purpose: full interaction and editing
- Changes can be sent back to chat as new messages
- Examples: code editor (CodeMirror), form filling, approval workflow, spreadsheet editing, diagram creation
## Surface Transitions
```
Chat Preview --tap--> Panel Preview --tap item--> Panel Play
--tap item--> Panel Edit
Chat Play --expand--> Panel Play
Panel Edit --submit--> Chat (new message with result)
```
## Renderer Interface
Every renderer must export:
```typescript
interface RendererDefinition {
id: string
name: string
contentType: string // MIME-like type identifier
surfaces: SurfaceType[] // which surfaces this renderer supports
chatPreview?: Component // Surface 1
chatPlay?: Component // Surface 2
panelPreview?: Component // Surface 3
panelPlay?: Component // Surface 4
panelEdit?: Component // Surface 5
lazyDependencies?: () => Promise<any> // heavy libs loaded on demand
}
```
## Mobile Behavior
- On mobile, there is no side-by-side layout
- Panel surfaces open as a full-screen overlay or bottom sheet
- Chat Preview and Chat Play remain inline
- Transition: tap Chat Preview → full-screen Panel Preview (slide up)
- Back gesture or button returns to chat
## Performance Rules
- Chat Preview and Chat Play must render with zero lazy-loaded dependencies
- Panel surfaces may lazy-load heavy libraries (CodeMirror, pdf.js, etc.)
- Never block the chat scroll with renderer loading
- Use skeleton/placeholder while panel content loads
+98
View File
@@ -0,0 +1,98 @@
---
description: Plugin architecture rules - interfaces, registration, lifecycle, sandboxing
globs: "**/plugins/**,**/*.plugin.ts"
alwaysApply: false
---
# Plugin System
## Philosophy
Every external integration connects through a typed plugin interface. No direct coupling to any service, provider, or protocol.
## Plugin Types
```typescript
type PluginType =
| 'ai-provider' // LLM backends (OpenRouter, Ollama, Claude, etc.)
| 'media-source' // Content sources (Plex, YouTube, Nextcloud, Archive.org)
| 'messaging' // Chat protocols (Nostr, Matrix, local)
| 'storage' // File storage (local FS, IPFS, Nextcloud)
| 'renderer' // Custom content renderers
| 'file-handler' // File open/preview handlers
| 'crypto' // Encryption providers
| 'search' // Search backends (SearXNG, local)
| 'auth' // Authentication (Nostr keys, DID, passkeys)
| 'wallet' // Bitcoin wallet deep-linking (Phoenix, Zeus, Alby, etc.)
| 'social-embed' // Social post fetching (X, Nostr, Mastodon)
| 'mcp' // Model Context Protocol servers
| 'media' // Media processing (ffmpeg.wasm, whisper, TTS)
```
## Base Plugin Interface
```typescript
interface AIUIPlugin {
id: string
name: string
version: string
type: PluginType
description?: string
icon?: string
init(context: PluginContext): Promise<void>
destroy(): Promise<void>
isAvailable(): Promise<boolean>
}
```
## Plugin Context
Plugins receive a context object with access to:
- Settings store (read/write plugin-specific settings)
- Event bus (emit/listen for app events)
- Logger (structured logging)
- Crypto utilities (for encrypting plugin data at rest)
Plugins do NOT receive:
- Direct DOM access (community plugins)
- File system access (without explicit capability grant)
- Network access to arbitrary hosts (without declaration)
## Sandboxing Tiers
### Tier 1: Trusted (built-in, official)
Run in main thread with full API access. AI adapters, core renderers, crypto providers.
### Tier 2: Community
Run in sandboxed iframes with `postMessage` API. Custom renderers, themes, visual extensions. Cannot access host DOM, file system, or network directly.
### Tier 3: External Processes
MCP servers, local AI runners. Run as separate processes (Tauri IPC) or connect via HTTP. Isolated by OS process boundary.
## Plugin Lifecycle
1. `register()` — declare plugin to registry
2. `init()` — plugin sets up, connects to services
3. Active — plugin responds to requests
4. `destroy()` — cleanup on disable/uninstall
## Registration
```typescript
import { registerPlugin } from '@aiui/core'
registerPlugin({
id: 'ai-openrouter',
name: 'OpenRouter',
type: 'ai-provider',
version: '1.0.0',
async init(ctx) { /* setup */ },
async destroy() { /* cleanup */ },
// ... adapter methods
})
```
## Plugin Settings
Each plugin can declare settings schema. Settings are stored encrypted and exposed through a standard settings UI.
## Rules
- Every plugin must declare its type
- Every plugin must implement `init()` and `destroy()`
- Every plugin must implement `isAvailable()` to report its status
- Plugins must handle errors gracefully — never crash the host
- Community plugins must not load external scripts
- All network requests must go through the plugin context (for privacy/proxy control)
+83
View File
@@ -0,0 +1,83 @@
---
description: AI adapter patterns, streaming, tool calling, context injection
globs: "**/ai/**,**/plugins/ai-*/**"
alwaysApply: false
---
# AI Integration
## Universal AI Adapter
All AI providers connect through the `AIProviderAdapter` interface:
```typescript
interface AIProviderAdapter extends AIUIPlugin {
type: 'ai-provider'
chat(messages: Message[], options: ChatOptions): AsyncIterable<ChatChunk>
models(): Promise<Model[]>
supportsStreaming: boolean
supportsVision: boolean
supportsTools: boolean
supportsMultimodal: boolean
}
```
## Provider Hierarchy
1. **OpenAI-Compatible Adapter** — covers OpenRouter, Ollama, vLLM, llama.cpp, LocalAI, Mistral, DeepSeek, xAI, Qwen. Just change `baseURL` + API key.
2. **Anthropic Adapter** — Claude. Different tool_use format (content blocks vs tool_calls).
3. **Gemini Adapter** — Google. Different multimodal format.
4. **MCP Client** — connects to any MCP server for tools, resources, prompts.
## Streaming
- All AI responses use Server-Sent Events (SSE) over HTTP
- Pattern: `data: {"token": "Hello"}\n\n` with `data: [DONE]\n\n` termination
- Client: parse SSE stream, feed tokens to `StreamingTextRenderer`
- Always show a typing indicator while waiting for first token
- Handle connection drops gracefully (show error, offer retry)
## Tool Calling
AI can invoke tools. The adapter normalizes tool call formats:
```typescript
interface ToolCall {
id: string
name: string
arguments: Record<string, unknown>
}
interface ToolResult {
toolCallId: string
content: string | StructuredContent
isError: boolean
}
```
Normalize across providers:
- OpenAI: `tool_calls` in assistant message → `role: "tool"` result
- Claude: `type: "tool_use"` content block → `tool_result` in user message
- Map both to AIUI's unified `ToolCall` / `ToolResult` types
## Context Injection
The system prompt includes context about the user's environment:
- Connected media sources and their capabilities
- Available tools and plugins
- User preferences (language, theme, preferred wallet)
- In dev mode: mock data summaries
Never include sensitive data (API keys, passwords) in system prompts.
## Model Selection
Users can switch models within a conversation. The UI shows:
- Available models from all connected providers
- Model capabilities (vision, tools, streaming)
- Cost per token in sats (if applicable)
## Dev Mode
- `VITE_OPENROUTER_API_KEY` in `.env.local`
- Free models available (Llama, Mistral via OpenRouter)
- Mock tool responses available via dev fixtures
- Debug panel shows: raw messages, token count, latency
## Error Handling
- Rate limits: show user-friendly message, auto-retry with backoff
- Auth errors: prompt to check API key in settings
- Network errors: show offline indicator, queue message for retry
- Model errors: show error in chat, suggest alternative model
+80
View File
@@ -0,0 +1,80 @@
---
description: How to build content renderers - interfaces, lazy loading, accessibility
globs: "**/renderers/**"
alwaysApply: false
---
# Renderer Development
## What is a Renderer?
A renderer is a set of Vue components that know how to display a specific content type across the five content surfaces (chat-preview, chat-play, panel-preview, panel-play, panel-edit).
## Renderer Registration
```typescript
import { registerRenderer } from '@aiui/core'
registerRenderer({
id: 'film',
name: 'Film',
contentType: 'application/x-aiui-film',
surfaces: ['chat-preview', 'panel-preview', 'panel-play'],
chatPreview: () => import('./FilmChatPreview.vue'),
panelPreview: () => import('./FilmGrid.vue'),
panelPlay: () => import('./FilmDetail.vue'),
})
```
## Content Type Detection
Renderers are matched to content by `contentType` field in the message data:
```typescript
interface ContentBlock {
contentType: string // e.g., 'application/x-aiui-film'
data: Record<string, unknown> // renderer-specific data
title?: string // human-readable title for panel tab
}
```
## Performance Rules
1. Chat surfaces (preview, play) must render with ZERO lazy-loaded heavy dependencies
2. Panel surfaces may lazy-load libraries (CodeMirror, pdf.js, etc.)
3. Use `defineAsyncComponent` for panel components
4. Show skeleton/placeholder while loading
5. Never block the main thread — use Web Workers for heavy parsing
## Data Contracts
Each renderer defines its expected data shape as a TypeScript interface:
```typescript
interface FilmRendererData {
films: Film[]
query?: string
filters?: FilmFilters
}
```
Document the interface. Validate incoming data. Show graceful error if data is malformed.
## Accessibility Requirements
- All renderers must be keyboard navigable
- Images need alt text
- Interactive elements need ARIA labels
- Media players need captions/transcripts when available
- Focus management when transitioning between surfaces
## Mobile Behavior
- Chat Preview: constrained to message bubble width
- Chat Play: full message width, max 200px height
- Panel surfaces on mobile: full-screen overlay with back gesture
- Touch targets: minimum 44x44px
- Swipe gestures where appropriate (image gallery, film cards)
## Renderer Checklist
- [ ] TypeScript data interface defined and exported
- [ ] All applicable surfaces implemented
- [ ] Lazy loading for heavy dependencies
- [ ] Skeleton/placeholder states
- [ ] Error state (malformed data)
- [ ] Empty state (no data)
- [ ] Keyboard navigation
- [ ] ARIA labels on interactive elements
- [ ] Mobile responsive
- [ ] Dark mode compatible
- [ ] Transition animations (per motion design rules)
+60
View File
@@ -0,0 +1,60 @@
---
description: Cryptography and security rules - E2E encryption, key management, storage
globs: "**/crypto/**,**/*.ts"
alwaysApply: false
---
# Security & Cryptography
## Principles
- Privacy is a requirement, not a feature
- Zero telemetry, zero analytics unless user explicitly opts in
- Never transmit unencrypted sensitive data
- Never store plaintext credentials
- Minimal data collection — store only what's needed
## Encryption Stack
### E2E Message Encryption
- Library: **tweetnacl.js** (6KB, audited by Cure53)
- Algorithm: XSalsa20-Poly1305 via NaCl `box` (public-key authenticated encryption)
- Each conversation has a shared secret derived from key exchange
### Local Storage Encryption
- Library: **Web Crypto API** (native, zero bundle cost)
- Algorithm: AES-256-GCM for encrypting IndexedDB values
- Key derived from user's master password via PBKDF2 (100K+ iterations)
### Key Management
- **Desktop (Tauri)**: OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
- **Web**: Encrypted IndexedDB with user-derived key
- **Nostr compatibility**: secp256k1 keys via @noble/curves, NIP-07 browser extension support
- **Passkeys/WebAuthn**: For passwordless authentication
### Credential Storage
- API keys encrypted at rest using AES-256-GCM
- Never stored in localStorage (use encrypted IndexedDB or OS keychain)
- Never included in logs, error reports, or system prompts
- Display as masked values in settings UI (show last 4 chars only)
## Dev Mode Bypass
When `VITE_DISABLE_CRYPTO=true` (dev only):
- Skip E2E encryption (messages stored in plain text)
- Skip storage encryption (IndexedDB unencrypted)
- API keys stored in `.env.local` (gitignored)
- This flag must NEVER exist in production builds
## Security Rules for Code
- Never log sensitive data (keys, tokens, passwords, message content)
- Never include secrets in error messages
- Sanitize all user input before rendering (XSS prevention)
- Use Content Security Policy headers
- Validate all data from plugins before rendering
- Community plugins run in sandboxed iframes (no direct DOM access)
- Never eval() or innerHTML with untrusted content
## Network Security
- All external requests over HTTPS only
- Certificate pinning for known services (Tauri)
- Proxy social media fetches to avoid leaking user IP
- No third-party tracking scripts, analytics, or telemetry SDKs
+72
View File
@@ -0,0 +1,72 @@
---
description: Bitcoin-only payment and monetary policy - on-chain, Lightning, ecash
globs: "**/*"
alwaysApply: true
---
# Bitcoin Only
## Core Rule
Bitcoin is the only monetary unit in AIUI. This applies everywhere — UI labels, data models, API responses, documentation, and conversation context.
## Supported Payment Protocols
- **On-chain Bitcoin**: BIP21 URI scheme (`bitcoin:bc1q...?amount=0.001`)
- **Lightning Network**: BOLT11 invoices, LNURL-pay, LNURL-withdraw, keysend
- **Cashu ecash**: Cashu tokens, mint interactions (`cashu:` URI, `web+cashu:`)
- **Fedimint/Fedi**: Federation ecash (`fedi:` URI)
- **Nostr Zaps**: NIP-57 Lightning zaps (social tipping)
## AIUI is NEVER a Wallet
### Never Do
- Store private keys or seed phrases
- Sign Bitcoin transactions
- Build or broadcast transactions
- Track wallet balances
- Display transaction history
- Create send/receive screens
- Implement payment processing logic
- Hold funds in custody
### Always Do
- Construct deep-link URIs and hand off to external wallet apps
- Detect installed wallet apps (via URI scheme probing or Tauri app detection)
- Let users configure preferred wallets in settings
- Display payment requests as QR codes with "Open in Wallet" buttons
- Show invoice/address details (amount, memo, expiry) as read-only information
## Wallet Deep-Linking
```typescript
// Construct URI, open external wallet — that's it
const uri = `lightning:${bolt11Invoice}`
window.open(uri) // or Tauri shell.open(uri)
```
Supported wallet URI schemes:
- `bitcoin:` — BIP21 (any on-chain wallet)
- `lightning:` — BOLT11 (any Lightning wallet)
- `cashu:` — Cashu tokens
- `fedi:` — Fedimint
- Wallet-specific: `phoenix://`, `zeus://`, `mutiny://`, `alby://`
## Denomination
- Primary unit: **sats** (1 BTC = 100,000,000 sats)
- Display: `1,234 sats` or `₿0.00001234`
- User preference: sats or BTC (configurable in settings)
- AI cost tracking: show token costs in sats
## Prohibited
- No fiat currencies (USD, EUR, etc.) — not in UI, not in code, not in variable names
- No altcoins or tokens
- No stablecoins (USDT, USDC, etc.)
- No fiat-denominated pricing
- No payment processor integrations (Stripe, PayPal, etc.)
- No KYC/AML flows
## Renderer Components
- `LightningInvoiceRenderer` — BOLT11 QR + amount + memo + "Open in Wallet"
- `BitcoinAddressRenderer` — BIP21 QR + "Open in Wallet"
- `CashuTokenRenderer` — ecash token + mint info + "Redeem in Wallet"
- `FedimintRenderer` — federation ecash + "Open in Fedi"
- `PaymentRequestRenderer` — unified card with payment method options
- `ZapRenderer` — Nostr zap display (NIP-57)
+101
View File
@@ -0,0 +1,101 @@
---
description: Development vs production configuration, feature flags, mock data patterns
globs: "**/*"
alwaysApply: false
---
# Dev & Prod Modes
## Development Mode
### Environment
```env
# .env.local (gitignored)
VITE_OPENROUTER_API_KEY=sk-or-...
VITE_TMDB_API_KEY=...
VITE_DEV_MODE=true
VITE_MOCK_MEDIA_SOURCES=true
VITE_DISABLE_CRYPTO=true
```
### What's Enabled
- Hot reload via Vite HMR
- Debug panel overlay (AI context, plugin status, renderer registry, message data)
- Mock media source plugins (Plex, YouTube, Nextcloud from JSON fixtures)
- OpenRouter AI connection (real API, free models available)
- Component playground (Storybook/Histoire)
- Verbose logging
- TypeScript strict mode
- All renderers available without lazy loading (for dev speed)
### What's Disabled
- E2E encryption (plain text messages for debugging)
- Storage encryption (plain IndexedDB)
- Tauri features (dev runs as pure web app)
- Production optimizations (tree-shaking, minification)
- Service worker / offline mode
### Mock Data
- Film fixtures: 50-100 films with real TMDB poster URLs
- Media source mocks: JSON files returning fake Plex/YouTube/Nextcloud responses
- Located in: `packages/app/src/mocks/`
- Auto-loaded when `VITE_MOCK_MEDIA_SOURCES=true`
- Mock data must match production data interfaces exactly
### Dev Scripts
```
pnpm dev # Web dev server
pnpm dev:desktop # Tauri dev (when needed)
pnpm storybook # Component playground
pnpm test # Vitest
pnpm lint # ESLint + Prettier
pnpm typecheck # TypeScript
pnpm build # Production build
pnpm turbo build # Turborepo cached build
```
## Production Mode
### What's Enabled
- E2E encryption for all messages
- Encrypted local storage
- Key management via OS keychain (Tauri) or encrypted IndexedDB (web)
- User-configured AI providers (settings page)
- Real media source connections (Plex API, YouTube, etc.)
- Optimized builds (tree-shaken, code-split, minified)
- Lazy loading for all heavy renderers
- Service worker for offline support
- Auto-update (Tauri)
### What's Disabled
- Debug panels
- Mock data
- Dev logging
- Source maps (in distributed builds)
- `VITE_DISABLE_CRYPTO` flag (must not exist)
### Build Targets
- Web: Static SPA bundle (< 250KB initial gzipped)
- Desktop: Tauri app (macOS .dmg, Windows .msi, Linux .AppImage)
- Mobile: Tauri mobile (iOS .ipa, Android .apk)
## Feature Flags
Use composable `useFeatureFlags()`:
```typescript
const { isDev, isTauri, isMobile, isCryptoEnabled, isMockData } = useFeatureFlags()
```
Gate platform-specific features:
```typescript
if (isTauri()) {
// Native file system access
} else {
// File System Access API or file picker
}
```
## Environment Variable Rules
- All env vars prefixed with `VITE_` (Vite requirement)
- Secrets only in `.env.local` (gitignored)
- `.env.example` committed with placeholder values
- Never read `process.env` directly — use typed config module
+69
View File
@@ -0,0 +1,69 @@
---
description: Accessibility standards - WCAG AA, keyboard navigation, screen readers
globs: "**/*.vue"
alwaysApply: false
---
# Accessibility
## Standard
WCAG AA compliance minimum. Target AAA where feasible.
## Color Contrast
- Normal text: 4.5:1 minimum ratio
- Large text (18px+ or 14px+ bold): 3:1 minimum
- Interactive elements: 3:1 against adjacent colors
- Test with browser DevTools accessibility panel
## Keyboard Navigation
- All interactive elements focusable via Tab
- Visible focus indicators on every focusable element (`focus:ring-2`)
- Escape closes modals, drawers, dropdowns
- Arrow keys navigate within lists, grids, tabs
- Enter/Space activates buttons and controls
- Focus trap inside modals (Tab cycles within modal)
## Semantic HTML
```html
<header>, <nav>, <main>, <article>, <aside>, <footer>
```
Never `<div class="header">`. Use semantic elements.
## ARIA
- Icon-only buttons: `aria-label="Close modal"`
- Dynamic content: `aria-live="polite"` for updates
- Screen reader only text: `class="sr-only"`
- Expandable sections: `aria-expanded="true/false"`
- Form fields: `aria-describedby` for help text, `aria-invalid` for errors
## Images
- All `<img>` tags need `alt` text
- Decorative images: `alt=""`
- Complex images: `aria-describedby` pointing to description
## Media
- Audio/video players: keyboard-accessible controls
- Provide transcripts/captions when available
- Respect `prefers-reduced-motion` for animations
## Touch Targets
- Minimum: 44x44px (Apple HIG)
- Recommended: 48x48px (Material Design)
- Minimum 8px gap between adjacent targets
## Reduced Motion
```css
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
```
Check in JS: `window.matchMedia('(prefers-reduced-motion: reduce)').matches`
## Testing
- VoiceOver (macOS), TalkBack (Android), NVDA (Windows)
- Keyboard-only navigation test
- axe DevTools or Lighthouse accessibility audit
- High contrast mode test
+60
View File
@@ -0,0 +1,60 @@
---
description: Performance optimization - bundle budget, lazy loading, virtual scrolling
globs: "**/*"
alwaysApply: false
---
# Performance
## Bundle Budget
- Initial load: **< 250KB gzipped**
- Core (Vue + Tailwind + Pinia + Router + chat UI): ~150KB
- First renderer batch (markdown, streaming text): ~50KB
- Everything else: lazy-loaded on demand
## Lazy Loading Strategy
- Route-based code splitting via Vue Router `() => import(...)`
- Renderer components via `defineAsyncComponent`
- Heavy libraries loaded only when their renderer is activated:
- CodeMirror 6: ~300KB (on code edit)
- Monaco: ~5MB (on IDE panel open)
- pdf.js: ~400KB (on PDF view)
- KaTeX: ~300KB (on math render)
- Mermaid: ~200KB (on diagram render)
- Leaflet: ~40KB (on map render)
- Whisper WASM: ~50MB (on STT activation, cached)
- Piper TTS: ~100MB (on TTS activation, cached)
## Virtual Scrolling
- Chat message list uses TanStack Virtual
- Dynamic row heights (messages vary in size)
- Inverted scroll (newest at bottom, load older on scroll up)
- Buffer: render 5 items above and below viewport
- Recycle DOM nodes for off-screen messages
## GPU Acceleration
Only animate `transform` and `opacity` — never `width`, `height`, `top`, `left`.
Use `will-change` sparingly and remove after animation.
## Image Optimization
- Use `loading="lazy"` on all non-critical images
- Provide `srcset` with multiple sizes
- Use WebP/AVIF where supported
- Skeleton placeholders while loading
## Network
- Preconnect to known API hosts
- Preload critical resources
- Debounce scroll and resize handlers (100ms)
- Batch API requests where possible
## Memory
- Clean up event listeners in `onUnmounted`
- Use `shallowRef` for large data sets
- Dispose heavy library instances when panel closes
- Monitor memory with browser DevTools
## Core Web Vitals Targets
- LCP (Largest Contentful Paint): < 2.5s
- FID (First Input Delay): < 100ms
- CLS (Cumulative Layout Shift): < 0.1
+79
View File
@@ -0,0 +1,79 @@
---
description: Animation principles - timing, easing, stagger, reduced motion
globs: "**/*.vue,**/*.css"
alwaysApply: false
---
# Animation & Motion Design
## Philosophy
Every animation serves a purpose: guide attention, provide feedback, show relationships, enhance perceived performance, or add delight. Never animate for decoration alone.
## Duration Scale
```
100ms - Instant: micro-feedback (hover states, button press)
200ms - Fast: small elements (tooltips, dropdowns)
300ms - Moderate: standard UI transitions (modals, cards)
500ms - Normal: page sections, complex components
600ms - Slow: hero animations, page transitions (max for UI)
```
Never exceed 600ms for UI element animations.
## Easing Functions
- **ease-out** (90% of animations): elements entering viewport
- **ease-in**: elements exiting viewport
- **ease-in-out**: elements moving within viewport
- **spring**: playful interactions (button press, drag-and-drop)
- **linear**: progress bars, loading spinners only
Custom smooth deceleration: `cubic-bezier(0.16, 1, 0.3, 1)`
## Common Patterns
### Fade & Slide Up (entrance)
```css
@keyframes fadeSlideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
```
### Scale & Fade (emphasis)
```css
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}
```
### Hover feedback
```css
.interactive {
transition: transform 0.1s ease, opacity 0.1s ease;
}
.interactive:active {
transform: scale(0.95);
opacity: 0.8;
}
```
## Staggered Animations
When animating multiple elements, stagger by 50-150ms per item:
```css
.card { animation-delay: calc(var(--index) * 0.1s); }
```
Max items in a stagger cascade: 6-8. Total cascade: under 1 second.
## Reduced Motion
Always respect `prefers-reduced-motion`. Provide instant transitions as fallback.
## Performance
- Only animate `transform` and `opacity` (GPU-composited)
- Use `will-change` sparingly, remove after animation
- Limit simultaneous animations
- Use `requestAnimationFrame` for JS animations
## Loading States
- Skeleton shimmer: 2s infinite, `linear-gradient` sweep
- Pulse: 2s infinite, opacity 1 → 0.5 → 1
- Spinner: 1s infinite linear rotation
+73
View File
@@ -0,0 +1,73 @@
---
description: Mobile UX patterns - touch targets, gestures, safe areas, viewport
globs: "**/*.vue,**/*.css"
alwaysApply: false
---
# Mobile UX
## Philosophy
Design for mobile first, enhance for desktop. Mobile constraints force focus on essential features.
## Touch Targets
- Minimum: 44x44px (Apple HIG)
- Optimal: 48x48px (Material Design)
- Minimum gap between targets: 8px
- Icon buttons: use padding to reach target size (`p-3` for 20px icon = 44px total)
## Thumb-Friendly Zones
```
Top 20%: Hard to reach — header, info
Middle 60%: Easy reach — main content
Bottom 20%: Natural thumb zone — primary actions
```
Place primary actions (send, approve, play) in the bottom zone. Use FABs at bottom-center on mobile, bottom-right on desktop.
## Gestures
- Swipe left/right: gallery navigation, dismiss
- Swipe down: close overlay/bottom sheet, pull-to-refresh
- Long press: context menu, selection
- Pinch: zoom on images
- Minimum swipe distance: 50px before triggering
## Viewport & Safe Areas
```css
/* Dynamic viewport height (avoids iOS address bar issue) */
height: 100dvh;
/* Safe area insets for notched devices */
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
```
## Scroll Behavior
- Lock body scroll when modal/drawer is open
- `overscroll-behavior: contain` on modal content
- `touch-action: manipulation` to prevent zoom on double-tap
- `-webkit-overflow-scrolling: touch` for smooth iOS scroll
## Form Inputs
- Font size minimum 16px on inputs (prevents iOS zoom on focus)
- Use appropriate `inputmode` (numeric, email, tel, url)
- Use `autocomplete` attributes
- Auto-focus next input on completion (OTP pattern)
## Content Panel on Mobile
- No side-by-side layout (chat + panel)
- Panel opens as full-screen overlay or bottom sheet
- Bottom sheet: drag handle at top, swipe down to dismiss
- Back gesture or button returns to chat
- Maintain scroll position when returning
## Orientation
Support both portrait and landscape. Adjust layout with:
```css
@media (orientation: landscape) { /* landscape overrides */ }
```
## Performance on Mobile
- Test on real devices, not just emulators
- Test on 3G/4G connections
- Debounce scroll handlers
- Lazy load images with `loading="lazy"`
- Critical CSS inlined, rest loaded async
+52
View File
@@ -0,0 +1,52 @@
---
description: Git workflow - commit conventions, branching, PR process
globs: "**/*"
alwaysApply: false
---
# Git Workflow
## Commit Messages
Format: `type(scope): description`
Types:
- `feat`: new feature
- `fix`: bug fix
- `refactor`: code restructuring (no behavior change)
- `style`: formatting, whitespace (no code change)
- `docs`: documentation
- `test`: adding/updating tests
- `chore`: build, dependencies, tooling
- `perf`: performance improvement
Scope: the package or area (`core`, `app`, `plugin-x`, `renderer-film`, etc.)
Examples:
```
feat(core): add renderer registry with lazy loading
fix(chat): prevent scroll jump on new message
refactor(plugin-system): simplify adapter interface
chore(deps): update Vue to 3.6
```
## Branching
- `main`: production-ready, always deployable
- `dev`: integration branch for features
- `feat/description`: feature branches (from dev)
- `fix/description`: bug fix branches
- `release/x.y.z`: release preparation
## Pull Requests
- One feature per PR
- Description: what changed, why, how to test
- All tests pass
- TypeScript strict mode passes
- No linter errors
- Reviewed before merge
## Rules
- Never force push to `main` or `dev`
- Never commit `.env.local` or any secrets
- Never commit `node_modules`
- Squash merge feature branches to keep history clean
- Tag releases with semver: `v1.0.0`