Skip to content

Commit ebdb370

Browse files
committed
docs: add npm-first quickstart
1 parent dc995d4 commit ebdb370

3 files changed

Lines changed: 231 additions & 6 deletions

File tree

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ npm install @intent-framework/core@alpha @intent-framework/dom@alpha @intent-fra
4747
```
4848

4949
```txt
50-
First public alpha: v0.1.0-alpha.0
51-
GitHub Release: https://github.com/intent-framework/intent/releases/tag/v0.1.0-alpha.0
50+
First public alpha: v0.1.0-alpha.1
51+
GitHub Release: https://github.com/intent-framework/intent/releases/tag/v0.1.0-alpha.1
5252
```
5353

5454
Published packages:
@@ -64,6 +64,10 @@ Published packages:
6464

6565
This is experimental alpha software. APIs are subject to change. Not recommended for production use.
6666

67+
## Quickstart
68+
69+
See [docs/Quickstart.md](docs/Quickstart.md) for a step-by-step guide — install, define a screen, render it, test it, and inspect the semantic graph.
70+
6771
## Why Intent exists
6872

6973
Modern app code often scatters product meaning across UI components, route files, data hooks, form handlers, test selectors, and backend endpoints.

docs/MVP-Checkpoint.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Date
44

5-
2026-06-25
5+
2026-06-26
66

77
## Current status
88

@@ -27,6 +27,7 @@ It now has:
2727

2828
## What is proven
2929

30+
- An npm-first quickstart guides new developers through install, definition, rendering, testing, and graph inspection.
3031
- Screens can be authored semantically.
3132
- DOM can materialize the graph without being the source of truth.
3233
- Actions can be independently executable.
@@ -134,6 +135,7 @@ Do not jump into these without a very specific narrow PR:
134135

135136
## Recommended next moves
136137

137-
1. Add a tiny diagnostics/dev-inspection page or command that prints `inspectScreen()` output for demo screens without `MutationObserver`.
138-
2. Improve resource semantics documentation: runtime-scoped resources, reload, stale, invalidation, context.
139-
3. Add one or two graph diagnostics that catch real authoring mistakes before adding any new package.
138+
1. Add a canonical example app that new developers can clone and run after the quickstart.
139+
2. Add a tiny diagnostics/dev-inspection page or command that prints `inspectScreen()` output for demo screens without `MutationObserver`.
140+
3. Improve resource semantics documentation: runtime-scoped resources, reload, stale, invalidation, context.
141+
4. Add one or two graph diagnostics that catch real authoring mistakes before adding any new package.

docs/Quickstart.md

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Intent Quickstart
2+
3+
**Product intent is the program.**
4+
5+
This guide shows the fastest path from zero to a semantic screen using published alpha packages.
6+
7+
## 1. Install
8+
9+
```sh
10+
pnpm add @intent-framework/core@alpha @intent-framework/dom@alpha @intent-framework/testing@alpha
11+
```
12+
13+
Or with npm:
14+
15+
```sh
16+
npm install @intent-framework/core@alpha @intent-framework/dom@alpha @intent-framework/testing@alpha
17+
```
18+
19+
You also need `typescript` and `vitest` for type checking and tests.
20+
21+
The router (`@intent-framework/router`) and server (`@intent-framework/server`) are separate — the quickstart stays screen-first.
22+
23+
## 2. Define a screen
24+
25+
Create a file that describes what the screen means, not what it looks like:
26+
27+
```ts
28+
import { screen } from "@intent-framework/core"
29+
30+
export const InviteMember = screen("InviteMember", $ => {
31+
const email = $.state.text("email")
32+
33+
const emailAsk = $.ask("Email", email)
34+
.required("Email is required")
35+
.validate(value => value.includes("@"), "Enter a valid email")
36+
37+
const invite = $.act("Invite member")
38+
.primary()
39+
.when(emailAsk.valid, "Enter a valid email first")
40+
.does(() => {
41+
console.log("invite", email.value)
42+
})
43+
44+
$.surface("main").contains(emailAsk, invite)
45+
})
46+
```
47+
48+
This describes:
49+
50+
- A screen named `InviteMember`
51+
- A text state named `email`
52+
- An ask labelled `Email` that is required and validated
53+
- A primary action labelled `Invite member` that is blocked until the email is valid
54+
- A surface named `main` that groups the ask and action
55+
56+
State, validation, actions, and surfaces are semantic nodes — not yet DOM.
57+
58+
## 3. Render with DOM
59+
60+
The DOM renderer materializes the screen into real HTML. No JSX required.
61+
62+
```ts
63+
import { renderDom } from "@intent-framework/dom"
64+
import { InviteMember } from "./InviteMember.js"
65+
66+
const root = document.getElementById("root")!
67+
renderDom(InviteMember, { target: root })
68+
```
69+
70+
This produces:
71+
72+
```html
73+
<main>
74+
<form method="POST" novalidate>
75+
<div class="ask-group">
76+
<label for="ask_email">Email</label>
77+
<input id="ask_email" name="ask_email" type="text" required />
78+
</div>
79+
<button id="act_invite_member" type="button" class="primary" disabled>
80+
Invite member
81+
</button>
82+
<output id="feedback-output" aria-live="polite"></output>
83+
</form>
84+
</main>
85+
```
86+
87+
- Labels, inputs, buttons, and live regions are real DOM.
88+
- The submit button is initially disabled because the email is empty.
89+
- Typing a valid email enables the button reactively.
90+
- Pressing Enter triggers the default action when unambiguous.
91+
92+
The DOM renderer also accepts services and an option to show the screen name as an `<h1>`:
93+
94+
```ts
95+
renderDom(InviteMember, {
96+
target: root,
97+
showScreenName: true,
98+
})
99+
```
100+
101+
## 4. Test semantically
102+
103+
The testing package lets you assert product behavior without touching the DOM:
104+
105+
```ts
106+
import { test, expect } from "vitest"
107+
import { testScreen } from "@intent-framework/testing"
108+
import { InviteMember } from "./InviteMember.js"
109+
110+
test("invite is blocked until email is valid", async () => {
111+
await testScreen(InviteMember, async app => {
112+
await app.act("Invite member").toBeBlockedBy("Enter a valid email first")
113+
114+
await app.answer("Email", "ada@example.com")
115+
116+
await app.act("Invite member").toBeEnabled()
117+
})
118+
})
119+
120+
test("invite feedback shows after execution", async () => {
121+
await testScreen(InviteMember, async app => {
122+
await app.answer("Email", "ada@example.com")
123+
await app.act("Invite member").run()
124+
// Action status is available via feedback()
125+
})
126+
})
127+
```
128+
129+
The harness:
130+
131+
- Creates a runtime for the screen
132+
- Answers asks by setting state directly
133+
- Checks action enabled/blocked state via semantic conditions
134+
- Executes actions through the runtime context
135+
136+
No DOM, no selectors, no waiting for renders. Tests speak product language.
137+
138+
## 5. Inspect the semantic graph
139+
140+
`inspectScreen()` returns a snapshot of the screen's semantic nodes, their states, and diagnostics:
141+
142+
```ts
143+
import { inspectScreen } from "@intent-framework/core"
144+
import { InviteMember } from "./InviteMember.js"
145+
146+
const graph = inspectScreen(InviteMember)
147+
console.log(JSON.stringify(graph, null, 2))
148+
```
149+
150+
Example output:
151+
152+
```json
153+
{
154+
"name": "InviteMember",
155+
"semanticId": "screen:invite-member",
156+
"asks": [
157+
{
158+
"id": "ask_email",
159+
"semanticId": "ask:email",
160+
"label": "Email",
161+
"kind": "text",
162+
"required": true,
163+
"valid": false,
164+
"error": "Email is required"
165+
}
166+
],
167+
"acts": [
168+
{
169+
"id": "act_invite_member",
170+
"semanticId": "action:invite-member",
171+
"label": "Invite member",
172+
"primary": true,
173+
"enabled": false,
174+
"blockedReasons": ["Enter a valid email first"]
175+
}
176+
],
177+
"surfaces": [
178+
{
179+
"id": "surface_main",
180+
"semanticId": "surface:main",
181+
"name": "main",
182+
"itemCount": 2
183+
}
184+
],
185+
"diagnostics": []
186+
}
187+
```
188+
189+
Every node carries a stable, deterministic `semanticId`:
190+
191+
| Node | semanticId |
192+
|------|------------|
193+
| Screen | `screen:invite-member` |
194+
| Ask | `ask:email` |
195+
| Action | `action:invite-member` |
196+
| Surface | `surface:main` |
197+
198+
Diagnostics catch common authoring mistakes — multiple primary actions, secret asks that are not private, nodes not included in any surface.
199+
200+
## 6. What just happened?
201+
202+
You defined a screen in product terms — not as components, markup, or CSS classes.
203+
204+
- **The screen describes product intent.** It says: there is an email to collect, an invite action to offer, and a rule that the invite is blocked until the email is valid.
205+
- **The DOM renderer materialized** that intent into real, inspectable HTML with labels, inputs, disabled states, and live regions — automatically.
206+
- **The testing harness validated** that the action is blocked for the right reason and becomes enabled when the ask is satisfied — without touching the DOM.
207+
- **`inspectScreen()` exposed the graph** for diagnostics and future tooling (DevTools, analytics, code generation).
208+
209+
This is the pattern:
210+
211+
```txt
212+
Screen intent → Semantic graph → Materialized output (DOM, tests, diagnostics)
213+
```
214+
215+
Intent does not replace components for everything. It replaces components as the source of truth for product behavior.
216+
217+
---
218+
219+
**Next steps:** See the [web demo](../examples/web-basic) for a full team invite flow with routing, resources, and diagnostics. Read the [Specification](Specification.md) for the architecture. Check the [MVP Checkpoint](MVP-Checkpoint.md) for current boundaries.

0 commit comments

Comments
 (0)