Skip to content

Summary and Byproducts

Project summary, decisions made during implementation, and lessons learned for the Agentation → Hypothesis bridge integration.

  • 2026-03-25: Feasibility analysis, specification, project plan
  • 2026-03-25: Implementation (Run 1: hypothesis-mcp, Run 2: ux-prototype)
  • 2026-03-25: Interactive testing and iterative fixes
  • 2026-03-25/26: CI fixes, PR creation, merge

These decisions were made during implementation based on testing feedback, diverging from the original specification where noted.

D-01: Vite Plugin Instead of Express Middleware

Section titled “D-01: Vite Plugin Instead of Express Middleware”

Spec said: Use .storybook/middleware.ts (Express middleware). Actual: Storybook 10 uses Vite, not Express. The middleware file is ignored. Implemented as a Vite configureServer plugin inline in .storybook/main.ts.

D-02: TextQuoteSelector Required for Sidebar Display

Section titled “D-02: TextQuoteSelector Required for Sidebar Display”

Spec said: CssSelector alone would work for anchoring. Actual: The Hypothesis sidebar hides annotations it cannot anchor to page text (“orphaned”). A TextQuoteSelector matching visible text on the page is required for annotations to appear in the Annotations tab. Without it, the annotation is invisible (not even in Page Notes).

Resolution: The bridge enriches each annotation with live DOM text from the annotated element or its ancestors (within #storybook-root only). When no anchorable text exists, the annotation is posted as a page note (target with source but no selectors), visible in the Page Notes tab.

D-03: URL Normalization for Annotation Matching

Section titled “D-03: URL Normalization for Annotation Matching”

Spec said: Normalize to iframe.html?id=<storyId> (strip all params). Actual: The Hypothesis sidebar resolves the document URI from <link rel="canonical"> and the document.uri config option. Both the canonical link and the config are set dynamically via preview-head.html and preview-body.html scripts. The normalized URL retains id and viewMode but strips globals and args.

D-04: onCopy Instead of onSubmit for Bridge Trigger

Section titled “D-04: onCopy Instead of onSubmit for Bridge Trigger”

Spec said: Wire the bridge to onSubmit. Actual: Agentation’s onSubmit is the per-annotation comment dialog submit, not the toolbar export action. The toolbar Copy button triggers onCopy, which receives markdown output (not JSON). The bridge reads raw annotations from Agentation’s localStorage via loadAnnotations() instead of parsing the callback argument.

D-05: Agentation Overlay Cycle for Badge Cleanup

Section titled “D-05: Agentation Overlay Cycle for Badge Cleanup”

Spec said: Clear localStorage to remove badges. Actual: saveAnnotations(pathname, []) clears localStorage but the Agentation React component’s useState still holds annotations in memory. Badges persist until component remount. The bridge cycles the Storybook global off/on (300ms gap) via __STORYBOOK_ADDONS_CHANNEL__ to force unmount/remount, clearing all badges.

Spec said: Set group: "e4e5jGAx" in the payload. Actual: Setting the group is not enough. The permissions.read field must explicitly include ["group:e4e5jGAx"]. Without it, annotations default to user-only read permission and are invisible to other group members in the sidebar.

The .storybook/middleware.ts file was initially created per the spec but removed after discovering Storybook 10 does not load it. The proxy logic lives entirely in the Vite plugin in .storybook/main.ts.

During CI validation, the Storybook build failed because next-themes (required by the vendored sonner.tsx component) was listed in vendored-deps.json but not in package.json. Added as an explicit dependency.

  • Hypothesis MCP server as a reference implementation made the API integration straightforward. The createAnnotation function pattern was directly reusable.
  • Agentation’s exported utilities (loadAnnotations, saveAnnotations, getStorageKey) enabled programmatic access to annotation state without hacking into the component internals.
  • Playwright MCP tools for browser automation during debugging helped identify the URL mismatch and orphaned annotation issues.
  • Storybook’s __STORYBOOK_ADDONS_CHANNEL__ global provided a clean way to toggle the Agentation overlay from the preview iframe without needing cross-frame postMessage hacks.
  • Hypothesis URL matching: The sidebar uses a complex URI resolution chain (canonical link, document.uri config, raw window.location.href) and the exact behavior wasn’t documented. Required multiple iterations to get the annotation URI, canonical link, and config to all agree.
  • Hypothesis orphan filtering: Annotations without a TextQuoteSelector are silently hidden. The sidebar shows “no annotations” with no indication that orphaned annotations exist. This was the hardest bug to diagnose — annotations were being created correctly but invisible.
  • Storybook 10 middleware: The .storybook/middleware.ts convention from Storybook 7 documentation is silently ignored in Storybook 10 (Vite-based). No error, no warning — the routes simply don’t exist.
  • Agentation’s onCopy vs onSubmit: The API naming doesn’t match the toolbar UI. onCopy is the toolbar Copy button; onSubmit is the per-annotation comment dialog. Required reading the minified source to understand.
  1. Agentation onCopy callback should receive annotations array — currently receives markdown text only, requiring the bridge to read localStorage directly.
  2. Hypothesis document.uri config should be better documented for SPA/iframe environments where the page URL contains volatile parameters.
  3. Badge click → scroll to sidebar annotation — currently badges show a tooltip but don’t navigate to the corresponding sidebar entry.
  4. Severity/intent from Agentation — the Agentation component’s comment dialog doesn’t expose intent/severity fields to the onCopy callback, so these tags are only present when the annotation data includes them (not always).
  5. Deployed Storybook integration — the Hypothesis sidebar works on localhost but the URI matching needs testing on the deployed Vercel/GitHub Pages URLs.