245 lines
12 KiB
Markdown
245 lines
12 KiB
Markdown
# Mimik
|
|
|
|
Open-source Chrome extension that auto-captures browser workflows and generates step-by-step guides. No backend, no account, no data leaves the browser.
|
|
|
|
## What It Does
|
|
|
|
You click "Record," perform a workflow in your browser, and Mimik automatically captures each action as a step with an annotated screenshot and description. You can edit the guide, replay it on a live page, or export it as a file.
|
|
|
|
**Core loop: Record → Edit → Replay or Export.**
|
|
|
|
## Architecture
|
|
|
|
**Everything runs in the Chrome extension. No backend.**
|
|
|
|
- Storage: IndexedDB via Dexie.js (browser-local)
|
|
- AI descriptions: optional, user provides their own API key in settings
|
|
- Export: generated client-side (no server rendering)
|
|
- No auth, no database, no hosting, no Docker
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── core/ # Business logic (no UI dependencies)
|
|
│ ├── capture/ # Recording pipeline
|
|
│ │ ├── ai/ # AI description + title generation (Vercel AI SDK)
|
|
│ │ │ ├── description.ts # getAIDescription (DOM context → AI → step text)
|
|
│ │ │ ├── title.ts # generateGuideTitle (steps → AI → guide name)
|
|
│ │ │ ├── models.ts # AI_PROVIDERS config (OpenAI/Anthropic model lists)
|
|
│ │ │ ├── prompts.ts # Prompt templates
|
|
│ │ │ └── provider.ts # createModel factory (OpenAI/Anthropic)
|
|
│ │ ├── dom/ # DOM extraction utilities
|
|
│ │ │ ├── context.ts # DOMContext extraction + serialization
|
|
│ │ │ ├── element-meta.ts # extractElementMeta (selector, text, aria, rect)
|
|
│ │ │ └── element-utils.ts # findFocusableAncestor, isTextField, etc.
|
|
│ │ ├── events/ # Event capture system
|
|
│ │ │ ├── handlers.ts # CaptureController class + startCapture
|
|
│ │ │ ├── highlight.ts # HighlightManager (dashed border overlay)
|
|
│ │ │ └── input-session.ts # InputSession (typing lifecycle)
|
|
│ │ ├── machine.ts # xstate capture state machine
|
|
│ │ ├── rrweb-recorder.ts # DOM recording for replay
|
|
│ │ ├── session.ts # CaptureSession (lifecycle manager)
|
|
│ │ ├── spa-nav.ts # SPA navigation tracking
|
|
│ │ ├── start-notification.ts # Recording notification overlay
|
|
│ │ └── step-description.ts # Fallback rule-based descriptions
|
|
│ ├── blur/ # Smart blur: regex presets, DOM scanner, element picker, panel UI
|
|
│ ├── export/ # HTML, PDF, Markdown export generators + shared utils
|
|
│ └── guides/ # Data layer: types, Dexie DB, CRUD service
|
|
├── entrypoints/ # Chrome extension entry points (WXT)
|
|
│ ├── background/ # Service worker: state machine, message handlers, tab management
|
|
│ ├── content.ts # Content script: CaptureSession, event listeners, rrweb
|
|
│ ├── sidepanel/ # Side panel React mount
|
|
│ ├── fullview/ # Full-page view React mount
|
|
│ ├── onboarding/ # Onboarding wizard (opens on first install)
|
|
│ └── options/ # Settings page React mount
|
|
├── lib/ # Shared utilities
|
|
│ ├── messaging.ts # Extension messaging protocol (webext-core)
|
|
│ ├── port.ts # Long-lived port: background ↔ sidepanel
|
|
│ ├── browser-api.ts # Chrome API wrappers
|
|
│ ├── tab-messages.ts # Content script message types
|
|
│ ├── logger.ts # Logging utility
|
|
│ └── utils.ts # Shared helpers (dates, URLs, cn)
|
|
├── stores/ # Zustand state stores
|
|
│ └── fullview.ts # Fullview UI state (search, counts, guide data)
|
|
└── ui/ # React components
|
|
├── components/ui/ # shadcn/ui primitives (button, input, dialog, badge)
|
|
├── fullview/ # Full-page dashboard
|
|
│ ├── components/ # Extracted sub-components (grid, list, search, etc.)
|
|
│ ├── App.tsx
|
|
│ ├── TopNav.tsx
|
|
│ ├── SearchModal.tsx
|
|
│ ├── GuideContent.tsx
|
|
│ ├── LibraryContent.tsx
|
|
│ └── router.ts
|
|
├── sidepanel/ # Side panel UI
|
|
│ ├── App.tsx
|
|
│ ├── LibraryView.tsx
|
|
│ ├── GuideEditor.tsx
|
|
│ ├── RecordingView.tsx
|
|
│ ├── StepCard.tsx
|
|
│ ├── ExportMenu.tsx
|
|
│ ├── BlurCanvas.tsx
|
|
│ └── ZoomScreenshot.tsx
|
|
├── onboarding/ # Onboarding wizard UI
|
|
│ └── App.tsx # 5-step wizard (welcome, AI, blur, pin, done)
|
|
├── shared/ # Shared UI components
|
|
│ └── SettingsView.tsx # AI settings (provider, model, API key)
|
|
└── options/ # Settings page
|
|
└── App.tsx
|
|
```
|
|
|
|
## State Management
|
|
|
|
| Layer | Tool | Purpose |
|
|
|-------|------|---------|
|
|
| Capture lifecycle | xstate | State machine (IDLE ↔ RECORDING) in background service worker |
|
|
| Fullview UI | Zustand | Search modal, guide counts, active guide data |
|
|
| Persistence | Dexie (IndexedDB) | Guides, steps, screenshots, rrweb chunks |
|
|
| Service worker recovery | sessionStorage | xstate machine snapshot persistence |
|
|
| Background → Sidepanel | Port messaging | Real-time state broadcast |
|
|
| Cross-context sync | BroadcastChannel | Guide mutations (star, delete) across sidepanel/fullview |
|
|
|
|
## Extension Entry Points
|
|
|
|
| Entry Point | File | Purpose |
|
|
|-------------|------|---------|
|
|
| Background | `entrypoints/background/` | Service worker: xstate actor, message handlers, tab management |
|
|
| Content Script | `entrypoints/content.ts` | Injected into all tabs: CaptureSession, event listeners, rrweb |
|
|
| Side Panel | `entrypoints/sidepanel/` | Recording controls, library, guide editor, settings |
|
|
| Full View | `entrypoints/fullview/` | Dashboard: library browse, guide viewer, Ctrl+K search |
|
|
| Onboarding | `entrypoints/onboarding/` | First-install wizard: AI setup, smart blur, pin extension |
|
|
| Options | `entrypoints/options/` | Settings page (shared SettingsView in centered card) |
|
|
|
|
## Messaging
|
|
|
|
```
|
|
Content Script ←→ Background Service Worker ←→ Sidepanel / Fullview
|
|
```
|
|
|
|
**Extension messages** (webext-core, `lib/messaging.ts`):
|
|
- `getState` → current capture state, step count, guide ID
|
|
- `startRecording({url})` → creates guide, returns guideId
|
|
- `stopRecording()` → finalizes guide, generates AI title
|
|
- `captureStep({guideId, action, elementMeta, domContext})` → screenshots + creates step
|
|
- `updateInputStep({stepId, description})` → updates typing step description
|
|
- `finalizeInputStep({stepId, elementMeta, domContext})` → final screenshot + AI description
|
|
- `rrwebChunk({guideId, events, timestamp})` → stores DOM recording chunk
|
|
|
|
**Tab messages** (content script ↔ background, `lib/tab-messages.ts`):
|
|
- `PING` / `START_CAPTURE` / `STOP_CAPTURE` — lifecycle
|
|
- `HIDE_OVERLAY` / `SHOW_OVERLAY` — overlay toggle (fallback, used for non-click captures)
|
|
- `SHOW_NOTIFICATION` — "Recording started" overlay
|
|
- `URL_CHANGED` / `GET_ROUTE` — SPA navigation tracking
|
|
|
|
## Capture Pipeline
|
|
|
|
**Start recording:**
|
|
1. User clicks "Start Capture" in sidepanel
|
|
2. Background transitions xstate machine IDLE → RECORDING
|
|
3. Creates Guide in IndexedDB, broadcasts `START_CAPTURE` to all tabs
|
|
4. Content scripts create CaptureSession → CaptureController (event listeners) + rrweb
|
|
5. Shows recording notification overlay on active tab
|
|
|
|
**Capture a click:**
|
|
1. Content script's CaptureController detects click via DOM event listener
|
|
2. Click handler pushes async work into PQueue (concurrency: 1)
|
|
3. Queue processes: hides overlay instantly → sends `captureStep` to background → waits
|
|
4. Background calls `captureVisibleTab` (overlay is hidden, page hasn't reacted yet)
|
|
5. Saves Screenshot + Step to IndexedDB
|
|
6. Optionally generates AI description from DOM context text (not screenshot)
|
|
7. Returns `{ stepId }` → content script shows overlay → queue processes next event
|
|
|
|
**Capture text input (typing):**
|
|
1. Click on text field → CaptureController starts InputSession → `captureStep` with initial screenshot
|
|
2. Each keystroke → InputSession.update() → `updateInputStep` (description only, fire-and-forget)
|
|
3. Enter/Escape/focusout → InputSession.finalize() → `finalizeInputStep` → final screenshot replaces initial + AI description
|
|
4. Result: one step for entire typing interaction
|
|
|
|
**Stop recording:**
|
|
1. Background transitions RECORDING → IDLE
|
|
2. Broadcasts `STOP_CAPTURE`, content scripts flush pending input sessions + rrweb events
|
|
3. Background generates guide title from step descriptions + URLs via AI
|
|
4. Opens fullview dashboard with the guide
|
|
|
|
## DOM Context (AI Input)
|
|
|
|
Instead of sending screenshots to the AI for step descriptions, Mimik extracts a lightweight DOM context (~50-100 tokens) from around the target element:
|
|
|
|
```
|
|
Page: "Public profile - Settings" /settings/profile
|
|
Container: form "Public profile"
|
|
Heading: "Public profile"
|
|
Siblings: input "Name", input "Email", textarea "Bio", button "Update profile"
|
|
→ Target: button "Update profile" (click)
|
|
```
|
|
|
|
Extraction walks up from the target element to find:
|
|
- Page title + URL path
|
|
- Nearest semantic container (form, nav, dialog, section) or 3 levels up
|
|
- Nearest heading
|
|
- Sibling interactive elements in the same container (max 10)
|
|
|
|
## Export Formats
|
|
|
|
| Format | Generator | Details |
|
|
|--------|-----------|---------|
|
|
| HTML | `core/export/html-export.ts` | Self-contained, base64 images, inline CSS |
|
|
| PDF | `core/export/pdf-export.ts` | jsPDF, A4 portrait, auto page breaks |
|
|
| Markdown | `core/export/markdown-export.ts` | Standard MD with base64 image data URLs |
|
|
|
|
## Tech Stack
|
|
|
|
| Layer | Technology |
|
|
|-------|-----------|
|
|
| Extension framework | WXT (Manifest V3) |
|
|
| Language | TypeScript |
|
|
| UI | React 19 |
|
|
| Styling | Tailwind CSS v4 |
|
|
| Components | shadcn/ui |
|
|
| State (capture) | xstate |
|
|
| State (UI) | Zustand |
|
|
| Storage | Dexie.js (IndexedDB) |
|
|
| Messaging | webext-core |
|
|
| Session recording | rrweb |
|
|
| Export | jsPDF, client-side HTML/Markdown |
|
|
| AI (optional) | Vercel AI SDK (`ai`, `@ai-sdk/openai`, `@ai-sdk/anthropic`) |
|
|
| Event queue | p-queue (concurrency: 1) |
|
|
| Icons | Lucide React |
|
|
| Dates | dayjs |
|
|
| DOM utils | css-selector-generator |
|
|
|
|
## Design System
|
|
|
|
All colors are defined as CSS variables in `src/ui/global.css` and used via Tailwind classes:
|
|
|
|
| Token | Color | Usage |
|
|
|-------|-------|-------|
|
|
| `--color-foreground` | `#1E1B4B` | Primary text (deep navy) |
|
|
| `--color-muted-foreground` | `#6B7280` | Secondary text |
|
|
| `--color-border` | `#C7D2FE` | Borders, dividers (lavender) |
|
|
| `--color-secondary` | `#EEF2FF` | Light wash backgrounds |
|
|
| `--color-accent` | `#4F46E5` | Accent / interactive (indigo) |
|
|
| `--color-primary` | `#1E1B4B` | Dark backgrounds, badges |
|
|
| `--color-primary-foreground` | `#C7D2FE` | Text on dark backgrounds |
|
|
| `--color-lavender` | `#C7D2FE` | Soft accent |
|
|
| `--color-purple` | `#4F46E5` | Primary indigo |
|
|
| `--color-deep` | `#1E1B4B` | Deepest navy |
|
|
| `--color-violet` | `#38BDF8` | Sky blue accent |
|
|
| `--color-success` | `#059669` | Success green |
|
|
|
|
Font: Poppins (loaded via `@fontsource/poppins`).
|
|
|
|
## Key Technical Details
|
|
|
|
- **Async event queue** in content script (PQueue, concurrency 1) serializes capture work — each action awaits the full background round-trip before the next starts
|
|
- **Overlay hidden from content script side** (instant `display:none`) before sending capture message — no round-trip delay for overlay toggle
|
|
- **Input session** aggregates all typing on a field into one step — click creates it, keystrokes update description, finalize takes final screenshot
|
|
- **DOM context** sent as text to AI instead of screenshots — 15-30x cheaper per step
|
|
- **Highlight overlay** uses a custom web component (`<mimik-highlight>`) with closed Shadow DOM at max z-index
|
|
- **Content script injection** pings first, falls back to `chrome.scripting.executeScript()` for tabs without the script
|
|
- **xstate snapshot** persisted to sessionStorage so the state machine survives service worker restarts
|
|
- **Recording notification** uses `animationend` event (not hardcoded delays) for timing
|
|
- **Font loading** uses `@fontsource/poppins` (CSP-safe, no CDN dependency)
|
|
- **Cross-context sync** via BroadcastChannel — star/delete events update other views without full reload
|