Skip to content

Commit 1ca9712

Browse files
Added Semantic DOM Debugging guide (#74)
* Added Semantic DOM Debugging guide Co-authored-by: mahyarmlk <mahyarmlk@users.noreply.github.com> * Remove unintended changeset file Co-authored-by: mahyarmlk <mahyarmlk@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mahyarmlk <mahyarmlk@users.noreply.github.com>
1 parent e2d6958 commit 1ca9712

3 files changed

Lines changed: 185 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ Diagnostics are not lint rules for HTML. They are semantic graph feedback.
299299

300300
For a detailed walkthrough of diagnostics, semantic IDs, and development workflow, see the [Inspect Screen and Diagnostics Guide](docs/Inspect-Screen.md).
301301

302+
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).
303+
302304
## Development
303305

304306
Run clean validation locally:

docs/Semantic-DOM-Debugging.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Semantic DOM Debugging
2+
3+
## Problem
4+
5+
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.
6+
7+
## Solution
8+
9+
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()`.
10+
11+
## Data attributes produced
12+
13+
| Attribute | On element | Value example |
14+
|-----------|-----------|--------------|
15+
| `data-intent-screen` | `<main>` | `screen:invite-member` |
16+
| `data-intent-ask` | `<label>`, `<input>` | `ask:email` |
17+
| `data-intent-action` | `<button>` | `action:invite-member` |
18+
19+
The attribute values are the same `semanticId` strings documented in the [Inspect Screen and Diagnostics Guide](Inspect-Screen.md).
20+
21+
## Usage
22+
23+
### With `renderDom()`
24+
25+
```ts
26+
import { renderDom } from "@intent-framework/dom"
27+
28+
const cleanup = renderDom(MyScreen, {
29+
target: document.getElementById("root")!,
30+
showSemanticIds: true,
31+
})
32+
```
33+
34+
### With `renderRouter()`
35+
36+
```ts
37+
import { renderRouter } from "@intent-framework/dom"
38+
39+
const handle = renderRouter(myRouter, {
40+
target: document.getElementById("root")!,
41+
showSemanticIds: true,
42+
})
43+
```
44+
45+
### Opt-in only
46+
47+
By default, no `data-intent-*` attributes are emitted. Your production output is unaffected. Enable the flag only during development or in debug builds.
48+
49+
## Debugging workflow
50+
51+
### 1. Enable semantic IDs
52+
53+
```ts
54+
renderDom(MyScreen, {
55+
target: document.getElementById("root")!,
56+
showSemanticIds: import.meta.env.DEV, // only in dev
57+
})
58+
```
59+
60+
### 2. Inspect in browser DevTools
61+
62+
Open the Elements panel. Select any rendered label, input, or button. The `data-intent-ask` or `data-intent-action` attribute tells you which semantic node it belongs to.
63+
64+
Example — inspecting a rendered email input:
65+
66+
```html
67+
<main data-intent-screen="screen:invite-member">
68+
<form method="POST" novalidate>
69+
<div class="ask-group">
70+
<label for="ask_email" data-intent-ask="ask:email">Email</label>
71+
<input id="ask_email" name="ask_email" type="email" required autocomplete="email" data-intent-ask="ask:email">
72+
</div>
73+
<button id="act_invite_member" type="button" data-intent-action="action:invite-member">Invite member</button>
74+
<output id="feedback-output" aria-live="polite"></output>
75+
</form>
76+
</main>
77+
```
78+
79+
### 3. Cross-reference with `inspectScreen()`
80+
81+
Open the console and run:
82+
83+
```js
84+
import { inspectScreen } from "@intent-framework/core"
85+
console.log(inspectScreen(MyScreen))
86+
```
87+
88+
The `semanticId` values in the console output match the `data-intent-*` attribute values in the Elements panel.
89+
90+
### 4. Use in tests
91+
92+
Query elements by their semantic attributes in browser tests:
93+
94+
```ts
95+
const emailInput = document.querySelector('[data-intent-ask="ask:email"]')
96+
const inviteButton = document.querySelector('[data-intent-action="action:invite-member"]')
97+
const screen = document.querySelector('[data-intent-screen="screen:invite-member"]')
98+
```
99+
100+
This is more resilient than querying by generated class names or internal IDs, and expresses the test's intent at the semantic level.
101+
102+
## How the mapping works
103+
104+
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.
105+
106+
The mapping is:
107+
108+
1. `renderDom()` calls `inspectScreen(screenDef)`.
109+
2. The returned `InspectedScreen` object contains `asks[].semanticId` and `acts[].semanticId` values.
110+
3. These values are matched to DOM elements by the internal node `id` field (e.g., `ask_email`, `act_invite_member`).
111+
4. The semantic ID is set as the corresponding `data-intent-*` attribute.
112+
113+
This is the same `inspectScreen()` call you would make manually — no extra computation, just reuse of the existing graph snapshot.
114+
115+
### Screen-level attribute
116+
117+
The `<main>` element receives `data-intent-screen` with the screen's own `semanticId`:
118+
119+
```ts
120+
main.setAttribute("data-intent-screen", inspected.semanticId)
121+
```
122+
123+
The screen `semanticId` follows the same normalization rules: `screen("InviteMember")``screen:invite-member`.
124+
125+
## What is not included
126+
127+
- **Floating blocked-reason paragraphs** (`<p id="act_*-reason">`) — these are runtime feedback, not semantic nodes. They do not receive data attributes.
128+
- **Enter-key hints** (`<p id="*-enter-hint">`) — UI affordance, not a semantic node.
129+
- **Feedback output** (`<output>`) — runtime status, not a semantic node.
130+
- **Flow elements** — flows are graph concepts without a DOM representation.
131+
- **Surface elements** — the surface's DOM id is set from the surface's internal id, but no `data-intent-surface` attribute is added. The `<main>` element id doubles as the surface id.
132+
133+
## Example end-to-end
134+
135+
Given this screen:
136+
137+
```ts
138+
const InviteScreen = screen("InviteMember", $ => {
139+
const email = $.state.text("email")
140+
const emailAsk = $.ask("Email", email).required()
141+
const invite = $.act("Invite member").primary().when(emailAsk.valid)
142+
$.surface("main").contains(emailAsk, invite)
143+
})
144+
```
145+
146+
Rendering with `showSemanticIds: true` produces DOM elements with these attributes:
147+
148+
| DOM element | Attribute | Value |
149+
|------------|-----------|-------|
150+
| `<main>` | `data-intent-screen` | `screen:invite-member` |
151+
| `<label>` | `data-intent-ask` | `ask:email` |
152+
| `<input>` | `data-intent-ask` | `ask:email` |
153+
| `<button>` | `data-intent-action` | `action:invite-member` |
154+
155+
Calling `inspectScreen(InviteScreen)` returns matching `semanticId` values:
156+
157+
```json
158+
{
159+
"name": "InviteMember",
160+
"semanticId": "screen:invite-member",
161+
"asks": [
162+
{
163+
"id": "ask_email",
164+
"semanticId": "ask:email",
165+
"label": "Email"
166+
}
167+
],
168+
"acts": [
169+
{
170+
"id": "act_invite_member",
171+
"semanticId": "action:invite-member",
172+
"label": "Invite member"
173+
}
174+
]
175+
}
176+
```
177+
178+
## See also
179+
180+
- [Inspect Screen and Diagnostics Guide](Inspect-Screen.md) — full `inspectScreen()` reference, semantic ID rules, and diagnostics reference.
181+
- [Quickstart](Quickstart.md) — step-by-step guide with `renderDom()`.
182+
- [Root README](../README.md) — project overview.

packages/dom/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ DOM is a renderer for Intent screens. It depends on `@intent-framework/core` and
5757

5858
- [Root README](../../README.md) — project overview and philosophy
5959
- [Quickstart](../../docs/Quickstart.md) — step-by-step guide with DOM rendering
60+
- [Semantic DOM Debugging](../../docs/Semantic-DOM-Debugging.md) — how `showSemanticIds` maps `inspectScreen()` IDs to DOM data attributes
6061
- [Canonical runnable example](../../examples/canonical-invite) — matches the Quickstart one-to-one
6162

6263
## Status

0 commit comments

Comments
 (0)