← ALL WORK
CASE STUDY · OUR OWN PRODUCT

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.

Product Brightmark — visual website feedback
Scope Our own product · full-stack build · AI handoff
Built by Brightwork's AI-agent orchestration
brightmark · review canvas
Brightmark's review canvas: a client's live site rendered inside the tool with numbered feedback pins dropped on the navigation and buttons.
No login for the client they just click a share link
3-layer code anchor survives re-deploys
1 loop comment → issue → fix closed automatically
~185 PRs to build it by our own AI agents

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.

01 — The starting point

"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.

brightmark · dashboard
The Brightmark projects dashboard, with an 'Add a website' action and cards for each website under review.
It starts here: add a website, and Brightmark generates a share link you can hand to the client. Everything that follows hangs off that one project.
02 — The client experience

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.

brightmark · leaving a comment
A client leaving a pinned comment in Brightmark: the targeted element is highlighted and a popover composer shows a preview of that element beside the comment rail.
brightmark · comment rail
The Brightmark comment rail: a list of pinned comments, each with an 'anchored' status badge and a timestamp.

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.

03 — The technical centerpiece

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.

brightmark · comment detail
A Brightmark comment detail slide-over showing the three-layer anchor: a CSS selector .hero-btn--primary, an XPath, a text snippet, the element's HTML and a source-file hint, alongside a screenshot of the pinned element.
The detail view lays the anchor bare: the CSS selector (.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.

04 — The operator side

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.

brightmark · triage board
Brightmark's Kanban triage board with Open, In review, In progress and Resolved columns, cards tagged by severity and developer, and an Export control.
The triage board: severity and dev tags, drag-and-drop between columns, and an Export control that bundles work orders for a coding agent — the hinge between "client feedback" and "work getting done".
05 — The dev workflow

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.

06 — The differentiator

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

Comment

A client leaves a pinned comment on their live site.

Capture

A three-layer code anchor + reproduction context are captured with it.

Work order

It is auto-raised as a GitHub issue and exposed as a work order over MCP.

AI fix

A coding agent pulls the exact anchor plus context and makes the fix.

Resolved

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_summary project name, repo, counts and open work-order totals
  • list_work_orders the comments as work orders, filterable by status and severity
  • get_work_order the three-layer anchor + source_file hint + redacted repro context
  • update_work_order sets 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.

07 — How we build

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.

182 commits
185 pull requests merged
#7→#420 issue range
~5 wks to build

The assembly line

Issue

A task is filed on GitHub — a feature, a fix, an idea.

Curate

An agent enriches it into a clear, buildable spec.

Build

A builder agent implements it in an isolated worktree, opens a PR.

Review

A judge agent reviews the PR for quality and safety.

Merge

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:issueloom:buildingloom:review-requestedloom: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.

08 — Under the hood

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.

The safety perimeter
  • 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, snapdom can screenshot an element without the canvas ever tainting — the element captures just work.
  • Never indexable. noindex / nofollow is 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.

09 — The result

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.

For the client

No login, point-and-click feedback on their own live site — anchored to the exact element, not a coordinate that drifts.

For the fix

Three-layer anchor + redacted repro context, auto-raised as a GitHub issue and exposed as an AI-ready work order.

For us

One closed loop — comment to issue to AI fix to resolved — and a triage board that keeps the whole thing honest.

The full stack

Back-end
  • 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
AI handoff
  • 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
Front-end
  • 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
Infrastructure
  • 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.

LIKE WHAT YOU SEE?

Building a website — and tired of
feedback that gets lost in translation?

That's exactly the problem Brightmark solves, and the way we work. Tell us about your project — we'll show you what point-and-click feedback plus an AI handoff can do.