npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@llui/devmode-annotate

v0.4.7

Published

LLui dev-mode HUD — drop annotated notes from the running app into the shared notebook for the LLM to consume

Readme

@llui/devmode-annotate

A HUD that lets you drop annotated notes from a running LLui app into a shared on-disk notebook — picked up automatically by the LLM via @llui/mcp.

What it does

Mounts a floating 📝 button (bottom-right, draggable). Clicking it (or Cmd/Ctrl+Shift+A) opens a composer: a plain Markdown textarea plus the capture tools — draw a rectangle, pick an element under the cursor, attach a screenshot, record an interaction trace to replay. Submitting POSTs the note to the @llui/vite-plugin middleware at /_llui/notes, which writes a markdown file under .llui/notes/session-…/ with full metadata (URL, viewport, route, component path under the cursor, scope state, recent messages, LLui versions).

The HUD also browses the notebook, files notes as tasks, shows the LLM's replies and proposed fixes, and answers LLM-initiated capture requests over the dev server's SSE channel. Both sides read and write the same directory.

Install

The HUD is an optional, consumer-provided package — @llui/vite-plugin deliberately does not depend on it. Add the standalone core yourself:

pnpm add -D @llui/devmode-annotate

The core has no Lexical dependency. To upgrade the textarea to the rich Markdown surface, install the optional editor too:

pnpm add -D @llui/devmode-annotate @llui/devmode-annotate-editor

In development the Vite plugin detects the editor package and registers it before mounting the HUD. Manual mounts opt in with import '@llui/devmode-annotate-editor' before calling mountAnnotateHud().

That is all the dev-server setup there is: the plugin resolves the package from your project root and injects a <script type="module"> that mounts the HUD, via transformIndexHtml in dev. A build never runs that hook, so a production bundle contains no reference to this package at all — no app-entry import, nothing to tree-shake. (Injection silently no-ops when the package isn't installed.) See Opting out / customizing.

For a live app that ships the HUD deliberately, install it as a regular dependency and wire it yourself — next section.

Shipping it in a live app

The HUD is not free: even the standalone core uses html-to-image and fflate for capture/export. The optional editor adds Lexical. Keep either graph off the initial production path until an authorized user activates it.

Use @llui/devmode-annotate/install. It registers the activation trigger and import()s the HUD only when the trigger fires, so the bundler splits the whole HUD into a chunk nobody fetches until it is opened:

// src/main.ts
import { installAnnotateHud } from '@llui/devmode-annotate/install'

// Behind the host's own authorization — this is a live app.
if (currentUser.isStaff) installAnnotateHud()

installAnnotateHud(opts) takes every mountAnnotateHud option plus trigger (default true, the Cmd/Ctrl+Shift+A bootstrap listener), and defaults allowProduction: true + isolate: true (shadow-DOM style isolation). It returns { activate, dispose }: activate() resolves to the live AnnotateHudHandle and is idempotent, dispose() drops the bootstrap listener.

Do not import mountAnnotateHud from the barrel in a production entry. The mount gate (import.meta.env.DEV, unless allowProduction) is a runtime check, so no bundler can drop the core module graph. test/entry-boundaries.test.ts pins the properties the lazy path rests on: src/install.ts reaches the HUD only through an erased import type and a dynamic import(), the core graph reaches no editor or Lexical module, and the store entry never reaches the HUD at all.

This is deliberately the production-entry guidance fixed by #116, not a replacement for it: #63 changes the editor package boundary, while ./install remains the production-safe core entry.

To keep the rich editor lazy too, disable the installer's built-in trigger and load the registration immediately before activation:

import { installAnnotateHud } from '@llui/devmode-annotate/install'

const hud = installAnnotateHud({ trigger: false })

async function activateRichHud() {
  await import('@llui/devmode-annotate-editor')
  return hud.activate()
}

Use under the dev server

The plugin already mounts it. To mount it yourself in a dev-only entry, keep the import dynamic so the build can drop it:

// src/main.ts
if (import.meta.env.DEV) {
  void import('@llui/devmode-annotate').then((m) => m.mountAnnotateHud())
}

A static import { mountAnnotateHud } from '@llui/devmode-annotate' at the top of your entry ships the HUD in every build, dev gate or not.

Without a dev server

The HUD talks to a NotesStore, not to /_llui/* directly. Pass store to run it anywhere:

  • indexedDbStore() — the notebook lives in the browser; notes and screenshots persist locally and the HUD's Export button produces a .zip in the canonical on-disk layout.
  • httpStore({ baseUrl, headers }) — a host backend speaking the same wire protocol.
  • Your own adapter implementing NotesStore (including dispose(), which the HUD calls from destroy() to release object URLs and connections).

destroy() disposes the store it was given, not only one it created — it has to, because an inline installAnnotateHud({ store: indexedDbStore() }) leaves nobody else holding a reference and object URLs are never garbage-collected. So the HUD takes over that instance: indexedDbStore revokes every screenshot URL it handed out, and httpStore closes every live EventSource. If your own code also drives a store, give the HUD a separate instance — they are cheap and share the same backing storage.

Import them from @llui/devmode-annotate/stores, not from the package barrel — the barrel is the HUD, so naming a store there would pull the capture UI into your entry chunk and undo the lazy install:

import { installAnnotateHud } from '@llui/devmode-annotate/install'
import { indexedDbStore } from '@llui/devmode-annotate/stores'

installAnnotateHud({ store: indexedDbStore() })

The store entry is eager (you construct the store up front), contains no editor or Lexical edge, and leaves the HUD itself waiting for activation.

Opting out / customizing

The plugin owns both the notebook endpoint and the injected HUD:

// vite.config.ts
import { defineConfig } from 'vite'
import llui from '@llui/vite-plugin'

export default defineConfig({
  plugins: [
    llui({
      devmodeAnnotate: false, // opt out entirely
      // or: devmodeAnnotate: {
      //   notesDir: 'tmp/notes',
      //   captureTimeoutMs: 60_000,
      //   hud: false,                  // keep the endpoint, skip the HUD injection
      // }
    }),
  ],
})

API

Full signatures: llui.dev/api/devmode-annotate.

  • mountAnnotateHud(opts?) → AnnotateHudHandle — mount now. Idempotent: a second call (including one re-entered from inside the first) returns the handle of the one live HUD. destroy() tears down every listener, timer, subscription and node it created.
  • installAnnotateHud(opts?) → { activate, dispose } — the lazy production entry, at @llui/devmode-annotate/install.
  • registerAnnotateEditor(registration) → dispose — optional editor seam, also published at @llui/devmode-annotate/editor; core falls back to its textarea when nothing is registered.
  • devServerStore / httpStore / indexedDbStore + the NotesStore types — at @llui/devmode-annotate/stores (also re-exported from the barrel, for code that has already paid for it).
  • Handle: open / close / destroy / setProse / submit / drawRect / handleCaptureRequest / setIntent / replayRepro / exportBundle.
  • Notable options: store, allowProduction, isolate, hidden, redact (per-channel state/repro/screenshot redaction hooks), captureDebug, repro, elementPick, autoCaptureOnError, solveEnabled.

Keyboard

  • Cmd/Ctrl+Shift+A — open the HUD modal
  • Escape — close it

What ends up on disk

After submitting "edit button copy is wrong":

.llui/notes/
  session-2026-05-23-1432/
    001-human-text-edit-button-copy-wrong.md
    001-human-text-edit-button-copy-wrong.png   # when the note carries a screenshot
    status.jsonl                                # task status transitions

The .md file carries the prose plus a frontmatter block with URL, viewport, route, component path, LLui versions — everything the LLM needs to act on the note without round-trips. The format is @llui/notes-format; the design notes live in the devmode-annotate proposal (current-state.md is the one kept current).