Skip to content

Devtools Console Refine: intuitive help + mobile access โ€‹

Status: APPROVED + BUILT (2026-08-03). Operator approved Option B; placement confirmed as a dedicated in-app page at #/devtools. Release held until EOD on operator's mark.

Decisions taken during build (operator can veto any at review):

  • Section 2 gating move: implemented. Gated on import.meta.env.VITE_APP_ENV !== 'production' (statically replaced, so prod builds still dead-code-eliminate the block; verified by building both ways and grepping dist/). Deployed dev + previews now expose 43 commands, up from 18. Isolated in its own commit if it needs to be dropped.
  • Panel entry: direct hash only. The 7-tap-on-version entry has no anchor: no screen currently displays a version line. Adding one to ProfileSettings would be new user-visible UI, so it was left out; say the word if you want it.
  • Hands-on findings folded in: the only boot hints pointed at SUB-helps (lantern.spoof.venue.help()), never lantern.help() itself; debug.auth.* summaries were just the legacy binding names (now real descriptions); PR preview builds registered zero commands because 'preview' was missing from the env allowlists (now included).

Heritage: builds on #367 (closed), which consolidated the scattered window.* debug helpers into the window.lantern.* registry (apps/web/src/lib/lanternDevtools/index.js). This spec is about making that registry pleasant to discover and usable from a phone.

Problem โ€‹

Verified via headless chromium against https://dev.ourlantern.app (2026-08-03):

  1. Nothing announces the tooling. lantern.help() is mentioned only in code comments. A dev opening the console sees scattered boot logs, none of which say "type lantern.help()". You have to already know.
  2. help() buries its own output. It prints a clean grouped list, then returns the raw registry array, so the console immediately dumps [{...}, {...}, ...] noise right under the pretty text.
  3. No argument hints. Help shows lantern.spoof.location.setStatus() with a one-line summary but no clue what arguments it takes. Usage examples live only in scattered one-off boot logs and per-namespace help() subcommands.
  4. Deployed dev only has 18 of ~51 commands. Everything registered inside if (import.meta.env.DEV) in main.jsx (seeders, cache utils, venue create/enrich/cleanup, area staleness) exists only on a local Vite server. import.meta.env.DEV is false in the deployed dev build, even though the registry's own env allowlist (DEFAULT_ENVS) was designed to make exactly this distinction. On-the-go testing happens against dev.ourlantern.app, where the most useful helpers are absent.
  5. Mobile browsers have no console at all. The entire lantern.* surface is unreachable from a phone, which is where venue/location testing actually happens.

Goals โ€‹

  • A first-time dev (or the operator on a new machine) discovers lantern.help() without being told.
  • Help output shows what each command takes, not just what it does.
  • Every registered command is runnable from a mobile browser on dev.ourlantern.app, no cable or remote-debugging session required.
  • Production stays locked out exactly as today (registry env allowlist; prod exposure remains explicit opt-in per command).

Non-goals โ€‹

  • No generic remote-debugging bridge (no chrome://inspect replacement, no network tab). If we ever want raw logs on device, an eruda toggle is a small later add (see matrix).
  • No changes to what the commands do, and no new commands beyond what the panel needs.
  • No prod-facing UI. The panel ships dev-env-only.

Recommendation โ€‹

Option B: polish help() + a registry-driven in-app dev panel. The registry already knows every command, its grouping, its summary, and its env gating; rendering it as a tap-to-run screen is the natural payoff of the #367 consolidation, and it solves mobile properly (typing lantern.spoof.location.set(37.77, -122.41) on a phone keyboard is not a solution). Eruda would bolt on a generic console without solving tap-to-run, so it stays a possible later add inside the panel, not the foundation.

OptionWhat it doesWhat it affectsRisks / implications
A: help polish onlyBoot banner, arg signatures, clean return, help('spoof') filterlanternDevtools/index.js + register call sitesCheap, but mobile stays unsolved; desktop-only win
B: A + registry-driven dev panel (recommended)Everything in A, plus a dev-only #/devtools screen listing commands by group, tap-to-run, arg inputs, output displaySame as A + one new screen, route, story, lazy chunkMost build effort of the three; needs arg metadata on ~51 registrations; payoff is on-the-go testing on any device
C: B + embedded eruda toggleB, plus lazy-load eruda from the panel for a full console/network/DOM inspector on deviceB + one lazy dev-only dependencyThird-party dep (~100KB, lazy). Real diagnostic value (network tab on phone), but generic UI; fine as a later increment

Design โ€‹

1. Help polish (lanternDevtools/index.js) โ€‹

  • Boot banner: one concise line at init in dev envs: ๐Ÿฎ lantern devtools ready: N commands. Type lantern.help(). Replaces the need to already know; the scattered per-module โœ… Dev mode: lines get consolidated or demoted.
  • Clean return: help() prints and returns undefined. Programmatic access stays via getRegistry() (and powers the panel).
  • Arg signatures: register() gains an optional usage string (e.g. 'set(lat, lng)') and optional example. Help renders lantern.spoof.location.set(lat, lng) instead of bare (). Registrations without usage render as today, so this rolls out incrementally; the ~10 arg-taking commands get usage in this pass.
  • Filter: lantern.help('spoof') prints just that group. No-arg behavior unchanged.
  • Alignment: compute pad width from the longest rendered path instead of the hardcoded padEnd(44).

2. Close the deployed-dev gap (decision needed) โ€‹

Move the main.jsx dev-only registrations from if (import.meta.env.DEV) to the registry's env allowlist (hostname/VITE_APP_ENV based, same detection the registry already inlines). Result: seeders, cache utils, and venue tools register on dev.ourlantern.app (and locally, as today) and stay locked out of prod via DEFAULT_ENVS. These commands mutate dev Firestore, which is what dev is for, but it widens who can invoke them from a browser on the dev site (dev is invite-gated and the URL is unlisted; commands still require the site to be open in your browser). Operator call: without this, the mobile panel on deployed dev shows only the 18 currently-registered commands and the panel loses most of its point.

3. Mobile dev panel (apps/web) โ€‹

  • Route: #/devtools, added to App.jsx hash routing. Direct navigation works (typeable on a phone). Secondary entry: 7 taps on the version line in the profile/settings screen (the Android developer-mode homage), which just navigates to the hash.
  • Gating: the screen component lazy-loads (import()) only when the route matches AND the registry env check passes (same appEnv resolution as the registry). In prod the route renders nothing and the chunk never loads. Prod-safe commands (forceRefresh, version) are deliberately NOT enough to mount the panel in prod; prod keeps the existing documented window.lanternForceRefresh() path.
  • UI: groups as collapsible sections mirroring help() grouping; each command row shows dotted path + summary; tap runs it. Commands with usage metadata render labeled text inputs (comma-split args, JSON-ish coercion: numbers, booleans, quoted strings). Output area shows the command's return value (pretty-printed JSON) and anything it console.logs during execution (temporary console tee while a command runs). Design-system tokens throughout (design skill before styling); colocated Storybook story (enforced).
  • Safety valve: rows for commands whose summary starts with a destructive verb (clear/cleanup/strip/force) get a confirm tap (tap once to arm, tap again to run). Cheap, prevents fat-finger seed-wipes on a phone.

4. Testing โ€‹

  • Unit: usage/example registration + help rendering (extend lanternDevtools.test.js), filter behavior, clean return.
  • Component: panel renders registry groups, runs a mock command, arg coercion cases, destructive-confirm arming.
  • Manual: headless chromium against a local build for the panel route; operator on-device pass on Android for the tap-to-run flow (headless signed-in flows on deployed dev stay blocked by App Check, so the on-device pass is the real verification).

Rollout โ€‹

Single PR (one session, one theme): help polish + gating move + panel, logical commits, draft until EOD mark. File one tracking issue on approval; PR closes it. No prod implications (dev-env-only surface).

Open questions (operator) โ€‹

  1. Approve Option B as scoped? (Or trim to A / extend to C.)
  2. Section 2 gating move: expose the import.meta.env.DEV-only helpers on deployed dev, yes or no?
  3. Panel entry points: is #/devtools + 7-tap on version both, or direct hash only?

Built with VitePress