A dev overlay inspector for cssints, built on what the dev server knows of every class: an element's classes grouped
by the css.* call that made them, the winner per property by the sheet's own rules (layer, specificity, order, the
conditions that hold now), and each winner checked against the browser's computed style.
// vite.config.ts of the app; @cssints/devtools in its devDependencies
import cssints from "cssints/vite";
import { defineConfig } from "vite";
export default defineConfig({ plugins: [cssints({ devtools: true })] });Alt+Shift+C or the cssints button (bottom right) opens it. Hover previews an element, a click picks it; ↑ parent,
↓ first child, ← → siblings; Esc stops picking, then closes. The four buttons in the panel header (◧ ⬒ ⬓ ◨) dock it
left, top, bottom or right (the default); drag the strip on its inner edge to resize it (at least 200 px, and a strip of
the page stays). The side and the two sizes are kept in localStorage (cssints-devtools:dock) per origin. The panel
covers the page beside it, and picking works on the rest of the page; the page is not pushed aside. The panel and the cascade rule are described in
docs/API.md, "Debugging: the devtools overlay"; the design in docs/research/devtools.md, "Our devtools".
devtools: true adds a plugin with apply: "serve": an HTML script tag, the module
virtual:cssints/devtools and two HMR events (cssints:devtools:get → cssints:devtools, and
cssints:devtools:stale after each transform). A build has none of it.devtools: "build". The table is a JSON asset, the overlay a chunk of its own, and the entry holds a loader
(a cssints button and Alt+Shift+C). The first open loads both; a call shows as file:line and its source, with no link
to an editor. The table has no path of the machine that built it. Sizes are logged by the build (this site: table 70 kB,
overlay 190 kB, the entry +0.85 kB).@media, @container, @supports, @scope) are evaluated for
the element by a probe sheet; states by Element.matches.dist/overlay.js
(Solid, Effect and the core bundled): an app needs neither Solid nor a second CSS file. No tokens in its styles:
@property is ignored inside a shadow root.// dev only, off by default; @opencode/client in the app's devDependencies
cssints({ devtools: true, opencode: true });
cssints({ devtools: true, opencode: { agent: "build", skills: ["cssints"] } });With an element picked, the panel has a prompt box (Enter sends, Shift+Enter is a new line). The dev server starts a session
on the running OpenCode V2 service (Service.discover of @opencode/client; the page never talks to it) in the nearest
folder up from the Vite root that has a .git, with a prompt of src/core/prompt.ts: your instruction, the element (tag,
DOM path, text, classes), the css.* calls that style it (file:line and the call's source), the winner of each property
and what it beat, and the tokens it reads; every list is cut. The box then shows running · <session id>, and completed,
failed or interrupted; with no service running it says so and keeps your text. The page and the server talk over the
HMR channel (cssints:devtools:ask, cssints:devtools:asked), not a route. A build has none of it. The idea, and the
calls to the V2 client, follow brendonovich/vite-plugin-opencode
(MIT, Brendan Allan), which marks the markup with Babel and so needs no table; here the table is the context.
src/core: Effect code, no overlay in it (an extension could host it): table.ts (the decoded table and its
model), css.ts (sheet parser, specificity, states), cascade.ts (inspect), page.ts (the Page service),
dom.ts (its browser layer), session.ts (the protocol and the state), prompt.ts (the prompt for OpenCode).src/ui: the Solid overlay (overlay.tsx, mount) and its cssints styles (styles.ts).Chrome, Firefox and WebKit, checked by test:browsers on apps/demo's dev server.
@scope proximity is not ranked; a logical and a physical property are two properties.<style> and attribute.The prompt box that starts an OpenCode session follows brendonovich/vite-plugin-opencode (MIT, Brendan Allan): the idea and the calls to the V2 client. No code is copied. The overlay is built with Solid (MIT).
bun run test # test/core.mjs (the cascade on happy-dom: hover vs media, @container, @supports, @scope,
# an unlayered rule, frame's kernel, within(), grouping), test/build.mjs (nothing in a build
# without the option; with "build": the table, the lazy chunk, the loader's size; apps/docs/test/browser.mjs opens it in Chrome),
# test/opencode.mjs (the dev server and a fake OpenCode service: the prompt as sent, the agent, the directory, the status, no service)
bun run test:browsers # builds dist/overlay.js, then the demo in Chrome, Firefox and WebKit (test/browsers.mjs; each dock: the panel on its side, a pick beside it, a drag of the grip, the button after Esc, the dock and sizes after a reload), then the prompt box in Chrome (test/ask-browser.mjs)