Make Pages Interactive
Turns any folder of HTML files into a place the user can leave inline comments on (text selections, element selections, page-level notes). Comments POST to a local JSONL inbox; you (the agent) Monitor that inbox, edit the HTML in response, append to feedback/history.json, and the page auto-reloads with a walkthrough of what changed.
When to invoke
User says any of:
- "make this page interactive" / "make these pages interactive" → Setup flow
- "add feedback to this page" / "let me comment on this page" → Setup flow
- "set up feedback on " → Setup flow
- "stop the feedback server" / "kill the server" / "shut it down" → Stop flow
- "remove the feedback layer" / "make pages static again" → Removal flow
- "update the make-pages-interactive skill" → Update flow
Setup flow (when user wants to make pages interactive)
- Identify the target directory. Usually the user's current working directory or a folder they named. If ambiguous, ask.
- Inject the feedback tags into every
*.html in that directory:python ~/.claude/skills/make-pages-interactive/scripts/inject.py <dir>
Add --recursive if the pages live in subfolders. The script is idempotent — safe to re-run. It also creates <dir>/feedback/inbox.jsonl and <dir>/feedback/history.json if missing.
- Pick a port. Default 5050. Before starting, check what's there:
curl -s --max-time 2 http://localhost:5050/info
- JSON with
artifact_dir matching this <dir> → reuse it, skip to step 5.
- JSON with a different
artifact_dir → port is held by another exploration. Either ask the user to free it (lsof -ti:5050 | xargs kill) or use port 5051, 5052, … (try the next port; tell the user the URL).
- No response → port 5050 is free.
- Start the server in the background via Bash with
run_in_background: true:python ~/.claude/skills/make-pages-interactive/lib/server.py <dir> --port <chosen>
The server auto-shuts-down on parent death or 10 min of idle, so you don't need to manage its lifecycle.
- Tell the user the URL. For example:
http://localhost:5050/index.html (use whatever filename they actually have — index.html, report.html, etc.). If they have multiple pages, list the top-level ones.
- Start a Monitor on the inbox so new comments notify you immediately:
Monitor on path: <dir>/feedback/inbox.jsonl
Do NOT poll — let the Monitor notification arrive.
Responding to a feedback batch
When a new batch arrives in inbox.jsonl:
- Read the entry. Each comment has a stable
cf_id and a selector pointing to the exact element/text the user commented on.
- Edit the relevant HTML files to address each comment. Wrap each modified region with
<span data-cf-change="ch-<short-slug>">…</span> (or add data-cf-change to an existing wrapping element) so the post-reload walkthrough can find the change. One anchor per change.
- Append a new batch object to the end of
<dir>/feedback/history.json (newest = last; the library walks from the end to find the latest batch). Schema:{
"batch_id": "b-<timestamp-or-slug>",
"timestamp": "<ISO 8601>",
"comments": [ /* echo back the inbox comments you addressed */ ],
"changes": [
{
"id": "ch-<slug>",
"in_response_to": ["<cf_id from inbox>"],
"anchor": "ch-<slug>", // must match a data-cf-change in the HTML
"title": "short, concrete",
"description": "longer prose (hidden in UI, just for the record)"
}
]
}
- The page polls
history.json, sees the new batch, auto-reloads (scroll position preserved), and offers the user a walkthrough of the changes. The "processing…" banner clears automatically when any in_response_to matches a submitted comment id.
On startup in a directory that already has feedback
If you find <dir>/feedback/inbox.jsonl and <dir>/feedback/history.json and the skill has been invoked in this session:
- Scan inbox for comment ids.
- Scan history's
changes[*].in_response_to union — those are already processed.
- If unprocessed comments exist, tell the user the count and ask whether to process now.
- Either way, set up the Monitor on the inbox.
Stop flow (user wants to kill the server)
- Identify the port. If you started the server in this session, you know it. Otherwise check
curl -s http://localhost:5050/info (try 5051, 5052 if 5050 returns nothing or a different artifact).
- Kill it:
lsof -ti:<port> | xargs kill (use kill -9 only if a plain kill doesn't free the port within a few seconds — the server traps SIGTERM and exits cleanly).
- Confirm:
lsof -i :<port> should be silent.
- If you also started a
Monitor on the inbox in this session, it will keep watching the file — that's fine, the file just won't get new entries.
Note: in most cases the user doesn't need to manually stop the server. It auto-shuts-down when (a) the parent process dies (e.g. they close the Claude Code window — within ~5–10 s) or (b) no client requests for 10 min. Manual stop is for the case where they want the port back right now in the same session.
Update flow (user wants the latest lib/)
python ~/.claude/skills/make-pages-interactive/scripts/update.py
Runs git pull --ff-only inside the skill dir. Requires git-clone install (the script tells the user how to re-install if not).
Removal flow (clean static copy)
If the user wants their HTML back to a clean, server-independent state:
python ~/.claude/skills/make-pages-interactive/scripts/inject.py <dir> --remove
Strips both tags from every *.html. Leaves the feedback/ directory alone (delete manually if not wanted).
Files in this skill
~/.claude/skills/make-pages-interactive/
├── SKILL.md # this file (agent-facing)
├── README.md # GitHub-facing docs (human readers)
├── LICENSE
├── lib/
│ ├── feedback.js # client library: selection + commenting + tour
│ ├── feedback.css # styles
│ └── server.py # stdlib-only HTTP server
└── scripts/
├── inject.py # idempotent tag injection / removal
└── update.py # git pull --ff-only
Gotchas
- The injected
<link> and <script> reference absolute paths /lib/feedback.css and /lib/feedback.js. These resolve through server.py, which routes /lib/* to the skill's own lib/ directory. So pages only work when opened through this server — opening the HTML file directly in a browser will silently fail to load the feedback widget (the page itself still renders).
history.json order matters: append (don't prepend). The library walks from the end to find the latest batch for the walkthrough.
anchor values must match a data-cf-change attribute actually present in the HTML. Typos here cause "anchor not found" warnings post-reload.
Source: paraschopra/make-pages-interactive — distributed by TomeVault.
1---2name: make-pages-interactive3description: Turn a directory of static HTML pages into a live commenting surface. Injects a feedback library, starts a tiny server, and routes user comments into a JSONL inbox that the agent monitors and responds to by editing the pages. Trigger phrases — "make this page interactive", "make these pages interactive", "let me comment on this page", "add feedback to these pages". Use when this capability is needed.4---56# Make Pages Interactive78Turns any folder of HTML files into a place the user can leave inline comments on (text selections, element selections, page-level notes). Comments POST to a local JSONL inbox; you (the agent) Monitor that inbox, edit the HTML in response, append to `feedback/history.json`, and the page auto-reloads with a walkthrough of what changed.910## When to invoke1112User says any of:13- "make this page interactive" / "make these pages interactive" → **Setup flow**14- "add feedback to this page" / "let me comment on this page" → **Setup flow**15- "set up feedback on <dir>" → **Setup flow**16- "stop the feedback server" / "kill the server" / "shut it down" → **Stop flow**17- "remove the feedback layer" / "make pages static again" → **Removal flow**18- "update the make-pages-interactive skill" → **Update flow**1920## Setup flow (when user wants to make pages interactive)21221. **Identify the target directory.** Usually the user's current working directory or a folder they named. If ambiguous, ask.232. **Inject the feedback tags** into every `*.html` in that directory:24 ```25 python ~/.claude/skills/make-pages-interactive/scripts/inject.py <dir>26 ```27 Add `--recursive` if the pages live in subfolders. The script is idempotent — safe to re-run. It also creates `<dir>/feedback/inbox.jsonl` and `<dir>/feedback/history.json` if missing.283. **Pick a port.** Default 5050. Before starting, check what's there:29 ```30 curl -s --max-time 2 http://localhost:5050/info31 ```32 - JSON with `artifact_dir` matching this `<dir>` → reuse it, skip to step 5.33 - JSON with a *different* `artifact_dir` → port is held by another exploration. Either ask the user to free it (`lsof -ti:5050 | xargs kill`) or use port 5051, 5052, … (try the next port; tell the user the URL).34 - No response → port 5050 is free.354. **Start the server in the background** via Bash with `run_in_background: true`:36 ```37 python ~/.claude/skills/make-pages-interactive/lib/server.py <dir> --port <chosen>38 ```39 The server auto-shuts-down on parent death or 10 min of idle, so you don't need to manage its lifecycle.405. **Tell the user the URL.** For example: `http://localhost:5050/index.html` (use whatever filename they actually have — `index.html`, `report.html`, etc.). If they have multiple pages, list the top-level ones.416. **Start a Monitor on the inbox** so new comments notify you immediately:42 ```43 Monitor on path: <dir>/feedback/inbox.jsonl44 ```45 Do NOT poll — let the Monitor notification arrive.4647## Responding to a feedback batch4849When a new batch arrives in `inbox.jsonl`:50- Read the entry. Each comment has a stable `cf_id` and a selector pointing to the exact element/text the user commented on.51- Edit the relevant HTML files to address each comment. Wrap each modified region with `<span data-cf-change="ch-<short-slug>">…</span>` (or add `data-cf-change` to an existing wrapping element) so the post-reload walkthrough can find the change. One anchor per change.52- **Append** a new batch object to the end of `<dir>/feedback/history.json` (newest = last; the library walks from the end to find the latest batch). Schema:53 ```json54 {55 "batch_id": "b-<timestamp-or-slug>",56 "timestamp": "<ISO 8601>",57 "comments": [ /* echo back the inbox comments you addressed */ ],58 "changes": [59 {60 "id": "ch-<slug>",61 "in_response_to": ["<cf_id from inbox>"],62 "anchor": "ch-<slug>", // must match a data-cf-change in the HTML63 "title": "short, concrete",64 "description": "longer prose (hidden in UI, just for the record)"65 }66 ]67 }68 ```69- The page polls `history.json`, sees the new batch, auto-reloads (scroll position preserved), and offers the user a walkthrough of the changes. The "processing…" banner clears automatically when any `in_response_to` matches a submitted comment id.7071## On startup in a directory that already has feedback7273If you find `<dir>/feedback/inbox.jsonl` and `<dir>/feedback/history.json` and the skill has been invoked in this session:741. Scan inbox for comment ids.752. Scan history's `changes[*].in_response_to` union — those are already processed.763. If unprocessed comments exist, tell the user the count and ask whether to process now.774. Either way, set up the Monitor on the inbox.7879## Stop flow (user wants to kill the server)80811. Identify the port. If you started the server in this session, you know it. Otherwise check `curl -s http://localhost:5050/info` (try 5051, 5052 if 5050 returns nothing or a different artifact).822. Kill it: `lsof -ti:<port> | xargs kill` (use `kill -9` only if a plain kill doesn't free the port within a few seconds — the server traps SIGTERM and exits cleanly).833. Confirm: `lsof -i :<port>` should be silent.844. If you also started a `Monitor` on the inbox in this session, it will keep watching the file — that's fine, the file just won't get new entries.8586Note: in most cases the user doesn't need to manually stop the server. It auto-shuts-down when (a) the parent process dies (e.g. they close the Claude Code window — within ~5–10 s) or (b) no client requests for 10 min. Manual stop is for the case where they want the port back *right now* in the same session.8788## Update flow (user wants the latest lib/)8990```91python ~/.claude/skills/make-pages-interactive/scripts/update.py92```93Runs `git pull --ff-only` inside the skill dir. Requires git-clone install (the script tells the user how to re-install if not).9495## Removal flow (clean static copy)9697If the user wants their HTML back to a clean, server-independent state:98```99python ~/.claude/skills/make-pages-interactive/scripts/inject.py <dir> --remove100```101Strips both tags from every `*.html`. Leaves the `feedback/` directory alone (delete manually if not wanted).102103## Files in this skill104105```106~/.claude/skills/make-pages-interactive/107├── SKILL.md # this file (agent-facing)108├── README.md # GitHub-facing docs (human readers)109├── LICENSE110├── lib/111│ ├── feedback.js # client library: selection + commenting + tour112│ ├── feedback.css # styles113│ └── server.py # stdlib-only HTTP server114└── scripts/115 ├── inject.py # idempotent tag injection / removal116 └── update.py # git pull --ff-only117```118119## Gotchas120121- The injected `<link>` and `<script>` reference absolute paths `/lib/feedback.css` and `/lib/feedback.js`. These resolve through `server.py`, which routes `/lib/*` to the skill's own `lib/` directory. So pages only work when opened through this server — opening the HTML file directly in a browser will silently fail to load the feedback widget (the page itself still renders).122- `history.json` order matters: append (don't prepend). The library walks from the end to find the latest batch for the walkthrough.123- `anchor` values must match a `data-cf-change` attribute actually present in the HTML. Typos here cause "anchor not found" warnings post-reload.124125---126> Source: [paraschopra/make-pages-interactive](https://github.com/paraschopra/make-pages-interactive) — distributed by [TomeVault](https://tomevault.io).127<!-- tomevault:4.0:skill_md:2026-06-17 -->