Skip to content

Commit f81e12b

Browse files
Improve API reference navigation and changelog
1 parent 24b03af commit f81e12b

8 files changed

Lines changed: 518 additions & 23 deletions

File tree

.changeset/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
11
# Changesets
22

33
Run `pnpm changeset` for every user-facing change.
4+
5+
## Writing changelog entries
6+
7+
Changeset descriptions are published directly on the documentation website. Write them for library users rather than repository maintainers.
8+
9+
- Lead with the user-visible outcome and name the affected API when useful.
10+
- Keep the entry short and specific. One to three brief paragraphs is usually enough.
11+
- Use separate paragraphs when they make the outcome, motivation, or migration clearer.
12+
- Use inline code for API names, types, and short expressions.
13+
- Include at most one small fenced TypeScript example when an API is added or its usage changes meaningfully.
14+
- For a breaking change, state what changed and show the replacement or migration directly.
15+
- Omit commit hashes, pull request numbers, implementation history, test details, and internal refactoring unless they affect users.
16+
17+
A typical API entry looks like:
18+
19+
````md
20+
Add `Machine.example` for describing the user-visible behavior.
21+
22+
Use it when a short explanation would not make the new calling pattern clear:
23+
24+
```ts
25+
const value = Machine.example(input)
26+
```
27+
````
28+
29+
Prefer a shorter entry without an example for fixes and internal improvements:
30+
31+
```md
32+
Fix resumed machines so nested history is restored before raised events are processed.
33+
34+
This preserves the same observable transition order as a freshly started machine.
35+
```

.github/workflows/website.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ jobs:
2222
pages: read
2323
steps:
2424
- uses: actions/checkout@v7
25+
with:
26+
fetch-depth: 0
2527
- uses: pnpm/action-setup@v6
2628
- uses: actions/setup-node@v7
2729
with:

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,5 @@ Every package directly below `examples/` must have a `check` script and a commit
4949

5050
## Pull request conventions
5151

52-
- Add or update a changeset for changes under `src/` or changes to `package.json`.
52+
- Add or update a changeset for changes under `src/` or changes to `package.json`, following the changelog-writing guide in `.changeset/README.md`.
5353
- Fill in the pull request template, including the validation performed and the changeset decision.

CHANGELOG.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,31 @@
44

55
### Minor Changes
66

7-
- b192484: Allow state query helpers to inspect extracted snapshot subtrees, and add
8-
equality-aware `AtomMachine.selectSnapshot` and `selectSnapshotChild`
9-
combinators.
7+
- b192484: Allow `Machine.defineStates` query helpers to inspect an extracted
8+
snapshot subtree. Paths remain absolute and type-safe, but `get`,
9+
`getSnapshot`, and `matches` can now continue from a snapshot selected
10+
earlier instead of requiring the complete root snapshot.
11+
12+
```ts
13+
const readySnapshot = States.getSnapshot(snapshot, "Ready")
14+
15+
if (Option.isSome(readySnapshot)) {
16+
States.get(readySnapshot.value, "Ready.editor")
17+
States.matches(readySnapshot.value, "Ready.editor.Editing")
18+
}
19+
20+
const editorSnapshotAtom = AtomMachine.selectSnapshot(
21+
machineAtom,
22+
"Ready.editor"
23+
)
24+
```
25+
26+
Add equality-aware `AtomMachine.selectSnapshot` and
27+
`AtomMachine.selectSnapshotChild` combinators for reactive consumers that
28+
need the complete logical snapshot subtree instead of only its state value.
29+
The selected atoms retain nested topology, suppress structurally equal
30+
updates, and produce `Option.none()` while the path or invoked child is
31+
inactive.
1032

1133
## 0.6.1
1234

scripts/api-reference-site/api-reference-site.test.mjs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ import {
77
normalizeBasePath,
88
normalizeGitHubStars,
99
normalizeOrigin,
10+
parseChangelog,
11+
parseChangeset,
12+
renderChangelogPage,
1013
renderIndexPage,
1114
renderLayout,
1215
renderMarkdown,
16+
renderModulePage,
1317
renderRobots,
1418
renderSitemap,
1519
siteManifest,
@@ -30,6 +34,22 @@ test("renders documentation prose while escaping source HTML", () => {
3034
assert.doesNotMatch(renderMarkdown("<script>alert(1)</script>"), /<script>/)
3135
})
3236

37+
test("renders fenced code with highlighting and copy controls", () => {
38+
const html = renderMarkdown(`Call the new API:
39+
40+
\`\`\`ts
41+
const value = Machine.example("safe")
42+
\`\`\`
43+
44+
Then reuse \`value\`.`)
45+
assert.match(html, /<p>Call the new API:<\/p>/)
46+
assert.match(html, /class="code-block code-block--markdown"/)
47+
assert.match(html, /aria-label="Copy ts code"/)
48+
assert.match(html, /syntax-keyword">const<\/span>/)
49+
assert.match(html, /syntax-string">&quot;safe&quot;<\/span>/)
50+
assert.match(html, /<p>Then reuse <code>value<\/code>\.<\/p>/)
51+
})
52+
3353
test("assigns deterministic unique anchors to duplicate declarations", () => {
3454
const first = { name: "Machine" }
3555
const second = { name: "Machine" }
@@ -88,6 +108,92 @@ const site = {
88108
title: "Effect Machine"
89109
}
90110

111+
test("parses Changesets release entries and pending descriptions", () => {
112+
const releases = parseChangelog(`# Package
113+
114+
## 1.2.0
115+
116+
### Minor Changes
117+
118+
- abc1234: Add a typed \`make\` helper.
119+
120+
Preserve inference across multiple lines.
121+
122+
\`\`\`ts
123+
const machine = make()
124+
\`\`\`
125+
126+
### Patch Changes
127+
128+
- def5678: Fix escaped output.
129+
`, new Map([["1.2.0", "2026-08-13"]]))
130+
assert.deepEqual(releases, [{
131+
version: "1.2.0",
132+
date: "2026-08-13",
133+
groups: [{
134+
type: "minor",
135+
entries: [{
136+
description: "Add a typed `make` helper.\n\nPreserve inference across multiple lines.\n\n```ts\nconst machine = make()\n```"
137+
}]
138+
}, {
139+
type: "patch",
140+
entries: [{ description: "Fix escaped output." }]
141+
}]
142+
}])
143+
assert.deepEqual(parseChangeset(`---
144+
"@typeonce/effect-machine": minor
145+
---
146+
147+
Add snapshot selectors.
148+
`, "@typeonce/effect-machine"), { type: "minor", description: "Add snapshot selectors." })
149+
assert.equal(parseChangeset(`---
150+
"another-package": patch
151+
---
152+
153+
Ignore this package.
154+
`, "@typeonce/effect-machine"), undefined)
155+
})
156+
157+
test("renders a navigable changelog with release dates", () => {
158+
const html = renderChangelogPage({
159+
...site,
160+
changelog: [{
161+
version: "1.2.0",
162+
date: "2026-08-13",
163+
groups: [{ type: "minor", entries: [{ description: "Add a feature." }] }]
164+
}]
165+
})
166+
assert.match(html, /class="navigation-changelog is-current"/)
167+
assert.match(html, /id="release-1-2-0">v1\.2\.0/)
168+
assert.match(html, /datetime="2026-08-13">August 13, 2026/)
169+
assert.match(html, /<li><p>Add a feature\.<\/p><\/li>/)
170+
})
171+
172+
test("renders collapsible category navigation with declaration anchors", () => {
173+
const declaration = {
174+
name: "make",
175+
kind: "variable",
176+
description: "Creates a machine.",
177+
examples: [],
178+
see: []
179+
}
180+
const module = {
181+
api: {
182+
declarationCount: 1,
183+
description: "State machine APIs",
184+
groups: [{ category: "constructors", declarations: [declaration] }]
185+
},
186+
export: "./Machine",
187+
label: "Machine",
188+
route: "Machine"
189+
}
190+
const html = renderModulePage({ ...site, modules: [module] }, module)
191+
assert.match(html, /<details class="page-toc__group">/)
192+
assert.match(html, /<summary>[\s\S]*Constructors[\s\S]*<\/summary>/)
193+
assert.match(html, /<a href="#make">make<\/a>/)
194+
assert.match(html, /<h3 id="make">make<\/h3>/)
195+
})
196+
91197
test("renders canonical and social metadata without exposing the internal channel", () => {
92198
const html = renderLayout(site, {
93199
content: '<section class="reference-hero"><div class="eyebrow">API reference</div></section>',
@@ -162,6 +268,7 @@ test("generates manifest, robots, and sitemap URLs from the deployment base", ()
162268
assert.equal(manifest.icons[2].purpose, "maskable")
163269
assert.match(renderRobots(site), /Sitemap: https:\/\/docs\.example\.com\/docs\/sitemap\.xml/)
164270
assert.match(renderSitemap(site), /<loc>https:\/\/docs\.example\.com\/docs\/Machine\/<\/loc>/)
271+
assert.match(renderSitemap(site), /<loc>https:\/\/docs\.example\.com\/docs\/changelog\/<\/loc>/)
165272
})
166273

167274
test("accepts only pathless HTTPS production origins", () => {

scripts/api-reference-site/assets/client.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,26 @@ searchInput?.addEventListener("input", async () => {
8585
const search = await pagefind.search(query)
8686
const data = await Promise.all(search.results.slice(0, 12).map((result) => result.data()))
8787
if (sequence !== searchSequence) return
88-
searchStatus.textContent = `${search.results.length} result${search.results.length === 1 ? "" : "s"}`
89-
for (const result of data) {
88+
const seen = new Set()
89+
const results = data.flatMap((result) => {
90+
const sections = result.sub_results?.filter((section) => section.url.includes("#")) ?? []
91+
const candidates = sections.length === 0
92+
? [{ title: result.meta.title, url: result.url, excerpt: result.excerpt }]
93+
: sections.map((section) => ({ ...section, title: `${section.title} · ${result.meta.title}` }))
94+
return candidates.filter((candidate) => {
95+
if (seen.has(candidate.url)) return false
96+
seen.add(candidate.url)
97+
return true
98+
})
99+
}).slice(0, 12)
100+
searchStatus.textContent = `${results.length} result${results.length === 1 ? "" : "s"}`
101+
for (const result of results) {
90102
const item = document.createElement("li")
91103
const link = document.createElement("a")
92104
const title = document.createElement("strong")
93105
const excerpt = document.createElement("span")
94106
link.href = result.url
95-
title.textContent = result.meta.title
107+
title.textContent = result.title
96108
excerpt.innerHTML = result.excerpt
97109
link.append(title, excerpt)
98110
item.append(link)

0 commit comments

Comments
 (0)