One feedback queue for all your apps. You write notes inside each app, Claude works on them and records exactly what it did, and you confirm with 👍 or reopen with 👎.
Screenshots · Pieces · Use it in an app · Theming · Run from your phone · Dashboard · Deploy
The computer dashboard: the run in progress, cost and activity, plan limits and every run.
![]() |
![]() |
| Cost and activity · per day, per model, per note kind | Every run · filter, search, open details, undo |
The panel inside each app: write notes, follow them, and confirm what Claude delivered.
![]() |
![]() |
| The queue · bugs, ideas, UX and questions, numbered per app | Delivered · what was done, commits, version, and your 👍/👎 |
Fictional apps and runs on a local Worker with a throwaway database. The dashboard interface is in Brazilian Portuguese; the panel speaks English or Portuguese.
Every app I build ended up with its own "notes" screen, and they kept drifting apart. The kit replaces them with one Worker, one database and one Web Component, and turns the notes into a work queue that Claude Code can pick up, resolve and report on.
Four pieces, each updated in one place only:
| Piece | What it is | How it reaches the apps |
|---|---|---|
Worker + D1 (worker/, migrations/) |
The API and the database, shared by every app | npm run deploy |
Widget (widget/) |
<feedback-panel>, a Web Component (Preact, shadow DOM) |
Apps load /v1/widget.js from the Worker, so a deploy reaches all of them, no rebuild |
Skill + CLI (skill/, cli/) |
The queue rules and Claude's tool | git pull here; the installed skill only points to this repository |
Agent (agent/) |
Runs on your computer and executes in Claude Code what the panel asks | git pull here; the tray icon restarts it |
<script type="module" src="https://<your-worker>.workers.dev/v1/widget.js"></script>
<feedback-panel app="recipes" screen="home" lang="en" closable></feedback-panel>| Attribute | Meaning |
|---|---|
app |
The app id in the kit (required) |
screen |
Where the panel was opened from; saved with each note (never a file path) |
lang |
pt or en (default: the browser's) |
theme |
light or dark (default: the system's). Hosts that map their own tokens can omit it |
closable |
Shows the X and closes on Esc; the app listens to feedback-close |
api |
Another Worker (local development); by default, the one the script came from |
| Property | Meaning |
|---|---|
appContext |
An object, or a function returning one: the app state (version, stage, sync...), saved in context.app |
accessCode |
A code the app already holds, so the person is never asked for one |
| Event | Meaning |
|---|---|
feedback-close |
The person closed the panel |
feedback-link |
detail.url, cancelable: preventDefault() to open the link yourself (Tauri sends it to the system browser) |
React
useEffect(() => {
void import(/* @vite-ignore */ 'https://<your-worker>.workers.dev/v1/widget.js')
}, [])
<feedback-panel ref={ref} app="recipes" closable />
// ref.current.appContext = () => ({ version, stage })
// ref.current.addEventListener('feedback-close', onClose)The element type for TSX:
declare module 'react' { namespace JSX { interface IntrinsicElements { 'feedback-panel': any } } }.
The panel draws inside a shadow DOM: the app's CSS does not leak in and the panel's does not leak out. Its look is adjusted through tokens, CSS variables that cross that boundary. Every token has a light and a dark default, and the app maps its own on top:
feedback-panel {
--fb-bg: var(--bg-content);
--fb-surface: var(--surface-1);
--fb-border: var(--border-default);
--fb-text: var(--text-primary);
--fb-focus: var(--accent);
--fb-radius-lg: var(--radius-lg);
--fb-font: var(--font-ui);
}All tokens and parts
Tokens: --fb-bg, --fb-surface, --fb-surface-hover, --fb-surface-active,
--fb-input, --fb-border, --fb-border-subtle, --fb-border-strong, --fb-text,
--fb-text-secondary, --fb-text-muted, --fb-focus, --fb-primary-bg,
--fb-primary-text, --fb-danger, --fb-positive, --fb-warning, --fb-claude, the
tones --fb-blue, --fb-purple, --fb-cyan, --fb-yellow, --fb-red, --fb-teal,
--fb-gray, --fb-radius-sm|md|lg, --fb-font, --fb-font-mono, --fb-font-size,
--fb-max-width and --fb-padding.
For fine tuning, the parts: page, card, note, composer, resolution, pill,
chip, input, button, button-primary, filters
(feedback-panel::part(card) { … }).
Important
/v1/ is the contract. Renaming or changing the meaning of a token, attribute,
event or part breaks apps: that goes to /v2/widget.js, and v1 stays as it was.
npm run install-skill # once per machine: the Claude Code skill
node cli/feedback.mjs apps # registered appsReading, updating and delivering notes is described in skill/SKILL.md.
A note goes through open → in_progress → done, or discussing when it needs a decision,
and every change is logged with who made it. Claude's delivery is structured: a summary,
what was done, what was ignored, decisions, commits, the deployed version and follow-ups.
The panel has a Run N notes button. It queues the open notes on the Worker; the agent
on your computer picks them up, runs Claude Code headless in the project folder and
streams everything back to the panel while it works: what Claude is saying, the final
report, the commits, the cost and the tokens. Each run can be canceled and, once
finished, undone (git revert of its commits, push, the project's deploy and the notes
back to open; this runs without Claude and costs nothing).
phone ── POST /runs ──▶ Worker (D1: runs, agents) ◀── polls every 20 s ── agent on your computer
▲ │
└──────── the panel reads progress every 4 s ◀── reports ── claude -p ◀────────┘
- Which Claude. Bugs go to Sonnet; ideas, UX and questions go to Opus, both at medium
effort (
modelsin the agent config). A run with both kinds runs two sessions in a row, bugs first. - Idle costs nothing. The agent's poll is a plain HTTP call to the Worker (about 4,000 a day, far from the free limit). Claude Code only starts when there is a run.
- Plan limits. Claude Code reports the 5-hour and weekly windows on every run; the
agent stores them and the panel shows them. Above
maxFiveHour(90%) no new run starts. - Nobody around. The run prompt tells Claude not to ask: an ambiguous note goes to
discussingwith the question, and it moves on. The agent only starts on a clean working tree, pulls with--ff-onlyfirst and makes sure to push afterwards. - Canceling does not publish. Commits not pushed yet stay on the computer in a
feedback-kit/parada-<id>branch, loose changes go togit stash, and notes in the middle go back to open.
Set it up (once)
-
On the Worker, the run code (the panel asks for it once per device; it is the same for every app):
node cli/feedback.mjs run-code | npx wrangler secret put RUN_HASH npm run deploy # applies migration 0002 and publishes the new panel
-
On a Windows computer (Node 22+, Git, Claude Code installed and signed in,
~/.feedback-kit/admin-code.txt, and your Worker's URL in~/.feedback-kit/url.txt):git clone https://github.com/megomes/feedback-kit $HOME\Code\feedback-kit cd $HOME\Code\feedback-kit; npm ci powershell -ExecutionPolicy Bypass -File agent\install-windows.ps1
It installs the skill, creates
~/.feedback-kit/agent.json, lists the projects it found, adds a Startup shortcut and starts the tray icon. -
On your phone, in any app's panel: Run on computer... and paste the contents of
~/.feedback-kit/run-code.txt.
On macOS or Linux the same agent runs with npm run agent (no tray icon; put it in
launchd, systemd or pm2).
Projects and configuration
The agent finds projects on its own: every folder with a feedback-kit.json inside the
roots (up to two levels deep). The deploy command used by undo lives in the same file:
{ "app": "recipes", "deploy": "npm run deploy" }~/.feedback-kit/agent.json:
| Key | Default | Meaning |
|---|---|---|
name |
the computer's name | How it shows up in the panel |
roots |
["~/Code"] |
Where to look for projects |
projects |
{} |
{ "<app>": "<folder>" }, by hand, wins over the search |
models |
bug: sonnet/medium; rest: opus/medium |
--model and --effort per note kind (bug, idea, ux, question; default for the rest) |
maxBudgetUsd |
5 |
--max-budget-usd per run: stops a runaway run |
timeoutMinutes |
45 |
Stops after this |
permissionMode |
bypassPermissions |
Nobody approves tools; auto is more careful and may stop midway |
maxFiveHour |
0.9 |
No new run when the 5-hour window is above this |
dashboardPort |
47820 |
The local dashboard port (127.0.0.1 only) |
While running, the agent serves a dashboard for that computer only at
http://127.0.0.1:47820: the live run (what Claude is saying, the model of each step,
time and cost, and cancel), the plan limits, runs and cost per day, cost per model, note
kinds, projects, alerts, the latest answers and the full history, with each run's details
and undo. It opens in its own window from the Start menu, by double-clicking the tray
icon, or with node agent/agent.mjs open, and only accepts requests from its own page.
Screenshots are at the top.
Each run keeps Claude Code's full output in ~/.feedback-kit/runs/<id>.jsonl, and the log
is in ~/.feedback-kit/agent.log.
Warning
Security. An app's code can only write notes; running, canceling and undoing also require the run code. Still, note text becomes instructions for a Claude with full permissions on your computer: only give an app's code to people you would let touch the code.
The CLI and the agent find the Worker in ~/.feedback-kit/url.txt (one line with the URL)
or in FEEDBACK_KIT_URL.
- Admin: the CLI sends the code in
~/.feedback-kit/admin-code.txt; the Worker only stores its SHA-256, in theADMIN_HASHsecret. - Remote runs:
~/.feedback-kit/run-code.txt; the Worker stores its SHA-256 in theRUN_HASHsecret (without it, the button does not show up). Rotate it withnode cli/feedback.mjs run-code --new | npx wrangler secret put RUN_HASH. - Per app:
node cli/feedback.mjs apps add <id> "<Name>" [repo]generates a new code, saves it in~/.feedback-kit/codes/<id>.txtand stores the hash in the database. It is pasted once on each device, in the panel itself (or the app passes it inaccessCode).
Rotate the admin code
node -e 'process.stdout.write(require("crypto").randomBytes(32).toString("base64url"))' > ~/.feedback-kit/admin-code.txt
node -e 'process.stdout.write(require("crypto").createHash("sha256").update(require("fs").readFileSync(process.argv[1],"utf8")).digest("hex"))' ~/.feedback-kit/admin-code.txt | npx wrangler secret put ADMIN_HASHnpm run verify # types, Worker tests (the real migration on Node's SQLite) and build
npm run dev # local Worker at http://localhost:8787 with a local D1Locally: .dev.vars with ADMIN_HASH=<sha-256> (git-ignored) and
npx wrangler d1 migrations apply feedback-kit --local. Point the CLI at it with
FEEDBACK_KIT_URL=http://localhost:8787 FEEDBACK_KIT_ADMIN_CODE=<code>, and the panel with
the api attribute. The Worker's root serves a demo page: ?app=<id>&theme=dark&lang=en.
Tip
In D1 triggers, write BEGIN and END in uppercase. Remote D1 does not recognize a
trigger body written in lowercase.
- Create the database with
npx wrangler d1 create feedback-kitand put its id inwrangler.jsonc. - Set the admin hash: see Rotate the admin code.
- Publish:
npm run deployIt builds the widget, applies pending migrations and publishes the Worker. Bump version
in package.json on every widget change: it goes into context.kit.version of each note.






