← Blog Building Luminair 25 September 2026 9 min read

Our homepage hero is the real app, captured over the DevTools protocol

The big app window on luminair.io is not a screenshot and not a mock. It is the desktop app's own markup, captured from a running copy over the Chrome DevTools Protocol, then wired so folders fold, tabs switch and layouts change when you click them.

Fig 01  From running app to homepageSchematic · one script, five stops
01 · THROWAWAY APP Electron, from source Fresh profile in /tmp Clean environment (env -i) debug port 9333 02 · CONNECT OVER CDP Playwright attaches Finds the index.html window, sets the viewport to 1680 × 940 connectOverCDP() 03 · STAGE THE SCENE Demo mode enterDemo('expert') 3 connected accounts 4 layouts: Build, Review, Research, Writing 1 queue + branch-out card page.evaluate() 04 · SNAPSHOT DOM, not pixels One <template> per layout, plus the whole document with scripts stripped demo/app.html 05 · HOMEPAGE Scaled iframe demo.js wires the clicks that make sense on a web page /demo/app Steps 01 to 04 are desktop/scripts/website-demo-capture.cjs. Step 05 is website/demo/demo.js, demo.css and one iframe in website/index.html.
The pipeline as it exists in the repo since 25 September 2026. Nothing in it draws the app: every pixel on the homepage comes from the app's own HTML and stylesheets.

The short version

  1. The app window in the “Many minds. One train of thought.” section of our homepage is a capture of the real app, not a picture of it and not a rebuild of it.
  2. A script starts a throwaway copy of the desktop app, connects to it over the Chrome DevTools Protocol, switches on the app's own demo mode, and saves the live DOM.
  3. A 135-line script on the website adds back only the clicks that make sense: folders fold, tabs and layouts switch, the account menu opens, the composer takes a caret but no typing.
  4. When the app's UI changes, we rerun the capture instead of redrawing a mock.
02Built with Luminair

The capture was an agent job.

The capture script and the replica landed on 25 September 2026, in two commits that both carry a Claude co-author line. It is the kind of job agents are now good at, because the agent can start the app, attach to it, look at what came out and try again. This post was drafted the same way: a session reading the script, the replica and the git history, with every outside claim checked against the page it links to.

Luminair · demo workspace
Luminair desktop app in the light theme: a folder sidebar on the left and three chat sessions side by side, one with a queue of three follow-up requests
The same demo workspace as a flat image. On screens narrower than 760 pixels the homepage shows this picture instead of the live replica, because a 1680 pixel wide app scaled to a phone is too small to click.
03Agents got eyes

Letting the agent see the page.

For most of the short history of AI coding tools, the agent wrote front-end code it could not look at. It changed a stylesheet and hoped. The Chrome team put it plainly when it announced Chrome DevTools MCP on 23 September 2025: “Coding agents face a fundamental problem: they are not able to see what the code they generate actually does when it runs in the browser. They're effectively programming with a blindfold on.”

The fix was an MCP server, a plug-in that exposes tools to the agent, which drives a real Chrome. Underneath, as Addy Osmani wrote two days later in Give your AI eyes, it rests on the Chrome DevTools Protocol, “Chrome's native debugger interface”. In December 2025 Chrome added a way for the server to attach to the browser session you already have open, with a permission dialog each time. When that post reached the front page of Hacker News in March 2026, the thread drew 604 points and 234 comments.

The protocol is the interesting part for us. It is not Chrome-only in practice: Electron apps are built on Chromium, so a desktop app started with a debug port speaks the same protocol. That means an agent can open Luminair itself the way it would open a web page: read the DOM, run code inside the window, and take the result away.

A distinctionOur capture does not use the MCP server. It is a plain Playwright script that connects over the same protocol. The idea is the same: give the agent a real, running interface to look at instead of a description of one.
04The mock that drifted

Every redesign broke the picture.

Before the capture, the homepage showed a hand-built HTML version of the app. It looked close enough on the day it was made. Then the app changed, and the mock did not. The header comment on the capture script records why it exists: “A hand-built HTML mock drifted off-brand every time the app changed; this captures the real DOM instead.”

The brief behind it, recorded in the same comment, was that the hero must be “100% a screenshot of our UI” and must be clickable. A screenshot is exact but dead. A mock is alive but wrong. The capture is both: exact because it is the app's own markup and CSS, alive because the markup still responds to a small script.

We had used the trick once before. Three days earlier, a design bench script had started capturing the running app's DOM into a local page, so styling passes could be tried in a normal browser tab that reloads on every save. The homepage capture reuses the same moves and adds a staged scene.

05How the capture works

Attach, stage, snapshot.

The first step is a clean instance. The script's usage notes launch Electron from the source folder under env -i, with a debug port and a profile folder in /tmp. The profile matters: the app reads an OW_USER_DATA override so a test copy never touches the real accounts and folders, and a fresh profile opens on the login screen, which the script simply removes.

desktop/scripts/website-demo-capture.cjslines 32–44, abridged
const browser = await chromium.connectOverCDP('http://127.0.0.1:' + PORT);
const page = browser.contexts().flatMap((c) => c.pages())
  .find((p) => /index\.html/.test(p.url()));
await page.setViewportSize({ width: 1680, height: 940 });
await page.evaluate(async () => {
  try { removeConnectGate(); } catch {}
  window.checkPaywall = () => {};
  enterDemo('expert');
  // ...three accounts, four layouts, a queue
});

enterDemo is not something we wrote for the website. It is the app's own Demo Account switch, which sits in the account menu for admins and swaps the whole workspace for a fake one, with a made-up user called Alex Rivera, so screenshots never show real work. It freezes workspace saves while it is on, so the fake layout can never overwrite the real one, and it restores everything on exit. It has three views: First run, Casual and Expert. The capture uses Expert.

From there the script sets the stage with ordinary app functions: three connected accounts (Claude, Codex and Antigravity) with fixed usage figures, four named layouts, and a three-column Build layout. The left column is a group of four session tabs, the middle one a single session, and the right one a session with a queue and a running Solace branch-out card.

Then it takes the pictures, except they are not pictures. For each layout it renders the arrangement, scrolls every thread to its latest message, opens the queue panel and saves the pane area's HTML. Last, it clones the whole document with the Build layout showing.

Fig 02  What the capture removes and rewritesFrom the script · real
Removed from the clone
  • ×Every <script> tag
  • ×The Content-Security-Policy meta tag
  • ×The boot cover and the old demo bar
  • ×Overlays: Quick Ask, Settings, voice, image viewer, permissions intro
  • ×The Journal browser, plus the terminal and Journal stylesheets
Kept or fixed up
  • ✓The full DOM, class names and all
  • ✓luminair://app/ links rewritten to plain relative paths
  • ✓Checkbox state copied into the checked attribute before cloning
  • ✓Four <template> blocks, one per layout
  • ✓A noindex tag, demo.css and demo.js
Summarised from the second page.evaluate in the capture script. The checkbox line matters because serialised HTML carries attributes, not the live state of a toggle. Without it, every switch would be saved in its default position.

The output is one file, website/demo/app.html. The current one is 312,332 bytes and holds exactly one script tag, the replica's own. The app's stylesheet and skin are copied next to it unchanged, along with its fonts, which is why the replica looks right: it is wearing the same CSS the app ships.

Two gotchas are written into the notes. The layout helper resets custom column widths, so the script puts the 1.45 : 1 : 0.95 split back after every render. And the app window is found by its URL, so the capture stops with an error if no window on port 9333 is serving the app's index.html.

06A replica that answers clicks

Alive, but only where it's honest.

A captured DOM has no behaviour; the app's scripts were stripped on purpose. So the website adds demo.js, 135 lines that bring back a short list of interactions and block everything else. On the homepage the replica loads in an iframe that is always 1680 by 940 pixels inside, scaled with a CSS transform to fit the width of the page.

Fig 03  What you can do in the heroFrom demo.js · real
ActionWhat happens
Fold a folderThe folder row toggles open and closed, as in the app.
Scroll a sessionThreads scroll; each starts at its latest message.
Switch layoutBuild, Review, Research and Writing swap in their captured pane arrangements.
Switch session tabThe four-tab group on the left shows the chosen session.
Open accountsThe switcher opens; picking an account moves the “using now” mark and the label.
Branch-out cardThe request counter ticks up every 1.9 seconds and loops. Staged: no agent is running.
Type in the composerIt takes focus and shows a caret. Every keystroke, paste and drop is cancelled.
×Everything elseLinks and buttons do nothing. Drag, right-click and form submits are blocked.
behaves like the appstaged or limited× blocked
Every row maps to a handler in website/demo/demo.js. The only animation that is not the app's own is the branch-out counter and a slow pulse on the card's agent links, from demo.css.

The rule behind the list is simple: anything that would pretend to do work is either blocked or clearly staged. You can click a layout tab and get the real Research layout, because that arrangement was captured from the app. You cannot send a message, because nothing is on the other end, so the composer swallows the keystroke instead of faking a reply.

The one staged motion is the Solace card. The captured text reads “4 of 4 agents working · 0/11 requests done”, and a timer counts it up so the card does not look frozen. We would rather say that here than let it pass for a live run.

07Honest limits

What a capture can't do.

4
Layouts captured, each stored as a template in app.html
1
Script tag in the captured file: the replica's own demo.js
1680×940
Pixels inside the iframe, scaled to fit the page
760
Pixel width below which the homepage shows a flat image instead

Worth knowing

  • The capture is a snapshot. It stays exact only as long as someone reruns it after a UI change; the refresh is a manual step, not a build hook.
  • All content is demo data. Session titles, the user and the usage figures are made up by demo mode and the script.
  • We have not measured how the replica's load time compares with the flat image. The captured page and its stylesheets weigh more than the single picture.
  • The capture uses the same protocol as Chrome DevTools MCP, not the MCP server itself.

For a feature-by-feature look at the layouts shown in the hero, see Layouts and Split & Tab View.

Sources

Click around the real thing

The hero on our homepage is the app itself. The download is the rest of it.

Download Luminair →