Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The current repository proves the core shape:
- Typed router
- Runtime-scoped resources
- Resource reload and invalidation
- Resource cache phase 1 (staleTime, deduplicate)
- Resource cache phase 2 (staleTime, deduplicate, cache.key, ResourceKey)
- Independent executable actions
- Keyboard Enter default action
- Accessible Enter hints
Expand All @@ -40,17 +40,17 @@ It is not production-ready yet. The goal right now is to keep the foundation sma
Intent is available as experimental alpha packages on npm:

```sh
pnpm add @intent-framework/core@0.1.0-alpha.8 @intent-framework/dom@0.1.0-alpha.8 @intent-framework/router@0.1.0-alpha.8 @intent-framework/testing@0.1.0-alpha.8
pnpm add @intent-framework/core@0.1.0-alpha.9 @intent-framework/dom@0.1.0-alpha.9 @intent-framework/router@0.1.0-alpha.9 @intent-framework/testing@0.1.0-alpha.9
```

```sh
npm install @intent-framework/core@0.1.0-alpha.8 @intent-framework/dom@0.1.0-alpha.8 @intent-framework/router@0.1.0-alpha.8 @intent-framework/testing@0.1.0-alpha.8
npm install @intent-framework/core@0.1.0-alpha.9 @intent-framework/dom@0.1.0-alpha.9 @intent-framework/router@0.1.0-alpha.9 @intent-framework/testing@0.1.0-alpha.9
```

The quickstart pins the current alpha version so examples match the published APIs.

```txt
Current alpha: v0.1.0-alpha.8
Current alpha: v0.1.0-alpha.9
First public alpha: v0.1.0-alpha.0
GitHub Releases: https://github.com/intent-framework/intent/releases
```
Expand Down Expand Up @@ -347,7 +347,7 @@ Current limitations include:
* No native renderer yet
* No SSR story yet
* No backend persistence yet
* Resource cache is phase 1 (staleTime, deduplicate); cache.key, cacheTime, swr, and cross-navigation cache are future
* Resource cache is phase 2 (staleTime, deduplicate, cache.key); cacheTime, swr, and cross-navigation cache are future
* No DevTools package yet
* Automated version PR workflow and manual publish workflow are set up (Changesets configured)
* Demo side panels use manual DOM
Expand Down
6 changes: 3 additions & 3 deletions docs/Quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ This guide shows the fastest path from zero to a semantic screen using published
## 1. Install

```sh
pnpm add @intent-framework/core@0.1.0-alpha.8 @intent-framework/dom@0.1.0-alpha.8 @intent-framework/testing@0.1.0-alpha.8
pnpm add @intent-framework/core@0.1.0-alpha.9 @intent-framework/dom@0.1.0-alpha.9 @intent-framework/testing@0.1.0-alpha.9
```

Or with npm:

```sh
npm install @intent-framework/core@0.1.0-alpha.8 @intent-framework/dom@0.1.0-alpha.8 @intent-framework/testing@0.1.0-alpha.8
npm install @intent-framework/core@0.1.0-alpha.9 @intent-framework/dom@0.1.0-alpha.9 @intent-framework/testing@0.1.0-alpha.9
```

The quickstart pins `0.1.0-alpha.8` so the examples match the APIs shown below.
The quickstart pins `0.1.0-alpha.9` so the examples match the APIs shown below.

You also need `typescript` and `vitest` for type checking and tests.

Expand Down
8 changes: 4 additions & 4 deletions docs/agent/RELEASE-STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

| Package | Version | Published | Notes |
|---------|---------|-----------|-------|
| `@intent-framework/core` | `0.1.0-alpha.1` | Yes | alpha prerelease |
| `@intent-framework/dom` | `0.1.0-alpha.2` | Yes | alpha prerelease |
| `@intent-framework/router` | `0.1.0-alpha.1` | Yes | alpha prerelease |
| `@intent-framework/testing` | `0.1.0-alpha.1` | Yes | alpha prerelease |
| `@intent-framework/core` | `0.1.0-alpha.9` | Yes | alpha prerelease |
| `@intent-framework/dom` | `0.1.0-alpha.9` | Yes | alpha prerelease |
| `@intent-framework/router` | `0.1.0-alpha.9` | Yes | alpha prerelease |
| `@intent-framework/testing` | `0.1.0-alpha.9` | Yes | alpha prerelease |
| `@intent-framework/server` | `0.1.0` | No | private workspace package |

`@intent-framework/server` must remain `"private": true`. Never publish it.
Expand Down
59 changes: 57 additions & 2 deletions examples/resource-lifecycle/src/ResourceDemo.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect } from "vitest"
import { testScreen } from "@intent-framework/testing"
import { createScreenRuntime, inspectScreen } from "@intent-framework/core"
import { ResourceDemo, teamLoadCount, cachedTeamLoadCount, dedupeReportLoadCount } from "./ResourceDemo.js"
import { ResourceDemo, teamLoadCount, cachedTeamLoadCount, dedupeReportLoadCount, keyedTeamLoadCount } from "./ResourceDemo.js"

const testServices = {
route: { name: "demo", path: "/:id", params: { id: "team_1" } },
Expand Down Expand Up @@ -116,12 +116,67 @@ describe("ResourceDemo", () => {
}, { services: testServices as any })
})

it("cache.key derives resource value from route context", async () => {
const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any })
await runtime.start()
const keyed = runtime.resources.find(r => r.name === "keyedTeam")!
expect(keyed.status).toBe("ready")
expect((keyed.value as any)?.id).toBe("team_1")
expect((keyed.value as any)?.name).toBe("Team-team_1")
runtime.dispose()
})

it("cache.key: different keys are independent", async () => {
const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any })
await runtime.start()
const keyed = runtime.resources.find(r => r.name === "keyedTeam")!
const before = keyedTeamLoadCount
// Load with a different key
await keyed.load({ route: { name: "demo", path: "/:id", params: { id: "team_b" } } })
expect(keyed.status).toBe("ready")
expect((keyed.value as any)?.id).toBe("team_b")
expect((keyed.value as any)?.name).toBe("Team-team_b")
expect(keyedTeamLoadCount).toBe(before + 1)
runtime.dispose()
})

it("cache.key: switching active key updates visible value/status", async () => {
const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any })
await runtime.start()
const keyed = runtime.resources.find(r => r.name === "keyedTeam")!
// Initially keyed by route param "team_1"
expect((keyed.value as any)?.id).toBe("team_1")
// Switch to team_b
await keyed.load({ route: { name: "demo", path: "/:id", params: { id: "team_b" } } })
expect(keyed.status).toBe("ready")
expect((keyed.value as any)?.id).toBe("team_b")
// Switch back to team_1
await keyed.load({ route: { name: "demo", path: "/:id", params: { id: "team_1" } } })
expect(keyed.status).toBe("ready")
expect((keyed.value as any)?.id).toBe("team_1")
runtime.dispose()
})

it("cache.key: no-arg reload reuses last active key", async () => {
const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any })
await runtime.start()
const keyed = runtime.resources.find(r => r.name === "keyedTeam")!
// Load team_b — changes active key
await keyed.load({ route: { name: "demo", path: "/:id", params: { id: "team_b" } } })
const afterFirst = keyedTeamLoadCount
// no-arg reload uses lastContext → reloads team_b
await keyed.reload()
expect(keyedTeamLoadCount).toBe(afterFirst + 1)
expect((keyed.value as any)?.id).toBe("team_b")
runtime.dispose()
})

it("inspectScreen reports resources with status/stale/error", async () => {
const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any })
await runtime.start()
const graph = runtime.graph
const resources = graph.resources
expect(resources).toHaveLength(5)
expect(resources).toHaveLength(6)
const teamRes = resources.find(r => r.name === "team")!
expect(teamRes.status).toBe("ready")
expect(teamRes.hasValue).toBe(true)
Expand Down
29 changes: 29 additions & 0 deletions examples/resource-lifecycle/src/ResourceDemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export let teamLoadCount = 0
export let auditLogLoadCount = 0
export let cachedTeamLoadCount = 0
export let dedupeReportLoadCount = 0
export let keyedTeamLoadCount = 0

export type Team = {
id: string
Expand Down Expand Up @@ -63,6 +64,19 @@ export const ResourceDemo = screen<AppServices>("Resource Demo", $ => {
cache: { deduplicate: true },
})

const keyedTeam = $.resource("keyedTeam", {
load: async ({ route }) => {
keyedTeamLoadCount++
return {
id: route.params.id,
name: `Team-${route.params.id}`,
members: 3,
version: keyedTeamLoadCount,
} satisfies Team
},
cache: { key: ({ route }) => route.params.id },
})

const reloadTeam = $.act("Reload team")
.does(async () => {
await team.reload()
Expand Down Expand Up @@ -98,6 +112,19 @@ export const ResourceDemo = screen<AppServices>("Resource Demo", $ => {
await dedupeReport.load()
})

const loadKeyedTeamB = $.act("Load keyed team (team_b)")
.does(async ({ navigate }) => {
await keyedTeam.load({
route: { name: "demo", path: "/:id", params: { id: "team_b" } },
navigate,
})
})

const reloadKeyedTeam = $.act("Reload keyed team")
.does(async () => {
await keyedTeam.reload()
})

$.surface("main").contains(
reloadTeam,
invalidateTeam,
Expand All @@ -106,5 +133,7 @@ export const ResourceDemo = screen<AppServices>("Resource Demo", $ => {
loadAuditLog,
reloadCachedTeam,
loadDedupeReport,
loadKeyedTeamB,
reloadKeyedTeam,
)
})