@ingcreators/annot-product-docs-astro 0.1.0 → 0.2.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 ADDED
@@ -0,0 +1,303 @@
1
+ # @ingcreators/annot-product-docs-astro
2
+
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 87a8bad: **Fix `./playwright` subpath build.** The published `0.1.0`
8
+ and `0.2.0` tarballs shipped `dist/playwright/*.d.ts` (type
9
+ declarations) but NOT the runtime `dist/playwright/index.js`,
10
+ because `vite.config.ts`'s `lib.entry` was single-entry —
11
+ only the top-level `src/index.ts` got built.
12
+
13
+ Multi-entry library mode now emits both bundles:
14
+
15
+ ```
16
+ dist/
17
+ index.js # main entry (re-exports integration + components + render)
18
+ playwright/index.js # `test`, `expect`, `patchScreenshot`, `rebaseAnnotations`
19
+ ```
20
+
21
+ Any consumer doing `import { test } from
22
+ "@ingcreators/annot-product-docs-astro/playwright"`
23
+ previously got `Cannot find module ...dist/playwright/index.js`
24
+ at runtime; the `0.2.1` republish makes the subpath actually
25
+ loadable.
26
+
27
+ Also marks `@playwright/test` as external in the Rollup
28
+ config (matches the package.json `peerDependencies` shape;
29
+ prevents accidentally bundling Playwright into the playwright
30
+ adapter).
31
+
32
+ No public-API change — same exports, same call shapes,
33
+ same TypeScript types. Pure packaging fix.
34
+
35
+ ## 0.2.0
36
+
37
+ ### Minor Changes
38
+
39
+ - 2e92c97: First publish of the living-product-docs package family. Phases
40
+ 1-5 + 7 of `docs/plans/living-product-docs.md` landed across
41
+ PRs 876-899; this entry flips the three packages from
42
+ `private: true` to publishable and stamps `0.1.0`.
43
+
44
+ ### `@ingcreators/annot-product-docs`
45
+ - MDX parser (`parseMdx` / `parseMdxFile`) — Remark / unified
46
+ pipeline that walks `.mdx` files with `annot:` frontmatter
47
+ and extracts `<Screen>` / `<Overlay>` / `<Transition>` /
48
+ `<HistoryEntry>` / `<ScreenList>` JSX components.
49
+ - Match resolver (`parseSnapshot` / `resolveMatch` /
50
+ `resolveOverlays`) for the Playwright `aria-snapshot`
51
+ YAML, honouring `match.under` disambiguation and emitting
52
+ `not-found` / `ambiguous` / `renamed` / `role-changed` /
53
+ `live-mismatch` diagnostics.
54
+ - `screen` fixture extending `@ingcreators/annot-playwright`
55
+ with `screen.capture({ id, mdxPath })` that re-syncs
56
+ `annot:snapshot` + `annot:attributes` MDX comment blocks
57
+ in place.
58
+ - Drift detector (`detectDrift` / `detectDriftFromYaml`) — six
59
+ finding kinds (added / removed / renamed / role-changed /
60
+ duplicated / attribute-drift) with severity buckets.
61
+ - `annot-docs` CLI (`init` / `sync` / `lint`) with `--json`
62
+ / `--ci` / `--fix` flags + a sample GitHub Actions workflow
63
+ emitting GitHub annotations on PR diff views.
64
+
65
+ ### `@ingcreators/annot-product-docs-astro`
66
+ - `productDocsIntegration()` Astro 5.x integration factory.
67
+ - 7 docs components: `<Screen>`, `<Overlay>`, `<Transition>`,
68
+ `<TransitionTable>`, `<HistoryEntry>`, `<ScreenList>`,
69
+ `<TransitionGraph>`. Shipped as `.astro` source under
70
+ `./components/*.astro` exports.
71
+ - Image Service (`renderAnnotatedScreen` + SHA-keyed
72
+ `createFileCache` / `createMemoryCache`) that composes the
73
+ base screenshot with overlay callouts at build time.
74
+
75
+ ### `@ingcreators/annot-product-docs-xlsx`
76
+ - MDX → normalised bundle extractor; per-role default layout
77
+ (cover / history / list / screen / reference); customer-
78
+ template support with `{var}` placeholder substitution
79
+ (including `{annot:date}` special vars + `{name:format}`
80
+ date formatting); Excel Named Range writers
81
+ (`annotImage` / `annotItemTable` / `annotHistory` /
82
+ `annotList` / `annotSnapshot` / `annotAttributes`).
83
+ - `annot-docs-xlsx render` CLI with multi-book emit + per-book
84
+ template config.
85
+
86
+ - 4768855: **`@ingcreators/annot-product-docs-astro` — new `/playwright`
87
+ subpath** that re-exports an extended Playwright `test` fixture
88
+ whose `page.screenshot()` accepts a compositional `annot: { … }`
89
+ option:
90
+
91
+ ```ts
92
+ import { test } from "@ingcreators/annot-product-docs-astro/playwright";
93
+
94
+ test("login flow", async ({ page }) => {
95
+ await page.goto("/login");
96
+ await page.screenshot({
97
+ path: "public/login.png",
98
+ annot: {
99
+ mdx: { id: "login", path: "src/content/docs/login.mdx" },
100
+ tags: { source: "docs-tour", capturedAt: new Date().toISOString() },
101
+ },
102
+ });
103
+ });
104
+ ```
105
+
106
+ The `annot` option is compositional — each field is an
107
+ independent contribution to the embedded XMP record:
108
+ - `mdx: { id, path }` — refresh the MDX's `annot:snapshot` block
109
+ against the current page, then resolve `<Overlay match>` blocks
110
+ for the named `<Screen>`.
111
+ - `overlays: BboxAnnotation[]` — caller-supplied annotations
112
+ (same DSL `@ingcreators/annot-annotator` accepts). Merged with
113
+ MDX-derived overlays when both are present.
114
+ - `tags: Record<string, string>` — provenance metadata written
115
+ verbatim into the XMP. No auto-fill — callers write the
116
+ `WELL_KNOWN_TAG_KEYS` they want.
117
+ - `editable: boolean` (default `true`) — toggle between
118
+ "annotations preserved as SVG layer + embedded original"
119
+ (re-editable in Annot Cloud) and "annotations baked into
120
+ visible pixels" (flat PNG, no XMP layer).
121
+
122
+ `page.screenshot()` calls WITHOUT `annot` fall through to
123
+ vanilla Playwright byte-for-byte — codegen / DevTools Recorder
124
+ output keeps working unedited.
125
+
126
+ Phase 1 of
127
+ `docs/plans/playwright-screenshot-annot-fixture.md`. Phase 2 will
128
+ add the same interception on `locator.screenshot()` with
129
+ coordinate rebasing for sub-region overlays.
130
+
131
+ Two helpers also exported from the main `@ingcreators/annot-product-docs-astro`
132
+ entry for callers who want to compose annotations themselves:
133
+ - `resolveMdxAnnotations({ mdxPath, screenId, dims })` — extract
134
+ the MDX's `<Overlay>` blocks into a typed `BboxNumberedBadgeAnnotation[]`
135
+ (the underlying step the fixture uses internally).
136
+ - `svgFromBboxAnnotations(annotations)` — wrap a
137
+ `BboxAnnotation[]` into a single-root `<svg>` ready for
138
+ `Annotator.toEditablePng()` / `toPng()`.
139
+
140
+ **`@ingcreators/annot-core` — new `writePngWithTagsOnly`
141
+ helper** exported from `/xmp-bytes` (and re-exported from
142
+ `/xmp`). Writes `tags` into a PNG's XMP iTXt chunk without
143
+ embedding an original capture or annotations layer — for the
144
+ "PNG with provenance metadata sidecar" path (CI failure
145
+ screenshots, VRT references, etc.). The resulting bytes are
146
+ still a valid PNG and the Annot editor treats them as a normal
147
+ PNG (no `<annot:annotations>` element → not editable round-trip;
148
+ opens as fresh canvas).
149
+
150
+ - 49b5585: **`locator.screenshot({ annot: { … } })` support** — Phase 2 of
151
+ `docs/plans/playwright-screenshot-annot-fixture.md`. The fixture
152
+ now patches `Locator.prototype.screenshot` alongside
153
+ `Page.prototype.screenshot`, and overlay coordinates are
154
+ automatically rebased into the cropped image's coordinate space:
155
+
156
+ ```ts
157
+ await page.locator("header").screenshot({
158
+ path: "header.png",
159
+ annot: {
160
+ overlays: [
161
+ // page-space bbox — rebased onto the header-locator's clip
162
+ { type: "rect", bbox: { x: 120, y: 60, width: 50, height: 30 } },
163
+ ],
164
+ },
165
+ });
166
+ ```
167
+
168
+ Overlays whose page-space bbox falls outside the locator's
169
+ bounding box are dropped (warning + skip per Open Question 4)
170
+ and surfaced via:
171
+ - `console.warn(...)` to stderr — always
172
+ - `test.info().annotations` as a `warning` entry when running
173
+ under Playwright — best-effort, guarded so vitest unit tests
174
+ don't crash
175
+
176
+ `page.screenshot({ clip, annot })` honours `clip` the same way —
177
+ explicit clip + auto rebase. Mirrors vanilla
178
+ `page.screenshot({ clip })` semantics; only the overlay-rebase
179
+ behaviour is annot-specific.
180
+
181
+ When the locator has no bounding box (off-screen / hidden), the
182
+ fixture throws a friendly diagnostic asking the caller to use
183
+ a stable selector / `waitFor()`.
184
+
185
+ **Coordinate-rebase API** — exported alongside the fixture for
186
+ callers who want to compose annotations themselves:
187
+
188
+ ```ts
189
+ import { rebaseAnnotations } from "@ingcreators/annot-product-docs-astro/playwright";
190
+ // Or for direct algorithmic use without the fixture:
191
+ // import { rebaseAnnotations } from "@ingcreators/annot-product-docs-astro/playwright/rebase";
192
+
193
+ const { kept, dropped } = rebaseAnnotations(annotations, clip);
194
+ ```
195
+
196
+ Returns `{ kept, dropped }` for each shape in the
197
+ `BboxAnnotation` union (rect / numberedBadge / circle / arrow /
198
+ text / callout). Raw SVG fragments (`type: "raw"`) pass through
199
+ unchanged — the caller is responsible for emitting clip-space
200
+ coords inside arbitrary SVG.
201
+
202
+ `numberedBadge`'s `imageWidth` / `imageHeight` are also rebased
203
+ to the clip dimensions so `placement: "auto"` picks the corner
204
+ against the cropped image edge rather than the page edge.
205
+
206
+ - 730fab7: `renderAnnotatedScreen()` gains an optional `editable?: boolean |
207
+ { tags?: Record<string, string> }` field. Pass `true` (or an
208
+ object) and the function routes through the new
209
+ `Annotator.toEditablePng()` path: the returned PNG carries the
210
+ same visible callouts plus the original capture + the
211
+ annotations SVG embedded in XMP / custom `svGo` chunk, so
212
+ re-opening the file in the Annot editor / Cloud restores the
213
+ overlays as selectable / movable / restylable objects rather
214
+ than a flat bitmap.
215
+
216
+ ```ts
217
+ const result = await renderAnnotatedScreen({
218
+ mdxPath: "docs/app/index.mdx",
219
+ screenId: "app-overview",
220
+ basePngBytes,
221
+ editable: {
222
+ tags: {
223
+ source: "docs-tour",
224
+ capturedAt: new Date().toISOString(),
225
+ },
226
+ },
227
+ });
228
+ await writeFile("public/app/shots/app-overview.png", result.bytes);
229
+ ```
230
+
231
+ The cache key folds in the `editable` flag, so flat and editable
232
+ variants of the same screen don't collide. Existing flat-raster
233
+ callers are byte-for-byte unaffected — the option defaults to
234
+ `undefined` (flat).
235
+
236
+ The `CacheKeyInput` type gains a parallel `editable?: boolean`
237
+ field; pure helpers that compute the cache key directly should
238
+ forward the bit when threading the flag through.
239
+
240
+ Internal note: this PR also adds `@ingcreators/annot-core` as a
241
+ devDependency so tests can import `readEditablePngBytes` from
242
+ `/xmp-bytes` for round-trip verification. Runtime dependencies
243
+ are unchanged.
244
+
245
+ ### Patch Changes
246
+
247
+ - 657a685: **Republish with `dist/` included.** The `0.1.0` tarballs of all
248
+ three packages shipped to npm without their `dist/` directory —
249
+ the `publish.yml` workflow's pre-pack `pnpm build` step had only
250
+ filtered four other packages, so `pnpm pack` packed the three
251
+ `product-docs*` packages against empty `dist/`s. The
252
+ `publishConfig.main` (`./dist/index.js`) consequently pointed at
253
+ a missing file, breaking `npm install` for every consumer.
254
+
255
+ The source fix landed in
256
+ [#947](https://github.com/ingcreators/annot/pull/947) with two
257
+ defences:
258
+ 1. Three new `--filter` lines in the workflow's build step so
259
+ all seven publishable packages get built before pack.
260
+ 2. A per-package `prepack` script (`pnpm run build`) so even a
261
+ misconfigured workflow (or a manual `pnpm pack` / `pnpm
262
+ publish`) refreshes `dist/` before packing.
263
+
264
+ No source-code changes in any of the three packages — only the
265
+ packaging is fixed. This patch publish exists solely to deliver
266
+ working tarballs to the registry; the public API surface is
267
+ byte-identical to `0.1.0`.
268
+
269
+ Verified locally:
270
+
271
+ ```
272
+ $ pnpm --filter @ingcreators/annot-product-docs pack --dry-run
273
+ Tarball Contents
274
+ bin/annot-docs.mjs
275
+ dist/cli.d.ts
276
+ dist/config.d.ts
277
+ dist/drift.d.ts
278
+ dist/fixture.d.ts
279
+ dist/index.d.ts
280
+ dist/index.js
281
+ dist/mdx.d.ts
282
+ dist/resolver.d.ts
283
+ dist/types-config.d.ts
284
+ dist/types.d.ts
285
+ LICENSE
286
+ package.json
287
+ README.md
288
+ ```
289
+
290
+ Before the fix the same command produced 4 files (LICENSE +
291
+ README + package.json + bin/annot-docs.mjs), no compiled code.
292
+
293
+ - Updated dependencies [806badc]
294
+ - Updated dependencies [2e8d397]
295
+ - Updated dependencies [780985d]
296
+ - Updated dependencies [df1a429]
297
+ - Updated dependencies [2e92c97]
298
+ - Updated dependencies [5e74421]
299
+ - Updated dependencies [4768855]
300
+ - Updated dependencies [657a685]
301
+ - @ingcreators/annot-annotator@0.5.0
302
+ - @ingcreators/annot-core@0.2.0
303
+ - @ingcreators/annot-product-docs@0.2.0
package/README.md CHANGED
@@ -9,7 +9,7 @@ docs-MDX components into an Astro site and ships an Image Service
9
9
  that renders annotated PNGs from `<Screen>` blocks at build time.
10
10
 
11
11
  Phase 2 of
12
- [`docs/plans/living-product-docs.md`](https://github.com/ingcreators/annot/blob/main/docs/plans/living-product-docs.md).
12
+ [`docs/plans/_done/living-product-docs.md`](https://github.com/ingcreators/annot/blob/main/docs/plans/_done/living-product-docs.md).
13
13
 
14
14
  ## Install
15
15
 
@@ -89,6 +89,42 @@ When the stored snapshot lacks `[box=x,y,w,h]` markers (no
89
89
  Playwright tour has run yet), the function returns the base
90
90
  PNG verbatim with `hadBoundingBoxes: false`.
91
91
 
92
+ ## Playwright fixture (`/playwright` subpath)
93
+
94
+ For Playwright specs producing screenshots of a documented
95
+ screen, the `./playwright` subpath ships an extended `test`
96
+ fixture that intercepts `page.screenshot()` and
97
+ `locator.screenshot()`. Calls carrying a compositional
98
+ `annot: { mdx | overlays | tags | editable }` option get a
99
+ one-line capture pipeline that replaces the previous
100
+ `page.screenshot` + `screen.capture` + `renderAnnotatedScreen`
101
+ + `writeFile` quartet:
102
+
103
+ ```ts
104
+ import { test } from "@ingcreators/annot-product-docs-astro/playwright";
105
+
106
+ test("app overview", async ({ page }) => {
107
+ await page.goto("https://annot.work/app/");
108
+ await page.screenshot({
109
+ path: "public/app/shots/app-overview.png",
110
+ annot: {
111
+ mdx: { id: "app-overview", path: "src/content/docs/app/index.mdx" },
112
+ tags: { source: "docs-tour", capturedAt: new Date().toISOString() },
113
+ },
114
+ });
115
+ });
116
+ ```
117
+
118
+ Calls without `annot` (or with `annot: true` / `{}`) fall
119
+ through to vanilla Playwright byte-for-byte — codegen-emitted
120
+ calls keep working unedited. See the docs at
121
+ [`annot.work/docs/product-docs/playwright-fixture/`](https://annot.work/docs/product-docs/playwright-fixture/)
122
+ for the compositional vocabulary, locator screenshot semantics,
123
+ and the codegen→hand-edit workflow.
124
+
125
+ `@playwright/test` is an **optional peer** — Astro-only users
126
+ don't need to install it.
127
+
92
128
  ## Tier
93
129
 
94
130
  Tier B-render — Astro build-time, no live editor.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Bumped any time the rendering pipeline changes the bytes it
3
+ * produces for the same input. Forces a cold cache on every
4
+ * consumer the next time they build. Keep this small (a single
5
+ * digit per breaking change) — large jumps don't help and just
6
+ * obscure the upgrade history.
7
+ */
8
+ export declare const RENDER_PIPELINE_VERSION = 1;
9
+ export interface CacheKeyInput {
10
+ mdxSource: string;
11
+ screenId: string;
12
+ /**
13
+ * Whether the render targets a re-editable PNG (vs the default flat
14
+ * raster). Folded into the cache key so flat / editable variants of
15
+ * the same screen don't collide. Defaults to `false` to preserve the
16
+ * historical key shape for flat callers.
17
+ */
18
+ editable?: boolean;
19
+ }
20
+ /**
21
+ * Compute the cache key for a `<Screen>` block. Stable across
22
+ * runs as long as the MDX source bytes and the screen id are
23
+ * unchanged.
24
+ */
25
+ export declare function cacheKey(input: CacheKeyInput): string;
26
+ export interface FileCache {
27
+ /** Returns the cached bytes for `key`, or `null` on a miss. */
28
+ get(key: string): Promise<Uint8Array | null>;
29
+ /** Writes `bytes` under `key`. Overwrites silently. */
30
+ set(key: string, bytes: Uint8Array): Promise<void>;
31
+ /** Filesystem path the cache writes into. */
32
+ readonly dir: string;
33
+ }
34
+ /**
35
+ * Disk-backed cache rooted at `dir`. The directory is created on
36
+ * first `set`; reads of nonexistent paths return `null`. Files
37
+ * are flat (no subdirs), one per key — SHA-256 collisions are
38
+ * a non-concern given the keyspace.
39
+ */
40
+ export declare function createFileCache(dir: string): FileCache;
41
+ /**
42
+ * In-memory cache — useful in tests and as a fallback when the
43
+ * caller doesn't want disk persistence. Same shape as the disk
44
+ * cache for swap-in convenience.
45
+ */
46
+ export declare function createMemoryCache(): FileCache;
@@ -0,0 +1,34 @@
1
+ ---
2
+ // `<HistoryEntry>` — single revision-history row.
3
+ //
4
+ // MDX usage (inside a `role: history` doc):
5
+ // <HistoryEntry version="1.0" date="2026-03-15" author="Alice">
6
+ // Initial draft, requirements v2.0.
7
+ // </HistoryEntry>
8
+ //
9
+ // Multiple entries stack vertically; the Excel adapter renders
10
+ // them into the workbook's history sheet via the `annotHistory`
11
+ // named range.
12
+
13
+ interface Props {
14
+ version: string;
15
+ date: string;
16
+ author?: string;
17
+ }
18
+ const { version, date, author } = Astro.props;
19
+ ---
20
+ <div
21
+ class="annot-history-entry"
22
+ data-version={version}
23
+ data-date={date}
24
+ data-author={author}
25
+ >
26
+ <div class="annot-history-meta">
27
+ <span class="annot-history-version">v{version}</span>
28
+ <time class="annot-history-date" datetime={date}>{date}</time>
29
+ {author && <span class="annot-history-author">{author}</span>}
30
+ </div>
31
+ <div class="annot-history-body">
32
+ <slot />
33
+ </div>
34
+ </div>
@@ -0,0 +1,50 @@
1
+ ---
2
+ // `<Overlay>` — caption for one numbered callout on a screen.
3
+ //
4
+ // MDX usage:
5
+ // <Overlay match={{ role: "textbox", name: "Email" }}
6
+ // intent="required" number={1}>
7
+ // **Email** — enter your registered email.
8
+ // </Overlay>
9
+ //
10
+ // Build-time positioning of the visual callout on the
11
+ // screenshot is handled by the Image Service. This component
12
+ // emits the prose body as a `<li>` in a description list
13
+ // alongside the screen — readers see the screenshot + a
14
+ // numbered legend in parallel.
15
+
16
+ interface Match {
17
+ role: string;
18
+ name: string;
19
+ under?: Match;
20
+ }
21
+ type Intent =
22
+ | "info"
23
+ | "warning"
24
+ | "error"
25
+ | "success"
26
+ | "neutral"
27
+ | "required"
28
+ | "action";
29
+
30
+ interface Props {
31
+ match: Match;
32
+ intent?: Intent;
33
+ number?: number;
34
+ }
35
+ const { match, intent, number } = Astro.props;
36
+ ---
37
+ <li
38
+ class="annot-overlay"
39
+ data-intent={intent}
40
+ data-overlay-number={number}
41
+ data-match-role={match.role}
42
+ data-match-name={match.name}
43
+ >
44
+ {number !== undefined && (
45
+ <span class="annot-overlay-number" aria-hidden="true">{number}</span>
46
+ )}
47
+ <div class="annot-overlay-body">
48
+ <slot />
49
+ </div>
50
+ </li>
@@ -0,0 +1,27 @@
1
+ ---
2
+ // `<Screen>` — annotated screenshot block.
3
+ //
4
+ // MDX usage:
5
+ // <Screen id="login" src="./shots/login.png">
6
+ // <Overlay match={...} number={1}>...</Overlay>
7
+ // </Screen>
8
+ //
9
+ // The annotated PNG is produced at build time by the Image
10
+ // 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.
14
+
15
+ interface Props {
16
+ id: string;
17
+ src: string;
18
+ alt?: string;
19
+ }
20
+ const { id, src, alt } = Astro.props;
21
+ ---
22
+ <figure class="annot-screen" data-screen-id={id}>
23
+ <img src={src} alt={alt ?? `Screen ${id}`} loading="lazy" />
24
+ <div class="annot-screen-overlays" data-screen-overlays={id}>
25
+ <slot />
26
+ </div>
27
+ </figure>
@@ -0,0 +1,58 @@
1
+ ---
2
+ // `<ScreenList>` — auto-enumerated list of every `role: screen`
3
+ // MDX in the same `book`.
4
+ //
5
+ // MDX usage (inside a `role: list` doc):
6
+ // <ScreenList book="Screen spec" sort="byId" entries={...} />
7
+ //
8
+ // The `entries` prop is normally populated by the Astro
9
+ // integration's `astro:config:setup` walker (Phase 4 polish).
10
+ // Until that's wired up, callers pass `entries` explicitly so
11
+ // the component renders deterministically in tests and in
12
+ // dev-mode-without-build.
13
+
14
+ export interface ScreenListEntry {
15
+ id: string;
16
+ title?: string;
17
+ href?: string;
18
+ order?: number;
19
+ }
20
+
21
+ interface Props {
22
+ book?: string;
23
+ sort?: "byId" | "byOrder" | "byFilePath";
24
+ entries: ScreenListEntry[];
25
+ }
26
+ const { book, sort = "byId", entries } = Astro.props;
27
+
28
+ const sortedEntries = [...entries].sort((a, b) => {
29
+ if (sort === "byOrder") {
30
+ return (a.order ?? 100) - (b.order ?? 100);
31
+ }
32
+ // byId / byFilePath both fall back to alphabetical for
33
+ // determinism.
34
+ return a.id.localeCompare(b.id);
35
+ });
36
+ ---
37
+ <table class="annot-screen-list" data-book={book} data-sort={sort}>
38
+ <thead>
39
+ <tr>
40
+ <th>ID</th>
41
+ <th>Screen</th>
42
+ </tr>
43
+ </thead>
44
+ <tbody>
45
+ {sortedEntries.map((entry) => (
46
+ <tr data-screen-id={entry.id}>
47
+ <td>{entry.id}</td>
48
+ <td>
49
+ {entry.href ? (
50
+ <a href={entry.href}>{entry.title ?? entry.id}</a>
51
+ ) : (
52
+ <span>{entry.title ?? entry.id}</span>
53
+ )}
54
+ </td>
55
+ </tr>
56
+ ))}
57
+ </tbody>
58
+ </table>
@@ -0,0 +1,48 @@
1
+ ---
2
+ // `<Transition>` — describes one screen-to-screen transition.
3
+ //
4
+ // MDX usage:
5
+ // <Transition
6
+ // trigger={{ role: "button", name: "Sign in" }}
7
+ // on="click"
8
+ // to="dashboard"
9
+ // >
10
+ // Successful login redirects to the dashboard.
11
+ // </Transition>
12
+ //
13
+ // Renders inline as part of the screen's prose; for a tabular
14
+ // list of every transition on the screen, wrap in
15
+ // `<TransitionTable>` instead.
16
+
17
+ interface Match {
18
+ role: string;
19
+ name: string;
20
+ under?: Match;
21
+ }
22
+
23
+ interface Props {
24
+ trigger: Match;
25
+ on?: string;
26
+ to?: string;
27
+ }
28
+ const { trigger, on, to } = Astro.props;
29
+ ---
30
+ <div
31
+ class="annot-transition"
32
+ data-trigger-role={trigger.role}
33
+ data-trigger-name={trigger.name}
34
+ data-on={on}
35
+ data-to={to}
36
+ >
37
+ <div class="annot-transition-summary">
38
+ <code class="annot-transition-trigger">{trigger.name}</code>
39
+ {on && <span class="annot-transition-event">{on}</span>}
40
+ {to && (
41
+ <span class="annot-transition-arrow" aria-hidden="true">→</span>
42
+ )}
43
+ {to && <code class="annot-transition-target">{to}</code>}
44
+ </div>
45
+ <div class="annot-transition-body">
46
+ <slot />
47
+ </div>
48
+ </div>
@@ -0,0 +1,46 @@
1
+ ---
2
+ // `<TransitionGraph>` — Mermaid-rendered cross-screen graph.
3
+ //
4
+ // MDX usage (typically in a `role: list` doc):
5
+ // <TransitionGraph entries={...} title="Auth flow" />
6
+ //
7
+ // Emits a `<pre class="mermaid">` block containing a Mermaid
8
+ // flowchart definition. Rendering happens client-side via the
9
+ // Mermaid runtime the consumer adds to their Astro site
10
+ // (`<script type="module" src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs">`),
11
+ // or build-time via Mermaid CLI / `@napi-rs/canvas` if the
12
+ // consumer opts in.
13
+
14
+ export interface GraphEdge {
15
+ from: string;
16
+ to: string;
17
+ label?: string;
18
+ }
19
+
20
+ interface Props {
21
+ entries: GraphEdge[];
22
+ title?: string;
23
+ direction?: "TB" | "LR" | "BT" | "RL";
24
+ }
25
+ const { entries, title, direction = "TB" } = Astro.props;
26
+
27
+ const escape = (id: string): string => id.replace(/[^A-Za-z0-9_]/g, "_");
28
+
29
+ const mermaid = ["flowchart " + direction];
30
+ if (title) {
31
+ mermaid.push(`%% ${title.replace(/\r?\n/g, " ")}`);
32
+ }
33
+ for (const edge of entries) {
34
+ const from = escape(edge.from);
35
+ const to = escape(edge.to);
36
+ const label = edge.label
37
+ ? `|${edge.label.replace(/\|/g, "&#124;")}|`
38
+ : "";
39
+ mermaid.push(` ${from}[${edge.from}] -->${label} ${to}[${edge.to}]`);
40
+ }
41
+ const definition = mermaid.join("\n");
42
+ ---
43
+ <figure class="annot-transition-graph" data-direction={direction}>
44
+ {title && <figcaption>{title}</figcaption>}
45
+ <pre class="mermaid">{definition}</pre>
46
+ </figure>