Skip to content

Commit ae62868

Browse files
angusbezzinaclaude
andcommitted
docs: copy matches what the code emits; README walks through adding AnnotKit to an app
- Drop the screenshot claim: the toolbar never exports one. - NotesFileSink rewrites, it does not append; iOS walks the view tree. - Sample note is verbatim formatter output for a seeded button. - Position AnnotKit as a Swift package in dateline, hero and meta. - README: step-by-step install for Xcode and Package.swift, mount points per platform, the working-directory gotcha, platform-coverage note. - Masthead test follows site.repoIsPublic instead of hard-coding the label. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1847b77 commit ae62868

4 files changed

Lines changed: 145 additions & 29 deletions

File tree

README.md

Lines changed: 123 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,82 @@
22

33
Site: <https://annotkit.gpu-cli.sh>
44

5-
Native in-app annotation for AI coding agents. Click a UI element in your own
6-
macOS or iOS appor drag a frame around itattach a note, and emit an
7-
agent-readable, code-locating annotation. The native analogue of the web
8-
Agentation tool.
5+
A Swift package for native in-app annotation, built for AI coding agents. Click
6+
a UI element in your own macOS or iOS app, or drag a frame around it, attach a
7+
note, and emit an agent-readable, code-locating annotation. The native analogue
8+
of the web Agentation tool.
99

10-
The gesture becomes a stable selector, an element path, a screenshot, and your
11-
comment, so an AI coding agent can locate the exact view instead of guessing
12-
from a verbal description.
10+
The gesture becomes a stable selector, an element path, the element's role and
11+
text, and your comment, so an AI coding agent can locate the exact view instead
12+
of guessing from a verbal description.
1313

14-
## Install
14+
**Requirements:** Swift 6.1 toolchain (Xcode 16.3 or later), macOS 15+ or
15+
iOS 17+. The package is Swift 6 language mode with strict concurrency. It has no
16+
dependencies.
1517

16-
Add the package, then mount the toolbar (dev builds only).
18+
## Add it to an existing Swift app
19+
20+
### 1. Add the package
21+
22+
**Xcode:** File ▸ Add Package Dependencies…, paste
23+
`https://github.com/gpu-cli/annotkit`, choose the version rule **Up to Next
24+
Major** from `0.8.0`, and add the `AnnotKit` library to your app target. Leave
25+
`AnnotKitMCP` unchecked unless you want the [agent bridge](#agent-bridge-optional);
26+
the toolbar does not need it.
27+
28+
**Package.swift:**
29+
30+
```swift
31+
dependencies: [
32+
.package(url: "https://github.com/gpu-cli/annotkit", from: "0.8.0")
33+
],
34+
targets: [
35+
.executableTarget(
36+
name: "MyApp",
37+
dependencies: [.product(name: "AnnotKit", package: "annotkit")]
38+
)
39+
]
40+
```
41+
42+
### 2. Mount the toolbar once the app has a window
43+
44+
**AppKit (macOS):** in `applicationDidFinishLaunching`, after your main window
45+
is up:
1746

1847
```swift
1948
import AnnotKit
2049

21-
#if DEBUG
22-
Annotation.install() // floating toolbar; click a view, type a note
23-
#endif
50+
func applicationDidFinishLaunching(_ notification: Notification) {
51+
// ... make and show your window ...
52+
#if DEBUG
53+
Annotation.install() // floating toolbar; click a view, type a note
54+
#endif
55+
}
2456
```
2557

26-
SwiftUI iOS hosts can attach it to a view instead:
58+
If several windows are open at launch, name the one to annotate instead of
59+
letting AnnotKit pick: `Annotation.install(on: window)`.
60+
61+
**SwiftUI (macOS):** call it from the root view's `.onAppear` or from an
62+
`NSApplicationDelegateAdaptor`:
63+
64+
```swift
65+
@main
66+
struct MyApp: App {
67+
var body: some Scene {
68+
WindowGroup {
69+
ContentView()
70+
.onAppear {
71+
#if DEBUG
72+
Annotation.install()
73+
#endif
74+
}
75+
}
76+
}
77+
}
78+
```
79+
80+
**SwiftUI (iOS):** attach it to the root view:
2781

2882
```swift
2983
ContentView()
@@ -32,13 +86,46 @@ ContentView()
3286
#endif
3387
```
3488

35-
`install()` defaults to the platform AX/view source and writes notes to
36-
`ANNOTKIT_NOTES.md`. Pass a different sink to override:
89+
UIKit iOS hosts call `Annotation.install()` from the scene delegate once the
90+
window is key.
91+
92+
`install()` is a no-op in release builds unless the `ANNOTKIT_ENABLE`
93+
environment variable is set, and a no-op in debug builds when `ANNOTKIT_DISABLE`
94+
is set, so the `#if DEBUG` above is belt and braces: the toolbar never appears
95+
in a normal shipping build either way.
96+
97+
### 3. Decide where the notes go
98+
99+
By default notes are written to `ANNOTKIT_NOTES.md` in the process's **current
100+
working directory**. For an app launched from Xcode that is usually *not* your
101+
project folder. Either set the working directory in the scheme
102+
(Edit Scheme ▸ Run ▸ Options ▸ Working Directory), point the path at the repo
103+
through the scheme's environment variables (`ANNOTKIT_NOTES_MD=/path/to/repo/ANNOTKIT_NOTES.md`),
104+
or pass a sink with an explicit path:
105+
106+
```swift
107+
Annotation.install(sink: NotesFileSink(path: "/path/to/repo/ANNOTKIT_NOTES.md"))
108+
```
109+
110+
Other sinks: `ClipboardSink(format: .markdown | .json)` copies instead of writing,
111+
`JSONFileSink` writes the JSON store the MCP bridge reads, and `MultiSink` fans
112+
out to several.
37113

38114
```swift
39115
Annotation.install(sink: ClipboardSink(format: .json))
40116
```
41117

118+
### 4. Seed identifiers where it matters
119+
120+
Selectors anchor to the nearest `accessibilityIdentifier`
121+
(`#Settings.Models >> @Save`). Views with no identifier anywhere above them
122+
still get a resolvable selector, but it is built from roles and indices and is
123+
fragile across layout changes. Put `.accessibilityIdentifier("Settings.Models")`
124+
on the components you expect to annotate and the notes will point an agent
125+
straight at that code.
126+
127+
### Optional: world context
128+
42129
Register a **world-context provider** and every captured note snapshots it, so an
43130
agent can put back the world the note was made in instead of guessing:
44131

@@ -48,6 +135,13 @@ Annotation.install(
48135
)
49136
```
50137

138+
### Then use it
139+
140+
Press **Annotate** on the floating pill, click a control (or switch to the frame
141+
tool and drag around a card), type a note, and save. Export writes every pending
142+
note through the sink. Run `swift run AnnotKitDemo` in this repo to try the whole
143+
loop in a sample app first.
144+
51145
## How it works
52146

53147
- **macOS** queries the app's own accessibility tree (the only strategy that
@@ -66,7 +160,18 @@ Annotation.install(
66160
tightest element enclosing it, and the drawn rect rides along on the note.
67161
Saves hunting for the one pixel that hit-tests to a composite component.
68162
- Notes are written in the `ANNOTKIT_NOTES.md` format that the
69-
`process-agentation-notes` skill consumes, or copied to the clipboard.
163+
`process-agentation-notes` skill consumes, or copied to the clipboard. The
164+
file is rewritten with the full set of notes on every export, so it never
165+
holds duplicates or stale entries. A screenshot of the element is captured
166+
in memory on request (`AnnotationSession.screenshotSelected()`) but is not
167+
part of the exported note; the markdown and JSON carry only pixel
168+
dimensions.
169+
- **Platform coverage is uneven.** The selector engine, target rules, session
170+
and sinks are shared and unit-tested on both platforms, and CI cross-compiles
171+
for the iOS simulator. The live macOS path is additionally exercised by
172+
on-device probes and an end-to-end test; the iOS adapter's live behaviour is
173+
covered by unit tests over the pure rules only. See `PARITY.md` for the
174+
per-capability matrix.
70175

71176
## Agent bridge (optional)
72177

@@ -115,7 +220,8 @@ swift run AnnotKitDemo # interactive demo app (overlay mounted; good for re
115220
swift run AnnotKitEnvProbe # one env-configured embedding host, driven in code
116221
```
117222

118-
Swift 6 (strict concurrency), macOS 15+, iOS 17+.
223+
Swift 6 (strict concurrency), macOS 15+, iOS 17+. Cross-compile for the iOS
224+
simulator with the command in `CONTRIBUTING.md`.
119225

120226
## License
121227

web/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<title>AnnotKit: native annotation for AI coding agents</title>
88
<meta
99
name="description"
10-
content="Click a view in your own macOS or iOS app, type a note, and hand your coding agent a selector it can trace back to code. MIT, Swift 6, pre-1.0."
10+
content="A Swift package for macOS and iOS apps. Click a view in your own dev build, type a note, and hand your coding agent a selector it can trace back to code. MIT, Swift 6, pre-1.0."
1111
/>
1212
<link rel="canonical" href="%SITE_URL%/" />
1313
<meta name="robots" content="index, follow" />
@@ -52,7 +52,7 @@
5252
<meta property="og:title" content="AnnotKit: native annotation for AI coding agents" />
5353
<meta
5454
property="og:description"
55-
content="Click a view in your own macOS or iOS app, type a note, and hand your coding agent a selector it can trace back to code."
55+
content="A Swift package for macOS and iOS apps. Click a view in your own dev build, type a note, and hand your coding agent a selector it can trace back to code."
5656
/>
5757
<meta property="og:url" content="%SITE_URL%/" />
5858
<meta property="og:image" content="%SITE_URL%/og.png" />

web/src/copy.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
export const meta = {
2121
title: "AnnotKit: native annotation for AI coding agents",
2222
description:
23-
"Click a view in your own macOS or iOS app, type a note, and hand your coding agent a selector it can trace back to code. MIT, Swift 6, pre-1.0.",
23+
"A Swift package for macOS and iOS apps. Click a view in your own dev build, type a note, and hand your coding agent a selector it can trace back to code. MIT, Swift 6, pre-1.0.",
2424
} as const;
2525

2626
export const masthead = {
27-
dateline: "Native annotation for AI coding agents",
27+
dateline: "A Swift package: native annotation for AI coding agents",
2828
facts: ["macOS 15", "iOS 17", "Swift 6", "MIT"],
2929
wordmark: "AnnotKit",
3030
/**
@@ -43,15 +43,15 @@ export const masthead = {
4343
export const hero = {
4444
display: "Point at the view. Hand the agent the map.",
4545
standfirst:
46-
"AnnotKit mounts a floating toolbar in your dev build. Click a control, or draw a frame around a whole card, then type a note. It writes an agent-readable annotation: a stable selector, an element path, a screenshot, and your words. Your coding agent stops guessing.",
46+
"AnnotKit is a Swift package that mounts a floating toolbar in the dev build of your macOS or iOS app. Click a control, or draw a frame around a whole card, then type a note. It writes an agent-readable annotation: a stable selector, an element path, the element’s role and text, and your words. Your coding agent stops guessing.",
4747
ctaPrimary: "Read the README",
4848
ctaSecondary: "Get release notes",
4949
} as const;
5050

5151
export const install = {
5252
number: "§01",
5353
title: "Install",
54-
lead: "Add the package. Mount the toolbar. Two lines, dev builds only.",
54+
lead: "Add the Swift package. Mount the toolbar. Two lines, dev builds only.",
5555
appKit: {
5656
caption: "AppKit / SwiftUI on macOS",
5757
code: `import AnnotKit
@@ -68,7 +68,7 @@ Annotation.install() // floating toolbar; click a view, type a note
6868
#endif`,
6969
},
7070
sinkNote:
71-
"`install()` defaults to the platform accessibility source and writes notes to `ANNOTKIT_NOTES.md`. Pass a different sink to override:",
71+
"`install()` reads the accessibility tree on macOS and walks the view hierarchy on iOS, and writes notes to `ANNOTKIT_NOTES.md` in the working directory. Pass a different sink to override:",
7272
sink: {
7373
caption: "Override the sink",
7474
code: `Annotation.install(sink: ClipboardSink(format: .json))`,
@@ -99,12 +99,18 @@ export const agentNotes = {
9999
number: "§03",
100100
title: "Hand it to the agent",
101101
lead: "Notes land in `ANNOTKIT_NOTES.md`, one markdown block per note, headed by the selector that located it. They can go to the clipboard instead, or out as JSON.",
102+
/**
103+
* Verbatim `AnnotationFormatter` output for a SEEDED button. Seeded on
104+
* purpose: an unseeded target adds a 150-character `**Unseeded**` hint line,
105+
* and the plate scrolls rather than wraps, so that one line would push the
106+
* whole sample off the page.
107+
*/
102108
sample: {
103109
caption: "The block `AnnotationFormatter` writes, one per note",
104-
code: `## [n-3f9c] /settings - #Settings.Profile >> @Save
110+
code: `## [3f9c1a] Settings - #Settings.Profile.Save
105111
**Timestamp**: 2026-08-19T09:14:02Z
106-
**Element Path**: Window > Settings > Profile > Save
107-
**Component**: #Settings.Profile
112+
**Element Path**: AXWindow[0] > #Settings.Profile > #Settings.Profile.Save
113+
**Component**: #Settings.Profile.Save
108114
**Element**: AXButton "Save"
109115
110116
This button should stay disabled until the name field
@@ -113,7 +119,7 @@ is non-empty. Right now it saves an empty profile.`,
113119
sinks: [
114120
{
115121
term: "`NotesFileSink`",
116-
body: "Appends to `ANNOTKIT_NOTES.md` in the working directory. The default.",
122+
body: "Rewrites `ANNOTKIT_NOTES.md` with the full set of notes on every save. The default.",
117123
},
118124
{
119125
term: "`ClipboardSink`",

web/tests/markup.dom.test.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { cleanup, render, screen } from "@testing-library/react";
33
import { App } from "../src/App";
44
import { withCode } from "../src/markup";
55
import { agentNotes, codeBlock, install, masthead, theme } from "../src/copy";
6+
import { site } from "../src/config";
67

78
/**
89
* The backtick convention and what it renders to.
@@ -125,7 +126,10 @@ describe("icons", () => {
125126
const nav = screen.getByRole("navigation", { name: /primary/i });
126127

127128
expect(screen.getByRole("link", { name: masthead.links.updates })).toBeTruthy();
128-
expect(nav.textContent).toBe(`${masthead.links.github}${masthead.links.updates}`);
129+
// The GitHub label follows the repo's visibility flag; the icon must stay
130+
// out of the text in either state.
131+
const github = site.repoIsPublic ? masthead.links.github : masthead.links.githubUnavailable;
132+
expect(nav.textContent).toBe(`${github}${masthead.links.updates}`);
129133
});
130134
});
131135

0 commit comments

Comments
 (0)