Skip to content

Commit 575e2f9

Browse files
committed
htmlnote v0.1.0 — visual review for AI-generated HTML
When Claude Code writes or edits an HTML file, htmlnote opens a browser tab so you can click any element, leave notes, and copy them back into chat. Loop until satisfied. The flow: - Claude writes/edits HTML during a turn — no popups, no blocking - Claude finishes responding → htmlnote tab opens (only if HTML was actually written this turn) - You annotate at your own pace, click "Copy for chat" - Paste into Claude Code chat — Claude reads + applies — next turn Stop hook opens a fresh tab with the new state Build: - Bun-compiled single-file CLI, ships per-platform binaries (macOS arm64/x64, Linux x64/arm64) - React + Vite SPA, sandboxed iframe (allow-scripts only) for the rendered HTML, postMessage bridge for click/text-select/wheel - Long-running local daemon serves a multi-session UI; sessions persist on disk under ~/.htmlnote/ Plugin hooks: - PostToolUse on Write|Edit|MultiEdit: records the file in a per-turn set, exits fast - Stop: if the turn touched any HTML, opens the daemon URL; exits - UserPromptSubmit: clears the per-turn set (turn boundary)
0 parents  commit 575e2f9

53 files changed

Lines changed: 7202 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude-plugin/marketplace.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
3+
"name": "htmlnote",
4+
"owner": {
5+
"name": "DavelRad"
6+
},
7+
"metadata": {
8+
"description": "Visual review for AI-generated HTML. Click elements, leave notes, copy back into chat.",
9+
"version": "0.1.0"
10+
},
11+
"plugins": [
12+
{
13+
"name": "htmlnote",
14+
"source": "./",
15+
"description": "Visual review for AI-generated HTML. When Claude Code writes or edits an HTML file, a browser tab opens so you can click elements, leave notes, and copy them back into chat.",
16+
"version": "0.1.0",
17+
"author": {
18+
"name": "DavelRad"
19+
},
20+
"keywords": [
21+
"html",
22+
"review",
23+
"annotation",
24+
"claude-code",
25+
"plugin"
26+
],
27+
"category": "development"
28+
}
29+
]
30+
}

.claude-plugin/plugin.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "htmlnote",
3+
"description": "Visual review for AI-generated HTML. When Claude Code writes or edits an HTML file, a browser tab opens so you can click elements, leave notes, and copy them back into chat.",
4+
"version": "0.1.0",
5+
"license": "MIT",
6+
"author": {
7+
"name": "DavelRad"
8+
},
9+
"repository": "https://github.com/DavelRad/htmlnote",
10+
"keywords": ["html", "review", "annotation", "claude-code", "plugin"]
11+
}

.github/workflows/ci.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
check:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-node@v4
14+
with:
15+
node-version: "20"
16+
cache: "npm"
17+
# Lockfile pins macOS Rollup native binding; Linux runners need their
18+
# own. Cheapest cross-platform fix is to regenerate it. Cost: no install
19+
# caching speedup. Worth it for a side project CI.
20+
# https://github.com/npm/cli/issues/4828
21+
- run: rm -rf node_modules package-lock.json
22+
- run: npm install --no-audit --no-fund
23+
- run: npm run typecheck
24+
- run: npm test
25+
- run: npm run build

.github/workflows/release.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: release
2+
3+
# Fires on tag push (v*). Builds per-platform binaries with bun --compile,
4+
# uploads them + sha256 checksums to a fresh GitHub Release.
5+
on:
6+
push:
7+
tags:
8+
- "v*"
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
build-release:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: "20"
22+
cache: "npm"
23+
24+
- uses: oven-sh/setup-bun@v2
25+
with:
26+
bun-version: latest
27+
28+
# Lockfile is generated on macOS (darwin-arm64) and pins Rollup's native
29+
# binding for that arch only; npm doesn't reconcile that on Linux runners.
30+
# Rollup itself recommends deleting both before re-installing. We trade
31+
# cached-install speed for cross-platform reliability — fine for a tag
32+
# release that runs maybe twice a month.
33+
# https://github.com/npm/cli/issues/4828
34+
- name: Install deps (regenerate lockfile for linux runner)
35+
run: |
36+
rm -rf node_modules package-lock.json
37+
npm install --no-audit --no-fund
38+
39+
- name: Build SPA + esbuild CLI bundle
40+
run: npm run build
41+
42+
- name: Compile binaries for all targets
43+
run: |
44+
mkdir -p release
45+
for tgt in darwin-arm64 darwin-x64 linux-x64 linux-arm64; do
46+
echo "::group::bun build --compile --target=bun-$tgt"
47+
bun build dist/cli.mjs --compile --target=bun-$tgt --outfile=release/htmlnote-$tgt
48+
echo "::endgroup::"
49+
done
50+
51+
- name: SHA256 checksums
52+
run: |
53+
cd release
54+
for f in htmlnote-*; do
55+
sha256sum "$f" > "$f.sha256"
56+
done
57+
ls -lh
58+
59+
- name: Create GitHub Release
60+
env:
61+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62+
run: |
63+
gh release create "$GITHUB_REF_NAME" \
64+
--title "$GITHUB_REF_NAME" \
65+
--generate-notes \
66+
release/*

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
node_modules/
2+
dist/
3+
.DS_Store
4+
*.log
5+
*.tsbuildinfo
6+
.env
7+
.env.local
8+
.vscode/
9+
.idea/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Davel Radindra
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# htmlnote
2+
3+
Visual review for AI-generated HTML. When Claude Code writes or edits an HTML file, htmlnote opens a browser tab that lets you click any element, leave notes, and copy them back into chat so Claude can apply your changes.
4+
5+
No interruption mid-turn. No "Send to agent" hidden in a button somewhere. You see the final result, you mark it up, you paste back. Honest about how Claude Code plugins actually work.
6+
7+
## How it works
8+
9+
```
10+
You: "make me a landing page"
11+
12+
Claude writes/edits HTML (no popups during the turn)
13+
14+
Claude finishes responding
15+
16+
↳ htmlnote tab opens in your browser ← only if HTML was touched
17+
18+
You click elements / drag-select text / leave notes
19+
20+
Click "Copy for chat" → your notes are on the clipboard
21+
22+
Paste into Claude Code chat → submit
23+
24+
Claude applies your notes → loop until you're happy
25+
```
26+
27+
If your prompt isn't about HTML (asking a question, editing Python, etc.) — no tab. htmlnote only fires when Claude actually wrote/edited a `.html` or `.htm` file in the turn that just ended.
28+
29+
## Install
30+
31+
Two pieces — the Claude Code plugin and the binary.
32+
33+
**1. Register the plugin** — inside Claude Code, run each line as a **separate** prompt (don't paste both at once):
34+
35+
```
36+
/plugin marketplace add https://github.com/DavelRad/htmlnote.git
37+
```
38+
39+
```
40+
/plugin install htmlnote
41+
```
42+
43+
**2. Install the binary** — in your terminal:
44+
45+
```
46+
curl -fsSL https://raw.githubusercontent.com/DavelRad/htmlnote/main/install.sh | bash
47+
```
48+
49+
Downloads a precompiled binary for your platform (macOS arm64/x64, Linux x64/arm64), verifies the SHA256, and installs to `~/.local/bin/htmlnote`. Make sure that directory is in your `PATH`.
50+
51+
After both steps, **restart Claude Code** (quit and relaunch — not just a new conversation) so it loads the new hook configuration.
52+
53+
## Using it
54+
55+
You don't run any command. After Claude writes or edits HTML during a turn, a tab opens automatically when the turn ends. Click any element to annotate it; drag-select text to highlight a specific phrase; press `N` for a general note not tied to anything.
56+
57+
When you're done annotating, click **Copy for chat**. Your notes are formatted as markdown and put on your clipboard. Paste into Claude Code chat and submit — Claude reads them like normal input and applies the changes. If the file gets touched again during the new turn, a fresh tab opens with the updated state. Loop until you're satisfied.
58+
59+
### Keyboard shortcuts
60+
61+
| Key | What |
62+
|---|---|
63+
| `E` | Toggle annotate mode (click elements vs. just look) |
64+
| `Click` | Drop a pin on an element |
65+
| `Drag` | Highlight text and annotate it |
66+
| `N` | Add a general note (no element target) |
67+
| `J` / `K` | Walk through annotations |
68+
| `Del` / `Backspace` | Delete the selected annotation |
69+
| `⌘+↵` | Copy notes for chat |
70+
| `⌘+K` | Command palette |
71+
| `?` | Show this list |
72+
| `Esc` | Cancel composer / exit annotate mode |
73+
74+
### Manual review
75+
76+
Want to review an HTML file outside of Claude Code's flow?
77+
78+
```
79+
htmlnote path/to/file.html
80+
```
81+
82+
Opens the same review UI in your browser. Same hotkeys, same Copy button.
83+
84+
## Updating
85+
86+
```
87+
htmlnote --update
88+
```
89+
90+
Self-updates the binary to the latest release. Verifies SHA256 before replacing itself. Won't downgrade.
91+
92+
For the plugin side (if a release changes hook configuration), refresh inside Claude Code:
93+
94+
```
95+
/plugin marketplace update DavelRad/htmlnote
96+
/plugin update htmlnote
97+
```
98+
99+
Then restart Claude Code so the new hooks load.
100+
101+
## Troubleshooting
102+
103+
**The tab doesn't pop up after Claude finishes editing HTML.**
104+
Claude Code caches the plugin's hook config at app startup. After installing or updating, fully **quit and relaunch Claude Code** (not just a new conversation). Then try again.
105+
106+
**Daemon isn't responding.**
107+
htmlnote runs a small background process (the "daemon") that serves the review UI. Restart it:
108+
109+
```
110+
htmlnote --stop
111+
htmlnote --daemon
112+
```
113+
114+
Your saved annotations live on disk and survive the restart.
115+
116+
**Reset everything.**
117+
118+
```
119+
htmlnote --stop
120+
rm -rf ~/.htmlnote
121+
```
122+
123+
Wipes all session history and the daemon's discovery file. Next HTML edit will start fresh.
124+
125+
**Dev install (different platform, or contributing).**
126+
If your platform doesn't have a prebuilt binary, clone and install from source:
127+
128+
```
129+
git clone https://github.com/DavelRad/htmlnote
130+
cd htmlnote
131+
bash install.sh --local .
132+
```
133+
134+
Requires Node 18+ and npm. Builds and symlinks `~/.local/bin/htmlnote` to your checkout.
135+
136+
## What it does NOT do
137+
138+
- Doesn't intercept non-HTML edits. Only `.html` and `.htm` files trigger the popup.
139+
- Doesn't send anything to the network. The daemon binds to `127.0.0.1` only; no telemetry; no cloud.
140+
- Doesn't auto-submit feedback. You explicitly hit Copy and paste — htmlnote never types into Claude on your behalf.
141+
- Doesn't loosen the iframe sandbox. AI-generated HTML runs `sandbox="allow-scripts"` with no `allow-same-origin`. Your host page, cookies, and DOM are safe from whatever Claude wrote.
142+
143+
## License
144+
145+
[MIT](./LICENSE)

bin/htmlnote.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env node
2+
import { fileURLToPath } from "node:url";
3+
import { dirname, resolve } from "node:path";
4+
import { existsSync } from "node:fs";
5+
6+
const here = dirname(fileURLToPath(import.meta.url));
7+
const cli = resolve(here, "..", "dist", "cli.mjs");
8+
9+
if (!existsSync(cli)) {
10+
console.error(
11+
"[htmlnote] dist/cli.mjs not found. Run `npm run build` first.",
12+
);
13+
process.exit(1);
14+
}
15+
16+
await import(cli);

commands/htmlnote.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
description: Open visual review on an HTML file
3+
allowed-tools: Bash(htmlnote:*)
4+
disable-model-invocation: true
5+
---
6+
7+
## htmlnote Review
8+
9+
!`htmlnote $ARGUMENTS --json`
10+
11+
## Your task
12+
13+
The output above is one of:
14+
15+
1. A JSON object with a non-empty `feedback` array — the user marked up the rendered HTML. Address each note in `feedback[]`, re-render the file, and stop.
16+
2. Empty output or `{"feedback":[]}` — the user closed the review without leaving notes. Acknowledge in one sentence and stop.

hooks/hooks.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"hooks": {
3+
"PostToolUse": [
4+
{
5+
"matcher": "Write|Edit|MultiEdit",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "htmlnote --hook",
10+
"timeout": 30
11+
}
12+
]
13+
}
14+
],
15+
"Stop": [
16+
{
17+
"hooks": [
18+
{
19+
"type": "command",
20+
"command": "htmlnote --stop-hook",
21+
"timeout": 30
22+
}
23+
]
24+
}
25+
],
26+
"UserPromptSubmit": [
27+
{
28+
"hooks": [
29+
{
30+
"type": "command",
31+
"command": "htmlnote --user-prompt-tick",
32+
"timeout": 5
33+
}
34+
]
35+
}
36+
]
37+
}
38+
}

0 commit comments

Comments
 (0)