document360-engine 0.2.9 → 0.2.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document360-engine",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "Headless documentation agent engine for document360-writer / -desktop. Emits a typed event stream; no UI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -2,43 +2,69 @@
2
2
 
3
3
  **Activate when** you've just emitted a `<!-- SCREENSHOT ... -->` placeholder block in an article, OR the user runs `/screenshot <id>`.
4
4
 
5
- **Goal:** generate a Playwright `.spec.ts` file in the configured `captureDir` that the `document360-capture` (alias `d360-capture`) CLI can execute end-to-end against the user's running product.
5
+ **Goal:** generate a Playwright `.spec.ts` in the configured `captureDir` that the `document360-capture` (alias `d360-capture`) CLI runs against the user's live product.
6
6
 
7
- ## Where the spec lands
7
+ ## Read the product FIRST — never guess (non-negotiable)
8
8
 
9
- `<captureDir>/<placeholder.id>.spec.ts` read `captureDir` from `.d360-writer.json` (default: `user-docs/_capture`).
9
+ Every recurring capture failure has traced back to guessing. Before you write a single line, READ the source and ground each of these in it:
10
+
11
+ 1. **Routes** — find the router (e.g. the app's route table) and use the EXACT path. Do not assume `/spec` vs `/spec-files`, `/tests` vs `/test`. If you can't find it, say so and ask.
12
+ 2. **Selectors** — open the component and use a stable `data-testid` you actually see. Never invent one, and never fall back to `li`, `ul.divide-y li`, or other structural CSS.
13
+ 3. **Context selection** — find how the app enters the context the shot needs (selecting a project / workspace / org / etc.). You'll drive it with the capture *scope* (below), not by clicking "the first one".
14
+ 4. **Data prerequisites** — determine what state the screen needs to look real (≥1 of something, a specific record, an open modal). This becomes both the placeholder's `prerequisites:` and a runtime guard (below).
15
+
16
+ If a fact isn't in the source, do not fabricate it.
17
+
18
+ ## Capture scope — enter a KNOWN context, deterministically
19
+
20
+ The capture profile declares a generic `scope` (key→value) for the prepared demo context — e.g. `{project: "Docs Demo"}`, or `{project, workspace, language}`, or `{org}` — whatever THIS product scopes by (you learned that by reading the source; it is not always "project"). The CLI injects it; read it with the `captureScope()` / `scopeValue(key)` helpers.
21
+
22
+ - Select the context using the scope value (e.g. the project whose name === `scope.project`), via the selection mechanism you read from the source — **not** `.first()`.
23
+ - Fall back to `.first()` ONLY when the relevant scope key is unset, so single-context setups still work.
24
+
25
+ ## Data prerequisites — skip clearly, don't time out
26
+
27
+ If the data the shot needs is absent, a 15s selector timeout is a terrible error. Instead, guard it and skip with a reason that tells the user exactly what to stage:
28
+
29
+ ```typescript
30
+ if ((await rows.count()) === 0) {
31
+ test.skip(true, 'Prerequisite not met: needs ≥1 suite in project "' + (scopeValue('project') ?? '<scope>') + '". Stage it, then re-run.');
32
+ }
33
+ ```
34
+
35
+ The skip reason MUST match what you wrote in the placeholder's `prerequisites:` line — they're the same fact, surfaced two ways.
10
36
 
11
37
  ## Spec template
12
38
 
13
39
  ```typescript
14
40
  import { test } from '@playwright/test';
15
- import { waitPastLogin, dumpAnnotations, type Placeholder } from 'document360-capture/helpers';
41
+ import { waitPastLogin, dumpAnnotations, captureScope, scopeValue, type Placeholder } from 'document360-capture/helpers';
16
42
 
17
43
  const PLACEHOLDER: Placeholder = {
18
44
  id: '<placeholder.id>',
19
45
  saveTo: '<placeholder.save-to>',
20
- highlight: [
21
- // one stable selector per placeholder.highlight entry
22
- ],
23
- annotations: [
24
- // { target: '<stable selector>', label: '1', text: '<short text>' }
25
- ],
26
- redact: [
27
- // one stable selector per placeholder.redact entry
28
- ],
46
+ highlight: [ /* one stable selector per placeholder.highlight entry */ ],
47
+ annotations: [ /* { target: '<stable selector>', label: '1', text: '<short text>' } */ ],
48
+ redact: [ /* one stable selector per placeholder.redact entry */ ],
29
49
  };
30
50
 
31
51
  test('<placeholder.id>', async ({ page }) => {
32
52
  await page.goto(process.env.CAPTURE_START_URL!);
33
53
  await waitPastLogin(page, new RegExp(process.env.CAPTURE_AUTH_BOUNDARY!));
34
54
 
35
- // Translated from placeholder.steps one Playwright action per step.
36
- // Use STABLE selectors only.
37
- // ...
55
+ // 1. Enter the prepared context using the scope (selection mechanism read from source).
56
+ const scope = captureScope();
57
+ // …select the context by scope.<key>; fall back to first only when that key is unset…
58
+
59
+ // 2. Navigate using the EXACT route read from the router (never a guessed path).
60
+ // …
61
+
62
+ // 3. Prerequisite guard — skip with a clear reason instead of timing out.
63
+ // if ((await <rows>.count()) === 0) test.skip(true, 'Prerequisite not met: …');
38
64
 
65
+ // 4. Reach the target state, settle, capture.
39
66
  await page.waitForSelector('<target state selector>', { state: 'visible' });
40
67
  await page.waitForTimeout(500);
41
-
42
68
  await page.locator('<capture target selector>').screenshot({ path: PLACEHOLDER.saveTo });
43
69
  await dumpAnnotations(page, PLACEHOLDER);
44
70
  });
@@ -46,31 +72,27 @@ test('<placeholder.id>', async ({ page }) => {
46
72
 
47
73
  ## Selector quality rule (non-negotiable)
48
74
 
49
- Use stable selectors in this priority order:
50
-
75
+ Stable selectors in priority order:
51
76
  1. `[data-testid="..."]` — best.
52
77
  2. `[aria-label="..."]` — good.
53
- 3. `<role>:has-text("...")` / `role=button[name="..."]` — acceptable when unique.
54
- 4. Visible text (`text=...`, `:has-text(...)`) — acceptable for top-level nav and unique buttons.
78
+ 3. `role=button[name="..."]` / `<role>:has-text("...")` — acceptable when unique.
79
+ 4. Visible text (`text=...`) — acceptable for top-level nav and unique buttons.
55
80
 
56
- **Never** use CSS classes, `nth-child`, deep DOM paths, or auto-generated IDs (`#mui-12345`).
81
+ **Never** CSS classes, `nth-child`, structural `li`/`div.x`, deep DOM paths, or auto-generated IDs (`#mui-12345`).
57
82
 
58
- ## TODO escape hatch (when stable selectors don't exist)
59
-
60
- If a required interaction has no stable selector, do NOT write a fragile one. Instead:
83
+ ## TODO escape hatch (when a stable selector truly doesn't exist)
61
84
 
85
+ Don't write a fragile selector. Instead:
62
86
  1. Use `test.skip(...)` instead of `test(...)`.
63
- 2. Add a top-of-file comment: `// TODO: add data-testid="<suggested-name>" to <ComponentName> at <src/.../File.tsx>. Capture spec disabled until then.`
64
- 3. Tell the user which file + component to fix, plus the `data-testid` value you'd recommend.
65
-
66
- The manual intern path in the placeholder still works — only the automated capture is blocked.
87
+ 2. Top-of-file comment: `// TODO: add data-testid="<name>" to <Component> at <src/.../File.tsx>. Capture disabled until then.`
88
+ 3. Tell the user which file + component to fix and the `data-testid` to add.
67
89
 
68
90
  ## Capture region (the placeholder's `capture` field)
69
91
 
70
92
  | `capture` value | Spec uses |
71
93
  |---|---|
72
94
  | `full-page` | `await page.screenshot({ path, fullPage: true })` |
73
- | `main-panel` | a stable container selector (read it from the project's main layout source) |
95
+ | `main-panel` | a stable container selector (read it from the layout source) |
74
96
  | `modal-only` | `page.locator('[role="dialog"]').first().screenshot(...)` |
75
97
  | `panel-left` / `panel-right` | the project's named-panel selector |
76
98
  | `sidebar` | the project's sidebar selector |
@@ -78,8 +100,9 @@ The manual intern path in the placeholder still works — only the automated cap
78
100
  ## After writing the spec
79
101
 
80
102
  - Report the spec's path.
81
- - If you used the TODO escape hatch, surface the blocking selectors so the dev can fix them in one pass.
82
- - Remind the user how to capture it (screenshots are a separate tool — `document360-capture` is not bundled with the writer):
103
+ - List anything that needs the user: the `data-testid`s a dev must add (TODO hatch), and the **data prerequisites** to stage (project/workspace + the records each shot needs).
104
+ - Remind how to capture (screenshots are a separate tool — not bundled with the writer):
83
105
  1. First time only: `npm i -g document360-capture`
84
- 2. Once per machine: `d360-capture auth --profile <name>`
85
- 3. `d360-capture capture <id>` to take the screenshot.
106
+ 2. Set the capture `scope` for the prepared demo context in `.d360-capture.json`.
107
+ 3. Once per machine: `d360-capture auth --profile <name>`
108
+ 4. `d360-capture capture <id>`.