AnnotEditButton (Phase 5)
<AnnotEditButton> is the docs-site-side entry point for Phase 5
of the living-spec authoring
roadmap — embedded editing
with GitHub round-trip. Mount the component next to an
<AnnotFigure> (<Screen> in pre-Phase-7a docs) and visitors get
a one-click path to the annot-cloud editor for that PNG +
annotation yaml pair.
Quick start
Section titled “Quick start”Import the component + drop it into MDX:
import AnnotEditButton from "@ingcreators/annot-product-docs-astro/components/AnnotEditButton.astro";
<AnnotEditButton repo="ingcreators/annot" pngPath="docs/shots/login.png" annotationsPath="docs/annotations/login.annotations.yaml"/>That’s it for the default newTab mode. Click → new tab opens
https://annot.work/embed?repo=...&pngPath=...&.... Save in the
cloud editor → the tab returns with #edit-complete=<editId> →
the <AnnotEditCompleteListener> (Phase 5g)
on your layout shows a “Edit saved — site rebuilds in ~1 minute”
toast.
Embed modes
Section titled “Embed modes”Three modes per OQ-09 of the plan:
| Mode | UX | Best for |
|---|---|---|
newTab (default) | Click → new tab opens annot-cloud editor → save → tab redirects to docs site → toast | Enterprise / Team / Air-gapped (with on-prem cloudUrl) |
inline (opt-in) | Click → modal iframe loads annot-cloud editor → save → modal dismisses + toast | Consumer / Pro / personal sites |
disabled | Button not rendered | Read-only docs / air-gapped without on-prem cloud |
Why newTab is the default
Section titled “Why newTab is the default”The plan analyses nine deployment constraints (CSP frame-src,
third-party cookies, SAML / Okta / Azure AD SSO redirect,
on-prem cloudUrl, network isolation, audit log capture,
long-running edit sessions, visual continuity, initial
discovery). Eight of nine favour newTab. inline mode is the
better UX for consumer-grade deployments where the constraints
don’t apply; Enterprise tier disables it by default.
Opting into inline mode
Section titled “Opting into inline mode”Two pieces are needed:
{/* In your layout (typically one of `src/layouts/*.astro`): */}import AnnotEditorIframeModal from "@ingcreators/annot-product-docs-astro/components/AnnotEditorIframeModal.astro";
<slot /><AnnotEditorIframeModal />
{/* In MDX, opt-in per-call: */}<AnnotEditButton repo="ingcreators/annot" pngPath="docs/shots/login.png" annotationsPath="docs/annotations/login.annotations.yaml" mode="inline"/>If you forget the modal mount, mode="inline" gracefully
degrades to newTab + a one-time console.warn in DevTools.
Project-wide defaults
Section titled “Project-wide defaults”Don’t repeat cloudUrl on every button — pass it once to the
integration:
import { defineConfig } from "astro/config";import { productDocsIntegration } from "@ingcreators/annot-product-docs-astro";
export default defineConfig({ integrations: [ productDocsIntegration({ editor: { embedMode: "newTab", cloudUrl: "https://annot.work", }, }), ],});Precedence (highest wins):
- Per-call props on
<AnnotEditButton>(mode,cloudUrl) productDocsIntegration({ editor: { embedMode, cloudUrl } })- Built-in defaults (
mode="newTab",cloudUrl="https://annot.work")
On-prem cloudUrl
Section titled “On-prem cloudUrl”For Enterprise deployments behind a firewall, point the button at the customer’s annot-cloud instance:
productDocsIntegration({ editor: { cloudUrl: "https://annot.internal.example.com", },})The on-prem cloudUrl is the canonical Enterprise affordance —
visitors of the docs site stay on the customer’s network end to
end, no traffic to annot.work.
Troubleshooting
Section titled “Troubleshooting”mode="inline" falls back to newTab in DevTools
Section titled “mode="inline" falls back to newTab in DevTools”The <AnnotEditorIframeModal /> component isn’t on the page.
Mount it once in your layout (see “Opting into inline mode”
above).
inline mode breaks in Safari / Brave / corporate Chrome
Section titled “inline mode breaks in Safari / Brave / corporate Chrome”These browsers ship third-party cookie blocking by default (ITP / Brave Shields / corporate policy). The cloud editor’s auth session fails to persist across the iframe boundary. Two remediations:
- Switch to
mode="newTab"for this audience — the new tab inherits first-party cookies natively. - Configure the cloud editor’s auth to use
SameSite=None- the FedCM API where available (annot-cloud’s responsibility, not the docs site’s).
CSP frame-src rejection on inline mode
Section titled “CSP frame-src rejection on inline mode”Your docs site’s CSP needs the cloud editor’s origin whitelisted. Add to your CSP header:
Content-Security-Policy: frame-src https://annot.work;For on-prem: substitute your cloudUrl. The newTab mode
sidesteps this entirely.
#edit-complete=<editId> hash sticks around after the toast
Section titled “#edit-complete=<editId> hash sticks around after the toast”The Phase 5g <AnnotEditCompleteListener>
component clears the hash via history.replaceState on toast
render. Verify the listener is mounted on the page. If you
intentionally don’t want hash-clearing (e.g. for analytics
URL capture), fork the component.
What’s behind the scenes
Section titled “What’s behind the scenes”Phase 5 lands across eight OSS-side sub-phases (5a–5h) plus ~eight annot-cloud-side sub-phases (5y / 5z). The OSS bits:
@ingcreators/annot-embed-protocol— Tier A wire-format (5a–5c)<AnnotEditButton>— this component (5d, plus 5e’s inline-mode wiring)<AnnotEditorIframeModal>— inline-mode iframe modal (5e)productDocsIntegration({ editor })— project-wide defaults via Vite virtual module (5f)<AnnotEditCompleteListener>— post-edit toast (5g)- This dogfood + guide (5h)
The cloud-side /embed route + GitHub App + Worker proxy + on-prem deployable bundle (5y / 5z) live in the private annot-cloud repo and depend on the annot-cloud-roadmap.md Phase 3 auth foundation.