<p align="center">
  <img src="https://raw.githubusercontent.com/popmelt/core/main/src/assets/popmelt-logo.svg" alt="Popmelt" width="72" />
</p>

# Popmelt

Popmelt is a tiny, powerful design layer for humans and AI. Turn your local web app into a design sandbox with the AI tools you already love.

Run your app locally and Popmelt appears as a toolbar in the browser. Point to any part of your UI, describe what you have in mind, or steer the design directly. Work through the result with Codex or Claude Code in a thread right on the page. Stack as many threads as you want so different ideas can move forward without losing their context.

As you collaborate, Popmelt builds an Imprint: a living project memory of your intent, preferences, and design decisions. Later threads use that shared understanding instead of starting from scratch.

Runs locally, learns naturally. Your annotations, screenshots, threads, and Imprint live with your project under `.popmelt/`.

## What you need

- A local web project: React is the established integration; vanilla HTML/JS with Vite, Vue, Nuxt, Svelte/SvelteKit, and Astro have experimental DOM integrations
- Node.js 18 or newer, or a newer version required by your framework
- React 18 or newer for a React host; non-React integrations still require the renderer peers listed in their setup instructions
- The Codex CLI or Claude Code CLI installed, available in your terminal, and signed in

The Codex and Claude Code desktop apps work too. Open your local app in their built-in browser and use Popmelt there just as you would in any other browser. The corresponding CLI still needs to be installed and signed in because it runs the threads.

## Install with your coding agent

From inside your project, give your coding agent this prompt:

> Let's install `@popmelt.com/core` in this project

Your agent can detect the framework, add the matching integration, and preserve your existing development command. Restart the development server when it is finished.

For the installing agent: finish with the [installation checks](#verify-the-installation), including the applicable ambient integration below. Report what is verified and any remaining step; adding dependencies alone is not a ready-to-use installation.

For manual instructions, see [Manual setup](#manual-setup). Experimental non-React integrations support DOM annotations and style edits, but do not provide React-specific component hierarchy or library previews.

To add local object recognition to an existing Popmelt project, use:

> Add `@popmelt.com/see` to this Popmelt Core project

The integration installs See and adds it to the existing Core provider's
`extensions` array without replacing Core.

## Try your first change

Pick one thing in your own app that you already want to improve:

1. Open your local app and double-tap Command on macOS or Control on Windows/Linux to open Popmelt.
2. Choose Chat (`C`), click the element, and describe a concrete change—for example, “Give this card more breathing room without changing the text size.”
3. Press Command/Control+Enter to send it, then review the result on the page.
4. Refine it in the same thread: “A little less padding, and keep the button aligned with the text.”

Questions, ideas, or issues? [Email](mailto:reb@popmelt.com) or [DM on X](https://x.com/bekhovsgun).

## Use Popmelt

1. Run your project locally and open it in a browser
2. Double-tap Command on macOS or Control on Windows and Linux to open the Popmelt toolbar
3. Choose Chat (`C`), Steer (`S`), or Imprint (`I`)
4. Point to the relevant UI to add comments or preview design changes, or use Imprint to review the project's shared understanding
5. Press Command+Enter on macOS or Control+Enter on Windows and Linux to hand pending work to your coding agent
6. Review the result in its page-bound thread and keep iterating there

A handoff can contain several comments and changes, including work on different elements. Popmelt keeps that bundle together as one thread and presents its items separately at the beginning of the conversation. Status stays attached to the relevant parts of the page while work is queued or running, and you can reopen a thread from any of its badges. Tool and Imprint receipts show what helped the agent reach its result.

### Chat

Chat pins a comment to a specific element. Click the element, type the comment, and press Enter to finish the draft. Use Shift+Enter for a new line or Command/Control+Enter to send the full pending bundle.

Paste images into a comment to attach visual references. An image-count chip keeps attachments visible in the draft rail; after sending, the images remain in the thread without the extra rail chip.

Double-click visible text to edit it directly. Popmelt records the text change so it can be handed off alongside comments and Steer changes.

### Steer

Steer previews code-native design changes directly on the page. Point to an element, choose a family, narrow to a property, and choose a value. Click the target or press Enter at the value level to stage the preview.

| Keys        | Family                             |
| ----------- | ---------------------------------- |
| `P` / `M`   | Padding / margin                   |
| `B` / `R`   | Borders / radii                    |
| `F` / `G`   | Flexbox / grid                     |
| `FO` / `BG` | Type / background color            |
| `T` / `O`   | Transform / opacity and blend mode |

The omnidial can be driven entirely from the keyboard: `Q` and `E` move between properties, `A` and `D` or the arrow keys move between values, `W` or Enter goes deeper or applies, and `S` or Escape goes back. Press Space to search families, properties, colors, and values by name.

At the value level, padding, margin, gap, radius, and transform controls expose drag handles. Hold Shift while dragging to snap or constrain the change. For spacing and radius handles, hold Option/Alt or Control to isolate the touched side, axis, or corner.

### Imprint

Imprint opens the project's shared understanding. Use its filters to review grounded, emerging, and challenged explanations. Expand an explanation to see its rationale, concrete expressions, related understanding, and recent grounding evidence, or reopen its source thread. Changes to the Imprint are proposed and grounded through collaboration in threads rather than authored from this panel.

### Threads and drafts

- Click an annotation or status badge to open its thread
- Use the left and right chevrons in the thread header to cycle through visible threads
- Drag a thread header to move the panel; double-click the header to restore its default position
- Press Escape to close the open thread
- Press Backspace or Delete with a thread open, or with one of its historical annotations selected, to archive that thread
- Right-click a draft comment or staged Steer change in its annotation rail to remove only that draft item
- Right-click a replied rail to dismiss it from the canvas without deleting its history or reverting applied edits. Press **Command/Control+Z** outside a text editor to restore it before reloading. Rails with fresh drafts or active runs are protected.
- When a reply is queued, use its pencil or trash button to edit or delete that individual message before it starts. Save an edit with the checkmark or Command/Control+Enter; Escape cancels the edit
- Codex permission requests appear chronologically in the thread as a compact action card, while the canvas and toolbar show **Approval needed**. Expand the card to inspect the exact command or file changes, then choose **Allow once** or **Deny**. Answered requests become an inline tool icon and label, retained in the thread history. Chat text is not a permission grant; Popmelt does not create remembered approvals. Your existing Codex permission rules still apply, so previously allowed commands may run without a new card. After 10 seconds without a response, a run waiting solely on approval yields to other queued jobs. Approving it while parked moves it to the front of the waiting queue, without interrupting active work. The exact action resumes only after reacquiring a slot; unanswered requests expire on resumption (or at 10 seconds if the run cannot safely yield). Same-thread follow-ups cannot overtake a parked run, and disconnected runs cannot be approved.

Interactive approvals require a Codex CLI that supports the app-server human-approval protocol (verified with 0.153.3). Popmelt checks the effective sandbox and reviewer before starting a coding turn; it does not fall back to unrestricted execution. Background memory review remains read-only and noninteractive. Network-specific and session-wide permission grants are not supported by this first version.

For an opt-in automatic-review trial, start the bridge or wrapped dev server with `POPMELT_CODEX_APPROVALS_REVIEWER=auto_review`. Eligible Codex approvals go to its native reviewer rather than a human; the sandbox and existing permission rules remain in place. This adds model usage and may still deny an action. The default remains human review (`user`); restarting without the variable restores it. Other providers are unchanged.

Archiving hides a thread from the active canvas without deleting its stored conversation. Delete shortcuts are disabled while a text field is focused.

### Pointer gestures

| Gesture                                            | Action                                                                   |
| -------------------------------------------------- | ------------------------------------------------------------------------ |
| Shake the pointer quickly                          | Switch between Chat and Steer                                            |
| Hold Command/Control while pointing                | Pass through the default target to the deepest element under the pointer |
| Double-click text in Chat                          | Edit the text directly                                                   |
| Click the toolbar annotation counter               | Cycle through annotation bundles, including other routes                 |
| Scroll the annotation counter                      | Change the active annotation color                                       |
| Hold the annotation counter                        | Reset the active color                                                   |
| Right-click Popmelt's canvas                       | Enter persistent page passthrough while keeping Popmelt overlays visible |
| Drag, scroll, pinch, or right-click in passthrough | Interact with the page and open its native context menu                  |
| Click a valid target in passthrough                | Apply the active Comment or Steer action without leaving passthrough     |
| Press Escape or choose another tool                | Exit passthrough                                                         |

## Ambient Popmelt in Codex

Popmelt can also pair ordinary project-bound Codex conversations with the same local project memory. On supported Codex desktop or CLI surfaces, include this in setup unless the user wants toolbar-only use or has disabled the integration.

The bundled `popmelt-core` plugin installs the skill, MCP tools, and lifecycle hooks together; do not install separate copies of the skill or hooks. This is a personal installation, not just a project dependency. Explain that the hooks supply project context and record project-bound turns, and obtain any required permission before changing personal configuration. Preserve unrelated plugins, settings, and explicit opt-outs. See [OpenAI's plugin guidance](https://learn.chatgpt.com/docs/plugins) for supported surfaces and hook trust.

From the project, inspect the existing installation first:

```bash
npx @popmelt.com/core codex status
```

If missing or needing an update, install the bundled plugin:

```bash
npx @popmelt.com/core codex install
```

Follow the install command's restart instructions after ongoing work finishes, then begin a new Codex conversation in the project. Do not close active sessions automatically. A result of `files_installed` means plugin registration is incomplete; `installed` or `update_ready` still requires the reported restart and new conversation. Popmelt will orient the conversation automatically when one store applies. If multiple stores apply, Codex will ask you to choose one writable primary store and any read-only mounted stores; it never infers write authority from directory nesting, recency, or store size.

After restart, check that the plugin is registered and enabled and that the new session exposes the Popmelt skill, tools, and ambient context. Registration alone does not prove the hooks have loaded. If you cannot inspect the new session, report ambient activation as unverified rather than reinstalling repeatedly.

This installer is Codex-specific. Claude Code toolbar threads do not need it. For ordinary local Claude conversations, see the experimental native plugin below; do not copy Codex hooks into Claude settings.

Ambient capture stays out of toolbar-owned runs, so opening a thread from the in-page toolbar still produces exactly one toolbar job. Popmelt tools remain available for intentional context expansion, decision review, and Imprint grounding.

### Ambient Popmelt in Claude Code (local preview)

The package also includes an experimental native Claude plugin with the Popmelt skill, local MCP tools, and prompt/tool/completion hooks. For local Claude Code, inspect first:

```bash
npx @popmelt.com/core claude status
```

With the user's permission for a personal plugin installation, install or update the bundle:

```bash
npx @popmelt.com/core claude install
```

The installer registers `popmelt-core@popmelt-local` through Claude's native plugin commands, using a local marketplace under `CLAUDE_CONFIG_DIR` (or `~/.claude`). It preserves disabled plugins, refuses conflicting sources/scopes, and never writes execution permission settings. Partial registration is reported separately from success. After ongoing work finishes, reload plugins or start a new Claude Code session; don't interrupt active work automatically.

Then check the setup without launching an agent request or restarting a bridge:

```bash
npx @popmelt.com/core claude doctor
```

`infrastructureReady` covers registration, locally reported sign-in, and the identity/version of an already-running project bridge. `sessionVerified` requires recent prompt-hook, MCP-tool, and completion-hook observations from one session of that installed plugin version. `ok` requires both. Observations expire after 24 hours and do not prove the session is still live or that Claude read the skill. Missing observations are **unverified**, not a reason to reinstall repeatedly. Bridge probes cover the standard 1111–1120 range; verify a custom port separately.

In a multi-store workspace, diagnostics honor the same saved human-selected primary and read-only mounts as ambient sessions. If that choice is missing, invalid, or no longer matches the available stores, orientation remains required; diagnostics never pick a replacement or rewrite the saved choice.

For a one-session trial without changing personal settings, you can instead load the packaged plugin directly:

```bash
claude --plugin-dir ./node_modules/@popmelt.com/core/claude-plugin
```

This uses Claude's native [plugin](https://code.claude.com/docs/en/plugins-reference) and [hook](https://code.claude.com/docs/en/hooks) formats. Respect Claude's workspace trust, plugin/MCP consent, and existing permission settings. The plugin adds no permission policy or execution grants.

Check that the session exposes the Popmelt skill and tools and that the next ordinary prompt receives ambient context. Use the exact tool-session ID it supplies. Store orientation, mounted read stores, focus inspection, and Imprint use the same bridge as Codex. Toolbar-owned Claude runs skip ambient capture.

The adapter keeps a private, bounded current-turn journal locally, correlates Claude's hooks, and retries against the same durable turn after bridge recovery. Missing/failed hooks remain unverified; API-error and session-end events are not recorded as successful completions. Concurrent processes using the same native Claude session are not supported in this preview. No transcript copy is read or retained.

Local sessions in Desktop’s Code tab are expected to work through shared configuration. Chat, Cowork, and cloud sessions are outside this preview’s scope. Native plugin validation and isolated lifecycle checks do not prove activation in the user's session. Let the user choose the next real task; do not submit an automatic test annotation or paid request to manufacture readiness. The toolbar works independently.

### Point and discuss

With your app running and the Popmelt canvas active, point at an element and ask Codex, “What am I pointing at?” You can also ask it to inspect a comment or explain what was discussed in an existing thread, without pointing at that item first.

The prompt hook also supplies a small, timestamped snapshot of bridge-managed runs in the conversation's primary and mounted stores: running, awaiting approval, and queued. This conversation is identified separately; other externally hosted conversations are not counted. Reads are bounded to 500 ms and unavailable bridges are reported as unavailable, not idle. This context is advisory—agents must check live status before restarting or interrupting a bridge.

Popmelt supplies the canvas target and lets Codex read saved threads, unsent drafts, and staged changes as separate kinds of context. A small animated eye appears beside the target or its visible annotation rail during targeted tool access—even if the element itself is offscreen. It indicates access to that item, not the full time the agent spends thinking about it. Reading context does not submit your drafts or start another agent run.

When several copies of the app are open, recent canvas interaction helps select the intended page. If the target is still ambiguous, Codex can ask which page you mean instead of guessing. Focus is a timestamped snapshot, so keep the intended target selected or point again when needed.

Useful diagnostics:

```bash
npx @popmelt.com/core codex status
npx @popmelt.com/core codex doctor
```

`status` reports plugin registration and project discovery. `doctor` can start the local project bridge and check its health, so run it only for the intended project when starting that service is appropriate. Inspect the plugin fields and any project-selection warning as well as bridge health: a healthy bridge does not prove plugin activation, browser connectivity, or provider authentication.

## Imprint

As you work, Popmelt recognizes choices that may be useful beyond the current thread and proposes an Imprint update:

- **Yes** adds the learning to the project's shared understanding
- **This time** keeps it local to the current situation
- **No** leaves the Imprint unchanged and gives you room to explain what Popmelt misunderstood

Confirmed Imprint knowledge is available to future threads when it is relevant. Popmelt can also search the project's collaboration history when a question depends on earlier discussions, feedback, or the evolution of a part of the UI.

Enter Imprint mode with `I` to inspect the project's current understanding, supporting evidence, relationships, and exceptions.

## Manual setup

For React, add the provider below and follow the [Next.js](#nextjs),
[Vite](#vite), or [other development server](#other-development-servers) instructions.
For experimental non-React hosts, use the [standalone entry for vanilla HTML/JS or Vue](#experimental-standalone-browser-entry),
[Nuxt module](#experimental-nuxt-integration), [Svelte/SvelteKit setup](#experimental-svelte-and-sveltekit),
or [Astro integration](#experimental-astro-integration) instead of adding a React provider.

For a React app, install Popmelt and its icon peer dependency:

```bash
npm install @popmelt.com/core lucide-react
```

### Add the provider

Wrap your app with `PopmeltProvider`. In Next.js, the component that calls `useRouter()` must be a client component.

```tsx
'use client';

import { useRouter } from 'next/navigation';

import { PopmeltProvider } from '@popmelt.com/core';

export function Providers({ children }: { children: React.ReactNode }) {
  const router = useRouter();

  return <PopmeltProvider navigate={router.push}>{children}</PopmeltProvider>;
}
```

`navigate` is optional for single-page use. Pass your router's navigation function when Popmelt needs to restore annotations across routes.

### Add application context to comments

Use `getCommentMeta` to snapshot application-owned context whenever a human
submits annotations or replies in a thread. Popmelt validates and detaches the
JSON, includes it as contextual evidence for the model, and persists the same
snapshot with the stored human turn.

```tsx
<PopmeltProvider
  getCommentMeta={({ kind }) => ({
    'my-app/video-ui/v1': {
      kind,
      generation: {
        prompt: generation.prompt,
        dataVersion: store.getState().version,
      },
      frame: {
        assetId: video.id,
        timeMs: Math.round(video.currentTime * 1000),
      },
      interactions: recorder.snapshot(),
    },
  })}
>
  {children}
</PopmeltProvider>
```

The hook may be synchronous or asynchronous. Return a plain JSON object; use a
namespaced, versioned key so multiple integrations can coexist. Comment
metadata is limited to 64 KiB per human turn. Keep large binaries and long
event logs in external storage or attachments and place their stable IDs,
hashes, and timestamps in `meta`.

### Next.js

Next.js needs both the command wrapper and the config wrapper.

Update the existing development script while preserving any flags already in use:

```json
{
  "scripts": {
    "dev": "popmelt wrap -- next dev"
  }
}
```

Then wrap the Next config:

```ts
// next.config.ts
import { withPopmelt } from '@popmelt.com/core/next';

const nextConfig = {};

export default withPopmelt(nextConfig);
```

The command wrapper starts the project bridge. The config wrapper connects the browser toolbar to it. Using only one is not a complete setup.

### Vite

Add the Vite plugin:

```ts
// vite.config.ts
import { popmelt } from '@popmelt.com/core/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [react(), popmelt()],
});
```

### Experimental standalone browser entry

The first framework-independent integration is available at `@popmelt.com/core/browser`.
It mounts Popmelt beside the host application, without JSX or a React provider in
your application code. React still renders Popmelt internally; the package's
existing React, React DOM, and Lucide React peer dependencies are still required.
This is an experimental DOM integration, not a claim of Vue/Svelte/Angular feature
parity. React-specific component hierarchy and library previews are not supplied
for other frameworks yet.

For a Vite host, keep the existing `@popmelt.com/core/vite` bridge plugin, then use
this in your browser entry:

```js
if (import.meta.env.DEV) {
  const { mountPopmelt } = await import('@popmelt.com/core/browser');
  const popmelt = await mountPopmelt({ enabled: true });
  import.meta.hot?.dispose(() => popmelt.unmount());
}
```

Enablement is explicit: omitted/false `enabled` or a server-side call is a no-op
and does not load the renderer. `unmount()` removes the UI and releases its
subscriptions without deleting saved annotations or cancelling bridge jobs.
Call it before mounting again; simultaneous integrations on one page are rejected.
Use either this entry or `PopmeltProvider`, not both. Pass `bridgeUrl`, `projectId`,
or a host-router `navigate` callback when these are not supplied by your integration.
Explicit bridge/project options take effect before startup and restore prior
host bindings when unmounted; no manual browser-global assignment is needed.

For Vue, mount your app normally and mount Popmelt alongside it, not inside the
Vue component tree. Keep your Vue Vite plugin and add `popmelt()` to that same
plugin list. Connect the returned `unmount()` to your app's teardown (for example,
`app.onUnmount(() => popmelt.unmount())` in Vue 3.5+). With Vue Router, pass
`navigate: (url) => router.push(url)`; no router dependency is required by Core.

The vanilla HTML/JS integration requires Vite and installed npm peers; a
bundler-free script tag or arbitrary static-server setup is not covered.
Vue Router guards, nested routes, and hash-history mode are not yet tested.

### Experimental multiplayer (opt-in)

Multiplayer is off by default. The package includes peer canvases for early
testing, but ordinary installations do not show the host peer bubble or invite
controls and do not start the peer transport. Your solo annotations and threads
continue to work as before.

```tsx
<PopmeltProvider multiplayer>{children}</PopmeltProvider>
```

For Vue, Svelte/SvelteKit, or other browser mounts:

```ts
const popmelt = await mountPopmelt({ enabled: true, multiplayer: true });
```

Nuxt: `popmelt: { multiplayer: true }` in `nuxt.config.ts`.
Astro: `popmelt({ multiplayer: true })` in the integrations list.
When Astro uses `client: false`, set the option on your own provider/mount instead.
Next.js and React/Vite set it on the provider, not the dev-server plugin.

This is a client feature opt-in, not a security control or automatic server
configuration. Participant hosting still requires a participant-capable bridge
and dev-server bootstrap. Explicitly configured participant pages require this
opt-in too; disabling it stops enrollment and shows a disabled notice rather
than falling back to an unauthenticated solo writer. Preview frames never mount
a writer. Saved drafts and server-side invitations are not deleted or revoked
by changing the prop. See [participant integration](docs/participant-integration.md).

### Experimental Nuxt integration

For Nuxt 4.5.2, install Popmelt and its internal renderer peers as development
dependencies, then add the module:

```sh
npm install -D @popmelt.com/core react react-dom lucide-react
```

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@popmelt.com/core/nuxt'],
});
```

No separate Vite plugin, React provider, or manual browser mount is needed.
The module starts the local bridge in development and mounts Popmelt after Nuxt
has mounted the client app. It uses Nuxt's router for navigation and releases
its bridge lease on graceful shutdown. When Nuxt's CLI exits immediately, the
existing lease-expiry cleanup stops an idle bridge within about a minute.
Production builds do not register the client
plugin or start the bridge. Optional `popmelt` settings are `port`, `projectRoot`,
and `bridge` (set `bridge: false` to disable automatic bridge startup).

Use a Nuxt-compatible Node version (22.19+, 24.11+, or 26+).
Nuxt 3, component metadata, and advanced router configurations are not yet tested.

### Experimental Svelte and SvelteKit

Tested with Svelte 5.57.0, SvelteKit 2.70.3, and Vite 8.2.2. Install the same
development dependencies used by the standalone entry:

```sh
npm install -D @popmelt.com/core react react-dom lucide-react
```

Keep your existing Vite configuration and add `popmelt()` alongside `sveltekit()`
(or `svelte()` for a plain Svelte app):

```js
import { sveltekit } from '@sveltejs/kit/vite';
import { popmelt } from '@popmelt.com/core/vite';

export default { plugins: [sveltekit(), popmelt()] };
```

Mount once in your root `+layout.svelte`, without replacing its existing content:

```svelte
<script>
  import { onMount } from 'svelte';
  import { goto } from '$app/navigation';
  import { bridgeUrl, projectId } from 'virtual:popmelt/bridge';

  onMount(() => {
    if (!import.meta.env.DEV || !bridgeUrl || !projectId) return;
    let disposed = false;
    const pending = import('@popmelt.com/core/browser').then(({ mountPopmelt }) => {
      if (!disposed) return mountPopmelt({ enabled: true, bridgeUrl, projectId, navigate: goto });
    });
    void pending.catch(console.error);
    return () => {
      disposed = true;
      void pending.then((handle) => handle?.unmount()).catch(() => {});
    };
  });
</script>
```

For plain Svelte, place this in `App.svelte` and omit the `goto` import and
`navigate` option, or supply your own router callback. TypeScript projects can
add `/// <reference types="@popmelt.com/core/vite/client" />` to their app's
ambient declarations for the virtual module types.

This setup is development-only, including during SSR. It does not provide
Svelte component metadata or framework-specific library previews. Advanced
Kit routing, form actions, and deployment adapters beyond Node are not yet tested.

### Experimental Astro integration

For Astro 7.3.1, install Popmelt and its internal renderer peers as development
dependencies, then add the Astro integration:

```sh
npm install -D @popmelt.com/core react react-dom lucide-react
```

```js
// astro.config.mjs
import { popmelt } from '@popmelt.com/core/astro';
import { defineConfig } from 'astro/config';

export default defineConfig({
  integrations: [popmelt()],
});
```

The integration starts the bridge and mounts Popmelt automatically in development;
no React island or provider is needed. It supports DOM annotations and style edits,
with thread recovery through reloads and Astro ClientRouter navigation. Production
builds and previews do not mount Popmelt or start its bridge.

If your project already mounts a Popmelt provider, use `popmelt({ client: false })`
to keep that provider in charge of the UI. The existing `port`, `projectRoot`,
`basePath`, and `bridge` options remain available.

Astro component metadata, framework-specific island previews, and deployment
adapters are not yet covered.

### Other development servers

The wrapper can start Popmelt alongside another development command:

```json
{
  "scripts": {
    "dev": "popmelt wrap -- your-existing-dev-command"
  }
}
```

## Verify the installation

For the installing agent, verify the user's actual app, not a separate demo or fixture. Work from the correct application directory and preserve its existing scripts and configuration.

1. **Framework and app:** confirm the installed dependencies and matching provider/mount and framework integration. Explain any relevant experimental-support limit. Start the app using its existing development command, or restart only its own development process when needed and authorized. Do not interrupt active work or other projects.
2. **Browser:** open the exact local URL reported by the development server. Confirm the app loads and double-tap Command or Control to check that the toolbar appears. If browser access is unavailable, report the integration as configured but browser verification as pending.
3. **Bridge:** confirm the local bridge belongs to this application and is healthy, then check the toolbar's connection when browser access is available. A service responding on some port is not enough. Use the applicable Codex or Claude diagnostics above with their stated limits; do not treat them as a general end-to-end check.
4. **Provider:** confirm the selected CLI is available to the bridge process, not only to an unrelated terminal, and check version compatibility and sign-in status using supported, non-generating diagnostics where available. Do not print credentials, change sandbox/approval settings to make checks pass, or submit a paid test prompt. If authentication cannot be checked safely, say so. These checks establish readiness, not successful end-to-end execution; the user's first real request verifies that.
5. **Ambient integration, when applicable:** follow the bundled Codex setup or experimental local Claude Code setup above, keeping personal installation, registration, and activation in a new session distinct. Preserve disabled integrations and get permission before personal installation. Track a required restart or unavailable surface separately from toolbar readiness; an optional ambient gap should not prevent the user from trying a working toolbar.

To start the app manually, use its existing development command, for example:

```bash
npm run dev
```

### Finish with a useful handoff

Only describe a check as verified when you observed it. Keep the completion message brief: give the exact local app URL, distinguish configured from verified, and name each remaining gap with its next step. For example, when the relevant checks pass:

> Popmelt is ready at [the app's actual local URL]. Double-tap Command/Control, click something you want changed, and leave a comment.

If ambient setup still needs a restart, say so separately. Let the user choose the first change; do not create a test annotation or launch an agent run merely to validate installation.

### If something is not ready

- **Toolbar missing:** check the provider or browser mount, development-only enablement, framework config, and whether the running app picked up the change. In Next.js, both `withPopmelt` and `popmelt wrap` are required.
- **Bridge disconnected:** check the intended project's bridge and the toolbar's connection settings. If a process must be restarted, identify that process and check for active work first; do not restart everything.
- **Provider unavailable:** distinguish a missing executable, incompatible version, missing sign-in, or a bridge environment that differs from the terminal. Give the specific installation, sign-in, or targeted restart step supported by the evidence. A denied diagnostic is unverified, not proof the provider is broken.
- **Ambient context missing:** inspect `codex status` for registration/enabled state, follow any pending restart, and check a new project-bound session. Resolve project selection with the user when needed; do not infer the project or install duplicate hooks.

## React Three Fiber

Popmelt can target objects rendered by React Three Fiber while Core continues to
own the familiar hover, annotation, and thread presentation. Install the R3F
peers alongside Core:

```bash
npm install @popmelt.com/core @react-three/fiber three
```

Keep `PopmeltProvider` outside the canvas and mount `PopmeltThreeAdapter` inside
it. The adapter renders nothing; it registers the current scene, camera, and
renderer with the nearest provider.

```tsx
import { Canvas } from '@react-three/fiber';
import { PopmeltProvider } from '@popmelt.com/core';
import { PopmeltThreeAdapter } from '@popmelt.com/core/react-three-fiber';

export function ProductPreview() {
  return (
    <PopmeltProvider>
      <Canvas>
        <PopmeltThreeAdapter />
        <ProductScene />
      </Canvas>
    </PopmeltProvider>
  );
}
```

Named objects receive readable labels automatically. Add stable application
identity when object names or scene ordering may change:

```tsx
<mesh
  name="Bottle"
  userData={{ popmeltId: 'hero-bottle', popmeltLabel: 'Hero bottle' }}
>
  {/* geometry and material */}
</mesh>
```

Use a distinct `id` for each adapter when a page owns multiple canvases. Pass
`root` to target only one scene subtree, `captureFrame={false}` to omit renderer
screenshots, or `select`, `key`, `label`, and `describe` to adapt an application's
scene semantics.

For integrations that need the registered adapter instance, use the hook inside
the canvas instead of the component:

```tsx
import { usePopmeltThreeAdapter } from '@popmelt.com/core/react-three-fiber';

function ProductPopmeltAdapter() {
  const adapter = usePopmeltThreeAdapter({ id: 'product-preview' });
  // Optional application-specific coordination with adapter.
  void adapter;
  return null;
}
```

The component is the recommended default; the hook exposes the same lifecycle
for advanced composition.

## Media annotation

Manual image and video annotation are enabled by default in `PopmeltProvider`,
without a vision dependency or model download.

```tsx
<PopmeltProvider>{children}</PopmeltProvider>

// Optional: disable image annotation.
<PopmeltProvider imageAnnotation={false}>{children}</PopmeltProvider>

// Optional: disable video annotation.
<PopmeltProvider videoAnnotation={false}>{children}</PopmeltProvider>

// Optional: limit image discovery (all img elements are included by default).
<PopmeltProvider imageAnnotation={{ selector: 'img[data-popmelt-media]' }}>
  {children}
</PopmeltProvider>
```

In Comment, drag over an image to create a named object. Double-click an object to
rename or resize it, single-click it to comment, and right-click its label to
delete it. Saved targets stay aligned as the image resizes.

The same interactions work on a paused video. A manual box stays at the saved
position unless a perception extension supplies motion tracking. Motion tracks
produced by an extension are persisted and remain replayable by Core alone.

Image targets use browser-local storage by default. Supply
`imageAnnotation.store` when the project needs another persistence layer. Add a
stable `data-popmelt-media-id` to images whose URL may change or whose source
appears more than once. Use the same attribute for durable video identities:

```tsx
<img data-popmelt-media-id="hero-product" src={imageUrl} alt="Product" />
<video data-popmelt-media-id="hero-reel" src={videoUrl} controls />
```

No recognition or model download occurs unless See is installed and enabled.

## Add local object recognition with See

[`@popmelt.com/see`](https://www.npmjs.com/package/@popmelt.com/see) adds
browser-local object detection and optional video tracking to the existing media
annotation experience. It requires Core 0.17.0 or newer.

Install it:

```bash
npm install @popmelt.com/see
```

Then add the extension to the existing Core provider:

```tsx
import { PopmeltProvider } from '@popmelt.com/core';
import { see } from '@popmelt.com/see';

<PopmeltProvider extensions={[see]}>{children}</PopmeltProvider>;
```

Recognition loads only when it is needed. Other extensions can be included in
the same array. Model selection, self-hosting, recognition, and video tracking are
documented in the See package README.

## Data and privacy

Popmelt stores its annotations, screenshots, threads, UI preferences, and Imprint locally under `.popmelt/`. Unsent annotation drafts, preview state, and the default media-object stores use origin-scoped browser storage. Supply project-backed `imageAnnotation.store` and `videoAnnotation.store` implementations when media targets must survive browser-site-data removal or move between origins.

When you send a thread, the context needed to complete that work is passed to your selected AI provider and is subject to that provider's privacy policy.

Popmelt is designed for local development. As with any AI-assisted coding, your agent may edit files or run commands, so review its work, use version control, and run your project tests before you ship.

## Useful shortcuts

| Shortcut                         | Action                                                              |
| -------------------------------- | ------------------------------------------------------------------- |
| Double-tap Command or Control    | Open or collapse Popmelt                                            |
| `C`                              | Enter Chat mode                                                     |
| `S`                              | Enter Steer mode                                                    |
| `I`                              | Enter Imprint mode                                                  |
| Command/Control+Enter            | Send all pending work                                               |
| Command/Control+C                | Capture a screenshot for pending work when no text is selected      |
| Command/Control+Z                | Undo a pending change or thread dismissal                           |
| Command/Control+Shift+Z          | Redo a pending change                                               |
| Backspace/Delete                 | Archive the open or selected historical thread                      |
| Command/Control+Backspace/Delete | Clear all annotations and pending changes                           |
| Escape                           | Go back, cancel, close, discard, or deselect in the current context |

Steer additionally owns the unmodified letter, arrow, Enter, Escape, and Space keys described above while it is active. Popmelt leaves these shortcuts alone while you are typing in an input, textarea, or editable text surface.

## Bridge commands

Run a bridge without wrapping another command:

```bash
npx @popmelt.com/core bridge
```

Stop the bridge for the current project:

```bash
npx @popmelt.com/core stop
```

## License

[PolyForm Shield 1.0.0](./LICENSE)

You may use and extend Popmelt for yourself and your team. You may not sell it, offer it as a managed service, or use the code to create a competing product or service. The software is provided as-is.

For custom licensing, contact **reb@popmelt.com**.
