Archipelago — open-source initial import
This commit is contained in:
@@ -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."
|
||||
@@ -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).
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
description: Tailwind CSS utility-first styling conventions for AIUI, ported from Archy
|
||||
globs: "**/*.vue,**/*.css,**/*.ts"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Tailwind CSS Styling
|
||||
|
||||
## Source of Truth
|
||||
All glass morphism, container, and button patterns originate from the Archy project (`/Projects/Archy/neode-ui/src/style.css`). When in doubt, match Archy exactly.
|
||||
|
||||
## Utility-First
|
||||
Use Tailwind utilities directly in templates. Extract to component classes only when a pattern repeats 3+ times.
|
||||
|
||||
## 4px Spacing Grid
|
||||
```
|
||||
1 = 4px, 2 = 8px, 3 = 12px, 4 = 16px, 5 = 20px, 6 = 24px, 7 = 28px, 8 = 32px
|
||||
```
|
||||
|
||||
## 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).
|
||||
|
||||
## Glass Morphism (from Archy)
|
||||
|
||||
### Containers (exact Archy values)
|
||||
- `.glass` — base: `bg: rgba(0,0,0,0.35)`, `blur(18px)`, `border: 1px solid rgba(255,255,255,0.18)`, `shadow: 0 8px 24px rgba(0,0,0,0.45)`
|
||||
- `.glass-strong` — stronger blur: same bg but `blur(24px)`
|
||||
- `.glass-card` — primary card: `bg: rgba(0,0,0,0.65)`, `blur(18px)`, `border-radius: 1rem`, same border/shadow
|
||||
- `.gradient-card` — gradient: `linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(0,0,0,0.8) 100%)`
|
||||
- `.gradient-card-dark` — dark gradient: `linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.9) 100%)`
|
||||
- `.gradient-border-container` — gradient border with inner glass, `border-radius: 1.5rem`
|
||||
- `.toast-glass` — `border-radius: 0.75rem`, same glass as `.glass-card`
|
||||
|
||||
### Buttons (exact Archy values)
|
||||
- `.glass-button` — 48px height, `bg: rgba(0,0,0,0.6)`, `blur(18px)`, border `rgba(255,255,255,0.18)`, `color: rgba(255,255,255,0.9)`
|
||||
- `.glass-button-sm` — compact variant (auto height, `py-1.5 px-3`)
|
||||
|
||||
### Icon / Ghost buttons (Archy pattern)
|
||||
```html
|
||||
<button class="p-2 rounded-lg text-white/70 hover:text-white hover:bg-white/10 transition-colors">
|
||||
```
|
||||
Touch target: minimum 44x44px via padding.
|
||||
|
||||
### Active Navigation
|
||||
`.nav-tab-active` — `bg: rgba(0,0,0,0.35)`, inset highlight, gradient border via CSS mask `::before`
|
||||
|
||||
### Usage Rules
|
||||
- ✅ Cards, panels, modals, sidebars
|
||||
- ✅ Navigation bars, headers (fixed positioning)
|
||||
- ✅ Hover states, buttons
|
||||
- ❌ Body text containers (readability)
|
||||
- ❌ Form input fields (confusing UX)
|
||||
|
||||
## Inset Highlight
|
||||
The signature Archy inset glow:
|
||||
```css
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
```
|
||||
Apply to headers, selected cards, active nav items.
|
||||
|
||||
## Border — No Separators Between Sections
|
||||
Per Archy theme rules: no borders between sidebar and content, or header and content. Only subtle `rgba(255,255,255,0.06-0.08)` borders for internal dividers.
|
||||
|
||||
## Gradient Text
|
||||
```html
|
||||
<h1 class="gradient-text">Title</h1>
|
||||
```
|
||||
`linear-gradient(to right, #ffffff, #9ca3af)` with `background-clip: text`.
|
||||
|
||||
## Focus States — Gamepad/Keyboard Glow
|
||||
All focusable elements get a blue glow (no outline):
|
||||
```css
|
||||
*:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 16px rgba(120, 180, 255, 0.2), 0 0 32px rgba(100, 160, 255, 0.1);
|
||||
}
|
||||
```
|
||||
|
||||
## Scrollbar
|
||||
- `.custom-scrollbar` — gradient thumb (`rgba(255,255,255,0.3)` to `0.1`), dark track
|
||||
- `.scrollbar-hide` — hidden scrollbar, keeps scroll functionality
|
||||
|
||||
## Responsive — Mobile First
|
||||
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).
|
||||
|
||||
## Hover States (from Archy)
|
||||
```html
|
||||
<div class="transition-all duration-300 hover:bg-white/10 hover:text-white">
|
||||
```
|
||||
Interactive card lift: `hover:translateY(-2px)` with intensified shadow.
|
||||
|
||||
## Animations (Archy timings)
|
||||
- `animate-fade-up` — 900ms `cubic-bezier(0.22, 1, 0.36, 1)` with 120ms delay
|
||||
- `animate-fade-up-fast` — 400ms, no delay (for chat messages)
|
||||
- `animate-fade-in` — 500ms ease
|
||||
- `animate-scale-in` — 250ms for modals/popups
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
description: Design system foundations - glassmorphism from Archy, colors, typography, spacing
|
||||
globs: "**/*.vue,**/*.css,**/*.ts"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Design System
|
||||
|
||||
All glass morphism, container, and button patterns are ported from the Archy project and must match exactly.
|
||||
|
||||
## Glass Morphism Hierarchy (from Archy)
|
||||
|
||||
### Glass Intensity Levels
|
||||
| Class | Background | Blur | Use Case |
|
||||
|-------|-----------|------|----------|
|
||||
| `.glass` | `rgba(0,0,0,0.35)` | 18px | Sidebar, panels, inputs |
|
||||
| `.glass-strong` | `rgba(0,0,0,0.35)` | 24px | Headers, message bubbles (user) |
|
||||
| `.glass-card` | `rgba(0,0,0,0.65)` | 18px | Primary cards, modals, main containers |
|
||||
| `.gradient-card` | gradient white→black | 18px | Feature cards |
|
||||
| `.gradient-card-dark` | gradient black→black | 18px | Dark feature cards |
|
||||
|
||||
All share: `border: 1px solid rgba(255,255,255,0.18)`, `box-shadow: 0 8px 24px rgba(0,0,0,0.45)`.
|
||||
|
||||
### Button Hierarchy (from Archy)
|
||||
| Class | Purpose | Details |
|
||||
|-------|---------|---------|
|
||||
| `.glass-button` | Default | 48px height, `rgba(0,0,0,0.6)`, blur 18px |
|
||||
| `.glass-button-sm` | Compact | Auto height, smaller padding |
|
||||
| Ghost | Icon/text actions | `p-2 rounded-lg text-white/70 hover:text-white hover:bg-white/10` |
|
||||
|
||||
### Inset Highlight
|
||||
Signature Archy top-edge glow on focused/active elements:
|
||||
```css
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
```
|
||||
|
||||
### Gradient Border (CSS mask technique)
|
||||
For premium-feel borders on selected cards and active nav:
|
||||
```css
|
||||
::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 2px;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), transparent);
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
## Design Tokens
|
||||
|
||||
### Color Palette
|
||||
Semantic color tokens defined by purpose:
|
||||
- `primary` — main brand actions (#606060)
|
||||
- `accent` — highlight, Bitcoin orange (#F7931A)
|
||||
- `success` — positive states (#10B981)
|
||||
- `error` — negative states (#EF4444)
|
||||
- `warning` — caution states (#F59E0B)
|
||||
- `info` — informational (#3B82F6)
|
||||
|
||||
### Glass Tokens (from Archy Tailwind config)
|
||||
- `glass-dark`: `rgba(0, 0, 0, 0.35)`
|
||||
- `glass-darker`: `rgba(0, 0, 0, 0.6)`
|
||||
- `glass-border`: `rgba(255, 255, 255, 0.18)`
|
||||
- `glass-highlight`: `rgba(255, 255, 255, 0.22)`
|
||||
|
||||
### Shadows
|
||||
- `shadow-glass`: `0 8px 24px rgba(0, 0, 0, 0.45)`
|
||||
- `shadow-glass-sm`: `0 6px 18px rgba(0, 0, 0, 0.35)`
|
||||
- `shadow-glass-inset`: `inset 0 1px 0 rgba(255, 255, 255, 0.22)`
|
||||
|
||||
### Typography
|
||||
- Body font: Inter, system-ui (AIUI default)
|
||||
- Mono font: Menlo, Monaco, Courier New
|
||||
- Text opacity scale: `text-white/25` (placeholders), `text-white/40` (muted), `text-white/60` (secondary), `text-white/70` (interactive default), `text-white/80` (body), `text-white/90` (emphasis), `text-white/96` (headings), `text-white` (active/selected)
|
||||
|
||||
### Spacing
|
||||
4px grid: `4, 8, 12, 16, 20, 24, 28, 32` px.
|
||||
|
||||
### Border Radius
|
||||
- `rounded-lg` (8px) — buttons, nav items, inputs
|
||||
- `rounded-xl` (12px) — toasts, small cards
|
||||
- `rounded-2xl` (16px) — main cards, modals
|
||||
- `rounded-3xl` (24px) — bottom sheets
|
||||
- `rounded-full` — pills, avatars, FABs
|
||||
- `1rem` (16px) — `.glass-card` default
|
||||
|
||||
## Component Patterns
|
||||
|
||||
### Cards
|
||||
Use `.glass-card` with additional padding:
|
||||
```html
|
||||
<div class="glass-card p-6">Content</div>
|
||||
```
|
||||
|
||||
### Modals
|
||||
```html
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm">
|
||||
<div class="glass-card p-6 max-w-md w-full">...</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Icons
|
||||
- SVG, using `currentColor`
|
||||
- Sizes: 16px, 20px, 24px, 32px
|
||||
- Icon-only buttons: `p-2 rounded-lg` (reaches 44px touch target with 24px icon)
|
||||
- Must have `aria-label`
|
||||
|
||||
## Theme Architecture
|
||||
- Base background: `#0a0a0a` (near-black)
|
||||
- No separator borders between sidebar/header/content
|
||||
- Header, sidebar, root share same visual weight
|
||||
- CSS-based themes with reactive Vue state
|
||||
- `localStorage` persistence
|
||||
@@ -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
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
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
|
||||
|
||||
## Content Type Expert Rules
|
||||
For extraction, parsing, and surfacing logic, see:
|
||||
- `20-content-films.mdc` — Films
|
||||
- `21-content-songs.mdc` — Songs (includes looksLikeSong blocklist)
|
||||
- `22-content-podcasts.mdc` — Podcasts (includes looksLikePodcast)
|
||||
- `23-content-news.mdc` — News + RSS, ArticleDetail security
|
||||
- `24-content-websites.mdc` — Websites vs News, overlay
|
||||
- `25-content-magazine.mdc` — Magazine/Brief parsing, hero, meme
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,173 @@
|
||||
---
|
||||
description: Mobile UX patterns informed by Apple iOS HIG — touch targets, typography, spacing, navigation, animations
|
||||
globs: "**/*.vue,**/*.css"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Mobile UX (iOS HIG-Informed)
|
||||
|
||||
## Philosophy
|
||||
Design for mobile first, enhance for desktop. Follow Apple iOS Human Interface Guidelines for sizing, spacing, and interaction patterns. Adapt native iOS conventions to our glass morphism dark theme.
|
||||
|
||||
## Typography (iOS Dynamic Type Mapped to CSS)
|
||||
|
||||
| iOS Text Style | Default Size | CSS Equivalent | AIUI Usage |
|
||||
|---|---|---|---|
|
||||
| Large Title | 34pt | `text-[34px]` / `text-3xl` | Page titles (rare) |
|
||||
| Title 1 | 28pt | `text-[28px]` / `text-2xl` | Section headers |
|
||||
| Title 2 | 22pt | `text-[22px]` / `text-xl` | Sub-section headers |
|
||||
| Title 3 | 20pt | `text-[20px]` / `text-lg` | Card titles |
|
||||
| Headline | 17pt semibold | `text-[17px] font-semibold` | Emphasis labels |
|
||||
| Body | 17pt | `text-[17px]` / `text-base` | Primary content |
|
||||
| Callout | 16pt | `text-[16px]` | Secondary content |
|
||||
| Subheadline | 15pt | `text-[15px]` | Metadata |
|
||||
| Footnote | 13pt | `text-[13px]` / `text-xs` | Timestamps, captions |
|
||||
| Caption 1 | 12pt | `text-[12px]` | Badges, small labels |
|
||||
| Caption 2 | 11pt | `text-[11px]` | Smallest text (tab labels) |
|
||||
|
||||
### Key rules
|
||||
- **Minimum text size**: 11px (Caption 2) — never go smaller
|
||||
- **Body text on mobile**: 17px (not 14px/16px) for comfortable reading
|
||||
- Use `text-sm` (14px) sparingly — only for dense UI, not primary reading content
|
||||
- Chat messages should use at least 15-16px on mobile
|
||||
- Metadata/timestamps: 11-13px is acceptable
|
||||
|
||||
## Touch Targets
|
||||
|
||||
| Rule | Value | Tailwind |
|
||||
|---|---|---|
|
||||
| Minimum tap target | **44 × 44px** | `min-w-[44px] min-h-[44px]` |
|
||||
| Minimum gap between targets | **8px** | `gap-2` |
|
||||
| Comfortable button height | 44-50px | `h-11` to `h-[50px]` |
|
||||
| iOS nav bar button | 44px | `h-11` |
|
||||
|
||||
### Key rules
|
||||
- The 44px minimum applies to the **tappable area**, not the visual size
|
||||
- A 24px icon can have a 44px tap target via padding: `p-2.5` on a 24px icon
|
||||
- Our `w-9 h-9` (36px) header buttons are below 44px — compensate with generous spacing or padding hit areas
|
||||
- Text buttons must extend touch target beyond text bounds
|
||||
|
||||
## Spacing & Layout
|
||||
|
||||
| Element | iOS Value | CSS |
|
||||
|---|---|---|
|
||||
| Side margins (iPhone) | 16px | `px-4` |
|
||||
| Nav bar height | 44px | `h-11` |
|
||||
| Tab bar height | 49px (+34px safe area) | `h-[49px]` + `pb-[env(safe-area-inset-bottom)]` |
|
||||
| Bottom safe area (notch) | 34px | `env(safe-area-inset-bottom)` |
|
||||
| Search bar | 36px field + 8px padding | `h-9` + `py-1` |
|
||||
| Standard content inset | 16px horizontal | `px-4` |
|
||||
|
||||
### Safe area insets
|
||||
```css
|
||||
/* Always use for full-screen layouts */
|
||||
padding-top: env(safe-area-inset-top);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
height: 100dvh; /* Dynamic viewport height — avoids iOS Safari toolbar */
|
||||
```
|
||||
|
||||
## Navigation Patterns
|
||||
|
||||
### iOS-native patterns to follow
|
||||
- **Primary navigation**: Bottom tab bar (persists across screens)
|
||||
- **Secondary navigation**: Top nav bar with back button (left) and actions (right)
|
||||
- **Modals**: Sheet sliding up from bottom (half-screen or full)
|
||||
- **Context menus**: Long-press or action sheets from bottom
|
||||
|
||||
### Primary action placement
|
||||
```
|
||||
Top 20%: Navigation, info, secondary actions
|
||||
Middle 60%: Main content (scrollable)
|
||||
Bottom 20%: Primary actions (thumb zone) — send, approve, play
|
||||
```
|
||||
|
||||
### Sheets & modals on mobile
|
||||
- Use bottom sheets with three detents: small (~25%), medium (~50%), large (full)
|
||||
- Always provide a close button — don't rely solely on swipe-to-dismiss
|
||||
- Content panels: full-screen overlay or bottom sheet, never side-by-side
|
||||
|
||||
## Form Inputs
|
||||
|
||||
| Rule | Value | Why |
|
||||
|---|---|---|
|
||||
| **Minimum input font** | **16px** | Prevents iOS Safari auto-zoom on focus |
|
||||
| Minimum field height | 44px | Matches tap target |
|
||||
| Use `inputmode` | `numeric`, `email`, `tel`, `url`, `search` | Shows appropriate keyboard |
|
||||
| Use `autocomplete` | Standard attributes | Enables autofill |
|
||||
| Submit button placement | Bottom of form, thumb zone | Easy to reach |
|
||||
|
||||
## Animations & Motion (iOS Spring Model)
|
||||
|
||||
### Duration guidelines
|
||||
| Type | Duration | Tailwind |
|
||||
|---|---|---|
|
||||
| Micro-interaction (tap, toggle) | 100-200ms | `duration-150` |
|
||||
| Standard transition (push/pop) | 250-350ms | `duration-300` |
|
||||
| Modal presentation (sheet) | 300-400ms | `duration-300` |
|
||||
| Complex transitions | 400-500ms | `duration-500` |
|
||||
|
||||
### iOS-style easing
|
||||
```css
|
||||
/* Standard iOS-like transition (ease out / decelerate) */
|
||||
transition: transform 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.0);
|
||||
|
||||
/* Bouncy spring-like (for playful entrances) */
|
||||
transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
|
||||
/* Quick snap (micro-interactions) */
|
||||
transition: transform 0.25s cubic-bezier(0.0, 0.0, 0.2, 1.0);
|
||||
```
|
||||
|
||||
### Motion rules
|
||||
- Entrances: ease-out (decelerate)
|
||||
- Exits: ease-in (accelerate)
|
||||
- Only animate `transform` and `opacity`
|
||||
- **Always** respect `prefers-reduced-motion`:
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Gestures
|
||||
- Swipe left/right: gallery nav, 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
|
||||
|
||||
## 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
|
||||
|
||||
## Iconography
|
||||
| Context | Size | Style |
|
||||
|---|---|---|
|
||||
| Tab bar | 25px | Filled/solid |
|
||||
| Nav bar / toolbar | 22px | Outlined, 1.5px stroke |
|
||||
| Inline with text | Match font size | Outlined |
|
||||
| Standalone | 28-33px | Filled or outlined |
|
||||
|
||||
## AIUI Custom Overrides (Keep These)
|
||||
These deviate from stock iOS but are intentional for our design language:
|
||||
- **Dark-only theme**: No light mode. Background `#0a0a0a`, not iOS system colors
|
||||
- **Glass morphism**: Translucent surfaces with backdrop-blur instead of iOS solid materials
|
||||
- **Accent color**: Bitcoin orange `#F7931A` instead of iOS systemBlue
|
||||
- **Text opacity scale**: Our `/25` → `/96` scale instead of iOS label hierarchy
|
||||
- **No separator borders**: We use spacing and glass layering instead
|
||||
- **Custom animations**: `animate-fade-up`, `animate-scale-in` per our design system
|
||||
- **Header buttons**: Currently 36px (`w-9 h-9`), acceptable with generous spacing
|
||||
|
||||
## 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
|
||||
@@ -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`
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
description: Expert rules for Film content extraction, display, and surfacing
|
||||
globs: "**/useContentPanel.ts,**/FilmCard.vue,**/FilmGrid.vue,**/FilmDetail.vue,**/mocks/films*"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Films Content Surface
|
||||
|
||||
## Extraction Patterns
|
||||
|
||||
- **Tagged**: `[[film:f123]]` or `[[film:123]]` → resolved from mock library
|
||||
- **External**: `[[film_ext:Title|YYYY|Director]]` → create external film with fallback poster
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- `normalizeFilmId`: `f123` and `123` both become `f123`
|
||||
- Duplicate prevention: key by `title|year` for externals
|
||||
- Empty/malformed: skip if title < 2 chars, year invalid
|
||||
- Poster: use `generatePosterFallback(title, year)` for externals
|
||||
|
||||
## Strip Rules
|
||||
|
||||
- `stripFilmTags` removes `[[film:...]]` and `[[film_ext:...]]` before displaying text
|
||||
- Preserve `\n{3,}` → `\n\n` to avoid excessive whitespace
|
||||
|
||||
## Display
|
||||
|
||||
- FilmCard: poster, title, year, director
|
||||
- FilmDetail: full metadata, sources, cast
|
||||
- Panel: grid of FilmCards, click opens FilmDetail in panel
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
description: Expert rules for Song content extraction, display, and surfacing
|
||||
globs: "**/useContentPanel.ts,**/SongCard.vue,**/SongGrid.vue,**/SongDetail.vue,**/mocks/songs*"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Songs Content Surface
|
||||
|
||||
## Extraction Priority
|
||||
|
||||
1. Tagged: `[[song:s123]]` or `[[song_ext:Title|Artist|YYYY]]`
|
||||
2. Library match: title + artist within 120 chars
|
||||
3. Patterns: `"Title" by Artist`, `Title – Artist`, `**Title** by Artist`
|
||||
|
||||
## looksLikeSong Rejection
|
||||
|
||||
Reject when title/artist contains: news phrases, "BIP", "protocol", "web search", "mailing list", "training cutoff", etc. See `looksLikeSong()` blocklist.
|
||||
|
||||
- Max length: title 55 chars, artist 40 chars
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- If `extractFilmIds` or `extractPodcastIds` found → return [] (don't mix film/podcast with song patterns)
|
||||
- If `isNewsLikeResponse` → return [] (news bullets often look like "X – Y")
|
||||
- Skip if title/artist is 4-digit year
|
||||
- Skip if contains `[[film` or `[[song` tags
|
||||
- Dedupe by `title|artist` lowercase
|
||||
|
||||
## Strip Rules
|
||||
|
||||
- `stripSongTags` removes song tags before displaying text
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
description: Expert rules for Podcast content extraction, display, and surfacing
|
||||
globs: "**/useContentPanel.ts,**/PodcastCard.vue,**/PodcastGrid.vue,**/PodcastDetail.vue,**/mocks/podcasts*"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Podcasts Content Surface
|
||||
|
||||
## Extraction Patterns
|
||||
|
||||
- **Tagged**: `[[podcast:p123]]` or `[[podcast_ext:Title|Host|YYYY]]`
|
||||
- No pattern fallback (unlike songs) — only tags
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- Duplicate prevention: key by `title|host` lowercase
|
||||
- Empty: skip if title or host < 2 chars
|
||||
- Year optional in external format
|
||||
|
||||
## looksLikePodcast (when added)
|
||||
|
||||
Reject when title/host looks like: news source names, documentation sites, "Bitcoin Mailing List", etc. — same philosophy as `looksLikeSong`.
|
||||
|
||||
## Strip Rules
|
||||
|
||||
- `stripPodcastTags` removes podcast tags before displaying text
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
description: Expert rules for News content extraction, merge, and surfacing
|
||||
globs: "**/useContentPanel.ts,**/useRssFetch.ts,**/NewsGrid.vue,**/ArticleDetail.vue,**/vite-rss*"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# News Content Surface
|
||||
|
||||
## Sources
|
||||
|
||||
1. **Web search**: `message.webResults` from AI (with imgSrc, content)
|
||||
2. **RSS**: Fetched from website URLs only when `newsContext` is true
|
||||
|
||||
## newsContext
|
||||
|
||||
- `isNewsQuery(userQuery)` — "news", "latest", "what's happening", "what are people saying", etc.
|
||||
- `isNewsLikeResponse(text)` — "for instant news", "check these sources", "access to web search", etc.
|
||||
|
||||
## Merge Rules
|
||||
|
||||
- `mergeNewsResults(web, rss)` — dedupe by URL (normalized: lowercase, no trailing slash)
|
||||
- Web results take precedence when URL collision
|
||||
|
||||
## RSS Fetch Guard
|
||||
|
||||
- **Only fetch RSS when `newsContext` is true and `mergedWebsites.length > 0`** — avoid surfacing irrelevant RSS from docs/resource links when user asked "websites"
|
||||
- Max 8 URLs, 15 articles total, 5 sites tried
|
||||
- Timeout: 15s client, 5s per feed server-side
|
||||
|
||||
## Display
|
||||
|
||||
- NewsGrid (variant=news): articles open in **ArticleDetail** (in-panel)
|
||||
- Relevance sort when `query` provided
|
||||
- Search filter by title, content, url
|
||||
- imgSrc: validate with `isSafeImgUrl` (https only)
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- **RSS language**: Feeds return whatever the site publishes; no query/language filtering — may surface non-English articles
|
||||
- **RSS relevance**: No semantic filtering; articles are shown as published
|
||||
|
||||
## ArticleDetail Security
|
||||
|
||||
- `sanitizeHtml`: allow only safe tags (p, br, a, strong, em, ul, ol, li, blockquote, h1-h4)
|
||||
- Strip script, style, iframe, object, embed
|
||||
- Links: `href` must be `https?://`, reject `javascript:`
|
||||
- Images: `src` must be `https?://`
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
description: Expert rules for Websites content extraction and surfacing
|
||||
globs: "**/useContentPanel.ts,**/NewsGrid.vue,**/articleOverlay*"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Websites Content Surface
|
||||
|
||||
## Extraction
|
||||
|
||||
1. **Markdown links**: `[Title](https://...)` — extract all with `extractMarkdownLinks`
|
||||
2. **Bold domains**: `**Name** (domain.tld)` — extract with `extractBoldDomainLinks`
|
||||
3. Merge with `mergeNewsResults` (dedupe by URL)
|
||||
|
||||
## URLs Validation
|
||||
|
||||
- Scheme: `https?://` only
|
||||
- `new URL(raw)` must not throw
|
||||
- Min length: title 2, url 10 chars
|
||||
- Normalize for dedupe: lowercase, no trailing slash
|
||||
|
||||
## Display
|
||||
|
||||
- NewsGrid (variant=websites): card with favicon/globe icon
|
||||
- Click → **overlay iframe** (not ArticleDetail)
|
||||
- Use `articleOverlayStore.open(url, title, undefined, imgSrc)`
|
||||
|
||||
## Distinction from News
|
||||
|
||||
- News = articles (web search + RSS) → ArticleDetail in panel
|
||||
- Websites = plain links from response → overlay iframe
|
||||
- Same NewsGrid component, different `variant` and click handler
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
description: Expert rules for Magazine/Brief content extraction and surfacing
|
||||
globs: "**/useContentPanel.ts,**/MagazineGrid.vue"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Magazine Content Surface
|
||||
|
||||
## Detection
|
||||
|
||||
- `hasMagazine` = sections ≥ 1 AND (newsQuery OR newsLikeResponse OR context keywords)
|
||||
- Context keywords: sentiment, bearish, bull case, macro, %, BTC, bitcoin, BIP, protocol, debate, what's happening
|
||||
|
||||
## Section Extraction Order
|
||||
|
||||
1. `## Heading` blocks — content until next ## or **Section**
|
||||
2. `**Pro/Anti camp**` blocks with emoji
|
||||
3. Bullets: `- **Title**: Content` or `- **Title** — Content` (em/en dash)
|
||||
4. Attributed: `- **Name** (Role) description`
|
||||
5. Intro paragraph (before first ##)
|
||||
6. "Key takeaway" / "This is being called..."
|
||||
7. "For deeper analysis" / further reading
|
||||
|
||||
## Section Rules
|
||||
|
||||
- Min: title 2 chars, content 15 chars
|
||||
- Max content: 2000 chars per section
|
||||
- Dedupe by title prefix (first 50 chars)
|
||||
- Skip bullets already inside ## blocks (`blockContents`)
|
||||
- `addSection` extracts: url, author, imageUrl from content
|
||||
|
||||
## Hero Image
|
||||
|
||||
1. First markdown image in text
|
||||
2. First `.jpg|.png|.gif|.webp` URL
|
||||
3. `webResults[0]?.imgSrc`
|
||||
4. Picsum fallback seeded by query
|
||||
|
||||
## Format & Security
|
||||
|
||||
- `formatContent`: escape `&<>`, preserve `**bold**` as `<strong>`, `\n\n` → `</p><p>`
|
||||
- Meme: imgflip URLs, contextual by topic (bearish, bull, Bitcoin, macro)
|
||||
Reference in New Issue
Block a user