How the DevTools panel is built
The panel is a devframe: the small framework it is built on, which runs inside Vite DevTools, in its own tab, or from the CLI. Knowing that explains where settings live, how files open, and why an agent can read the same data.
One panel, three hosts
Section titled “One panel, three hosts”One definition declares what the panel needs rather than where it runs: its RPC functions, its shared state, and the timeline stream. The page runtime reaches it through an injected port, so nothing in the definition knows about Vite. That gives three ways to open the same UI.
Docked in Vite DevTools it appears as the lit dock: a panel slot inside Vite
DevTools, served as an iframe by the DevTools hub. This is the normal case, and
the only one with a live page behind it.
On its own it is served by the dev server at <origin>/__lit/. Same panel, same
data, its own browser tab.
From the command line, lit-devtools dev starts the panel with no page behind
it, so it is empty until a page connects. Any page can load the script tag the
server prints, and a page that already loads this plugin’s runtime can call
connectToDevServer() (see the
CLI reference); its tree, inspector and timeline data then
flow to that server over devframe RPC instead of the page’s own Vite socket.
For everyday debugging, run your Vite dev server with DevTools enabled; the
standalone server is for a panel that outlives the app’s dev server, and the
proof that the panel is framework-neutral.
How settings persist
Section titled “How settings persist”The panel’s overrides, and its own light or dark preference, are written to
devframe’s settings store. Under Vite DevTools that is
~/.vite/devtools/settings/lit.json.
That location is deliberate. The file lives in your home directory, not the
repository, so nothing needs a .gitignore entry and nothing reaches a
teammate. Settings follow you to another browser on the same machine, and
survive clearing site data or restarting the dev server. Project-wide defaults
belong in litPlugin({…}), which is committed on purpose.
localStorage is still written, and still matters. The inspected page reads its
overrides synchronously while its own modules initialise, long before any
connection to DevTools exists, so it cannot wait on a file-backed store. Treat
it as a per-browser cache that the panel refills from the durable copy on its
first connection.
The consequence is visible once: in a browser that has never opened the panel for this project, the page boots with the resolved config and picks the overrides up the moment you open the panel.
How source links open
Section titled “How source links open”Every source link in the panel prefers @devframes/service-open, the shared
open-in-editor service a Vite DevTools host installs. It resolves symlinks
before it decides, and it opens nothing outside the workspace root.
When that service is absent, or refuses a path outside the workspace root, the
panel falls back to the plugin’s own /__lit-open-in-editor endpoint. That one
trusts whatever Vite itself serves, server.fs.allow, so it covers linked
packages the stricter service turns down.
The in-page source overlay only ever uses the fallback endpoint: it runs in the
page, where no DevTools host and no service exist. Either path ends at
launch-editor, so both land on the same file and line.
Both pick the editor the same way. The panel passes the editor you chose
(config, env or the Settings tab’s override) to the service, mapped to the
command launch-editor knows, such as vscode to code. The overlay sends its
current editor key with the request, and the fallback endpoint maps it through
the same allow-list. A key with no command, or none at all, auto-detects.
How the docked panel talks to the page
Section titled “How the docked panel talks to the page”Hovering a row in the Components tree outlines that element in the app. How that instruction travels depends on where the panel is.
Docked inside the app’s own window, the panel talks to the page over a direct channel, with no round trip through the dev server. Opened as its own tab there is no page in that window to talk to, so the same command goes out over the server route instead. The outline is identical; only the path differs.
Deep links and RPC
Section titled “Deep links and RPC”Standing alone, the panel keeps its position in the URL hash and updates it as you click, so copying the address bar reopens the same view.
The hash, not the query string. The query is where the DevTools handshake token lives, and a token must not travel in a link you paste into an issue.
Docked, the panel is an iframe whose address nobody reads, so a link has to arrive as a call instead. Anything holding a DevTools client can ask the hub to bring the panel forward on a specific element:
await rpc.call('hub:docks:activate', { dockId: 'lit', params: {tab: 'components', componentId: 7},});Picking an element in the page uses exactly this. The plugin activates its own dock with the picked id attached.
Where the data goes
Section titled “Where the data goes”Because the definition owns the data rather than the UI, the panel is not its only reader. The same RPC functions are exposed as MCP tools on the DevTools hub, so a coding agent queries the live component tree, the update summary and the recorded events against the running app instead of guessing from source.
The panel and the agent see one authority, not two copies. That is why an agent turning recording on also changes what you see in an open panel.