Skip to content

analog: fixes and improvements for the Analog inspector #352

Description

@erkamyaman

Context

Fixes and improvements from a code audit of the analog inspector, done in October 2026. Roadmap work lives in #340, #341, #342 and #343. Items marked (to confirm) were not reproduced; check them before fixing.

Problems

Medium

  • Server calls mix across tabs: the call log is a module-level calls[] and AnalogCall has no pageId, so calls, duplicate loads, refetches and their lint findings from several tabs end up in one list. pangular:analog-server-calls has no page argument. Tag browser requests with the page id (a header set by the overlay, or a Referer-to-page map), store it on AnalogCall, add a page filter to the tool and the Server tab, and run duplicate and refetch detection per page (packages/devtools/src/analog-server-log.ts:40-89, packages/devtools/src/rpc/analog-register.ts:312-327).
  • Lint reads the newest page, not the shown one: analogLint uses state.pages[0], while the panel picks its page with hostPageId but calls analog-lint without it. A panel docked in tab A can show tab B's hydration errors and restart-needed findings. Add an optional pageId to analogLint, the analog-lint RPC and pangular:analog-lint, and pass hostPageId from the panel (packages/devtools/src/rpc/analog-tools.ts:665, app/src/pages/analog-inspector.ts:2273, app/src/pages/analog-inspector.ts:2426).
  • Synchronous rescans on every push: project() is cached for only 2 s (SCAN_CACHE_MS) and runs on every push-analog and every panel refresh, which fires 300 ms after any state change. Each scan walks up to 4000 files with readdirSync and readFileSync and blocks the Vite event loop. Invalidate the cache from Vite's watcher (src/app/pages, src/app/routes, src/server, src/content, vite.config) instead of a TTL, and have the panel refetch lint and render only when calls or pages change (packages/devtools/src/rpc/analog-register.ts:45, packages/devtools/src/rpc/analog-register.ts:197-215, app/src/pages/analog-inspector.ts:2379-2383).
  • Server functions with type arguments are missed: the regex =\s*(\w+)\s*\( doesn't match export const getX = serverFn<Input>(...), and re-exports are skipped too. Those functions drop out of the list and call names, and the server-without-load lint can fire falsely. Allow (?:<[^>]*>)? before the paren, or parse with ts.createSourceFile (packages/devtools/src/rpc/analog-scan.ts:411-413).
  • Config parsing stops after 4000 characters: analogConfig reads source.slice(start, start + 4000), so routeRules, prerender.routes or apiPrefix past a large nitro block are silently ignored. The lazy prerender regex can also pick up a routes: key outside the prerender block. Slice to the matching close paren with depth counting, read routes only inside prerender: {...}, and report when the config couldn't be fully parsed (packages/devtools/src/rpc/analog-scan.ts:560, packages/devtools/src/rpc/analog-scan.ts:578-580).

Low

  • Server files only looked up in src/app/pages: serverFiles walks src/app/pages only, while routeFiles() also walks app/routes and src/app/routes. .server.ts files beside routes there are missed by orphan-server-file and the existence check in resolveAnalogReport. Walk the same roots as routeFiles() (packages/devtools/src/rpc/analog-scan.ts:691) (to confirm).
  • load() payload stringified on every tick: loadSummary runs JSON.stringify twice (redacted preview and byte count) on every push, even when the data hasn't changed. Cache the summary in a WeakMap keyed by the data object (packages/devtools/src/analog-runtime.ts:152-166, packages/devtools/src/analog-runtime.ts:299-307).
  • Report strings have no length limit: isAnalogReport checks types and counts only, and redactAnalogReport clips hydrationErrors only after redaction. url, load.preview, configPaths entries and chain paths can be any size and are run through regex redaction and copied into shared state. Cap them in isAnalogReport or clip before redaction (url 2000, preview 1000, path 500) (packages/devtools/src/rpc/analog-tools.ts:52-82, packages/devtools/src/analog-redact.ts:10-23).
  • Analog RPCs missing from RPC_INSPECTOR: no push-analog, forget-analog-page or analog-* entries. It works only because registerAnalog runs behind on.analog and agent tools fall back on the analog- prefix. Add push-analog, forget-analog-page, analog-clear-calls, analog-project, analog-explain-url, analog-lint, analog-render, analog-text and analog-call-api (packages/devtools/src/config.ts:430, packages/devtools/src/config.ts:611).
  • list-pages leaves out Analog pages: summarizePages gets components, signals, injectors, NgRx, forms, router, pipes and http, but not analog, so the ids the page argument of analog-current-page needs aren't discoverable from Analog reports. Pass the analog pages in (packages/devtools/src/devframe.ts:2912-2925).
  • Lint badge counts info findings as warnings: const errors = this.findings().length counts every finding and tones it warn, so the info-only api-outside-prefix makes the tab look broken. Count errors and warnings apart, use the bad tone for errors, and leave info out (app/src/pages/analog-inspector.ts:2359, app/src/pages/analog-inspector.ts:2370).
  • Shadowed-page link only matches .page.ts: shadowed() pulls the file from the message with /(\/\S+\.page\.ts)/, so .page.analog and .page.ag pages are never linked. Add the page file as a structured field on the content-shadows-page finding instead of parsing the message (app/src/pages/analog-inspector.ts:2518-2523, packages/devtools/src/rpc/analog-scan.ts:1020-1028).
  • Page chain follows firstChild only: files rendered in named or auxiliary outlets never show in the current page chain. Walk every child snapshot and tag each entry with its outlet (packages/devtools/src/analog-runtime.ts:103-121).
  • Load endpoint URLs aren't encoded: loadEndpointUrl fills params without encodeURIComponent, and the catch-all falls back to the last param value, so explained endpoints are wrong for values with reserved characters or routes with several params plus a catch-all. Encode each value and map the catch-all by its own param name (packages/devtools/src/rpc/analog-tools.ts:137-150).

Improvements

  • Server route playground: fill :params from the route pattern, edit query, headers and cookies, keep request history per route, show response headers and timing, and copy as curl or fetch. Today the sender takes method, path and a JSON body only (app/src/pages/analog-inspector.ts, packages/devtools/src/rpc/analog-register.ts).
  • Payload tab: list each TransferState entry with its load() or server function name (nameServerFns already exists), size and whether the browser reused it, and link duplicate-load findings to the key (packages/devtools/src/analog-runtime.ts, packages/devtools/src/rpc/analog-tools.ts, app/src/pages/analog-inspector.ts).
  • Runtime config view: show import.meta.env VITE_/ANALOG_ keys, Nitro runtimeConfig and the resolved analog() options, redacted with redaction.secretNames (packages/devtools/src/rpc/analog-scan.ts, packages/devtools/src/rpc/analog-register.ts).
  • Nitro internals: list Nitro plugins, tasks (run action in ACTION_TOOLS), storage mounts and cached-route entries behind isr/swr rules, with a purge action (packages/devtools/src/rpc/analog-register.ts, packages/devtools/src/config.ts).
  • Per-route data inspector: show the full redacted load() value as a lazy tree on demand through an RPC to the page, not just the 1000-character preview (packages/devtools/src/analog-runtime.ts, app/src/pages/analog-inspector.ts).
  • Render mode check: fetch a page with Accept: text/html from the hub and compare cache-control, x-nitro-prerender and x-analog-no-ssr with the configured mode (packages/devtools/src/rpc/analog-tools.ts, app/src/pages/analog-inspector.ts).
  • Content tab: a frontmatter schema check driven by a config option, contentFilesSource/injectContentFiles usage, and a rendered markdown preview (packages/devtools/src/rpc/analog-scan.ts, app/src/pages/analog-inspector.ts).
  • Link a page file to its Vite transform stack so the .analog/.ag compile and the serverFn id rewrite can be seen (packages/devtools/src/vite.ts, app/src/pages/analog-inspector.ts).
  • Build analysis: after analog build, show per-route chunk sizes from dist/analog and which pages pull which lazy chunks, next to the prerender plan (packages/devtools/src/rpc/analog-scan.ts, app/src/pages/analog-inspector.ts).

Already tracked elsewhere


Generated by Claude Code

Activity

  1. added
    bugSomething is broken or shows wrong data
    featureA feature request or a pull request that adds one
    on Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or shows wrong datafeatureA feature request or a pull request that adds one

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions