Add custom context to Popmelt annotations and threads
Attach application-owned context to feedback and preserve it with each human thread turn.
Your app knows things the page alone cannot explain: the document being edited, a data revision, a selected asset, a generation prompt, or the current video frame. Use getCommentMeta on PopmeltProvider to supply that context when a person submits feedback or replies in a thread.
Core validates the returned JSON and takes a detached snapshot. It includes the metadata as contextual evidence for the agent and stores it on the corresponding human thread message as meta. Later changes to your application state do not rewrite that snapshot.
Supply application context
Read a small, relevant snapshot from your own application state. A namespaced, versioned key keeps your data identifiable as its schema evolves.
'use client';
import type { PropsWithChildren } from 'react';
import { PopmeltProvider } from '@popmelt.com/core';
type ApplicationContext = {
documentId: string;
revision: number;
selectedAssetId: string | null;
};
export function Providers({
children,
documentId,
revision,
selectedAssetId,
}: PropsWithChildren<ApplicationContext>) {
return (
<PopmeltProvider
getCommentMeta={(context) => ({
'my-app/editor/v1': {
documentId,
revision,
selectedAssetId,
turnKind: context.kind,
},
})}
>
{children}
</PopmeltProvider>
);
}
These props stand for your app's current state; the IDs are not supplied by Core. Add this option to your existing provider rather than mounting a second one. Preserve your router's navigate function and any other provider options.
The callback may return an object or a promise of an object. Return undefined when you have no metadata to attach. TypeScript applications can import CommentMeta, CommentMetaContext, and GetCommentMeta from @popmelt.com/core.
Know when the callback runs
| Context | When it is collected | Additional fields |
|---|---|---|
kind: 'annotation' | A pending annotation/change bundle is submitted | annotations contains the submitted annotations |
kind: 'reply' | A human reply is submitted in an existing thread | threadId, reply, and the thread annotations being re-tracked |
kind: 'annotation', purpose: 'inspection' | Core performs a read-only focus inspection | annotations contains a synthetic inspection target; no comment is submitted |
Every context includes timestamp (milliseconds since the Unix epoch), url, pathname, and annotations. Treat the context as read-only. Do not mutate the supplied annotations.
This collector belongs to your mounted application integration; it is not global middleware for every turn an agent may record in an ambient conversation elsewhere.
Keep the callback side-effect-free: it is a context collector, not a signal to start work, advance your app, or clear a recorder. In particular, an inspection call is not a submitted human turn and does not create a thread-history entry by itself.
If collection fails, throws, rejects, or returns invalid metadata, the annotation or reply send stops. Keep collection fast and handle optional application data deliberately. For example, return undefined if a nonessential source is unavailable; let a failure stop the send when that context is essential.
Keep metadata small and valid
- The root must be a plain JSON object, not an array or
null. - Nested values may be strings, finite numbers, booleans,
null, arrays, and plain objects. - Functions, DOM nodes, class instances such as
Date, cyclic values, and nestedundefinedare not valid metadata. Convert dates to strings and keep object references as stable IDs. - The entire serialized object is limited to 64 KiB of UTF-8 JSON, per collected snapshot—not 64 KiB per namespace.
Store large event logs and binaries separately. Include bounded summaries, stable asset IDs, revision IDs, timestamps, or hashes instead. A video integration might supply an asset ID, the paused frame time, a generation prompt, and a short interaction summary; it should not embed the video or an unlimited recorder history.
Read the context back from history
The stored human message carries the same meta snapshot. Metadata is optional: older messages, assistant turns, and turns without a collector may not have it.
The full-thread response from the local bridge (GET /thread/<threadId>) retains this field on messages[]. Use your existing authorized, project-bound bridge integration to obtain that response; do not assume a fixed port, bypass its access checks, or expose the bridge publicly. History-list/search summaries are not a metadata stream or an arbitrary JSON search API.
Once your integration has obtained a full thread, reading your namespace is ordinary JSON handling:
import type { CommentMeta } from '@popmelt.com/core';
type StoredTurn = {
role: string;
meta?: CommentMeta;
};
export function applicationSnapshots(thread: {
messages: readonly StoredTurn[];
}) {
return thread.messages
.filter((turn) => turn.role === 'human' && turn.meta !== undefined)
.map((turn) => turn.meta?.['my-app/editor/v1']);
}
Validate the namespace's schema before consuming it in your application. The example shows how to extract supplied context; it does not open a bridge connection or provide a public React history hook.
Thread data lives under .popmelt/ with the project. Bulky message details, including metadata, may be externalized into message artifacts and hydrated when the thread is read. Do not assume every meta object is directly embedded in a thread-envelope JSON file, and do not edit those files to synchronize application state.
What this does not do
getCommentMeta sends application context into Popmelt. It is not a two-way binding, an agent-response callback, or an automatic application-state replay mechanism. usePopmelt() currently exposes enablement and subject-adapter registration, not a thread-history subscription.
Keep any state restoration or downstream processing behind your own explicit application action. Historical context is evidence about a past turn, not authorization to replay it.
Do not include credentials, access tokens, unnecessary personal data, or other secrets. Submitted context goes to your selected AI provider with the turn; local storage does not make that transmission offline. Core labels application metadata as untrusted contextual evidence, not as instructions or authority. Read-only inspection may also make it available to the requesting agent without submitting a new comment.
Next: Creating annotations with the Chat tool or Helping your AI understand your taste with the Imprint tool.
Something you want to improve?
Leave a comment