@llui/notes-format
The on-disk format for the LLui devmode notebook. A zero-dependency (bar yaml) package that defines the note types and the filename/slug/session helpers and YAML (de)serialization shared by the vite-plugin server store, the dev-mode HUD, and the MCP server — so all three read and write the exact same notes on disk.
pnpm add @llui/notes-format
Usage
import { serializeNote, parseNote } from '@llui/notes-format/note-serialize'
import type { Note } from '@llui/notes-format/note-types'
// Round-trip a note through its YAML-front-matter on-disk form.
const text = serializeNote(note)
const parsed: Note = parseNote(text)
Entry points
| Import | Purpose |
|---|---|
@llui/notes-format |
Barrel — re-exports the modules below |
@llui/notes-format/note-types |
Note type definitions |
@llui/notes-format/note-format |
Filename / slug / session helpers |
@llui/notes-format/note-serialize |
YAML (de)serialization |
Functions
buildQueue()
Materialize per-note current status from a flat transition log, newest touched first. One entry per note id that has ever transitioned; optionally filtered by status.
function buildQueue(transitions: readonly StatusTransition[], filter?: { status?: NoteStatus | NoteStatus[] }): QueueEntry[]
currentStatusFromHistory()
Current status for a note: last to, or null when it has no transitions.
function currentStatusFromHistory(history: readonly StatusTransition[]): NoteStatus | null
defaultSessionName()
Default UTC session folder name: session-YYYY-MM-DD-HHMM.
function defaultSessionName(d: Date): string
deriveFilename()
function deriveFilename(id: string, author: Author, kind: NoteKind, slug: string): string
deriveSlug()
function deriveSlug(prose: string): string
nextId()
The next id given the ids already present (handles gaps): padId(max+1).
function nextId(existingIds: readonly number[]): string
padId()
3-digit zero-padded session-local sequence id (001, 002, … then 1000+).
function padId(n: number): string
parseFilename()
function parseFilename(filename: string): ParsedFilename | null
parseNote()
function parseNote(markdown: string): SerializedNote
preview()
One-line preview of prose for note summaries.
function preview(prose: string, max = 80): string
serializeNote()
function serializeNote(note: SerializedNote): string
Types
Annotation
export type Annotation =
| ({ type: 'rect' } & NoteRect & { label?: string })
| { type: 'element'; selector: string; bbox: NoteRect; label?: string }
Author
export type Author = 'human' | 'llm'
CaptureLevel
export type CaptureLevel = 'standard' | 'verbose'
LogLevel
export type LogLevel = 'log' | 'warn' | 'error' | 'info' | 'debug'
NoteIntent
export type NoteIntent = 'task' | 'note'
NoteKind
export type NoteKind = 'rect' | 'element' | 'text' | 'capture' | 'reply'
NoteStatus
export type NoteStatus =
| 'open'
| 'claimed'
| 'in-progress'
| 'proposed'
| 'accepted'
| 'applied'
| 'rejected'
| 'wontfix'
| 'failed'
ReproEvent
export type ReproEvent =
| { type: 'click'; t: number; selector: string }
| { type: 'input'; t: number; selector: string; value?: string; redacted?: boolean }
| { type: 'keydown'; t: number; key: string; mods?: string }
| { type: 'route'; t: number; pathname: string }
ServerEvent
export type ServerEvent =
| { type: 'note-created'; id: string; filename: string; author: Author }
| { type: 'note-updated'; id: string; sessionId: string }
| { type: 'note-deleted'; id: string; sessionId: string }
| {
type: 'task-progress'
noteId: string
elapsedMs: number
/** Token counters from the LLM stream.
* - `in`: latest cumulative input_tokens (context size). Grows
* monotonically across the conversation.
* - `out`: sum of all output_tokens generated so far.
* - `cacheRead`: prompt-cache hits, if the model reports them
* (claude's `cache_read_input_tokens`). Shows how much
* of the context was served from cache vs. reprocessed. */
tokens?: { in: number; out: number; cacheRead?: number }
toolSummary?: string
}
| { type: 'capture-request'; requestId: string; payload: CaptureRequestPayload }
| { type: 'capture-request-cancelled'; requestId: string }
| { type: 'session-rotated'; sessionId: string }
| {
type: 'status-changed'
noteId: string
from: NoteStatus | null
to: NoteStatus
/** Optional human-readable context — e.g. the LLM's proposed-fix
* summary, a failure message, or a git-apply conflict. The HUD
* surfaces this verbatim in its status line. */
reason?: string
}
SseRole
export type SseRole = 'hud' | 'mcp' | 'viewer'
Interfaces
AgentSchemaSummary
export interface AgentSchemaSummary {
msg: string
fields: Record<string, string>
}
CaptureRequestPayload
export interface CaptureRequestPayload {
route?: string
url?: string
selector?: string
annotate?: Annotation[]
prose?: string
waitForMessage?: string
captureLevel?: CaptureLevel
timeoutMs?: number
}
CaptureRequestResponse
export interface CaptureRequestResponse {
requestId: string
status: 'fulfilled' | 'timeout' | 'no-client'
note?: CreateNoteResponse
}
ComponentMetaRef
export interface ComponentMetaRef {
file: string
line: number
name: string
}
ConsoleLogEntry
export interface ConsoleLogEntry {
ts: string
level: LogLevel
text: string
}
CreateNoteRequest
export interface CreateNoteRequest {
body: string
frontmatter: Omit<NoteFrontmatter, 'id' | 'ts'>
noteBody: NoteBody
screenshot?: string
}
CreateNoteResponse
export interface CreateNoteResponse {
id: string
filename: string
path: string
sessionId: string
}
CurrentSessionResponse
export interface CurrentSessionResponse {
sessionId: string
startedAt: string
notesDir: string
}
DirtyTraceEntry
export interface DirtyTraceEntry {
component: string
pathsTracked: string[]
mask: number
maskHi?: number
lastFlippedBits: string[]
}
ListNotesQuery
export interface ListNotesQuery {
sessionId?: string
author?: Author
kind?: NoteKind | NoteKind[]
since?: string
limit?: number
}
ListNotesResponse
export interface ListNotesResponse {
sessionId: string
notes: NoteSummary[]
total: number
}
MessageLogEntry
export interface MessageLogEntry {
ts: string
component: string
msg: unknown
}
NoteBody
export interface NoteBody {
stateSnapshot?: unknown
messageLog?: MessageLogEntry[]
consoleLog?: ConsoleLogEntry[]
pendingMessages?: PendingMessage[]
effects?: {
pending: PendingEffectEntry[]
recent: RecentEffectEntry[]
}
dirtyTrace?: DirtyTraceEntry[]
structuralAt?: StructuralSnapshot
sourceMap?: SourceMapEntry[]
errors?: RuntimeErrorEntry[]
/** Captured user interactions from the HUD's repro recorder. The
* LLM uses this to understand what the developer did before the
* bug appeared. Times are milliseconds from the start of the
* recording, not absolute. */
repro?: ReproEvent[]
verbose?: VerboseNoteBody
}
NoteFrontmatter
export interface NoteFrontmatter {
// Identity (server-assigned on POST /_llui/notes)
id: string
ts: string
author: Author
kind: NoteKind
captureLevel: CaptureLevel
// Location
url: string
route: string | null
routeParams: Record<string, string>
viewport: { w: number; h: number; dpr: number }
// Component context (null when no element was targeted)
componentPath: string[] | null
componentMeta: ComponentMetaRef | null
// Annotations baked into the screenshot
annotations: Annotation[]
// Files
screenshot: string | null
// Agent surface available at target element
agentSchemas: AgentSchemaSummary[]
// Versioning
llui: { runtime: string; compiler: string }
// Cross-references (optional)
fulfillsRequestId?: string
// Task-mode (P6) — optional, present when the note participates in
// the task workflow. `intent: 'task'` puts the note in the status
// machine; `kind: 'reply'` with `replyTo` set is the LLM's response.
intent?: NoteIntent
replyTo?: string
proposedDiff?: ProposedDiff
/** When true (default true for HUD-originated tasks), the router
* spawns the LLM with its resume-previous-conversation flag (e.g.
* `claude --continue`) so the LLM keeps prior context. Presets
* without a resume flag treat this as a no-op. */
resume?: boolean
/** Name of the resume chain this task participates in. The router
* keeps a map of chain name → last session id and passes the
* corresponding id via `--resume` when `resume: true`. Lets the
* user maintain independent conversation threads (e.g. "refactor",
* "ui-polish") without them stomping on each other. Default
* `'default'`. */
chainName?: string
}
NoteRect
export interface NoteRect {
x: number
y: number
w: number
h: number
}
NoteSummary
export interface NoteSummary {
id: string
sessionId: string
filename: string
ts: string
author: Author
kind: NoteKind
url: string
componentPath: string[] | null
preview: string
hasScreenshot: boolean
/** Frontmatter shortcuts surfaced in the list so the HUD can
* rehydrate trackedTasks + chainHistories on reload without
* fetching each note individually. Optional for back-compat with
* servers that don't populate them. */
intent?: NoteIntent
chainName?: string
/** For reply notes only — the original task this reply addresses. */
replyTo?: string
/** For reply notes only — the LLM's one-line summary of the
* proposed change (extracted from proposedDiff). */
proposedSummary?: string
}
ParsedFilename
export interface ParsedFilename {
id: string
idNum: number
author: Author
kind: NoteKind
slug: string
}
PendingEffectEntry
export interface PendingEffectEntry {
id: string
component: string
effect: unknown
sinceMs: number
}
PendingMessage
export interface PendingMessage {
component: string
msg: unknown
}
ProposedDiff
export interface ProposedDiff {
files: Array<{ path: string; patch: string }>
summary: string
confidence: 'high' | 'medium' | 'low'
}
QueueEntry
export interface QueueEntry {
noteId: string
status: NoteStatus
transitions: StatusTransition[]
}
RecentEffectEntry
export interface RecentEffectEntry {
ts: string
component: string
effect: unknown
outcome: 'ok' | 'error' | 'cancelled'
error?: string
}
RuntimeErrorEntry
export interface RuntimeErrorEntry {
ts: string
kind: 'runtime' | 'compiler'
file?: string
line?: number
message: string
stack?: string
}
SerializedNote
export interface SerializedNote {
frontmatter: NoteFrontmatter
prose: string
body: NoteBody
}
SourceMapEntry
export interface SourceMapEntry {
selector: string
file: string
line: number
componentPath: string[]
}
StatusTransition
export interface StatusTransition {
ts: string
noteId: string
from: NoteStatus | null
to: NoteStatus
by: Author | 'system'
reason?: string
}
StructuralSnapshot
export interface StructuralSnapshot {
branches: Array<{ at: string; activeArm: string }>
shows: Array<{ at: string; visible: boolean }>
eachKeys: Array<{ at: string; keys: string[] }>
}
VerboseNoteBody
export interface VerboseNoteBody {
scopeTree?: Array<{
id: string
parent: string | null
component: string
key?: string
}>
bindings?: {
total: number
hottest: Array<{ component: string; path: string; firesPerSec: number }>
lastCycleMs: number
}
agentBridge?: {
connectedAgents: string[]
pendingToolCalls: number
recentMsgs: Array<{ ts: string; direction: 'in' | 'out'; payload: unknown }>
}
transitionsInFlight?: Array<{ component: string; name: string; progress: number }>
foreignInstances?: Array<{ component: string; library: string }>
}
Constants
NOTE_FILENAME_RE
const NOTE_FILENAME_RE
NOTE_SCHEMA_VERSION
On-disk note-format schema version. Stamped into export bundles and
checked on dev import so a producer and consumer never silently disagree.
v2 = the current "body under a body: frontmatter key" format (v1 was the
legacy trailing-```json fence, still readable by parseNote).
const NOTE_SCHEMA_VERSION