Skip to content

Commit 40b9e1d

Browse files
committed
copy(web): hero comma-split display; §01 Setup subsections; §03 absorbs MCP; §04 signup retitle
- Hero display 'Point at the view, pass the agent a map.' sets one clause per line, broken at the comma (Hero.tsx splits on it); standfirst rewritten; OG card regenerated - §01 retitled Setup; the Xcode/SwiftUI asides gain titled h3 subsections with clearer, README-grounded steps; mount snippet indents install(); README link hugs its label (.stack > .link justify-self) - §02 Click/Frame/Anchor rewritten outcome-first - §03 'Hand it to your agent' absorbs 'Ask over MCP' as an h3 subsection (id=mcp keeps the old anchor); McpBridge.tsx and .section--mcp removed; Signup renumbered §04 - §04 retitled 'Want more? Sign up for more free tips and tools.' with the list scope as the lead; privacy line removed - web/scripts/hero-check.mjs probes the two-line setting; Appendix A copy deck in docs/epic-landing-page.md synced to shipped strings 124/124 tests, build, responsive-check at 320-1280 all pass.
1 parent 02419e0 commit 40b9e1d

16 files changed

Lines changed: 317 additions & 169 deletions

File tree

.beads/interactions.jsonl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,12 @@
3434
{"id":"int-c66f1419329a991eb86760fed37ef61c","kind":"field_change","created_at":"2026-08-21T02:46:20.05137Z","actor":"Angus Bezzina","issue_id":"annotkit-0i4.10","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Closed"}}
3535
{"id":"int-6f4b0709bef645bef1fa00d3f3dc82d5","kind":"field_change","created_at":"2026-08-21T02:46:27.044421Z","actor":"Angus Bezzina","issue_id":"annotkit-0i4.5","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Measured, not a defect. The backdrop is viewport-anchored, so its frames sit behind running text on every section at every width, by design and at 10-12% accent on a 1px stroke; there is no §01-scoped fix and the finding's coordinates came from an artificial 4000px-tall viewport. See notes."}}
3636
{"id":"int-ab2eaeffeb29c6c884d50256e60012f4","kind":"field_change","created_at":"2026-08-21T02:46:39.863426Z","actor":"Angus Bezzina","issue_id":"annotkit-0i4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"All ten children resolved: nine implemented in web/, one (0i4.5, backdrop) closed as measured-not-a-defect. Gates green: npm run build, npm test (124), npm run responsive (320-1280 PASS). Not committed — conservative profile."}}
37+
{"id":"int-084d41fc23909ac950e1c16d1da7e283","kind":"field_change","created_at":"2026-08-24T20:05:08.0965Z","actor":"Angus Bezzina","issue_id":"annotkit-dvl.2","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
38+
{"id":"int-d3b5dcb4bf335e317ed7f73a3532fbaa","kind":"field_change","created_at":"2026-08-24T20:13:56.94297Z","actor":"Angus Bezzina","issue_id":"annotkit-dvl.2","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Display 'Point at the view, pass the agent a map.' splits at the comma (Hero.tsx); standfirst rewritten with the four brief beats; one honest-copy fix vs brief ('nearest control' was wrong for the frame gesture — now 'locates what you pointed at in code'); OG template + og.png regenerated; hero-check.mjs verifies 2 lines, comma break, no overflow at 1280; responsive-check passes at 320/375/414/768/1280."}}
39+
{"id":"int-3e963a3bcb51c763f2a089f8d0db0e13","kind":"field_change","created_at":"2026-08-24T20:13:57.213153Z","actor":"Angus Bezzina","issue_id":"annotkit-dvl.5","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"§01 retitled 'Setup' with brief lead; xcode/note asides are now titled h3 subsections (Xcode/SwiftUI) via shared .section__subsec/.section__subhead classes; working-directory warning preserved per README §3; hero CTA now 'Set it up'; tests pass."}}
40+
{"id":"int-cbb8f2b9a273c26c4b8076f9f2e46bf8","kind":"field_change","created_at":"2026-08-24T20:13:57.521025Z","actor":"Angus Bezzina","issue_id":"annotkit-dvl.3","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"New lead 'Attach notes directly to the elements you want to tweak.'; Click/Frame/Anchor bodies recast active-voice with colon construction; claims traceable to README 120/149-154; markup tests pass."}}
41+
{"id":"int-f582a4691039882ac03f77ff71807af3","kind":"field_change","created_at":"2026-08-24T20:13:57.806034Z","actor":"Angus Bezzina","issue_id":"annotkit-dvl.1","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"§03 retitled 'Hand it to your agent' with new lead naming the three routes; MCP bridge folded in as h3 subsection (id=mcp preserves the old anchor); mcpBridge copy block and McpBridge.tsx deleted; signup renumbered §04; .section--mcp CSS removed; App.tsx, highlight test, TermList/Signup comments updated; all 124 tests + build pass."}}
42+
{"id":"int-f191409db097d0817ba39549a2fd36bd","kind":"field_change","created_at":"2026-08-24T20:13:58.075054Z","actor":"Angus Bezzina","issue_id":"annotkit-dvl.4","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"vitest 124/124; production build clean; responsive-check PASS at all five widths; hero-check PASS (2 lines, comma break, no overflow); Appendix A + section list + file tree in docs/epic-landing-page.md synced to shipped copy; honest-copy audit clean — no metrics, filler, or em dashes in copy strings."}}
43+
{"id":"int-3f6de21d1b8ada2ed206443e45e56ed7","kind":"field_change","created_at":"2026-08-24T20:13:58.693065Z","actor":"Angus Bezzina","issue_id":"annotkit-dvl","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"All five children closed; gates green."}}
44+
{"id":"int-35c0bb4f4e051d956020fc636c4dbfe2","kind":"field_change","created_at":"2026-08-24T20:23:06.275957Z","actor":"Angus Bezzina","issue_id":"annotkit-6r4","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"All six feedback items fixed and verified visually at 1280: (1) .stack > .link { justify-self: start } stops the grid stretch that made the §01 link's accent rule span the column; label now 'Setup guide'. (2) Annotation.install() indented inside #if DEBUG. (3) Xcode body rewritten as steps grounded in README (Add Package Dependencies…, paste URL, Up to Next Major from 0.8.0, add library, leave AnnotKitMCP unchecked). (4) SwiftUI body split into mount fact + notes-path fact with the scheme path spelled out (Edit Scheme ▸ Run ▸ Options ▸ Working Directory). (5) §02 Click/Frame/Anchor rewritten outcome-first in plain language. (6) Privacy line now ends 'Unsubscribe at any time.' Appendix A synced; 124/124 tests, build, responsive-check at five widths all pass."}}
45+
{"id":"int-2ed0d7c69581f002560be1723554b3ff","kind":"field_change","created_at":"2026-08-24T20:30:37.553143Z","actor":"Angus Bezzina","issue_id":"annotkit-l9n","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Landed and verified: copy.ts title/lead replaced verbatim per maintainer; privacy string + .signup__privacy rule + its <p> removed; Appendix A synced; 124/124 tests, build, responsive-check PASS; §04 verified visually at 1280."}}

docs/epic-landing-page.md

Lines changed: 65 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ locked in L4, derived only from facts in `README.md`, `DECISIONS.md`, and
100100
1. **Masthead nav** — wordmark, dateline row (`macOS 15 · iOS 17 · Swift 6 ·
101101
MIT`), links: GitHub ↗, Updates (in-page anchor).
102102
2. **Hero** — oversized serif statement + standfirst + typographic CTAs
103-
("Read the README ↗" primary; "Get release notes ↓" secondary anchor).
104-
Typography only — no mockup in the fold.
103+
("Set it up ↓" primary anchor; "Read the README ↗" secondary), the muted
104+
annotate loop beside the copy in a bare `<figure>`.
105105
3. **§01 Install** — the two snippets from the README verbatim
106106
(`Annotation.install()` under `#if DEBUG`; `.installAnnotation()` for
107107
SwiftUI).
@@ -111,19 +111,20 @@ locked in L4, derived only from facts in `README.md`, `DECISIONS.md`, and
111111
AnnotKitDemo (click → composer → note) in a bare `<figure>` — the first
112112
visual on the page, sitting just under the hero fold — with a still figure
113113
(pin + composer detail) supporting the copy.
114-
5. **§03 Hand it to the agent**`ANNOTKIT_NOTES.md` sample excerpt
114+
5. **§03 Hand it to your agent**`ANNOTKIT_NOTES.md` sample excerpt
115115
(backed by a still capture of the written notes file), the sink story
116-
(file / clipboard / JSON), the `process-agentation-notes` skill mention.
117-
6. **§04 Ask over MCP** — optional bridge: `swift run annotkit-mcp`, the two
118-
tools (`annotation_get_pending`, `annotation_resolve`).
119-
7. **§05 Get updates** — the signup form (email only), one honest line about
120-
cadence, privacy microcopy.
121-
8. **Footer** — wordmark + tagline + links (GitHub, Issues, License) + a
116+
(file / clipboard / JSON), and the optional MCP bridge as a subsection:
117+
`swift run annotkit-mcp`, the two tools (`annotation_get_pending`,
118+
`annotation_resolve`). (The bridge was top-level §04 until the
119+
2026-08-24 copy pass, epic annotkit-dvl.)
120+
6. **§04 Get updates** — the signup form (email only), one line naming the
121+
list's scope.
122+
7. **Footer** — wordmark + tagline + links (GitHub, Issues, License) + a
122123
colophon line (typefaces, license, clean-room note re: the
123124
`ANNOTKIT_NOTES.md` format).
124125

125-
Every section gets a numbered left-margin label (Specimen macrostructure
126-
signature): `§01 … §05`.
126+
Every section gets a numbered label above its heading (Specimen
127+
macrostructure signature): `§01 … §04`.
127128

128129
## 6. Design direction (Hallmark)
129130

@@ -246,7 +247,7 @@ web/
246247
base.css # reset, elements, utilities
247248
sections/
248249
Masthead.tsx Hero.tsx Install.tsx Annotate.tsx
249-
AgentNotes.tsx McpBridge.tsx Signup.tsx Footer.tsx
250+
AgentNotes.tsx Signup.tsx Footer.tsx
250251
components/
251252
CodeBlock.tsx # mono block + copy button (8 states)
252253
SubscribeForm.tsx # the state machine from §7
@@ -490,59 +491,67 @@ once the maintainer actions below are done.
490491

491492
---
492493

493-
## Appendix A — Copy deck (draft, locked in L4)
494+
## Appendix A — Copy deck (revised 2026-08-24, epic annotkit-dvl)
494495

495496
Voice: editorial — specific, verbs over adjectives, slightly literary. Every
496497
claim traceable to `README.md` / `DECISIONS.md` / `Package.swift`.
497-
498-
**Masthead dateline:** `Native annotation for AI coding agents · macOS 15 ·
499-
iOS 17 · Swift 6 · MIT`
500-
501-
**Hero display (pick one in L2):**
502-
- *"Point at the view. Hand the agent the map."*
503-
- *"Annotate the app. The agent finds the code."*
504-
505-
**Hero standfirst:** "AnnotKit mounts a floating toolbar in your dev build.
506-
Click a control — or draw a frame around a whole card — type a note, and it
507-
writes an agent-readable annotation: a stable selector, an element path, a
508-
screenshot, and your words. Your coding agent stops guessing."
509-
510-
**CTAs:** `Read the README ↗` (github.com/gpu-cli/annotkit) ·
511-
`Get release notes ↓`
512-
513-
**§01 Install — lead:** "Add the package. Mount the toolbar. Two lines,
514-
dev builds only."
515-
516-
**§02 Annotate — lead:** "A click means *this exact spot*: AnnotKit descends
517-
to the deepest actionable control. A drawn frame means *this whole thing*:
518-
the largest element the frame surrounds wins. One rule, both platforms — the
519-
selector anchors itself to the nearest `accessibilityIdentifier`, so it
520-
round-trips back to code."
521-
522-
**§03 Hand it to the agent — lead:** "Notes land in `ANNOTKIT_NOTES.md` —
523-
the format the `process-agentation-notes` skill consumes — or on the
524-
clipboard, or as JSON."
525-
526-
**§04 Ask over MCP — lead:** "Run the optional bridge and let the agent ask
527-
what's pending: `annotation_get_pending`, `annotation_resolve`."
528-
529-
**§05 Get updates:**
530-
- Heading: *"One email when it's worth reading."*
531-
- Body: "Releases, design notes, and the 1.0 call. No digest, no drip
532-
campaign — AnnotKit is 0.x and honest about it."
498+
`web/src/copy.ts` is the source of truth; this deck mirrors what shipped.
499+
500+
**Masthead dateline:** `A Swift package: native annotation for AI coding
501+
agents · macOS 15 · iOS 17 · Swift 6 · MIT`
502+
503+
**Hero display:** *"Point at the view, pass the agent a map."* — one
504+
sentence, two clauses, set one clause per line with the break at the comma
505+
(Hero.tsx splits on it).
506+
507+
**Hero standfirst:** "AnnotKit mounts a floating toolbar in your macOS or
508+
iOS dev build. Click an element, or draw a frame around a region, and type
509+
a note. The note carries a selector that locates what you pointed at in
510+
code. Your agent stops guessing what to change and where."
511+
512+
**CTAs:** `Set it up ↓` (#install) · `Read the README ↗`
513+
(github.com/gpu-cli/annotkit)
514+
515+
**§01 Setup — lead:** "Add the package, then mount the toolbar in your dev
516+
build's window." Titled subsections beside the two plates: **Xcode** ("In
517+
Xcode, choose File ▸ Add Package Dependencies… and paste the same URL. Pick
518+
Up to Next Major from `0.8.0`, add the AnnotKit library to your app target,
519+
and leave `AnnotKitMCP` unchecked…") and **SwiftUI** (`.installAnnotation()`
520+
on the root view, plus the working-directory warning: notes land in
521+
`ANNOTKIT_NOTES.md`, usually not the repo when Xcode launches the app — set
522+
the working directory in the scheme or pass `install()` a sink with an
523+
explicit path).
524+
525+
**§02 Annotate — lead:** "Attach notes directly to the elements you want to
526+
tweak." Three rules, outcome first: **Click** (the note binds to the control
527+
itself, even when the pointer lands on a label inside it), **Frame** (the
528+
note binds to the largest element the box contains; the drawn rectangle is
529+
stored on the note), **Anchor** (a target with no identifier anchors to the
530+
nearest `accessibilityIdentifier` above it).
531+
532+
**§03 Hand it to your agent — lead:** "Copy a note to the clipboard, write
533+
the set to `ANNOTKIT_NOTES.md`, or let the agent ask over MCP. However the
534+
notes travel, each one carries the selector that located it." The MCP
535+
bridge is a subsection of §03 (h3, `id="mcp"`), no longer §04: "Ask over
536+
MCP — run the optional bridge and let the agent ask what's pending."
537+
538+
**§04 Get updates:**
539+
- Heading: *"Want more? Sign up for more free tips and tools."*
540+
- Body: "One list for everything GPU CLI ships, AnnotKit included."
541+
- No separate privacy line under the form (removed 2026-08-24; the body
542+
carries the list scope).
533543
- Submit label: `Get release notes`
534544
- Loading: `Subscribing…`
535545
- Success (new): `You're on the list.`
536546
- Success (duplicate): `You're already on the list.`
537-
- Error (invalid): `That address doesn't parse — check for typos.`
538-
- Error (rate-limited): `Slow down — try again shortly.`
547+
- Error (invalid): `That address doesn't parse. Check for typos.`
548+
- Error (rate-limited): `Slow down. Try again shortly.`
539549
- Error (upstream): `Something broke on our side. Try again in a minute.`
540-
- Privacy line: `Your address goes to Resend and nowhere else. Every email
541-
carries an unsubscribe link.`
542550

543-
**Footer colophon:** "MIT licensed. Reimplements the `ANNOTKIT_NOTES.md`
544-
format clean-room; contains no Agentation source. Set in Fraunces,
545-
Newsreader & IBM Plex Mono."
551+
**Footer tagline:** "Point at a view, leave a note your agent can act on. /
552+
Pre-1.0. The API may still shift."
553+
554+
**Footer colophon:** "Set in Fraunces, Newsreader & IBM Plex Mono."
546555

547556
## Appendix B — Resend setup runbook (maintainer)
548557

web/public/og.png

-511 Bytes
Loading

web/scripts/hero-check.mjs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* One-off probe for the 2026-08-24 hero copy pass (annotkit-dvl.2): the
3+
* display is one sentence broken at its comma, one clause per line from the
4+
* span breakpoint (60rem) up. Verifies at desktop width that
5+
*
6+
* · the display renders as exactly two lines,
7+
* · line 1 ends with the comma and line 2 starts the second clause,
8+
* · neither no-wrap span overflows the page or its own box.
9+
*
10+
* Serves ./dist itself (run `npm run build` first). Exits non-zero on
11+
* failure. Delete when the next copy pass needs different facts.
12+
*/
13+
14+
import { createServer } from "node:http";
15+
import { createReadStream, existsSync, statSync } from "node:fs";
16+
import { extname, join, resolve } from "node:path";
17+
import { connect, launch, openPage, sleep } from "./lib/chrome.mjs";
18+
19+
const MIME = {
20+
".html": "text/html; charset=utf-8",
21+
".js": "text/javascript",
22+
".css": "text/css",
23+
".svg": "image/svg+xml",
24+
".png": "image/png",
25+
".woff2": "font/woff2",
26+
".json": "application/json",
27+
".webm": "video/webm",
28+
".avif": "video/avif",
29+
".webp": "image/webp",
30+
};
31+
32+
const root = resolve("dist");
33+
if (!existsSync(root)) {
34+
console.error("hero-check: dist/ is missing — run `npm run build` first.");
35+
process.exit(1);
36+
}
37+
const server = createServer((request, response) => {
38+
const path = decodeURIComponent(new URL(request.url, "http://x").pathname);
39+
let file = join(root, path);
40+
if (!file.startsWith(root)) return response.writeHead(403).end();
41+
if (!existsSync(file) || statSync(file).isDirectory()) file = join(root, "index.html");
42+
response.writeHead(200, { "Content-Type": MIME[extname(file)] ?? "application/octet-stream" });
43+
createReadStream(file).pipe(response);
44+
});
45+
await new Promise((done) => server.listen(0, "127.0.0.1", done));
46+
const url = `http://127.0.0.1:${server.address().port}/`;
47+
48+
const PROBE = `(() => {
49+
const display = document.querySelector(".hero__display");
50+
const spans = [...display.querySelectorAll(".hero__sentence")];
51+
const lineRects = (node) => {
52+
const range = document.createRange();
53+
range.selectNodeContents(node);
54+
return [...range.getClientRects()];
55+
};
56+
const tops = [...new Set(lineRects(display).map((r) => Math.round(r.top)))];
57+
return JSON.stringify({
58+
lines: tops.length,
59+
clauses: spans.map((s) => s.textContent),
60+
spanLines: spans.map((s) => lineRects(s).length),
61+
displayWidth: Math.round(display.getBoundingClientRect().width),
62+
spanOverflow: spans.map((s) => Math.round(s.scrollWidth - s.clientWidth)),
63+
});
64+
})()`;
65+
66+
const { child, endpoint } = await launch();
67+
const cdp = connect(endpoint);
68+
await cdp.ready;
69+
const sessionId = await openPage(cdp);
70+
71+
await cdp.send(
72+
"Emulation.setDeviceMetricsOverride",
73+
{ width: 1280, height: 900, deviceScaleFactor: 1, mobile: false },
74+
sessionId,
75+
);
76+
await cdp.send("Page.navigate", { url }, sessionId);
77+
await sleep(1200);
78+
79+
const { result } = await cdp.send(
80+
"Runtime.evaluate",
81+
{ expression: PROBE, returnByValue: true },
82+
sessionId,
83+
);
84+
const report = JSON.parse(result.value);
85+
86+
cdp.close();
87+
child.kill();
88+
server.close();
89+
90+
console.log("\nhero-check · 1280px\n");
91+
console.log(` lines in display : ${report.lines}`);
92+
console.log(` clauses : ${report.clauses.join(" ⏎ ")}`);
93+
console.log(` lines per clause : ${report.spanLines.join(", ")}`);
94+
console.log(` display width : ${report.displayWidth}px`);
95+
console.log(` span overflow : ${report.spanOverflow.join(", ")}px\n`);
96+
97+
const failures = [];
98+
if (report.lines !== 2) failures.push(`display sets on ${report.lines} lines, expected 2`);
99+
if (!report.clauses[0]?.endsWith(",")) failures.push("line 1 does not end at the comma");
100+
if (report.spanLines.some((n) => n !== 1)) failures.push("a no-wrap clause wrapped inside itself");
101+
if (report.spanOverflow.some((n) => n > 0)) failures.push("a clause overflows its span box");
102+
103+
for (const failure of failures) console.log(` FAIL ${failure}`);
104+
console.log(failures.length === 0 ? " PASS\n" : "");
105+
process.exit(failures.length > 0 ? 1 : 0);

web/scripts/og-template.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
<body>
8181
<div class="og">
8282
<p class="og__dateline">macOS 15 · iOS 17 · Swift 6 · MIT</p>
83-
<h1 class="og__display">Point at the view. Hand the agent the map.</h1>
83+
<h1 class="og__display">Point at the view, pass the agent a map.</h1>
8484
<div class="og__foot">
8585
<p class="og__wordmark">AnnotKit</p>
8686
<p class="og__rule">annotkit.gpu-cli.sh</p>

web/src/App.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { Hero } from "./sections/Hero";
55
import { Install } from "./sections/Install";
66
import { Annotate } from "./sections/Annotate";
77
import { AgentNotes } from "./sections/AgentNotes";
8-
import { McpBridge } from "./sections/McpBridge";
98
import { Signup } from "./sections/Signup";
109
import { Footer } from "./sections/Footer";
1110
import { Backdrop } from "./components/Backdrop";
@@ -48,7 +47,6 @@ export function App() {
4847
<Install />
4948
<Annotate />
5049
<AgentNotes />
51-
<McpBridge />
5250
<Signup />
5351
</main>
5452
<Footer />

0 commit comments

Comments
 (0)