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.
Problem Statement
Section titled “Problem Statement”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.
- Persist Agentation annotations to Hypothesis — on submit, bridge posts annotations to the Hypothesis API so they survive beyond localStorage TTL.
- Enable threaded discussion — the Hypothesis sidebar in the Storybook preview iframe allows reviewers to reply, discuss, and resolve annotations.
- Visual DOM highlights — annotated elements display numbered badge markers (replicating Agentation’s visual style) sourced from Hypothesis data.
- Prevent double-marking — after posting to Hypothesis, annotations are cleared from Agentation’s localStorage.
- Backward compatibility — the JSON clipboard copy for the
/agentation-feedbackskill continues to work.
Architecture Overview
Section titled “Architecture Overview”Data Flow
Section titled “Data Flow”- Reviewer creates annotations in Agentation (click DOM elements, add comments).
- On submit → bridge transforms → POSTs to Hypothesis API with tag
Forensic+ metadata tags. - Bridge clears submitted annotations from Agentation localStorage.
- Highlight layer fetches Hypothesis annotations for the current story and renders badge markers on DOM elements.
- Hypothesis sidebar (
embed.jsin iframe) shows annotations with full threading support.
Component 1: Hypothesis API Token Proxy
Section titled “Component 1: Hypothesis API Token Proxy”Problem
Section titled “Problem”The Hypothesis API requires a Bearer token for private-group annotations. Exposing this token in browser JavaScript is a security risk.
Solution
Section titled “Solution”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 request | Proxied to | Method |
|---|---|---|
/hypothesis-proxy/annotations | https://hypothes.is/api/annotations | POST |
/hypothesis-proxy/search | https://hypothes.is/api/search | GET |
/hypothesis-proxy/annotations/:id | https://hypothes.is/api/annotations/:id | PATCH |
- Token sourced from
HYPOTHESIS_API_TOKENenv 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
Field Mapping
Section titled “Field Mapping”| Agentation field | Hypothesis field | Notes |
|---|---|---|
url | uri | Storybook story iframe URL (normalized) |
comment | text | Markdown with metadata block (see below) |
intent | tags[] | fix, change, question, or approve |
severity | tags[] | blocking, important, or suggestion |
| — | tags[] | Always includes Forensic |
| — | tags[] | Always includes agentation (origin marker) |
elementPath | tags[] | selector:<cssPath> for machine parsing |
elementPath | target[0].selector[0] | CssSelector (W3C Web Annotation) |
selectedText | target[0].selector[1] | TextQuoteSelector (when available) |
| — | group | e4e5jGAx (arda-products) |
thread[] | references[] | If replying to existing Hypothesis annotation |
Annotation Text Body Format
Section titled “Annotation Text Body Format”<!-- 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:** fixThe HTML comment is invisible in the Hypothesis sidebar but machine-parseable by
the highlight layer. The metadata section below the --- gives human context.
URL Normalization
Section titled “URL Normalization”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:
// CurrentonSubmit={(json) => navigator.clipboard.writeText(json)}
// New: post to Hypothesis, clear localStorage, copy to clipboardonSubmit={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'));}}localStorage Cleanup
Section titled “localStorage Cleanup”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.
Component 5: DOM Highlight Layer
Section titled “Component 5: DOM Highlight Layer”Design Constraints
Section titled “Design Constraints”-
Hypothesis sidebar cannot expose annotation focus events. The sidebar runs in a cross-origin iframe (
hypothes.is) with no publicpostMessageAPI for external consumers. The only outward-facing callback isonLayoutChange(sidebar open/close state). -
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. -
Badge style: replicate Agentation’s numbered circular markers for consistent visual language between annotation creation (Agentation) and annotation persistence (Hypothesis).
Module
Section titled “Module”New file: ux-prototype/.storybook/addons/hypothesis-bridge/highlight-layer.tsx
A React component rendered by a Storybook decorator:
- Fetch annotations from proxy:
GET /hypothesis-proxy/search?uri=<url>&tag=agentation - Parse CSS selector from
selector:<path>tag on each annotation. - For each annotation, call
document.querySelector(cssSelector)in the preview iframe. - Render a positioned badge overlay (numbered circle).
- On badge hover: show tooltip with annotation comment + severity.
ResizeObserver+MutationObserverto reposition badges when the DOM changes.- Listen for
hypothesis-annotations-updatedcustom event to refresh after new annotations are posted.
Badge Rendering
Section titled “Badge Rendering”- Absolutely-positioned
<div>usinggetBoundingClientRect()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: autoon the badge itself (for hover/click),pointer-events: noneon the overlay container.z-indexbelow Agentation’s overlay (100001) but above story content.- Semi-transparent highlight border around the target element.
Why Not @storybook/addon-highlight
Section titled “Why Not @storybook/addon-highlight”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.
Decorator
Section titled “Decorator”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.
Component 6: hypothesis-mcp Extension
Section titled “Component 6: hypothesis-mcp Extension”Extend the MCP server to support the target parameter with CSS selectors for
W3C Web Annotation compliance.
Modify: hypothesis-mcp/src/client.ts
Section titled “Modify: hypothesis-mcp/src/client.ts”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 }].
Modify: hypothesis-mcp/src/types.ts
Section titled “Modify: hypothesis-mcp/src/types.ts”Extend RawSelector to include value field (for CssSelector):
interface RawSelector { type: string; exact?: string; // TextQuoteSelector value?: string; // CssSelector}Tag Convention
Section titled “Tag Convention”All annotations created by the bridge carry these tags:
| Tag | Purpose |
|---|---|
Forensic | Default tag — identifies the annotation collection/purpose |
agentation | Origin 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 |
File Inventory
Section titled “File Inventory”New Files (ux-prototype)
Section titled “New Files (ux-prototype)”| File | Purpose |
|---|---|
.storybook/middleware.ts | Express middleware proxying Hypothesis API (token injection) |
.storybook/preview-body.html | Hypothesis embed.js injection into preview iframe |
.storybook/addons/hypothesis-bridge/transform.ts | Agentation → Hypothesis annotation payload transform |
.storybook/addons/hypothesis-bridge/client.ts | HTTP client for proxy endpoints |
.storybook/addons/hypothesis-bridge/highlight-layer.tsx | React component rendering DOM highlight badges |
.storybook/addons/hypothesis-bridge/with-highlights.tsx | Storybook decorator wrapping highlight layer |
.storybook/addons/hypothesis-bridge/constants.ts | Shared constants (group ID, tag conventions) |
Modified Files (ux-prototype)
Section titled “Modified Files (ux-prototype)”| File | Change |
|---|---|
.storybook/addons/agentation-toggle/with-agentation.tsx | onSubmit → bridge POST + localStorage clear |
.storybook/preview.ts | Add withHighlights decorator |
.storybook/main.ts | Add STORYBOOK_HYPOTHESIS_TOKEN to config.define |
Modified Files (hypothesis-mcp)
Section titled “Modified Files (hypothesis-mcp)”| File | Change |
|---|---|
src/client.ts | Add target parameter to createAnnotation |
src/types.ts | Extend RawSelector with value field |
tests/client.test.ts | Test for target/CssSelector |
Risks and Mitigations
Section titled “Risks and Mitigations”| Risk | Mitigation |
|---|---|
| URL instability — Storybook iframe URLs include volatile query params | Normalize URI to iframe.html?id=<storyId> before creating/searching annotations |
| CSS selector breakage — DOM structure changes across component versions | Store reactComponents path as fallback; highlight layer tries CSS selector first, then fuzzy component-path matching |
| Hypothesis embed.js + Agentation overlay conflict | Agentation uses z-index 100001; test coexistence; pointer-events: none on inactive areas prevents interference |
| Rate limiting — Hypothesis API has rate limits | Cache 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/8 | Verify against Storybook 10 docs; fallback to custom Vite plugin if needed |
Effort Estimate
Section titled “Effort Estimate”| Component | Effort |
|---|---|
| 1. Hypothesis API proxy middleware | 0.5 day |
| 2. Annotation transform function | 0.5 day |
| 3. Bridge integration + localStorage cleanup | 0.5 day |
| 4. Hypothesis sidebar embed | 0.25 day |
| 5. DOM highlight layer (badges + positioning + observers) | 2 days |
| 6. hypothesis-mcp target extension | 0.5 day |
| 7. Testing + edge cases + URL scoping | 1 day |
| 8. Documentation updates | 0.5 day |
| Total | ~5.75 days |
Verification Plan
Section titled “Verification Plan”- Bridge posting: Create an Agentation annotation on a story → submit
→ verify annotation appears in Hypothesis API (
search_annotationswith tagForensic). - localStorage cleanup: After submit, verify Agentation shows no annotations for that story (reopen overlay).
- Hypothesis sidebar: Open sidebar tab in story iframe → verify annotation is visible with formatted text body.
- Threading: Reply to annotation in Hypothesis sidebar → verify reply appears on subsequent loads.
- Highlight badges: Navigate to a story with existing annotations → verify numbered badges appear at correct element positions.
- Badge reposition: Resize viewport → verify badges reposition
correctly via
ResizeObserver. - Tag convention: Verify annotations have tags:
Forensic,agentation,selector:..., intent, severity. - Clipboard compatibility: Verify JSON is still copied to clipboard
(existing
/agentation-feedbackskill works). - hypothesis-mcp: Run
npm testin hypothesis-mcp repo after changes.
Copyright: © Arda Systems 2025-2026, All rights reserved