@ingcreators/annot-product-docs 0.1.0 → 0.3.0
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 +295 -0
- package/README.md +41 -6
- package/dist/cli.d.ts +29 -0
- package/dist/config.d.ts +184 -0
- package/dist/drift.d.ts +77 -0
- package/dist/fixture.d.ts +111 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +947 -0
- package/dist/mdx-annotations.d.ts +91 -0
- package/dist/mdx.d.ts +41 -0
- package/dist/playwright-screenshot-hook.d.ts +17 -0
- package/dist/resolver.d.ts +58 -0
- package/dist/types-config.d.ts +18 -0
- package/dist/types.d.ts +106 -0
- package/package.json +6 -6
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { BboxAnnotation, BboxNumberedBadgeAnnotation } from '@ingcreators/annot-annotator';
|
|
2
|
+
import { OverlaySpec } from './types.js';
|
|
3
|
+
/** A parsed entry of an aria-snapshot YAML line carrying both a
|
|
4
|
+
* `[ref=…]` marker and a `[box=x,y,w,h]` marker. */
|
|
5
|
+
export interface BoxedEntry {
|
|
6
|
+
role: string;
|
|
7
|
+
name: string;
|
|
8
|
+
ref: string;
|
|
9
|
+
box: {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse aria-snapshot YAML for entries that carry both `[ref=…]`
|
|
18
|
+
* and `[box=x,y,w,h]` markers. `box` is the Playwright addition
|
|
19
|
+
* available when `ariaSnapshot({ boxes: true })` was passed.
|
|
20
|
+
*
|
|
21
|
+
* Exposed for unit testing. Returns an empty array if no entries
|
|
22
|
+
* have boxes — callers fall back to a non-annotated render.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseSnapshotBoxes(yaml: string): BoxedEntry[];
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a screen's `<Overlay match>` blocks against the parsed
|
|
27
|
+
* `BoxedEntry[]` from its snapshot YAML, emitting typed
|
|
28
|
+
* `BboxNumberedBadgeAnnotation`s ready for
|
|
29
|
+
* `createAnnotator().toPng()` / `.toEditablePng()`.
|
|
30
|
+
*
|
|
31
|
+
* Overlays with no matching boxed entry are skipped silently —
|
|
32
|
+
* the drift detector (`detectDrift`) surfaces those upstream.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildBadgeAnnotations(overlays: readonly OverlaySpec[], boxed: readonly BoxedEntry[], dims: {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
}): BboxNumberedBadgeAnnotation[];
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the `<Overlay>` blocks inside `screenId` against the MDX
|
|
40
|
+
* file's stored `annot:snapshot` block and return a typed
|
|
41
|
+
* annotation array ready to feed into
|
|
42
|
+
* `createAnnotator().toEditablePng()`.
|
|
43
|
+
*
|
|
44
|
+
* Used by the Playwright screenshot hook (Tier C) and the Astro
|
|
45
|
+
* Image Service (Tier B-render). Throws the same diagnostics
|
|
46
|
+
* `renderAnnotatedScreen` would (no annot frontmatter / no
|
|
47
|
+
* matching `<Screen id>`).
|
|
48
|
+
*
|
|
49
|
+
* Returns an empty array when no `<Overlay>` matches a bbox marker
|
|
50
|
+
* — e.g. the snapshot was never captured, or `<Overlay match>`
|
|
51
|
+
* targets an element that's no longer in the page.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveMdxAnnotations(opts: {
|
|
54
|
+
mdxPath: string;
|
|
55
|
+
screenId: string;
|
|
56
|
+
dims: {
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
};
|
|
60
|
+
cwd?: string;
|
|
61
|
+
}): Promise<BboxNumberedBadgeAnnotation[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Wrap a `BboxNumberedBadgeAnnotation[]` (or any compatible badge
|
|
64
|
+
* subset) into a single-root `<svg>` ready for the annotator's
|
|
65
|
+
* `annotationsSvg` input.
|
|
66
|
+
*
|
|
67
|
+
* Mirrors the more general `svgFromBboxAnnotations` below but
|
|
68
|
+
* stays specifically typed to the badge shape so the Astro Image
|
|
69
|
+
* Service's `buildBadgeAnnotations()` output flows in without an
|
|
70
|
+
* extra cast.
|
|
71
|
+
*/
|
|
72
|
+
export declare function svgFromBadges(badges: BboxNumberedBadgeAnnotation[]): string;
|
|
73
|
+
/**
|
|
74
|
+
* Wrap a generic `BboxAnnotation[]` (the union DSL accepted by
|
|
75
|
+
* `@ingcreators/annot-annotator`) into a single-root `<svg>` ready
|
|
76
|
+
* for the annotator's `annotationsSvg` input. Mirrors `svgFromBadges`
|
|
77
|
+
* but stays type-permissive — the Playwright fixture's inline
|
|
78
|
+
* overlays can be any `BboxAnnotation` shape, not just numbered
|
|
79
|
+
* badges.
|
|
80
|
+
*
|
|
81
|
+
* Returns the empty wrapper `<svg/>` when `annotations` is empty —
|
|
82
|
+
* lets the editor open the file with no annotations layer rather
|
|
83
|
+
* than throwing.
|
|
84
|
+
*/
|
|
85
|
+
export declare function svgFromBboxAnnotations(annotations: BboxAnnotation[]): string;
|
|
86
|
+
/**
|
|
87
|
+
* Minimal SVG fragment for the "editable wrap, no overlays" case.
|
|
88
|
+
* The editor's import path reads `annotationsSvg` from the XMP and
|
|
89
|
+
* reconstructs an empty annotations layer — fine to be wrapper-only.
|
|
90
|
+
*/
|
|
91
|
+
export declare function emptyAnnotationsSvg(): string;
|
package/dist/mdx.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ParsedMdx } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse one MDX file from disk. Returns `null` if the file lacks
|
|
4
|
+
* an `annot:` frontmatter block (regular MDX files in customer
|
|
5
|
+
* docs sites pass through untouched).
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseMdxFile(filePath: string): Promise<ParsedMdx | null>;
|
|
8
|
+
export interface ParseMdxOptions {
|
|
9
|
+
/** Used only for error messages; defaults to `<inline>`. */
|
|
10
|
+
filePath?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Parse an MDX source string. Returns `null` for sources without
|
|
14
|
+
* an `annot:` frontmatter block.
|
|
15
|
+
*
|
|
16
|
+
* Throws if the `annot:` block exists but fails Zod validation —
|
|
17
|
+
* typos in `id` / `xlsx.role` etc. should fail loudly rather than
|
|
18
|
+
* silently dropping content from the output.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseMdx(source: string, options?: ParseMdxOptions): ParsedMdx | null;
|
|
21
|
+
/**
|
|
22
|
+
* Rewrite the `annot:snapshot` / `annot:attributes` MDX comment
|
|
23
|
+
* blocks in a source string in-place, returning a new source
|
|
24
|
+
* string with the updates applied.
|
|
25
|
+
*
|
|
26
|
+
* Used by the Playwright `screen` fixture (PR 3) to keep each
|
|
27
|
+
* MDX file's snapshot/attribute documentation in sync with what
|
|
28
|
+
* the live page actually exposes. Pure string transform — does
|
|
29
|
+
* NOT re-parse / re-stringify the full MDX tree, so authored
|
|
30
|
+
* Markdown / JSX / whitespace is byte-stable for the unchanged
|
|
31
|
+
* regions.
|
|
32
|
+
*
|
|
33
|
+
* If a block is absent and an update value is provided, the
|
|
34
|
+
* block is appended at end of file. If a block exists and the
|
|
35
|
+
* update value is `undefined`, the block is left untouched —
|
|
36
|
+
* pass an empty string to clear an existing block.
|
|
37
|
+
*/
|
|
38
|
+
export declare function updateCommentBlocks(source: string, updates: {
|
|
39
|
+
snapshot?: string;
|
|
40
|
+
attributes?: string;
|
|
41
|
+
}): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare module "@ingcreators/annot-playwright" {
|
|
2
|
+
interface AnnotScreenshotOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Refresh the MDX `annot:snapshot` block and resolve the
|
|
5
|
+
* `<Screen id>`'s `<Overlay>` blocks into bbox-keyed badge
|
|
6
|
+
* annotations. The MDX file is rewritten in-place with the
|
|
7
|
+
* current page's aria-snapshot before overlays resolve, so a
|
|
8
|
+
* single `page.screenshot({ annot: { mdx } })` call covers the
|
|
9
|
+
* "refresh + capture + bake + write" pipeline.
|
|
10
|
+
*/
|
|
11
|
+
mdx?: {
|
|
12
|
+
id: string;
|
|
13
|
+
path: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Locator, Page } from '@playwright/test';
|
|
2
|
+
import { MatchKey, OverlaySpec } from './types.js';
|
|
3
|
+
export interface SnapshotEntry {
|
|
4
|
+
role: string;
|
|
5
|
+
name: string;
|
|
6
|
+
ref: string;
|
|
7
|
+
depth: number;
|
|
8
|
+
/** Parent chain at parse time, oldest ancestor first. Used by `under`. */
|
|
9
|
+
ancestors: Array<{
|
|
10
|
+
role: string;
|
|
11
|
+
name: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export type ResolveResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
locator: Locator;
|
|
17
|
+
entry: SnapshotEntry;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
kind: ResolveFailureKind;
|
|
21
|
+
reason: string;
|
|
22
|
+
suggestion?: string;
|
|
23
|
+
candidates?: string[];
|
|
24
|
+
};
|
|
25
|
+
export type ResolveFailureKind = "not-found" | "ambiguous" | "live-mismatch" | "renamed" | "role-changed";
|
|
26
|
+
/**
|
|
27
|
+
* Parse the YAML-ish aria-snapshot output from
|
|
28
|
+
* `page.locator('body').ariaSnapshot({ mode: 'ai' })` into a
|
|
29
|
+
* flat list of entries with parent-chain context.
|
|
30
|
+
*
|
|
31
|
+
* Playwright emits a 2-space-per-level indented bullet list:
|
|
32
|
+
*
|
|
33
|
+
* - dialog "Confirm":
|
|
34
|
+
* - button "OK" [ref=e12]
|
|
35
|
+
* - button "Cancel" [ref=e13]
|
|
36
|
+
*
|
|
37
|
+
* Each line is `- <role> "<name>" [ref=eN]` optionally followed
|
|
38
|
+
* by `:` (for containers). We walk the lines once, maintaining a
|
|
39
|
+
* parent stack so each entry knows its full ancestor chain — the
|
|
40
|
+
* resolver uses that chain to honour `match.under` for
|
|
41
|
+
* disambiguation.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseSnapshot(yaml: string): SnapshotEntry[];
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a single `MatchKey` against a parsed snapshot + live
|
|
46
|
+
* `Page`. Honours `match.under` by filtering candidate entries
|
|
47
|
+
* whose ancestor chain ends with the `under` key.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveMatch(match: MatchKey, snapshot: SnapshotEntry[], page: Page): Promise<ResolveResult>;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve every `<Overlay>` in a screen against the snapshot.
|
|
52
|
+
* Returns one result per overlay in input order, so the caller
|
|
53
|
+
* can pair them with the source spans for drift reports.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveOverlays(overlays: OverlaySpec[], snapshot: SnapshotEntry[], page: Page): Promise<Array<{
|
|
56
|
+
overlay: OverlaySpec;
|
|
57
|
+
result: ResolveResult;
|
|
58
|
+
}>>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type { AnnotFrontmatter, AnnotFrontmatterRole, AnnotMeta, AnnotXlsxConfig, } from './types.js';
|
|
2
|
+
export interface BookConfig {
|
|
3
|
+
template?: string;
|
|
4
|
+
templateSheets?: {
|
|
5
|
+
cover?: string;
|
|
6
|
+
history?: string;
|
|
7
|
+
list?: string;
|
|
8
|
+
screen?: string;
|
|
9
|
+
reference?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface AnnotDocsConfig {
|
|
13
|
+
meta?: Record<string, unknown>;
|
|
14
|
+
xlsx?: {
|
|
15
|
+
defaultBook?: string;
|
|
16
|
+
books?: Record<string, BookConfig>;
|
|
17
|
+
};
|
|
18
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent match key — `role + name` pair, optionally
|
|
3
|
+
* constrained by a parent `under` key to disambiguate
|
|
4
|
+
* non-unique role+name combinations.
|
|
5
|
+
*
|
|
6
|
+
* Critical: `match` keys NEVER persist Playwright `ref=eN` ids —
|
|
7
|
+
* those are session-local and change between snapshots. The
|
|
8
|
+
* resolver walks the live snapshot to find the matching element
|
|
9
|
+
* each run.
|
|
10
|
+
*/
|
|
11
|
+
export interface MatchKey {
|
|
12
|
+
role: string;
|
|
13
|
+
name: string;
|
|
14
|
+
under?: MatchKey;
|
|
15
|
+
}
|
|
16
|
+
export type OverlayIntent = "info" | "warning" | "error" | "success" | "neutral" | "required" | "action";
|
|
17
|
+
export interface OverlaySpec {
|
|
18
|
+
match: MatchKey;
|
|
19
|
+
intent?: OverlayIntent;
|
|
20
|
+
number?: number;
|
|
21
|
+
/** Markdown body between the `<Overlay>` opening and closing tags. */
|
|
22
|
+
body: string;
|
|
23
|
+
}
|
|
24
|
+
export interface TransitionSpec {
|
|
25
|
+
trigger: MatchKey;
|
|
26
|
+
on?: string;
|
|
27
|
+
to?: string;
|
|
28
|
+
body: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ScreenSpec {
|
|
31
|
+
id: string;
|
|
32
|
+
src?: string;
|
|
33
|
+
overlays: OverlaySpec[];
|
|
34
|
+
}
|
|
35
|
+
export interface HistoryEntrySpec {
|
|
36
|
+
version: string;
|
|
37
|
+
date: string;
|
|
38
|
+
author: string;
|
|
39
|
+
body: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ScreenListSpec {
|
|
42
|
+
book?: string;
|
|
43
|
+
sort?: "byId" | "byOrder" | "byFilePath";
|
|
44
|
+
}
|
|
45
|
+
export type AnnotFrontmatterRole = "cover" | "history" | "list" | "screen" | "reference";
|
|
46
|
+
export interface AnnotXlsxConfig {
|
|
47
|
+
book?: string;
|
|
48
|
+
/** Single sheet name — used when the MDX contributes one sheet. */
|
|
49
|
+
sheet?: string;
|
|
50
|
+
/** Per-`<Screen>` sheet names — used when the MDX contributes multiple sheets. */
|
|
51
|
+
sheets?: Record<string, string>;
|
|
52
|
+
role?: AnnotFrontmatterRole;
|
|
53
|
+
order?: number;
|
|
54
|
+
}
|
|
55
|
+
export interface AnnotMeta {
|
|
56
|
+
author?: string;
|
|
57
|
+
createdDate?: string;
|
|
58
|
+
revisedDate?: string;
|
|
59
|
+
revision?: string;
|
|
60
|
+
reviewedBy?: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
export interface AnnotFrontmatter {
|
|
64
|
+
id: string;
|
|
65
|
+
title?: string;
|
|
66
|
+
purpose?: string;
|
|
67
|
+
meta?: AnnotMeta;
|
|
68
|
+
xlsx?: AnnotXlsxConfig;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Aria-snapshot and HTML-attribute blocks that live as MDX
|
|
72
|
+
* comments in the body. The Playwright `screen` fixture (PR 3)
|
|
73
|
+
* writes these in-place after each run so the file's "what the
|
|
74
|
+
* page looks like" stays accurate alongside the human-authored
|
|
75
|
+
* `<Overlay>` prose.
|
|
76
|
+
*
|
|
77
|
+
* Stored verbatim as the inner text between the open / close
|
|
78
|
+
* markers — the resolver parses snapshot YAML separately via
|
|
79
|
+
* `parseSnapshot` in `./resolver.ts`.
|
|
80
|
+
*/
|
|
81
|
+
export interface AnnotCommentBlocks {
|
|
82
|
+
/** Raw YAML between `{/* annot:snapshot */}` markers. */
|
|
83
|
+
snapshot?: string;
|
|
84
|
+
/** Raw text between `{/* annot:attributes */}` markers. */
|
|
85
|
+
attributes?: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The full extraction result for one MDX file. `frontmatter`
|
|
89
|
+
* is the `annot:` block; `screens` / `transitions` / `history`
|
|
90
|
+
* are the JSX components found in the body; `commentBlocks`
|
|
91
|
+
* captures the MDX comment blocks the fixture rewrites.
|
|
92
|
+
*
|
|
93
|
+
* Files without an `annot:` frontmatter block are not parsed —
|
|
94
|
+
* `parseMdx` returns `null` for them so the CLI can `glob` for
|
|
95
|
+
* every `*.mdx` and ignore non-annot files cheaply.
|
|
96
|
+
*/
|
|
97
|
+
export interface ParsedMdx {
|
|
98
|
+
frontmatter: AnnotFrontmatter;
|
|
99
|
+
screens: ScreenSpec[];
|
|
100
|
+
transitions: TransitionSpec[];
|
|
101
|
+
history: HistoryEntrySpec[];
|
|
102
|
+
screenLists: ScreenListSpec[];
|
|
103
|
+
commentBlocks: AnnotCommentBlocks;
|
|
104
|
+
/** Original source text — needed by `serialiseUpdate` for byte-stable rewrites. */
|
|
105
|
+
source: string;
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ingcreators/annot-product-docs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Living product docs core — parse `.mdx` files with `annot:` frontmatter, resolve `<Overlay match>` keys against live Playwright `aria-snapshot` trees, run drift detection against the rendered UI, and ship a Playwright `screen` fixture that re-captures + re-syncs MDX snapshot blocks. Tier A (Node-only). Phase 1 of docs/plans/living-product-docs.md.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "ingcreators",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"docs",
|
|
22
22
|
"documentation",
|
|
23
23
|
"screen-spec",
|
|
24
|
-
"
|
|
24
|
+
"screen-specifications",
|
|
25
25
|
"drift",
|
|
26
26
|
"aria-snapshot"
|
|
27
27
|
],
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"@types/mdast": "^4.0.4",
|
|
53
53
|
"@types/unist": "^3.0.3",
|
|
54
54
|
"typescript": "^6.0.3",
|
|
55
|
-
"vite": "^
|
|
56
|
-
"vite-plugin-dts": "^5.0.
|
|
55
|
+
"vite": "^8.0.13",
|
|
56
|
+
"vite-plugin-dts": "^5.0.1"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"js-yaml": "^4.1.1",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"unified": "^11.0.5",
|
|
66
66
|
"unist-util-visit": "^5.0.0",
|
|
67
67
|
"zod": "^3.25.0",
|
|
68
|
-
"@ingcreators/annot-annotator": "0.
|
|
69
|
-
"@ingcreators/annot-playwright": "0.
|
|
68
|
+
"@ingcreators/annot-annotator": "0.5.0",
|
|
69
|
+
"@ingcreators/annot-playwright": "0.4.0"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
72
|
"@playwright/test": "^1.40.0"
|