feat(app): update PWA icons to ✦ star design and fix chat panel default
Replace chat-bubble PWA icons with the four-pointed star (✦) used in the interface. Fix panelSide default so chat appears on the left and content surface on the right. Add CLAUDE.md project guide and .claude/launch.json dev server config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "app",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": ["--filter", "@aiui/app", "dev:vite"],
|
||||
"port": 5173,
|
||||
"autoPort": true
|
||||
},
|
||||
{
|
||||
"name": "claude-proxy",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": ["--filter", "@aiui/app", "dev:proxy"],
|
||||
"port": 3141,
|
||||
"autoPort": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```ts
|
||||
// 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-button` | Primary action gradient button |
|
||||
| `.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.
|
||||
|
||||
```ts
|
||||
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`.
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
- Primary actions in bottom thumb zone
|
||||
- Touch targets: min 44x44px, 8px gap between targets
|
||||
- Viewport: `height: 100dvh` with safe area insets
|
||||
- Form inputs: min 16px font (prevents iOS zoom), appropriate `inputmode`
|
||||
- Content panels: full-screen overlay or bottom sheet on mobile
|
||||
- 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`
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 6.0 KiB |
@@ -1,7 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="6" fill="#0a0a0a"/>
|
||||
<path d="M8,12 L8,22 Q8,24 10,24 L14,24 L17,27 L17,24 L22,24 Q24,24 24,22 L24,12 Q24,10 22,10 L10,10 Q8,10 8,12 Z" fill="#F7931A"/>
|
||||
<circle cx="13" cy="17" r="1.5" fill="#0a0a0a"/>
|
||||
<circle cx="16" cy="17" r="1.5" fill="#0a0a0a"/>
|
||||
<circle cx="19" cy="17" r="1.5" fill="#0a0a0a"/>
|
||||
<rect width="32" height="32" rx="6" fill="#111111"/>
|
||||
<path d="M 16,5.5 Q 16,16 26.5,16 Q 16,16 16,26.5 Q 16,16 5.5,16 Q 16,16 16,5.5 Z" fill="#fafafa"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 410 B After Width: | Height: | Size: 225 B |
@@ -1,23 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#1a1a1a"/>
|
||||
<stop offset="100%" stop-color="#0a0a0a"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="accent" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#F7931A"/>
|
||||
<stop offset="100%" stop-color="#E88410"/>
|
||||
<linearGradient id="border" x1="0" y1="0" x2="0" y2="512" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="rgba(255,255,255,0.18)"/>
|
||||
<stop offset="100%" stop-color="rgba(255,255,255,0.04)"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="512" height="512" rx="96" fill="url(#bg)"/>
|
||||
<rect x="8" y="8" width="496" height="496" rx="90" fill="none" stroke="rgba(255,255,255,0.12)" stroke-width="2"/>
|
||||
<g transform="translate(256,256)">
|
||||
<circle cx="0" cy="0" r="80" fill="none" stroke="url(#accent)" stroke-width="6" opacity="0.3"/>
|
||||
<circle cx="0" cy="0" r="120" fill="none" stroke="rgba(255,255,255,0.08)" stroke-width="2"/>
|
||||
<path d="M-50,-20 L-50,30 Q-50,40 -40,40 L-10,40 L10,55 L10,40 L40,40 Q50,40 50,30 L50,-20 Q50,-30 40,-30 L-40,-30 Q-50,-30 -50,-20 Z" fill="url(#accent)" opacity="0.9"/>
|
||||
<circle cx="-20" cy="5" r="5" fill="#0a0a0a"/>
|
||||
<circle cx="0" cy="5" r="5" fill="#0a0a0a"/>
|
||||
<circle cx="20" cy="5" r="5" fill="#0a0a0a"/>
|
||||
<text x="0" y="115" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="700" font-size="48" fill="rgba(255,255,255,0.9)" letter-spacing="8">AIUI</text>
|
||||
</g>
|
||||
<rect width="512" height="512" rx="108" fill="url(#bg)"/>
|
||||
<rect x="2" y="2" width="508" height="508" rx="106" fill="none" stroke="url(#border)" stroke-width="2"/>
|
||||
<circle cx="256" cy="256" r="120" fill="none" stroke="rgba(255,255,255,0.08)" stroke-width="2.5"/>
|
||||
<path d="M 256,106 Q 256,256 406,256 Q 256,256 256,406 Q 256,256 106,256 Q 256,256 256,106 Z" fill="#fafafa"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 922 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 26 KiB |
@@ -50,7 +50,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const loaded = ref(false)
|
||||
|
||||
const savedSide = localStorage.getItem('aiui-panel-side') as 'left' | 'right' | null
|
||||
const panelSide = ref<'left' | 'right'>(savedSide ?? 'right')
|
||||
const panelSide = ref<'left' | 'right'>(savedSide ?? 'left')
|
||||
|
||||
const webSearchEnabled = ref(localStorage.getItem('aiui-web-search') !== 'false')
|
||||
|
||||
|
||||