Product intent is the program.
Intent is an experimental TypeScript framework for describing applications as a semantic graph of screens, state, asks, actions, resources, routes, and surfaces.
It is not trying to make JSX nicer. It is not trying to wrap React. It is not a UI kit.
Intent asks a different question:
What if the source of truth for an app was not components, DOM nodes, routes, or handlers, but the product intent itself?
The DOM is one possible output. A server route is one possible transport. A test harness is one possible observer. The product model lives above them.
Intent is early and experimental.
The current repository proves the core shape:
- Platformless semantic core
- DOM renderer
- Typed router
- Runtime-scoped resources
- Resource reload and invalidation
- Independent executable actions
- Keyboard Enter default action
- Accessible Enter hints
- Screen-name headings
- Graph diagnostics via
inspectScreen() - Semantic test harness
- A runnable web demo
- CI clean-dist validation
It is not production-ready yet. The goal right now is to keep the foundation small, inspectable, and hard to fake.
Intent is available as experimental alpha packages on npm:
pnpm add @intent-framework/core@0.1.0-alpha.7 @intent-framework/dom@0.1.0-alpha.7 @intent-framework/router@0.1.0-alpha.7 @intent-framework/testing@0.1.0-alpha.7npm install @intent-framework/core@0.1.0-alpha.7 @intent-framework/dom@0.1.0-alpha.7 @intent-framework/router@0.1.0-alpha.7 @intent-framework/testing@0.1.0-alpha.7The quickstart pins the current alpha version so examples match the published APIs.
Current alpha: v0.1.0-alpha.7
First public alpha: v0.1.0-alpha.0
GitHub Releases: https://github.com/intent-framework/intent/releasesPublished packages:
| Package | npm |
|---|---|
@intent-framework/core |
Platformless semantic graph and runtime |
@intent-framework/dom |
DOM materializer for screens and router |
@intent-framework/router |
Typed route definitions and navigation |
@intent-framework/testing |
Semantic test harness |
@intent-framework/server remains private and unpublished.
This is experimental alpha software. APIs are subject to change. Not recommended for production use.
See docs/Quickstart.md for a step-by-step guide — install, define a screen, render it, test it, and inspect the semantic graph.
Modern app code often scatters product meaning across UI components, route files, data hooks, form handlers, test selectors, and backend endpoints.
Intent tries to keep the meaning in one graph.
Instead of starting with:
Which component renders this?
Which route owns this?
Which handler mutates this?
Which selector does the test click?Intent starts with:
What is the screen?
What does the user need to provide?
What actions are possible?
When is an action blocked?
What resource does this screen depend on?
What should tests be able to assert semantically?The renderer then materializes that graph.
import { screen, state } from "@intent-framework/core"
export const InviteScreen = screen("Invite", $ => {
const email = $.state.text("email")
const emailAsk = $.ask("Email", email)
.asContact("email")
.required()
const sendInvite = $.act("Send invite")
.primary()
.when(emailAsk.valid, "Enter a valid email.")
.does(async ({ navigate }) => {
// product behavior goes here
navigate?.("team")
})
$.surface("main").contains(emailAsk, sendInvite)
})This defines more than UI.
It defines:
- A screen named
Invite - A semantic email ask
- A required validation condition
- A primary action
- A blocked reason
- A surface containing product-relevant nodes
- Something renderers, tests, and diagnostics can inspect
Intent is a semantic application graph.
It gives names and behavior to product-level concepts:
screenstateaskactconditionresourcesurfaceflowroute
Renderers and adapters then materialize that graph into specific environments.
Today, the repository includes a DOM renderer, a router, a server package, and a testing package. More targets can exist later without making the core depend on them.
Intent is not:
- A React clone
- A JSX replacement
- A component library
- A UI kit
- A CSS framework
- An API route framework
- A backend framework
- A compiler yet
- A native renderer yet
- A DevTools package yet
Those may become outputs, adapters, or tools around the graph. They are not the center.
Intent separates product meaning from output mechanics.
Product graph → screen, ask, action, resource, route, surface
Runtime → state, conditions, blocked reasons, feedback, resource lifecycle
Materializers → DOM, tests, server adapters, future native/devtools/compilerThe DOM is an output, not the language.
Routes are navigation, not the product model.
API routes are transport, not the backend model.
Tests should assert product semantics, not DOM trivia.
This repository currently contains:
| Package | Purpose |
|---|---|
@intent-framework/core |
Platformless semantic graph and runtime |
@intent-framework/dom |
DOM materializer for screens and router |
@intent-framework/router |
Typed route definitions and navigation |
@intent-framework/testing |
Semantic test harness |
@intent-framework/server |
Early server-side package |
The core package must remain platformless. It should not import DOM, React, router internals, server framework code, or native APIs.
Install dependencies:
pnpm installRun the canonical example (matches the Quickstart one-to-one):
pnpm dev:canonicalOpen the local URL printed by Vite.
The example shows a single InviteMember screen — an email ask, a primary invite action blocked until the email is valid, and inspectScreen() output displayed on the page.
For a more complete demo with routing, resources, and diagnostics, see:
pnpm dev:web-basicOpen the local URL printed by Vite.
The demo shows a small team invite flow:
- Pick a team.
- View team details.
- Refresh the team resource.
- Open the invite screen.
- Try to send with an empty or invalid email.
- Type a valid email.
- Press Enter or click Send invite.
- Return to team details and see the member count update.
- Inspect the diagnostics panel.
For a guided walkthrough, see Demo Guide.
For a deep dive into resource semantics — load, reload, invalidation, staleness, and runtime scoping — see the Resources Guide.
The repository includes five runnable examples, each demonstrating a different set of Intent capabilities:
| Example | What it demonstrates |
|---|---|
canonical-invite |
Minimal screen: text ask, primary action, validation, blocked reasons, feedback, inspectScreen(). Matches the Quickstart one-to-one. |
choice-form |
All state kinds (text, choice, boolean) and ask kinds (asChoice(), asSecret(), hint()), multiple surfaces per screen, flow diagnostics, full test coverage. |
resource-lifecycle |
Full resource lifecycle: autoLoad, manual load, reload, invalidation, stale detection, failed state, action-driven invalidation with .invalidates(). |
secret-vault |
Multi-step flows (3+ steps), asSecret() + private(), BooleanState toggles, multi-screen router app, flow diagnostics. |
web-basic |
Multi-screen team invite demo with router, resources, diagnostics panel, feedback, and keyboard default action. |
Each example includes tests. Run with pnpm dev:<name> (e.g. pnpm dev:choice-form) and test with pnpm test from the example directory.
The web-basic demo is intentionally small. It is a dagger, not a cathedral.
It demonstrates:
- Semantic screens
- Independent actions rendered as buttons
- Typed router navigation
- Route context passed into actions and resources
- Runtime-scoped resources
- Resource reload
- Ask validation
- Blocked action reasons
- Feedback output
- Keyboard Enter default action
- Accessible Enter hint
- Opt-in screen-name headings
- Graph diagnostics via
inspectScreen() - Clean-dist CI validation
Intent tests should speak product language.
Instead of testing only DOM selectors, tests can ask questions like:
Is this action enabled?
Why is this action blocked?
What happens when this ask is answered?
What resources does this screen expose?
What diagnostics does this graph produce?The testing package exists so product behavior can be checked without pretending the DOM is the source of truth.
Intent can inspect a screen graph through inspectScreen().
Diagnostics currently catch graph-level issues such as:
- Multiple primary actions
- Secret asks that are not private
- Primary actions blocked without a human-readable reason
- Asks not included in a surface
- Actions not included in a surface
- Surfaces including nodes that no flow references
- Flow steps referencing nodes not in any surface
- Flows whose steps are entirely disconnected from surfaces
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.
For a focused guide on debugging rendered DOM by mapping semantic IDs to data-intent-* attributes, see the Semantic DOM Debugging Guide.
Run clean validation locally:
rm -rf packages/*/dist examples/*/dist
pnpm test
pnpm typecheck
pnpm build
pnpm lintCI runs the same clean-dist validation on every pull request and every push to main.
The clean-dist step matters. It prevents tests from accidentally passing because of stale package output.
OpenCode workflow commands live in .opencode/README.md.
Intent is still missing many things on purpose.
Current limitations include:
- No compiler yet
- No native renderer yet
- No SSR story yet
- No backend persistence yet
- No real resource cache policy yet
- No DevTools package yet
- Automated version PR workflow and manual publish workflow are set up (Changesets configured)
- Demo side panels use manual DOM
- Demo diagnostics panel uses
MutationObserver - Demo data is in memory only
These are not hidden. They are the next architectural decisions.
Intent should stay small and semantically sharp.
Core rules:
- Core stays platformless.
- DOM does not own product truth.
- Router does not own product truth.
- Server routes are transport.
- Public behavior gets tests.
- Types should stay precise.
- Avoid
any. - Avoid renderer-specific leakage into core.
- Prefer semantic primitives over framework glue.
Completed:
- ✦ Three focused examples:
choice-form,resource-lifecycle,secret-vault— covering all state kinds, choice/secret asks, multi-surface screens, resource lifecycle (autoLoad, reload, invalidation, failed state), complex flows, secret+private, and flow diagnostics - ✦ DOM multi-surface rendering — each surface renders as a semantic
<section>with surface-suffixed DOM ids, shared state, duplicate controls, and per-surface blocked reasons and feedback
Near-term work:
- Harden the demo path
- Improve graph diagnostics
- Add better resource semantics
- Add richer documentation
- Explore DevTools-style graph inspection
- Clarify server-side intent boundaries
Not yet:
- Compiler
- Native renderer
- SSR
- Production persistence
- Styling system
- Component ecosystem
See MVP Checkpoint for the current implementation boundary.
See Release Readiness for the current npm publishing and release plan.
Intent should let a product flow be authored once as meaning, then observed or materialized many ways.
A screen should be understandable before it becomes DOM.
An action should be testable before it becomes a button.
A resource should be inspectable before it becomes a fetch call.
A route should navigate the product graph, not become the product graph.
That is the line Intent is trying to hold.