@ingcreators/annot-product-docs-astro 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @ingcreators/annot-product-docs-astro
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [6124d59]
8
+ - Updated dependencies [b5d52f6]
9
+ - Updated dependencies [fa712fd]
10
+ - Updated dependencies [0c7ac26]
11
+ - Updated dependencies [f09a6b1]
12
+ - Updated dependencies [0d19345]
13
+ - Updated dependencies [64dc6e8]
14
+ - Updated dependencies [691bec5]
15
+ - Updated dependencies [9697f27]
16
+ - Updated dependencies [266b05a]
17
+ - @ingcreators/annot-annotator@0.6.0
18
+ - @ingcreators/annot-product-docs@0.4.0
19
+ - @ingcreators/annot-core@0.3.0
20
+ - @ingcreators/annot-playwright@0.4.1
21
+
3
22
  ## 0.3.0
4
23
 
5
24
  ### Minor Changes
package/dist/cache.d.ts CHANGED
@@ -4,8 +4,24 @@
4
4
  * consumer the next time they build. Keep this small (a single
5
5
  * digit per breaking change) — large jumps don't help and just
6
6
  * obscure the upgrade history.
7
+ *
8
+ * Bumped to 2 in Phase 1h of
9
+ * `docs/plans/living-spec-authoring-roadmap.md` — the renderer now
10
+ * prefers PNG XMP `annot:elementTree` bboxes over the legacy
11
+ * `annot:snapshot` MDX comment block when both are present. Forcing
12
+ * a cold cache avoids the stale-cache window where a migrated MDX
13
+ * (legacy comment block stripped) + new PNG XMP would otherwise
14
+ * map to the same cache key as the pre-migration state.
15
+ *
16
+ * Known limitation: post-migration, re-running the Playwright tour
17
+ * mutates the PNG XMP without touching the MDX. Today's cache key
18
+ * doesn't include PNG content, so the cache won't auto-invalidate
19
+ * on tour re-runs. Workaround until that's fixed: delete the
20
+ * `.astro/annot-product-docs/` cache directory between tour runs
21
+ * (or bump this version every time the tour is expected to change
22
+ * output). A follow-up will fold a PNG content hash into the key.
7
23
  */
8
- export declare const RENDER_PIPELINE_VERSION = 1;
24
+ export declare const RENDER_PIPELINE_VERSION = 2;
9
25
  export interface CacheKeyInput {
10
26
  mdxSource: string;
11
27
  screenId: string;
@@ -16,6 +32,13 @@ export interface CacheKeyInput {
16
32
  * historical key shape for flat callers.
17
33
  */
18
34
  editable?: boolean;
35
+ /**
36
+ * Phase 2b: raw bytes of the `<Screen annotations="…">` yaml,
37
+ * when set. Folded into the cache key so editing the yaml busts
38
+ * the cached PNG. Undefined when the screen uses the inline
39
+ * `<Overlay>` path (legacy callers' key shape is preserved).
40
+ */
41
+ annotationsYamlSource?: string;
19
42
  }
20
43
  /**
21
44
  * Compute the cache key for a `<Screen>` block. Stable across
@@ -0,0 +1,32 @@
1
+ ---
2
+ // `<AnnotCallout>` — Phase 2b of
3
+ // `docs/plans/living-spec-authoring-roadmap.md`. Caption for one
4
+ // callout entry on a `<Screen annotations="…">`. Replaces the
5
+ // inline `<Overlay match intent number>` form: match / intent /
6
+ // number now live in the screen's annotation yaml, and this
7
+ // component carries only the visible body.
8
+ //
9
+ // MDX usage:
10
+ // <Screen id="login" src="./shots/login.png"
11
+ // annotations="./annotations/login.yaml">
12
+ // <AnnotCallout for="o1">**Email** — registered address.</AnnotCallout>
13
+ // <AnnotCallout for="o2">**Password** — characters hidden.</AnnotCallout>
14
+ // </Screen>
15
+ //
16
+ // The visible badge / number / intent border is composed onto the
17
+ // annotated PNG by the Image Service (see `../render.ts`). The
18
+ // HTML side emits the body inside a `<li>` so the legend
19
+ // auto-numbers via the surrounding `<ol class="annot-screen-overlays">`
20
+ // counter — no per-call `number` prop required.
21
+
22
+ interface Props {
23
+ /** Targets `overlays[].id` in the screen's annotation yaml. */
24
+ for: string;
25
+ }
26
+ const { for: forId } = Astro.props;
27
+ ---
28
+ <li class="annot-callout" data-callout-for={forId}>
29
+ <div class="annot-callout-body">
30
+ <slot />
31
+ </div>
32
+ </li>
@@ -0,0 +1,186 @@
1
+ ---
2
+ // `<AnnotEditButton>` — Phase 5d of
3
+ // `docs/plans/living-spec-authoring-roadmap.md`. Mountable
4
+ // "✏️ Edit" button that routes visitors to the annot-cloud
5
+ // editor at `${cloudUrl}/embed?...`. Phase 5 ships three embed
6
+ // modes:
7
+ //
8
+ // - `newTab` (default): click → `window.open(...)`. Works
9
+ // under every Enterprise constraint (CSP / SSO / on-prem /
10
+ // air-gapped). See OQ-09 in
11
+ // `living-spec-authoring-roadmap.md`.
12
+ // - `disabled`: button is not rendered. For read-only docs /
13
+ // air-gapped deployments without on-prem annot-cloud.
14
+ // - `inline` (opt-in): modal `<iframe>` mounted on the docs
15
+ // site. Requires `<AnnotEditorIframeModal />` (5e) to be
16
+ // mounted somewhere on the same page (typically in the
17
+ // layout). If the modal isn't present, `inline` gracefully
18
+ // degrades to `newTab` with a one-time `console.warn`.
19
+ //
20
+ // MDX usage:
21
+ //
22
+ // <AnnotEditButton
23
+ // repo="ingcreators/annot"
24
+ // pngPath="docs/shots/login.png"
25
+ // annotationsPath="docs/annotations/login.annotations.yaml"
26
+ // />
27
+ //
28
+ // `cloudUrl` defaults to `https://annot.work`. Override via
29
+ // per-call prop OR (in Phase 5f) `annot-docs.config.ts`'s
30
+ // `editor.cloudUrl` default. The defaults landed here are
31
+ // what Phase 5f will lift into the config; Phase 5d ships
32
+ // per-call props only.
33
+
34
+ import type { EmbedMode } from "@ingcreators/annot-embed-protocol";
35
+ import { ANNOT_EDITOR_CONFIG } from "../editor-config-virtual.js";
36
+
37
+ interface Props {
38
+ /** Owner/repo slug, e.g. `"ingcreators/annot"`. */
39
+ repo: string;
40
+ /** Path within the repo of the PNG being edited. */
41
+ pngPath: string;
42
+ /** Path within the repo of the linked `.annotations.yaml`. */
43
+ annotationsPath: string;
44
+ /** Embed mode. Defaults to the integration-supplied
45
+ * `editor.embedMode` (or `"newTab"` if unset). */
46
+ mode?: EmbedMode;
47
+ /** Cloud editor origin override. Defaults to the
48
+ * integration-supplied `editor.cloudUrl` (or
49
+ * `"https://annot.work"` if unset). */
50
+ cloudUrl?: string;
51
+ /** Visible button label. Defaults to `"✏️ Edit"`. */
52
+ label?: string;
53
+ }
54
+
55
+ const {
56
+ repo,
57
+ pngPath,
58
+ annotationsPath,
59
+ mode = ANNOT_EDITOR_CONFIG.embedMode,
60
+ cloudUrl = ANNOT_EDITOR_CONFIG.cloudUrl,
61
+ label = "✏️ Edit",
62
+ } = Astro.props;
63
+ ---
64
+ {
65
+ mode !== "disabled" && (
66
+ <button
67
+ type="button"
68
+ class="annot-edit-button"
69
+ data-annot-edit-mode={mode}
70
+ data-annot-edit-cloud-url={cloudUrl}
71
+ data-annot-edit-repo={repo}
72
+ data-annot-edit-png-path={pngPath}
73
+ data-annot-edit-annotations-path={annotationsPath}
74
+ aria-label={`Edit ${pngPath}`}
75
+ >
76
+ {label}
77
+ </button>
78
+ )
79
+ }
80
+
81
+ <script>
82
+ import { encodeEmbedRequestUrl } from "@ingcreators/annot-embed-protocol";
83
+
84
+ // One-time warning when a `mode="inline"` button is clicked
85
+ // but `<AnnotEditorIframeModal />` isn't on the page. The
86
+ // warning lives at the page-script level so multiple buttons
87
+ // share it.
88
+ let warnedMissingModal = false;
89
+
90
+ interface ModalApi {
91
+ open: (options: { cloudUrl: string; editorUrl: string }) => void;
92
+ }
93
+
94
+ function findModalApi(): ModalApi | undefined {
95
+ return (window as unknown as { __annotEditorIframeModal?: ModalApi })
96
+ .__annotEditorIframeModal;
97
+ }
98
+
99
+ function activate(button: HTMLButtonElement): void {
100
+ const mode = button.dataset.annotEditMode ?? "newTab";
101
+ const cloudUrl = button.dataset.annotEditCloudUrl ?? "";
102
+ const repo = button.dataset.annotEditRepo ?? "";
103
+ const pngPath = button.dataset.annotEditPngPath ?? "";
104
+ const annotationsPath = button.dataset.annotEditAnnotationsPath ?? "";
105
+
106
+ button.addEventListener("click", () => {
107
+ // Resolve the effective mode. `inline` falls back to
108
+ // `newTab` if `<AnnotEditorIframeModal />` isn't on the
109
+ // page so the button still does something useful (warn
110
+ // once so authors notice in DevTools).
111
+ let effectiveMode: "newTab" | "inline" = mode === "inline" ? "inline" : "newTab";
112
+ const modalApi = effectiveMode === "inline" ? findModalApi() : undefined;
113
+ if (effectiveMode === "inline" && !modalApi) {
114
+ if (!warnedMissingModal) {
115
+ // biome-ignore lint/suspicious/noConsole: degraded-UX surface for the missing modal.
116
+ console.warn(
117
+ '[<AnnotEditButton>] mode="inline" requires <AnnotEditorIframeModal /> on the page; falling back to mode="newTab". Mount the modal component in your layout to enable the inline flow.',
118
+ );
119
+ warnedMissingModal = true;
120
+ }
121
+ effectiveMode = "newTab";
122
+ }
123
+
124
+ const returnUrl = window.location.href.split("#")[0];
125
+ try {
126
+ const url = encodeEmbedRequestUrl({
127
+ cloudUrl,
128
+ repo,
129
+ pngPath,
130
+ annotationsPath,
131
+ returnUrl,
132
+ mode: effectiveMode,
133
+ });
134
+ if (effectiveMode === "inline" && modalApi) {
135
+ modalApi.open({ cloudUrl, editorUrl: url });
136
+ return;
137
+ }
138
+ window.open(url, "_blank", "noopener,noreferrer");
139
+ } catch (error) {
140
+ // biome-ignore lint/suspicious/noConsole: encoder errors surface as runtime warnings.
141
+ console.error("[<AnnotEditButton>] failed to build embed URL:", error);
142
+ }
143
+ });
144
+ }
145
+
146
+ function init(): void {
147
+ const buttons = document.querySelectorAll<HTMLButtonElement>(
148
+ "button.annot-edit-button",
149
+ );
150
+ for (const button of Array.from(buttons)) {
151
+ activate(button);
152
+ }
153
+ }
154
+
155
+ if (document.readyState === "loading") {
156
+ document.addEventListener("DOMContentLoaded", init);
157
+ } else {
158
+ init();
159
+ }
160
+ </script>
161
+
162
+ <style>
163
+ .annot-edit-button {
164
+ display: inline-flex;
165
+ align-items: center;
166
+ gap: 0.4em;
167
+ padding: 0.4em 0.8em;
168
+ font-family: inherit;
169
+ font-size: 0.9em;
170
+ line-height: 1.2;
171
+ color: var(--annot-edit-button-fg, #ffffff);
172
+ background: var(--annot-edit-button-bg, #3b82f6);
173
+ border: 0;
174
+ border-radius: 0.4em;
175
+ cursor: pointer;
176
+ transition: background 0.15s ease-out;
177
+ }
178
+ .annot-edit-button:hover,
179
+ .annot-edit-button:focus-visible {
180
+ background: var(--annot-edit-button-bg-hover, #2563eb);
181
+ outline: none;
182
+ }
183
+ .annot-edit-button:focus-visible {
184
+ box-shadow: 0 0 0 2px var(--annot-edit-button-focus-ring, #93c5fd);
185
+ }
186
+ </style>
@@ -0,0 +1,186 @@
1
+ ---
2
+ // `<AnnotEditCompleteListener>` — Phase 5g of
3
+ // `docs/plans/living-spec-authoring-roadmap.md`.
4
+ //
5
+ // Invisible client-side component that surfaces a transient
6
+ // toast when the cloud editor returns control to the docs site
7
+ // after a save. Two trigger paths:
8
+ //
9
+ // - `newTab` mode: the cloud editor redirects to
10
+ // `${returnUrl}#edit-complete=<editId>`. On client-side mount
11
+ // this component parses `window.location.hash` via the 5b
12
+ // `parseEmbedReturnHash` codec, renders a toast when the
13
+ // hash carries an `EmbedReturnSignal` of kind `"complete"`,
14
+ // and dispatches an `annot:edit-complete` `CustomEvent` on
15
+ // `document` so downstream listeners (analytics, custom
16
+ // rebuild-status badges) can react.
17
+ //
18
+ // - `inline` mode: the 5e `<AnnotEditorIframeModal>` already
19
+ // dismisses itself on `EditCommitted`. This component
20
+ // listens for the same lifecycle event via an
21
+ // `annot-editor-iframe-modal-event` `CustomEvent` the 5e
22
+ // modal dispatches on `document`, then renders the same
23
+ // toast.
24
+ //
25
+ // Mount once per page (typically inside the layout). Multiple
26
+ // instances are safe — only the first activates; the rest
27
+ // short-circuit on init via the `data-annot-edit-complete-listener-active`
28
+ // marker.
29
+
30
+ interface Props {
31
+ /** Toast message override. Defaults to "Edit saved — site
32
+ * rebuilds in ~1 minute." */
33
+ message?: string;
34
+ /** Auto-dismiss timeout in milliseconds. Defaults to 8000.
35
+ * Set to `0` to keep the toast visible until the user
36
+ * dismisses it manually. */
37
+ dismissAfterMs?: number;
38
+ }
39
+
40
+ const { message = "Edit saved — site rebuilds in ~1 minute.", dismissAfterMs = 8000 } =
41
+ Astro.props;
42
+ ---
43
+
44
+ <div
45
+ class="annot-edit-complete-listener"
46
+ data-annot-edit-complete-listener-root
47
+ data-annot-edit-complete-message={message}
48
+ data-annot-edit-complete-dismiss-ms={dismissAfterMs}
49
+ aria-live="polite"
50
+ aria-atomic="true"
51
+ >
52
+ <slot />
53
+ </div>
54
+
55
+ <script>
56
+ import { parseEmbedReturnHash } from "@ingcreators/annot-embed-protocol";
57
+
58
+ const ROOT_SELECTOR = "[data-annot-edit-complete-listener-root]";
59
+ const ACTIVE_MARKER = "data-annot-edit-complete-listener-active";
60
+ const TOAST_DISMISS_FALLBACK_MS = 8000;
61
+
62
+ interface Detail {
63
+ readonly editId: string;
64
+ readonly source: "newTab" | "inline";
65
+ }
66
+
67
+ function findRoot(): HTMLElement | null {
68
+ // Pick the FIRST listener mounted on the page. Multiple
69
+ // copies are safe; subsequent ones short-circuit.
70
+ return document.querySelector<HTMLElement>(ROOT_SELECTOR);
71
+ }
72
+
73
+ function showToast(root: HTMLElement, detail: Detail): void {
74
+ const message = root.dataset.annotEditCompleteMessage ?? "Edit saved.";
75
+ const dismissAfterMs = Number(
76
+ root.dataset.annotEditCompleteDismissMs ?? TOAST_DISMISS_FALLBACK_MS,
77
+ );
78
+
79
+ const toast = document.createElement("div");
80
+ toast.className = "annot-edit-complete-toast";
81
+ toast.setAttribute("role", "status");
82
+ toast.dataset.annotEditCompleteId = detail.editId;
83
+ toast.dataset.annotEditCompleteSource = detail.source;
84
+ toast.textContent = message;
85
+
86
+ const dismissBtn = document.createElement("button");
87
+ dismissBtn.type = "button";
88
+ dismissBtn.className = "annot-edit-complete-toast-dismiss";
89
+ dismissBtn.setAttribute("aria-label", "Dismiss notification");
90
+ dismissBtn.textContent = "×";
91
+ dismissBtn.addEventListener("click", () => {
92
+ toast.remove();
93
+ });
94
+ toast.appendChild(dismissBtn);
95
+
96
+ root.appendChild(toast);
97
+
98
+ if (Number.isFinite(dismissAfterMs) && dismissAfterMs > 0) {
99
+ window.setTimeout(() => {
100
+ toast.remove();
101
+ }, dismissAfterMs);
102
+ }
103
+
104
+ document.dispatchEvent(
105
+ new CustomEvent("annot:edit-complete", { detail }),
106
+ );
107
+ }
108
+
109
+ function consumeReturnHash(root: HTMLElement): void {
110
+ const signal = parseEmbedReturnHash(window.location.hash);
111
+ if (!signal || signal.kind !== "complete") return;
112
+ showToast(root, { editId: signal.editId, source: "newTab" });
113
+ // Clear the hash so a page refresh doesn't re-toast.
114
+ if (typeof window.history.replaceState === "function") {
115
+ const cleanUrl = `${window.location.pathname}${window.location.search}`;
116
+ window.history.replaceState(null, "", cleanUrl);
117
+ }
118
+ }
119
+
120
+ function attachInlineListener(root: HTMLElement): void {
121
+ document.addEventListener("annot:editor-iframe-committed", (event: Event) => {
122
+ const detail = (event as CustomEvent<{ editId?: string }>).detail;
123
+ const editId = detail?.editId ?? "";
124
+ if (!editId) return;
125
+ showToast(root, { editId, source: "inline" });
126
+ });
127
+ }
128
+
129
+ function init(): void {
130
+ const root = findRoot();
131
+ if (!root) return;
132
+ if (root.hasAttribute(ACTIVE_MARKER)) return;
133
+ root.setAttribute(ACTIVE_MARKER, "1");
134
+
135
+ consumeReturnHash(root);
136
+ attachInlineListener(root);
137
+ }
138
+
139
+ if (document.readyState === "loading") {
140
+ document.addEventListener("DOMContentLoaded", init);
141
+ } else {
142
+ init();
143
+ }
144
+ </script>
145
+
146
+ <style>
147
+ .annot-edit-complete-listener {
148
+ position: fixed;
149
+ bottom: 1.5rem;
150
+ right: 1.5rem;
151
+ z-index: 9999;
152
+ display: flex;
153
+ flex-direction: column;
154
+ gap: 0.5rem;
155
+ pointer-events: none;
156
+ }
157
+ .annot-edit-complete-toast {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 0.75rem;
161
+ padding: 0.75rem 1rem;
162
+ color: var(--annot-edit-toast-fg, #ffffff);
163
+ background: var(--annot-edit-toast-bg, #16a34a);
164
+ border-radius: 0.4em;
165
+ box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.25);
166
+ font-family: inherit;
167
+ font-size: 0.95em;
168
+ line-height: 1.3;
169
+ pointer-events: auto;
170
+ }
171
+ .annot-edit-complete-toast-dismiss {
172
+ margin-left: auto;
173
+ background: transparent;
174
+ border: 0;
175
+ color: inherit;
176
+ font-size: 1.25em;
177
+ line-height: 1;
178
+ cursor: pointer;
179
+ padding: 0 0.25em;
180
+ }
181
+ .annot-edit-complete-toast-dismiss:hover,
182
+ .annot-edit-complete-toast-dismiss:focus-visible {
183
+ opacity: 0.8;
184
+ outline: none;
185
+ }
186
+ </style>
@@ -0,0 +1,202 @@
1
+ ---
2
+ // `<AnnotEditorIframeModal>` — Phase 5e of
3
+ // `docs/plans/living-spec-authoring-roadmap.md`. The inline-
4
+ // mode counterpart to `<AnnotEditButton>`'s default newTab
5
+ // behaviour. Mount once per page (typically in the docs site's
6
+ // layout) and any `<AnnotEditButton mode="inline">` on the
7
+ // same page opens the modal instead of a new tab.
8
+ //
9
+ // Lazy-loaded: the modal HTML + script only ship if this
10
+ // component is mounted. Pages that only use `mode="newTab"`
11
+ // (the default per OQ-09) never download the modal code.
12
+ // `<AnnotEditButton mode="inline">` on a page without
13
+ // `<AnnotEditorIframeModal />` gracefully degrades to newTab
14
+ // + a one-time `console.warn`.
15
+ //
16
+ // MDX usage in a layout:
17
+ //
18
+ // ---
19
+ // import { AnnotEditorIframeModal } from "@ingcreators/annot-product-docs-astro/components/AnnotEditorIframeModal.astro";
20
+ // ---
21
+ // <slot />
22
+ // <AnnotEditorIframeModal />
23
+ //
24
+ // Then any `<AnnotEditButton mode="inline" ... />` in MDX
25
+ // opens the modal on click. The modal listens for
26
+ // `EditCommitted` / `EditAbandoned` / `ResizeNeeded` via the
27
+ // 5c postMessage dispatcher and dismisses itself accordingly.
28
+
29
+ interface Props {
30
+ /** Modal aria-label. Overridable for localisation. */
31
+ ariaLabel?: string;
32
+ }
33
+
34
+ const { ariaLabel = "Annot editor" } = Astro.props;
35
+ ---
36
+
37
+ <dialog
38
+ class="annot-editor-iframe-modal"
39
+ data-annot-edit-modal-root
40
+ aria-label={ariaLabel}
41
+ >
42
+ <iframe
43
+ class="annot-editor-iframe-modal-frame"
44
+ title={ariaLabel}
45
+ allow="clipboard-read; clipboard-write"
46
+ referrerpolicy="origin"
47
+ ></iframe>
48
+ </dialog>
49
+
50
+ <script>
51
+ import {
52
+ createEmbedHostMessenger,
53
+ type EmbedEvent,
54
+ type EmbedMessenger,
55
+ } from "@ingcreators/annot-embed-protocol";
56
+
57
+ interface OpenModalOptions {
58
+ readonly cloudUrl: string;
59
+ readonly editorUrl: string;
60
+ }
61
+
62
+ const MAX_HEIGHT_FRACTION = 0.95;
63
+ const DEFAULT_FRAME_HEIGHT_PX = 720;
64
+
65
+ function findModal(): HTMLDialogElement | null {
66
+ return document.querySelector<HTMLDialogElement>(
67
+ "dialog[data-annot-edit-modal-root]",
68
+ );
69
+ }
70
+
71
+ function findFrame(modal: HTMLDialogElement): HTMLIFrameElement | null {
72
+ return modal.querySelector<HTMLIFrameElement>(
73
+ "iframe.annot-editor-iframe-modal-frame",
74
+ );
75
+ }
76
+
77
+ function clampHeight(requested: number): number {
78
+ if (!Number.isFinite(requested) || requested <= 0) {
79
+ return DEFAULT_FRAME_HEIGHT_PX;
80
+ }
81
+ const max = window.innerHeight * MAX_HEIGHT_FRACTION;
82
+ return Math.min(requested, max);
83
+ }
84
+
85
+ function openModal(options: OpenModalOptions): void {
86
+ const modal = findModal();
87
+ if (!modal) {
88
+ // biome-ignore lint/suspicious/noConsole: lazy-mount contract violation surfaces here.
89
+ console.warn(
90
+ "[<AnnotEditorIframeModal>] dialog element not found — was the component mounted on this page?",
91
+ );
92
+ return;
93
+ }
94
+ const frame = findFrame(modal);
95
+ if (!frame) return;
96
+
97
+ frame.style.height = `${DEFAULT_FRAME_HEIGHT_PX}px`;
98
+ frame.src = options.editorUrl;
99
+
100
+ let messenger: EmbedMessenger | null = null;
101
+
102
+ const teardown = (): void => {
103
+ if (messenger) {
104
+ messenger.cleanup();
105
+ messenger = null;
106
+ }
107
+ frame.removeAttribute("src");
108
+ };
109
+
110
+ const onEvent = (event: EmbedEvent): void => {
111
+ if (event.type === "EditCommitted") {
112
+ // Forward to the docs-site's `<AnnotEditCompleteListener>` so the
113
+ // post-edit toast surfaces consistently across newTab + inline
114
+ // modes (Phase 5g).
115
+ document.dispatchEvent(
116
+ new CustomEvent("annot:editor-iframe-committed", {
117
+ detail: {
118
+ editId: event.editId,
119
+ commitSha: event.commitSha,
120
+ branch: event.branch,
121
+ prUrl: event.prUrl,
122
+ },
123
+ }),
124
+ );
125
+ modal.close();
126
+ return;
127
+ }
128
+ if (event.type === "EditAbandoned") {
129
+ modal.close();
130
+ return;
131
+ }
132
+ if (event.type === "ResizeNeeded") {
133
+ frame.style.height = `${clampHeight(event.height)}px`;
134
+ }
135
+ };
136
+
137
+ modal.addEventListener(
138
+ "close",
139
+ () => {
140
+ teardown();
141
+ },
142
+ { once: true },
143
+ );
144
+
145
+ // Set up the host messenger after the iframe loads — its
146
+ // `contentWindow` is null until then. Once loaded, attach
147
+ // the messenger; subsequent postMessages from the cloud
148
+ // editor route through it.
149
+ frame.addEventListener(
150
+ "load",
151
+ () => {
152
+ try {
153
+ messenger = createEmbedHostMessenger({
154
+ frame,
155
+ expectedOrigin: options.cloudUrl,
156
+ onEvent,
157
+ });
158
+ } catch (error) {
159
+ // biome-ignore lint/suspicious/noConsole: surface failed host-messenger setup.
160
+ console.error("[<AnnotEditorIframeModal>] failed to attach host messenger:", error);
161
+ modal.close();
162
+ }
163
+ },
164
+ { once: true },
165
+ );
166
+
167
+ modal.showModal();
168
+ }
169
+
170
+ // Expose the open function for `<AnnotEditButton>`'s inline-
171
+ // mode click handler to call. Keep the surface narrow — just
172
+ // `open` — so future expansion (close-from-outside,
173
+ // post-event listener API) is additive.
174
+ type ModalApi = {
175
+ open: (options: OpenModalOptions) => void;
176
+ };
177
+ (window as unknown as { __annotEditorIframeModal?: ModalApi }).__annotEditorIframeModal =
178
+ { open: openModal };
179
+ </script>
180
+
181
+ <style>
182
+ .annot-editor-iframe-modal {
183
+ width: min(95vw, 1280px);
184
+ max-width: 95vw;
185
+ max-height: 95vh;
186
+ padding: 0;
187
+ border: 0;
188
+ border-radius: 0.6em;
189
+ background: var(--annot-editor-modal-bg, #ffffff);
190
+ box-shadow: 0 0.5em 2em rgba(0, 0, 0, 0.4);
191
+ overflow: hidden;
192
+ }
193
+ .annot-editor-iframe-modal::backdrop {
194
+ background: rgba(0, 0, 0, 0.5);
195
+ }
196
+ .annot-editor-iframe-modal-frame {
197
+ display: block;
198
+ width: 100%;
199
+ min-height: 360px;
200
+ border: 0;
201
+ }
202
+ </style>
@@ -1,27 +1,47 @@
1
1
  ---
2
2
  // `<Screen>` — annotated screenshot block.
3
3
  //
4
- // MDX usage:
4
+ // MDX usage (legacy, inline `<Overlay>` callouts):
5
5
  // <Screen id="login" src="./shots/login.png">
6
6
  // <Overlay match={...} number={1}>...</Overlay>
7
7
  // </Screen>
8
8
  //
9
+ // MDX usage (Phase 2b, yaml-driven callouts):
10
+ // <Screen id="login" src="./shots/login.png"
11
+ // annotations="./annotations/login.yaml">
12
+ // <AnnotCallout for="o1">...</AnnotCallout>
13
+ // </Screen>
14
+ //
9
15
  // The annotated PNG is produced at build time by the Image
10
16
  // Service in `@ingcreators/annot-product-docs-astro` (see
11
- // `../render.ts`). At render time we just emit an `<img>` that
12
- // points at the cache hash; the Astro pipeline resolves the
13
- // final URL.
17
+ // `../render.ts`). When `annotations` is set, the service reads
18
+ // the yaml + composes badges from `overlays[]`; otherwise it
19
+ // resolves the inline `<Overlay match>` children. At render time
20
+ // we just emit an `<img>` that points at the cache hash; the
21
+ // Astro pipeline resolves the final URL.
14
22
 
15
23
  interface Props {
16
24
  id: string;
17
25
  src: string;
18
26
  alt?: string;
27
+ /**
28
+ * Phase 2b of `docs/plans/living-spec-authoring-roadmap.md`.
29
+ * Path (relative to the MDX file) to an `.annotations.yaml`
30
+ * file describing the screen's overlays. When set, the Image
31
+ * Service prefers yaml-driven badge composition over the
32
+ * legacy inline `<Overlay>` children.
33
+ */
34
+ annotations?: string;
19
35
  }
20
- const { id, src, alt } = Astro.props;
36
+ const { id, src, alt, annotations } = Astro.props;
21
37
  ---
22
- <figure class="annot-screen" data-screen-id={id}>
38
+ <figure
39
+ class="annot-screen"
40
+ data-screen-id={id}
41
+ data-annotations-path={annotations}
42
+ >
23
43
  <img src={src} alt={alt ?? `Screen ${id}`} loading="lazy" />
24
- <div class="annot-screen-overlays" data-screen-overlays={id}>
44
+ <ol class="annot-screen-overlays" data-screen-overlays={id}>
25
45
  <slot />
26
- </div>
46
+ </ol>
27
47
  </figure>
@@ -0,0 +1,12 @@
1
+ import { EmbedMode } from '@ingcreators/annot-embed-protocol';
2
+ export interface ResolvedEditorConfig {
3
+ readonly embedMode: EmbedMode;
4
+ readonly cloudUrl: string;
5
+ }
6
+ /**
7
+ * Phase 5 OSS-side built-in defaults. Mirror the per-component
8
+ * defaults declared inside `<AnnotEditButton>` (`mode="newTab"`,
9
+ * `cloudUrl="https://annot.work"`) so consumers without the
10
+ * integration installed still see consistent behaviour.
11
+ */
12
+ export declare const ANNOT_EDITOR_CONFIG: ResolvedEditorConfig;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export type { CacheKeyInput, FileCache } from './cache.js';
2
2
  export { cacheKey, createFileCache, createMemoryCache, RENDER_PIPELINE_VERSION, } from './cache.js';
3
3
  export type { GraphDirection, GraphEdge, Match, OverlayIntent, ScreenListEntry, TransitionEntry, } from './components/types.js';
4
- export type { ProductDocsIntegrationOptions } from './integration.js';
5
- export { productDocsIntegration } from './integration.js';
4
+ export type { ResolvedEditorConfig } from './editor-config-virtual.js';
5
+ export { ANNOT_EDITOR_CONFIG } from './editor-config-virtual.js';
6
+ export type { ProductDocsEditorOptions, ProductDocsIntegrationOptions, } from './integration.js';
7
+ export { editorConfigVirtualPlugin, productDocsIntegration, resolveEditorConfig, } from './integration.js';
6
8
  export type { RenderAnnotatedScreenOptions, RenderResult, } from './render.js';
7
9
  export { renderAnnotatedScreen, resolveMdxAnnotations, svgFromBboxAnnotations } from './render.js';
package/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { createHash as e } from "node:crypto";
2
2
  import { mkdir as t, readFile as n, writeFile as r } from "node:fs/promises";
3
3
  import { dirname as i, isAbsolute as a, join as o, resolve as s } from "node:path";
4
- import { createAnnotator as c } from "@ingcreators/annot-annotator";
5
- import { buildBadgeAnnotations as l, emptyAnnotationsSvg as u, parseMdxFile as d, parseSnapshotBoxes as f, resolveMdxAnnotations as p, svgFromBadges as m, svgFromBboxAnnotations as h } from "@ingcreators/annot-product-docs";
4
+ import { burnRedactions as c, createAnnotator as l } from "@ingcreators/annot-annotator";
5
+ import { readElementTreePng as u } from "@ingcreators/annot-core";
6
+ import { buildBadgeAnnotations as d, buildBadgeAnnotationsFromYaml as f, buildRasterRedactRegionsFromYaml as p, buildShapeAnnotationsFromYaml as m, elementTreeToBoxedEntries as h, emptyAnnotationsSvg as g, parseAnnotationsYaml as _, parseMdxFile as v, parseSnapshotBoxes as y, resolveMdxAnnotations as b, svgFromBboxAnnotations as x, svgFromBboxAnnotations as S, warnLegacyOverlay as C } from "@ingcreators/annot-product-docs";
6
7
  //#region src/cache.ts
7
- var g = 1;
8
- function _(t) {
8
+ var w = 2;
9
+ function T(t) {
9
10
  let n = e("sha256");
10
- return n.update("v1\n"), n.update(t.mdxSource), n.update("\0"), n.update(t.screenId), t.editable && n.update("\0editable"), n.digest("hex");
11
+ return n.update("v2\n"), n.update(t.mdxSource), n.update("\0"), n.update(t.screenId), t.editable && n.update("\0editable"), t.annotationsYamlSource !== void 0 && (n.update("\0annotationsYaml\0"), n.update(t.annotationsYamlSource)), n.digest("hex");
11
12
  }
12
- function v(e) {
13
+ function E(e) {
13
14
  return {
14
15
  dir: e,
15
16
  async get(t) {
@@ -27,7 +28,7 @@ function v(e) {
27
28
  }
28
29
  };
29
30
  }
30
- function y() {
31
+ function D() {
31
32
  let e = /* @__PURE__ */ new Map();
32
33
  return {
33
34
  dir: ":memory:",
@@ -40,79 +41,133 @@ function y() {
40
41
  };
41
42
  }
42
43
  //#endregion
43
- //#region src/integration.ts
44
- var b = "@ingcreators/annot-product-docs-astro";
45
- function x(e = {}) {
44
+ //#region src/editor-config-virtual.ts
45
+ var O = {
46
+ embedMode: "newTab",
47
+ cloudUrl: "https://annot.work"
48
+ }, k = "@ingcreators/annot-product-docs-astro", A = {
49
+ embedMode: "newTab",
50
+ cloudUrl: "https://annot.work"
51
+ }, j = "virtual:annot-docs/editor-config", M = `\0${j}`, N = "editor-config-virtual";
52
+ function P(e) {
53
+ return {
54
+ name: "annot-docs:editor-config-virtual",
55
+ enforce: "pre",
56
+ resolveId(e) {
57
+ return e === j || e.endsWith(`${N}.ts`) || e.endsWith(`${N}.js`) ? M : null;
58
+ },
59
+ load(t) {
60
+ return t === M ? `export const ANNOT_EDITOR_CONFIG = ${JSON.stringify(e)};\n` : null;
61
+ }
62
+ };
63
+ }
64
+ function F(e) {
65
+ return {
66
+ embedMode: e?.embedMode ?? A.embedMode,
67
+ cloudUrl: e?.cloudUrl ?? A.cloudUrl
68
+ };
69
+ }
70
+ function I(e = {}) {
46
71
  let t = {
47
72
  contentDir: e.contentDir ?? "docs",
48
73
  configPath: e.configPath ?? "annot-docs.config.ts",
74
+ editor: e.editor,
49
75
  verbose: e.verbose ?? !1
50
- };
76
+ }, n = F(t.editor);
51
77
  return {
52
- name: b,
53
- hooks: { "astro:config:setup": ({ logger: e }) => {
54
- t.verbose && e.info(`${b} installed — contentDir=${t.contentDir} configPath=${t.configPath}`);
78
+ name: k,
79
+ hooks: { "astro:config:setup": ({ logger: e, updateConfig: r }) => {
80
+ t.verbose && e.info(`${k} installed — contentDir=${t.contentDir} configPath=${t.configPath} editor.embedMode=${n.embedMode} editor.cloudUrl=${n.cloudUrl}`), r({ vite: { plugins: [P(n)] } });
55
81
  } }
56
82
  };
57
83
  }
58
84
  //#endregion
59
85
  //#region src/render.ts
60
- async function S(e) {
61
- let t = e.cwd ?? process.cwd(), n = a(e.mdxPath) ? e.mdxPath : s(t, e.mdxPath), r = await d(n);
86
+ async function L(e) {
87
+ let t = e.cwd ?? process.cwd(), n = a(e.mdxPath) ? e.mdxPath : s(t, e.mdxPath), r = await v(n);
62
88
  if (!r) throw Error(`renderAnnotatedScreen: ${e.mdxPath} has no \`annot:\` frontmatter — cannot render.`);
63
89
  let o = r.screens.find((t) => t.id === e.screenId);
64
90
  if (!o) throw Error(`renderAnnotatedScreen: ${e.mdxPath} has no <Screen id="${e.screenId}"> block.`);
65
- let p = C(e.editable), h = _({
91
+ let u = R(e.editable), _ = i(n), b = await B(o.annotations, _), x = T({
66
92
  mdxSource: r.source,
67
93
  screenId: e.screenId,
68
- editable: p !== null
94
+ editable: u !== null,
95
+ annotationsYamlSource: b?.source
69
96
  });
70
97
  if (e.cache) {
71
- let t = await e.cache.get(h);
98
+ let t = await e.cache.get(x);
72
99
  if (t) return {
73
100
  bytes: t,
74
101
  fromCache: !0,
75
102
  hadBoundingBoxes: !0
76
103
  };
77
104
  }
78
- let g = e.basePngBytes ?? await w(r, o.src, i(n)), v = T(g), y = f(r.commentBlocks.snapshot ?? ""), b = l(o.overlays, y, v), x, S;
79
- if (b.length === 0) if (S = !1, p !== null) {
80
- let e = `data:image/png;base64,${Buffer.from(g).toString("base64")}`;
81
- x = c({ loadSystemFonts: !0 }).toEditablePng({
105
+ let w = e.basePngBytes ?? await V(r, o.src, _), E = H(w), D, O = z(w);
106
+ D = O ? h(O) : y(r.commentBlocks.snapshot ?? "");
107
+ let k = b && b.file.annotations ? p(b.file.annotations, D) : [], A = k.length > 0 ? await c(w, k) : w;
108
+ !b && o.overlays.length > 0 && C({
109
+ mdxPath: e.mdxPath,
110
+ screenId: e.screenId,
111
+ overlayCount: o.overlays.length
112
+ });
113
+ let j = b ? f(b.file.overlays, D, E) : d(o.overlays, D, E), M = [...b && b.file.annotations ? m(b.file.annotations, D, E) : [], ...j], N, P;
114
+ if (M.length === 0) if (P = k.length > 0, u !== null) {
115
+ let e = `data:image/png;base64,${Buffer.from(A).toString("base64")}`;
116
+ N = l({ loadSystemFonts: !0 }).toEditablePng({
82
117
  originalDataUrl: e,
83
- annotationsSvg: u(),
84
- width: v.width,
85
- height: v.height,
86
- tags: p.tags
118
+ annotationsSvg: g(),
119
+ width: E.width,
120
+ height: E.height,
121
+ tags: u.tags
87
122
  });
88
- } else x = g;
123
+ } else N = A;
89
124
  else {
90
- let e = `data:image/png;base64,${Buffer.from(g).toString("base64")}`, t = m(b), n = c({ loadSystemFonts: !0 }), r = {
125
+ let e = `data:image/png;base64,${Buffer.from(A).toString("base64")}`, t = S(M), n = l({ loadSystemFonts: !0 }), r = {
91
126
  originalDataUrl: e,
92
127
  annotationsSvg: t,
93
- width: v.width,
94
- height: v.height
128
+ width: E.width,
129
+ height: E.height
95
130
  };
96
- x = p === null ? n.toPng(r) : n.toEditablePng({
131
+ N = u === null ? n.toPng(r) : n.toEditablePng({
97
132
  ...r,
98
- tags: p.tags
99
- }), S = !0;
133
+ tags: u.tags
134
+ }), P = !0;
100
135
  }
101
- return e.cache && await e.cache.set(h, x), {
102
- bytes: x,
136
+ return e.cache && await e.cache.set(x, N), {
137
+ bytes: N,
103
138
  fromCache: !1,
104
- hadBoundingBoxes: S
139
+ hadBoundingBoxes: P
105
140
  };
106
141
  }
107
- function C(e) {
142
+ function R(e) {
108
143
  return e ? e === !0 ? {} : e : null;
109
144
  }
110
- async function w(e, t, r) {
145
+ function z(e) {
146
+ try {
147
+ return u(e);
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+ async function B(e, t) {
153
+ if (!e) return null;
154
+ let r = a(e) ? e : s(t, e), i;
155
+ try {
156
+ i = await n(r, "utf8");
157
+ } catch (t) {
158
+ throw Error(`renderAnnotatedScreen: <Screen annotations="${e}"> — failed to read ${r}: ${t.message}`);
159
+ }
160
+ return {
161
+ file: _(i),
162
+ source: i
163
+ };
164
+ }
165
+ async function V(e, t, r) {
111
166
  if (!t) throw Error("renderAnnotatedScreen: <Screen src=...> is required — cannot render without a base image.");
112
167
  let i = await n(a(t) ? t : s(r, t));
113
168
  return new Uint8Array(i.buffer, i.byteOffset, i.byteLength);
114
169
  }
115
- function T(e) {
170
+ function H(e) {
116
171
  if (e.length < 24) throw Error("renderAnnotatedScreen: base PNG too short to contain IHDR.");
117
172
  let t = new DataView(e.buffer, e.byteOffset, e.byteLength);
118
173
  return {
@@ -121,4 +176,4 @@ function T(e) {
121
176
  };
122
177
  }
123
178
  //#endregion
124
- export { g as RENDER_PIPELINE_VERSION, _ as cacheKey, v as createFileCache, y as createMemoryCache, x as productDocsIntegration, S as renderAnnotatedScreen, p as resolveMdxAnnotations, h as svgFromBboxAnnotations };
179
+ export { O as ANNOT_EDITOR_CONFIG, w as RENDER_PIPELINE_VERSION, T as cacheKey, E as createFileCache, D as createMemoryCache, P as editorConfigVirtualPlugin, I as productDocsIntegration, L as renderAnnotatedScreen, F as resolveEditorConfig, b as resolveMdxAnnotations, x as svgFromBboxAnnotations };
@@ -1,4 +1,27 @@
1
+ import { EmbedMode } from '@ingcreators/annot-embed-protocol';
1
2
  import { AstroIntegration } from 'astro';
3
+ import { ResolvedEditorConfig } from './editor-config-virtual.js';
4
+ export interface EditorConfigVitePlugin {
5
+ readonly name: string;
6
+ readonly enforce?: "pre" | "post";
7
+ readonly resolveId: (id: string) => string | null;
8
+ readonly load: (id: string) => string | null;
9
+ }
10
+ export interface ProductDocsEditorOptions {
11
+ /**
12
+ * Default embed mode for every `<AnnotEditButton>` in the
13
+ * site. Per-call `mode` prop wins when set. Defaults to
14
+ * `"newTab"`.
15
+ */
16
+ embedMode?: EmbedMode;
17
+ /**
18
+ * Cloud editor origin override for on-prem deployments
19
+ * (e.g. `"https://annot.internal.example.com"`). Per-call
20
+ * `cloudUrl` prop wins when set. Defaults to
21
+ * `"https://annot.work"`.
22
+ */
23
+ cloudUrl?: string;
24
+ }
2
25
  export interface ProductDocsIntegrationOptions {
3
26
  /**
4
27
  * Directory (relative to the Astro project root) that contains
@@ -13,6 +36,11 @@ export interface ProductDocsIntegrationOptions {
13
36
  * `meta` defaults + per-book template lookups.
14
37
  */
15
38
  configPath?: string;
39
+ /**
40
+ * Project-wide `<AnnotEditButton>` defaults. Phase 5f of
41
+ * `docs/plans/living-spec-authoring-roadmap.md`.
42
+ */
43
+ editor?: ProductDocsEditorOptions;
16
44
  /**
17
45
  * If `true`, the integration logs a debug line on every
18
46
  * config:setup. Useful for verifying the integration is
@@ -20,6 +48,28 @@ export interface ProductDocsIntegrationOptions {
20
48
  */
21
49
  verbose?: boolean;
22
50
  }
51
+ /**
52
+ * Vite plugin that:
53
+ *
54
+ * 1. Generates a virtual module (`virtual:annot-docs/editor-config`)
55
+ * exporting the resolved editor config.
56
+ *
57
+ * 2. Aliases every import of the package's
58
+ * `editor-config-virtual.ts` to the virtual module so
59
+ * `<AnnotEditButton>` picks up the project-wide defaults.
60
+ *
61
+ * Exported here for vitest. Consumers don't need to import it
62
+ * directly — `productDocsIntegration` wires it up automatically.
63
+ */
64
+ export declare function editorConfigVirtualPlugin(resolved: ResolvedEditorConfig): EditorConfigVitePlugin;
65
+ /**
66
+ * Merge the per-call editor options into the built-in defaults.
67
+ * Future revision (Phase 5f follow-up if needed) also reads
68
+ * `annot-docs.config.ts`'s `editor` config + merges in the
69
+ * middle of this precedence ladder; today the integration
70
+ * option is the only override layer.
71
+ */
72
+ export declare function resolveEditorConfig(editor: ProductDocsEditorOptions | undefined): ResolvedEditorConfig;
23
73
  /**
24
74
  * Astro integration factory. Drop into `astro.config.mjs`:
25
75
  *
@@ -28,19 +78,19 @@ export interface ProductDocsIntegrationOptions {
28
78
  * import { productDocsIntegration } from "@ingcreators/annot-product-docs-astro";
29
79
  *
30
80
  * export default defineConfig({
31
- * integrations: [productDocsIntegration()],
81
+ * integrations: [
82
+ * productDocsIntegration({
83
+ * editor: {
84
+ * embedMode: "newTab",
85
+ * cloudUrl: "https://annot.internal.example.com",
86
+ * },
87
+ * }),
88
+ * ],
32
89
  * });
33
90
  * ```
34
- *
35
- * Phase 2 PR 1 ships the scaffold — the hooks are no-ops aside
36
- * from optional verbose logging. PR 2 adds the Image Service;
37
- * PR 3 wires up the seven docs components.
38
91
  */
39
92
  export declare function productDocsIntegration(options?: ProductDocsIntegrationOptions): AstroIntegration;
40
93
  /**
41
94
  * Default-export shape some Astro integration authors prefer.
42
- * Both `import productDocsIntegration from ...` (default) and
43
- * `import { productDocsIntegration }` (named) resolve to the
44
- * same factory; consumers can pick whichever feels natural.
45
95
  */
46
96
  export default productDocsIntegration;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingcreators/annot-product-docs-astro",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Astro integration for `@ingcreators/annot-product-docs`. Wires the MDX components (`<Screen>` / `<Overlay>` / `<Transition>` / `<HistoryEntry>` / `<ScreenList>` / `<TransitionGraph>`) into an Astro site and supplies the Image Service that renders annotated PNGs from `<Screen>` blocks at build time. Phase 2 of docs/plans/living-product-docs.md.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ingcreators",
@@ -39,6 +39,10 @@
39
39
  },
40
40
  "./components/Screen.astro": "./dist/components/Screen.astro",
41
41
  "./components/Overlay.astro": "./dist/components/Overlay.astro",
42
+ "./components/AnnotCallout.astro": "./dist/components/AnnotCallout.astro",
43
+ "./components/AnnotEditButton.astro": "./dist/components/AnnotEditButton.astro",
44
+ "./components/AnnotEditorIframeModal.astro": "./dist/components/AnnotEditorIframeModal.astro",
45
+ "./components/AnnotEditCompleteListener.astro": "./dist/components/AnnotEditCompleteListener.astro",
42
46
  "./components/Transition.astro": "./dist/components/Transition.astro",
43
47
  "./components/TransitionTable.astro": "./dist/components/TransitionTable.astro",
44
48
  "./components/HistoryEntry.astro": "./dist/components/HistoryEntry.astro",
@@ -54,17 +58,18 @@
54
58
  "access": "public"
55
59
  },
56
60
  "devDependencies": {
57
- "@playwright/test": "^1.50.0",
58
- "astro": "^6.3.6",
61
+ "@playwright/test": "^1.61.1",
62
+ "astro": "^6.4.8",
59
63
  "typescript": "^6.0.3",
60
- "vite": "^8.0.13",
61
- "vite-plugin-dts": "^5.0.1"
64
+ "vite": "^8.0.16",
65
+ "vite-plugin-dts": "^5.0.3"
62
66
  },
63
67
  "dependencies": {
64
- "@ingcreators/annot-annotator": "0.5.0",
65
- "@ingcreators/annot-core": "0.2.1",
66
- "@ingcreators/annot-product-docs": "0.3.0",
67
- "@ingcreators/annot-playwright": "0.4.0"
68
+ "@ingcreators/annot-core": "0.3.0",
69
+ "@ingcreators/annot-annotator": "0.6.0",
70
+ "@ingcreators/annot-embed-protocol": "0.1.0",
71
+ "@ingcreators/annot-product-docs": "0.4.0",
72
+ "@ingcreators/annot-playwright": "0.4.1"
68
73
  },
69
74
  "peerDependencies": {
70
75
  "astro": "^5.0.0 || ^6.0.0"