diff --git a/README.md b/README.md index 9420d50..e45c29c 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,8 @@ Diagnostics are not lint rules for HTML. They are semantic graph feedback. For a detailed walkthrough of diagnostics, semantic IDs, and development workflow, see the [Inspect Screen and Diagnostics Guide](docs/Inspect-Screen.md). +For a focused guide on debugging rendered DOM by mapping semantic IDs to `data-intent-*` attributes, see the [Semantic DOM Debugging Guide](docs/Semantic-DOM-Debugging.md). + ## Development Run clean validation locally: diff --git a/docs/Semantic-DOM-Debugging.md b/docs/Semantic-DOM-Debugging.md new file mode 100644 index 0000000..1f5d080 --- /dev/null +++ b/docs/Semantic-DOM-Debugging.md @@ -0,0 +1,182 @@ +# Semantic DOM Debugging + +## Problem + +You are looking at a rendered Intent screen in the browser and want to know which semantic node each DOM element corresponds to. The DOM class names and IDs are internal renderer details, not product semantics. + +## Solution + +Pass `showSemanticIds: true` to `renderDom()` or `renderRouter()`. The renderer adds `data-intent-*` attributes to key DOM elements, mapping them to the same semantic IDs produced by `inspectScreen()`. + +## Data attributes produced + +| Attribute | On element | Value example | +|-----------|-----------|--------------| +| `data-intent-screen` | `
` | `screen:invite-member` | +| `data-intent-ask` | `
+``` + +### 3. Cross-reference with `inspectScreen()` + +Open the console and run: + +```js +import { inspectScreen } from "@intent-framework/core" +console.log(inspectScreen(MyScreen)) +``` + +The `semanticId` values in the console output match the `data-intent-*` attribute values in the Elements panel. + +### 4. Use in tests + +Query elements by their semantic attributes in browser tests: + +```ts +const emailInput = document.querySelector('[data-intent-ask="ask:email"]') +const inviteButton = document.querySelector('[data-intent-action="action:invite-member"]') +const screen = document.querySelector('[data-intent-screen="screen:invite-member"]') +``` + +This is more resilient than querying by generated class names or internal IDs, and expresses the test's intent at the semantic level. + +## How the mapping works + +When `showSemanticIds: true` is set, `renderDom()` calls `inspectScreen()` internally to resolve the semantic IDs for every ask and action node. It then applies them as data attributes during DOM construction. + +The mapping is: + +1. `renderDom()` calls `inspectScreen(screenDef)`. +2. The returned `InspectedScreen` object contains `asks[].semanticId` and `acts[].semanticId` values. +3. These values are matched to DOM elements by the internal node `id` field (e.g., `ask_email`, `act_invite_member`). +4. The semantic ID is set as the corresponding `data-intent-*` attribute. + +This is the same `inspectScreen()` call you would make manually — no extra computation, just reuse of the existing graph snapshot. + +### Screen-level attribute + +The `
` element receives `data-intent-screen` with the screen's own `semanticId`: + +```ts +main.setAttribute("data-intent-screen", inspected.semanticId) +``` + +The screen `semanticId` follows the same normalization rules: `screen("InviteMember")` → `screen:invite-member`. + +## What is not included + +- **Floating blocked-reason paragraphs** (`

`) — these are runtime feedback, not semantic nodes. They do not receive data attributes. +- **Enter-key hints** (`

`) — UI affordance, not a semantic node. +- **Feedback output** (``) — runtime status, not a semantic node. +- **Flow elements** — flows are graph concepts without a DOM representation. +- **Surface elements** — the surface's DOM id is set from the surface's internal id, but no `data-intent-surface` attribute is added. The `

` element id doubles as the surface id. + +## Example end-to-end + +Given this screen: + +```ts +const InviteScreen = screen("InviteMember", $ => { + const email = $.state.text("email") + const emailAsk = $.ask("Email", email).required() + const invite = $.act("Invite member").primary().when(emailAsk.valid) + $.surface("main").contains(emailAsk, invite) +}) +``` + +Rendering with `showSemanticIds: true` produces DOM elements with these attributes: + +| DOM element | Attribute | Value | +|------------|-----------|-------| +| `
` | `data-intent-screen` | `screen:invite-member` | +| `