Brightmark
Brightmark is the tool we built to collect feedback on the websites we make — and the clearest example of how we work. A client points at anything on their live site, no login required, and that note becomes a precise work order an AI coding agent can act on: bound to the exact element, carrying the reproduction context, straight into the dev workflow.
When we hand a client their new website, we need their feedback — and the usual ways of getting it are lossy. Brightmark fixes that at the source: the client opens a share link, sees their own live site, and clicks the thing they mean. Every comment is pinned to the exact element, and — this is the part that matters — it comes out the other side as a work order an AI coding agent can pick up and fix. It is Brightwork's own product, and it is how we work with website clients.
"The button on the about page"
Website feedback usually arrives as a guessing game. A long email. A screenshot with a red arrow. A message that says "the button on the about page looks off" — and there are three buttons on the about page. The developer has to reconstruct what the client meant before they can even start, and something always gets lost in the translation.
Brightmark removes the ambiguity by binding every comment to the exact spot in the page. There is nothing to describe, because the client didn't describe it — they pointed at it. The note and the element travel together from the moment it's left, so the person (or agent) doing the fix never has to guess which thing was meant.
Point at anything. No login.
The client's side is deliberately frictionless. They get a share link
— a clean /r/{host}/{code} URL, no account, no
password — and land on their own live site inside the review screen.
In Comment mode, hovering highlights whatever element is under the
cursor; a click opens a small composer to leave feedback pinned to
that exact spot.
It isn't only for buttons and boxes. A client can select a specific run of text and comment on the wording itself, or ask for a whole new page. Whatever they touch, the comment is anchored to it — not to a coordinate that drifts the next time the layout moves.
Left: click an element and a composer opens with a preview of exactly what's pinned. Right: every note lands in the comment rail with an "anchored" badge — a small promise that it still points at the right thing.
The comment that survives a redeploy
A pin is only useful if it keeps pointing at the right element after the site changes. A raw x/y coordinate doesn't — move a section, add a paragraph, ship a redesign, and the arrow now points at empty space. So Brightmark doesn't store a coordinate. It stores a three-layer anchor: a CSS selector, a hybrid XPath, and a text snippet — three independent ways to find the same element again.
On load, the in-page agent re-resolves every pin — selector first, then XPath, then a fuzzy text match — and reports honest status: anchored, fuzzy, or lost. It re-anchors on device-toggle, on resize, and across Astro view-transition swaps. A comment keeps its meaning even after the underlying site has moved on.
.hero-btn--primary), the XPath, the surrounding text,
the element's HTML and a source-file hint — plus a screenshot of the
exact element. This is what an agent receives.
◇ For engineers How the three-layer anchor is built and re-resolved
Layer one is a CSS selector generated with
@medv/finder, tuned to reject anything a build will
churn: Astro astro-XXXX scope classes, CSS-module and
emotion hashes are filtered out, while stable
data-* attributes, role and
aria-label are preferred — so the selector describes
the element's meaning, not its compiled output. Layer two
is a hybrid XPath anchored to the nearest stable
ancestor rather than an absolute root-to-node path. Layer three is
a text snippet carrying ~30 characters of
surrounding context for a fuzzy last-resort match.
Re-resolution walks the layers in order and records which one won,
surfacing the result as anchored /
fuzzy / lost. Because the review canvas
can re-render — a device toggle, a window resize, a view-transition
navigation — re-anchoring runs on each of those events instead of
trusting a stale node reference. The pin follows the element; it
doesn't wait for the element to come back to it.
The agency's cockpit
Where the client sees a clean review screen, the operator gets a cockpit. The review canvas can preview the site at mobile, tablet, laptop and desktop widths — a layout preview, stated honestly: it resizes the frame, it is not true device emulation. There is zoom, fullscreen, an address bar to navigate the preview, and an Inspect mode that overlays spacing, typography and WCAG contrast checks.
Alongside sits the comment rail and a Kanban triage board — Open, In review, In progress, Resolved — with severity, filters and drag-and-drop. It's where a pile of client notes becomes an ordered queue of work.
Straight into the dev workflow
A comment doesn't have to stay inside Brightmark. When a project is
wired to a repository, comments can auto-raise GitHub
issues — each tagged source:brightmark and a
severity:* label, so the work lands where the developers
already live. The whole integration is opt-in and completely inert
unless a repo and token are configured.
And it stays in sync both ways. Close the GitHub issue and the comment flips to resolved and disappears from the client's link; delete the comment and its issue is withdrawn or closed. One truth, mirrored — nobody has to remember to update the other side.
◇ For engineers Debounce, dedup and the resolve-sync webhook
Issue creation is debounced so a client editing a comment moments after leaving it doesn't spawn a flurry of issues, and a dedup guard keeps one comment mapped to one issue. The reverse direction runs on a signed webhook: GitHub calls back on issue close/reopen/delete, the signature is verified, and the linked comment's status is updated to match — which is what hides a resolved comment from the client's share link. Deleting a comment withdraws its issue rather than leaving an orphan. None of this runs unless the project has a repo and token set, so the default state is simply off.
Hand the fix to an AI agent
This is the part that makes Brightmark Brightmark. A pinned comment is already precise about where — but a fix also needs to know what was happening. So the in-page agent instruments the page and captures, per comment, the reproduction context a developer would otherwise have to reconstruct: console output and errors, failed or slow network requests, the environment (browser, OS, viewport, locale), and a breadcrumb trail of what the client did.
Crucially, that context is redacted in the browser before anything leaves the page — passwords, tokens and PII are scrubbed at the source, then re-scrubbed server-side and size-capped. The agent gets what it needs to reproduce the problem, and nothing it shouldn't have.
The loop
A client leaves a pinned comment on their live site.
A three-layer code anchor + reproduction context are captured with it.
It is auto-raised as a GitHub issue and exposed as a work order over MCP.
A coding agent pulls the exact anchor plus context and makes the fix.
Marking it resolved resolve-syncs the issue and clears it from the client's view.
A client's click at one end comes out as a resolved issue at the other — and the client, who never saw any of the machinery, just sees the thing they pointed at get fixed. The tool that hands feedback to an AI agent closes its own loop.
The work order, over MCP
The bridge to the agent is a Model Context Protocol
server (built on FastMCP, over stdio) that
exposes each comment as a work order through four
tools. A coding agent like Claude Code connects, sees the queue, pulls
the exact anchor and context for one item, does the work, and marks it
done:
-
project_summaryproject name, repo, counts and open work-order totals -
list_work_ordersthe comments as work orders, filterable by status and severity -
get_work_orderthe three-layer anchor + source_file hint + redacted repro context -
update_work_ordersets status — mirrored back onto the linked GitHub issue
Prefer not to plug an agent in live? Work orders also
export as a WORKORDER.md bundle — one
section per comment, screenshots included — written straight into a
folder you open in Claude Code.
◇ For engineers The MCP surface, token auth & the safety model
The four tools — project_summary,
list_work_orders, get_work_order (returns
the three-layer anchor, a source_file hint and the
redacted repro context) and update_work_order (sets
status, mirrored onto the linked GitHub issue) — are the entire
surface. There is no free-form shell, no filesystem, no way to
reach past the project the token is scoped to.
Access is a scoped, hashed project access token
(bmpat_…) with read / triage scopes, revocation and an
expiry — the raw token is shown once and only its hash is stored.
Two guardrails matter for an AI consumer: tool
errors are returned as data rather than thrown, so
a stuck agent gets a message it can reason about instead of a crash;
and comment text is treated as untrusted input —
it's client-authored, so it's never allowed to act as an
instruction to the agent. The work order describes a problem; it
never gets to pilot.
Built the way we build
There's a pleasing symmetry here: the tool that hands feedback to AI agents was itself built by AI agents. Brightmark came together through Brightwork's orchestration system — Loom on GitHub — where every task is an issue, built on its own branch, reviewed as a pull request, and auto-merged when it's clean.
The assembly line
A task is filed on GitHub — a feature, a fix, an idea.
An agent enriches it into a clear, buildable spec.
A builder agent implements it in an isolated worktree, opens a PR.
A judge agent reviews the PR for quality and safety.
A champion agent merges it — and the change ships.
Across roughly five weeks that produced about 182 commits and 185 merged pull requests spanning issues #7 through #420 — every decision auditable in a pull request you can still read. And in the same spirit: this very case study was built the same way, as a tracked GitHub issue run through a Loom cycle.
◇ For engineers The Loom role model
Loom coordinates agents through GitHub as the shared source of
truth, with one git worktree per issue (branch
feature/issue-N) so agents work in parallel without
colliding. Labels drive the state machine
(loom:issue → loom:building →
loom:review-requested → loom:pr). The
roles that built Brightmark:
- Curator enriches a raw idea into a spec-ready issue
- Builder implements it in an isolated git worktree, opens a PR
- Judge reviews the PR for quality, security & correctness
- Champion auto-merges once it is clean and mergeable
- Hermit hunts down complexity and proposes removing it
Governance is deliberate: a capped number of parallel agents, no unattended daemon, and auto-merge restricted to pull requests GitHub reports as genuinely mergeable and clean.
What's underneath
To read a client's live site well enough to highlight its elements and screenshot them, Brightmark renders the target through a same-origin proxy: it fetches every asset and rewrites its URLs so the whole page loads from Brightmark's own origin — which is what makes the target's DOM readable and its element captures clean. A proxy that fetches arbitrary URLs is also a security surface, so it is wrapped tightly.
- SSRF guard. Every proxied address validates the share token, resolves DNS per request, blocks private / internal IP ranges, allow-lists the project's own host, and pins the resolved IP to defeat DNS-rebinding.
- Same-origin by design. Because the page renders
same-origin,
snapdomcan screenshot an element without the canvas ever tainting — the element captures just work. - Never indexable.
noindex/nofollowis set everywhere: a review tool holding unreleased client work must never leak into a search engine. - Locked down. Frame-blocking headers are stripped only for the proxied preview; token auth gates the API; the schema syncs on startup so the running instance and its data model never drift.
Honest about scope: this approach is excellent on static and server-rendered sites — the Astro, WordPress and Squarespace sites we typically build — and heavy, client-rendered single-page apps are out of scope for v1. Brightmark is also desktop-only by design; it's an operator tool, and we haven't pretended otherwise.
◇ For engineers The stack, end to end
The back end is FastAPI + SQLModel on SQLite with
a startup schema-sync. The operator dashboard is
Astro + Preact with Tailwind CSS v4,
and the in-page review overlay is a
vanilla-TypeScript agent bundled with
esbuild — it has to run inside the proxied page, so it
carries no framework weight. Selectors come from
@medv/finder, element screenshots from
snapdom, the AI handoff from a FastMCP
stdio server. It runs on a Hetzner VPS behind Caddy
with Doppler for runtime secrets — and, like
everything Brightwork ships, it was built through Loom on GitHub.
How this shows up for clients
Strip away the machinery and here's what a website client actually gets. Giving feedback is effortless — no login, no account, just click the thing you mean. Nothing gets lost or misunderstood, because the exact element and the context travel with every note. And fixes happen fast, because that precise code location and reproduction context go straight to the people — and the agents — doing the work.
No login, point-and-click feedback on their own live site — anchored to the exact element, not a coordinate that drifts.
Three-layer anchor + redacted repro context, auto-raised as a GitHub issue and exposed as an AI-ready work order.
One closed loop — comment to issue to AI fix to resolved — and a triage board that keeps the whole thing honest.
The full stack
- FastAPI + SQLModel the API and data model, on SQLite with startup schema-sync
- Same-origin proxy fetches + rewrites the target so its DOM is readable
- SSRF guard per-address DNS, private-IP blocks, host allow-list, pinned IP
- GitHub sync debounced issue creation + signed resolve-sync webhooks
- MCP server (FastMCP) four brightmark_* work-order tools over stdio
- Project access token scoped, hashed bmpat_… with revoke + expiry
- Repro capture console, network, environment — redacted in-browser
- WORKORDER.md export a bundle you open straight in Claude Code
- Astro + Preact the operator dashboard, islands hydrated where needed
- Tailwind CSS v4 design tokens, no JS config
- In-page agent (vanilla TS) the review overlay, bundled with esbuild
- @medv/finder + snapdom stable selectors + same-origin element screenshots
- Hetzner VPS + Caddy serves the dashboard, proxies the API
- Doppler runtime secrets — no credentials on disk
- noindex / nofollow everywhere — a review tool must never leak to search
- Loom + GitHub AI agents building through issues & pull requests
This is how Brightwork works with website clients: point at anything, say what you mean, and let the exact code location — and an AI agent — do the rest.
Brightmark is an internal Brightwork product, not a public sign-up. It lives behind an operator login at brightmark.brightworkdigital.co — so rather than send you to a login wall, we'd rather just show it to you. Want to see it on your own site? Let's talk.