Dev Inspector

Introduction

The dev inspector is a click-to-source overlay that runs only under the dev server. Hold a modifier chord and hover any rendered element to highlight the source it came from; click to act on it - edit a translation string in place, edit a markdown/markdown-translation value in a dialog, edit an element's class, or open the source file in your editor at the exact line.

It is injected into every page the dev server renders, alongside the live-reload client, and is absent from static site generation (bun ssg). Nothing it does - the highlight overlay, the data-ree/data-md source stamps, the edit dialogs - ever reaches generated output.

The Two Chords

The inspector has two independent activation chords. Which one is held decides what a click does:

ChordTierWhat a click does
Meta + Shift + hover/clickContentEdit a translation string, edit markdown, or open the source in the editor
Alt + Shift + hover/clickClassEdit the nearest element's class attribute

Meta is Cmd on macOS and the Win key on Windows/Linux, so the content chord is Cmd+Shift / Win+Shift. Win+Shift+click does not collide with the reserved Win+Shift+<key> OS shortcuts (screenshot, move-window, ...) because those all need a third keyboard key. Plain Alt+click is avoided (PowerToys reserves it on Windows); Alt+Shift is not.

Hovering with a chord held draws a coloured overlay on the target and a label naming the action, so you always see what a click will do before you commit to it.

Content Tier (Meta + Shift)

Clicking with Meta+Shift held picks the action from what you clicked:

  • Edit text (green) - a plain {_ path } translation. Editable in place with a contenteditable outline. Enter saves; Esc or clicking away discards. A save only ever happens on an explicit Enter.
  • Edit markup (purple) - a {- path } (raw HTML) or {@ path } (markdown) translation. Opens a dialog with the raw source fetched over the socket; edit it and Save (or Ctrl+Enter). Esc or Cancel discards.
  • Open code (grey) - anything else stamped from a .ree source (a block element or a component). Opens the source file in your editor at the line the element came from. See Editor Configuration.
  • Open markdown (grey) - a rendered block stamped from a .md source. Same open-in-editor behavior, labeled distinctly so you know which file will open.

Translation edits are written back to the route's {lang}.json file; the inspector resolves the language and namespace from the page you are viewing. Because the dev server live-reloads on source changes, the page re-renders with your edit immediately.

Class Tier (Alt + Shift)

Clicking with Alt+Shift held edits the nearest element's class attribute (amber highlight). Hovering shows the "edit class" label next to the element's current class value, in light text on black, so you can see what you're about to change before clicking. A small input opens prefilled with the current class value:

  • Enter or Save writes the new class into the .ree source and the page live-reloads. Esc, Cancel, or clicking away discards.
  • Clearing the value (empty input) removes the class attribute entirely.

The class tier targets both block-level tags (div, section, h1-h6, p, ul, ...) and interactive/form elements (a, button, label, img, input, select, textarea), so a button or link inside a wrapper is directly reachable. Pure text-inline tags (span, strong, em, code, ...) are not targeted - a click on one resolves up to the nearest targetable ancestor.

Two safety limits:

  • Literal classes only. A dynamic class such as class="{= badge_color(x) }" is refused - the rendered value cannot be mapped back to the expression that produced it. Only the class value's own dynamism matters: a tag with a dynamic href but a literal class is still editable.
  • .ree source only. Class editing patches .ree templates. Blocks rendered from .md files are not class-editable (their source is markdown, which has no class attribute).

Editor Configuration

The Open code action launches an editor via the OPEN_IDE environment variable (set it in .env). It is strict - there is no default; if OPEN_IDE is unset or set to an unknown key, the open action reports a clear error rather than guessing.

OPEN_IDE=vscode
OPEN_IDEEditorLaunch command
vscodeVS Codecode --goto <file>:<line>
zedZedzed <file>:<line>
nvimNeovimnvim +<line> <file>
sublimeSublime Textsubl <file>:<line>
ideaIntelliJ IDEAidea --line <line> <file>

The editor's CLI launcher must be on your PATH (e.g. VS Code's code command, installed via its "Shell Command: Install 'code' command in PATH" action). To add another editor, extend the IDE_COMMANDS map in scripts/dev/open_in_editor.ts - each entry is an argv template where {file} and {line} are substituted, so editors with different flag ordering (like Neovim's +<line> before the file) are handled by their own row.

See also Configuration for the full environment-variable list.

How It Works

The inspector is stitched together from a few dev-only pieces, none of which run during generation:

  • Source stamps. As each .ree template is loaded, block and interactive tags are stamped with data-ree="<file>:<line>"; rendered markdown blocks are stamped with data-md="<file>:<line>". {_ }/{- }/{@ } lookups are wrapped in a marker span carrying their key. The browser resolves a click by walking up to the nearest stamped ancestor.
  • Browser client. scripts/dev/inspector-client.js is injected next to the live-reload client by the dev server's </body> rewrite. It draws the overlay, runs the edit dialogs, and talks to the server over the same /__reload WebSocket.
  • Server handlers. Editor opens go to the /__ree_open HTTP endpoint; translation and class edits ride the WebSocket. Every file path a request supplies is validated against the project root before any read or write, so a click can only ever touch a file inside the project.

Because all of this lives in scripts/dev/ and the dev-only template engine, bun ssg generation carries no stamps, no client script, and no endpoints.