Skip to content

Agentation → Hypothesis Bridge Specification

Implementation specification for bridging Agentation.ai (DOM-level UX annotation) with Hypothesis (persistent, threaded annotation) in the ux-prototype Storybook site.

Agentation.ai provides DOM-element annotation for UX review in the ux-prototype Storybook site but has no persistent storage (browser localStorage, 7-day TTL) and no threaded discussions. Comments are lost when localStorage expires or is cleared. There is no way for multiple reviewers to have a conversation about a specific element.

Hypothesis provides persistent, threaded annotations with a mature REST API and is already deployed in the documentation site. However, its annotation model is text-based (TextQuoteSelector), not DOM-element-based.

  1. Persist Agentation annotations to Hypothesis — on submit, bridge posts annotations to the Hypothesis API so they survive beyond localStorage TTL.
  2. Enable threaded discussion — the Hypothesis sidebar in the Storybook preview iframe allows reviewers to reply, discuss, and resolve annotations.
  3. Visual DOM highlights — annotated elements display numbered badge markers (replicating Agentation’s visual style) sourced from Hypothesis data.
  4. Prevent double-marking — after posting to Hypothesis, annotations are cleared from Agentation’s localStorage.
  5. Backward compatibility — the JSON clipboard copy for the /agentation-feedback skill continues to work.

PlantUML diagram

  1. Reviewer creates annotations in Agentation (click DOM elements, add comments).
  2. On submit → bridge transforms → POSTs to Hypothesis API with tag Forensic + metadata tags.
  3. Bridge clears submitted annotations from Agentation localStorage.
  4. Highlight layer fetches Hypothesis annotations for the current story and renders badge markers on DOM elements.
  5. Hypothesis sidebar (embed.js in iframe) shows annotations with full threading support.

The Hypothesis API requires a Bearer token for private-group annotations. Exposing this token in browser JavaScript is a security risk.

A Storybook middleware that proxies requests to the Hypothesis API, injecting the token server-side.

New file: ux-prototype/.storybook/middleware.ts

Storybook supports Express middleware via .storybook/middleware.ts (standard extension point).

Routes:

Browser requestProxied toMethod
/hypothesis-proxy/annotationshttps://hypothes.is/api/annotationsPOST
/hypothesis-proxy/searchhttps://hypothes.is/api/searchGET
/hypothesis-proxy/annotations/:idhttps://hypothes.is/api/annotations/:idPATCH
  • Token sourced from HYPOTHESIS_API_TOKEN env var (injected via 1Password at dev time).
  • Only the three routes above are proxied.
  • Hypothesis API responses are returned verbatim.

Component 2: Annotation Transform (Agentation → Hypothesis)

Section titled “Component 2: Annotation Transform (Agentation → Hypothesis)”

New file: ux-prototype/.storybook/addons/hypothesis-bridge/transform.ts

Agentation fieldHypothesis fieldNotes
urluriStorybook story iframe URL (normalized)
commenttextMarkdown with metadata block (see below)
intenttags[]fix, change, question, or approve
severitytags[]blocking, important, or suggestion
tags[]Always includes Forensic
tags[]Always includes agentation (origin marker)
elementPathtags[]selector:<cssPath> for machine parsing
elementPathtarget[0].selector[0]CssSelector (W3C Web Annotation)
selectedTexttarget[0].selector[1]TextQuoteSelector (when available)
groupe4e5jGAx (arda-products)
thread[]references[]If replying to existing Hypothesis annotation
<!-- agentation:elementPath=div.container > form > button.primary-action -->
The button text is truncated on narrow viewports
---
**Element:** `button.primary-action`
**Component:** `App > Dashboard > ActionPanel > SubmitButton`
**CSS Classes:** `primary-action btn-lg`
**Severity:** important | **Intent:** fix

The HTML comment is invisible in the Hypothesis sidebar but machine-parseable by the highlight layer. The metadata section below the --- gives human context.

Storybook iframe URLs include volatile query parameters (viewMode, args, etc.). The transform must normalize the URI to iframe.html?id=<storyId> before creating or searching annotations so that annotations remain anchored across sessions.


Component 3: Bridge Integration in onSubmit

Section titled “Component 3: Bridge Integration in onSubmit”

Modify: ux-prototype/.storybook/addons/agentation-toggle/with-agentation.tsx

Replace the current clipboard-only onSubmit handler:

// Current
onSubmit={(json) => navigator.clipboard.writeText(json)}
// New: post to Hypothesis, clear localStorage, copy to clipboard
onSubmit={async (json) => {
const annotations = JSON.parse(json);
const results = await postAnnotationsToHypothesis(annotations, currentStoryUrl);
clearAgentationAnnotations(window.location.pathname);
navigator.clipboard.writeText(json);
window.dispatchEvent(new CustomEvent('hypothesis-annotations-updated'));
}}

Uses Agentation’s exported saveAnnotations utility:

import { saveAnnotations } from 'agentation';
function clearAgentationAnnotations(pathname: string) {
saveAnnotations(pathname, []);
}

This clears all annotations for the current story path after they have been persisted to Hypothesis, preventing double-marking between the Agentation overlay badges and the Hypothesis-sourced highlight badges.


Component 4: Hypothesis Sidebar Embed (Story Iframe)

Section titled “Component 4: Hypothesis Sidebar Embed (Story Iframe)”

Inject the Hypothesis embed script into the Storybook preview iframe so annotations are scoped per-story URL.

New file: ux-prototype/.storybook/preview-body.html

<script type="application/json" class="js-hypothesis-config">
{
"openSidebar": false,
"showHighlights": "whenSidebarOpen",
"branding": {
"appBackgroundColor": "#1a1a2e"
}
}
</script>
<script src="https://hypothes.is/embed.js" async></script>

Scoping: Each story loads in the iframe at a URL like http://localhost:6006/iframe.html?id=use-cases-receiving--stepwise&viewMode=story. The Hypothesis client uses this as the annotation uri, giving per-story annotation scoping.

Sidebar toggle: The Hypothesis sidebar has its own toggle tab (< arrow on the right edge). No custom toolbar button needed — it coexists with the Agentation toolbar button.


  1. Hypothesis sidebar cannot expose annotation focus events. The sidebar runs in a cross-origin iframe (hypothes.is) with no public postMessage API for external consumers. The only outward-facing callback is onLayoutChange (sidebar open/close state).

  2. Strategy: show all highlights on page load. On story mount, fetch all agentation-tagged annotations for the current URL, extract CSS selectors, render badge overlays.

  3. Badge style: replicate Agentation’s numbered circular markers for consistent visual language between annotation creation (Agentation) and annotation persistence (Hypothesis).

New file: ux-prototype/.storybook/addons/hypothesis-bridge/highlight-layer.tsx

A React component rendered by a Storybook decorator:

  1. Fetch annotations from proxy: GET /hypothesis-proxy/search?uri=<url>&tag=agentation
  2. Parse CSS selector from selector:<path> tag on each annotation.
  3. For each annotation, call document.querySelector(cssSelector) in the preview iframe.
  4. Render a positioned badge overlay (numbered circle).
  5. On badge hover: show tooltip with annotation comment + severity.
  6. ResizeObserver + MutationObserver to reposition badges when the DOM changes.
  7. Listen for hypothesis-annotations-updated custom event to refresh after new annotations are posted.
  • Absolutely-positioned <div> using getBoundingClientRect() of target element.
  • Numbered circle (1, 2, 3…) matching Agentation’s marker style.
  • Color-coded by severity: red (blocking), orange (important), blue (suggestion).
  • pointer-events: auto on the badge itself (for hover/click), pointer-events: none on the overlay container.
  • z-index below Agentation’s overlay (100001) but above story content.
  • Semi-transparent highlight border around the target element.

This addon is not currently installed. It provides useChannel + HIGHLIGHT events to outline elements from addons, but supports only simple CSS outline highlighting with no badges, numbering, or metadata tooltips. The custom highlight layer is more capable and matches the Agentation visual language.

New file: ux-prototype/.storybook/addons/hypothesis-bridge/with-highlights.tsx

export const withHighlights: DecoratorFunction<ReactRenderer> = (StoryFn) => {
const storyUrl = window.location.href;
return (
<>
<StoryFn />
<HighlightLayer storyUrl={storyUrl} />
</>
);
};

Modify: ux-prototype/.storybook/preview.ts

decorators: [withAgentation, withHighlights, withFullAppProviders],

The highlight layer is always active (no toggle needed) — it shows badges only when annotations exist for the current story.


Extend the MCP server to support the target parameter with CSS selectors for W3C Web Annotation compliance.

Add optional target parameter to createAnnotation:

interface CreateAnnotationParams {
uri: string;
text: string;
group?: string;
tags?: string[];
references?: string[];
target?: Array<{
source: string;
selector: Array<{ type: string; value?: string; exact?: string }>;
}>;
}

When target is provided, include it in the POST body instead of the default [{ source: uri }].

Extend RawSelector to include value field (for CssSelector):

interface RawSelector {
type: string;
exact?: string; // TextQuoteSelector
value?: string; // CssSelector
}

All annotations created by the bridge carry these tags:

TagPurpose
ForensicDefault tag — identifies the annotation collection/purpose
agentationOrigin marker — distinguishes from manually-created Hypothesis annotations
selector:<cssPath>Machine-parseable CSS selector for the highlight layer
<intent>One of: fix, change, question, approve
<severity>One of: blocking, important, suggestion

FilePurpose
.storybook/middleware.tsExpress middleware proxying Hypothesis API (token injection)
.storybook/preview-body.htmlHypothesis embed.js injection into preview iframe
.storybook/addons/hypothesis-bridge/transform.tsAgentation → Hypothesis annotation payload transform
.storybook/addons/hypothesis-bridge/client.tsHTTP client for proxy endpoints
.storybook/addons/hypothesis-bridge/highlight-layer.tsxReact component rendering DOM highlight badges
.storybook/addons/hypothesis-bridge/with-highlights.tsxStorybook decorator wrapping highlight layer
.storybook/addons/hypothesis-bridge/constants.tsShared constants (group ID, tag conventions)
FileChange
.storybook/addons/agentation-toggle/with-agentation.tsxonSubmit → bridge POST + localStorage clear
.storybook/preview.tsAdd withHighlights decorator
.storybook/main.tsAdd STORYBOOK_HYPOTHESIS_TOKEN to config.define
FileChange
src/client.tsAdd target parameter to createAnnotation
src/types.tsExtend RawSelector with value field
tests/client.test.tsTest for target/CssSelector

RiskMitigation
URL instability — Storybook iframe URLs include volatile query paramsNormalize URI to iframe.html?id=<storyId> before creating/searching annotations
CSS selector breakage — DOM structure changes across component versionsStore reactComponents path as fallback; highlight layer tries CSS selector first, then fuzzy component-path matching
Hypothesis embed.js + Agentation overlay conflictAgentation uses z-index 100001; test coexistence; pointer-events: none on inactive areas prevents interference
Rate limiting — Hypothesis API has rate limitsCache fetched annotations per story URL in a session-level Map; refresh only on hypothesis-annotations-updated events
Storybook 10 middleware API — format may differ from Storybook 7/8Verify against Storybook 10 docs; fallback to custom Vite plugin if needed

ComponentEffort
1. Hypothesis API proxy middleware0.5 day
2. Annotation transform function0.5 day
3. Bridge integration + localStorage cleanup0.5 day
4. Hypothesis sidebar embed0.25 day
5. DOM highlight layer (badges + positioning + observers)2 days
6. hypothesis-mcp target extension0.5 day
7. Testing + edge cases + URL scoping1 day
8. Documentation updates0.5 day
Total~5.75 days

  1. Bridge posting: Create an Agentation annotation on a story → submit → verify annotation appears in Hypothesis API (search_annotations with tag Forensic).
  2. localStorage cleanup: After submit, verify Agentation shows no annotations for that story (reopen overlay).
  3. Hypothesis sidebar: Open sidebar tab in story iframe → verify annotation is visible with formatted text body.
  4. Threading: Reply to annotation in Hypothesis sidebar → verify reply appears on subsequent loads.
  5. Highlight badges: Navigate to a story with existing annotations → verify numbered badges appear at correct element positions.
  6. Badge reposition: Resize viewport → verify badges reposition correctly via ResizeObserver.
  7. Tag convention: Verify annotations have tags: Forensic, agentation, selector:..., intent, severity.
  8. Clipboard compatibility: Verify JSON is still copied to clipboard (existing /agentation-feedback skill works).
  9. hypothesis-mcp: Run npm test in hypothesis-mcp repo after changes.