Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7756029
ci: add oxlint with a ratchet on new warnings
erkamyaman Nov 7, 2026
9aa475c
refactor(overlay): type window.ng as NgDebugApi
erkamyaman Nov 7, 2026
af5a528
feat(hub): mount extra devframes with extraDevframes
erkamyaman Nov 7, 2026
3d27b88
feat(http): record the page's native fetch() calls
erkamyaman Nov 7, 2026
2c9b134
feat: capture SSR and Vite errors on the dev server
erkamyaman Nov 7, 2026
abe21b9
feat: add the Performance and Build inspectors
erkamyaman Nov 7, 2026
34d8cb1
fix: say Loading on the Build tab and keep its data in static reports
erkamyaman Nov 7, 2026
5a38922
ci: hold source files over 1,500 lines at their length
erkamyaman Nov 7, 2026
36ba562
refactor(mcp): move devframe.ts agent tools into their own modules
erkamyaman Nov 7, 2026
b5ac4f7
build(extension): rebuild the panel bundle
erkamyaman Nov 7, 2026
fa67e7e
fix: count every interaction for the INP percentile
erkamyaman Nov 7, 2026
921c46a
fix(ui): keep the server errors status read after the list clears
erkamyaman Nov 7, 2026
41372d7
docs(mcp): list the performance and build tabs for open-in-panel
erkamyaman Nov 7, 2026
1276626
Merge remote-tracking branch 'origin/main' into feat/collectors-refac…
erkamyaman Nov 7, 2026
c45a117
style: drop an unused import and mark an intended copy for the lint r…
erkamyaman Nov 7, 2026
2f61477
test: type the test doubles main added instead of using any
erkamyaman Nov 7, 2026
ff9c5aa
fix(ui): use the shared search field on the Build tab
erkamyaman Nov 7, 2026
6107feb
ci: record the line counts main added in the lint baseline
erkamyaman Nov 7, 2026
5931cd2
Merge remote-tracking branch 'origin/main' into feat/collectors-refac…
erkamyaman Nov 7, 2026
55bcbf9
style: clear the lint warnings #485 and #487 brought in
erkamyaman Nov 7, 2026
c923c60
ci: record the line counts #485 added to config.ts and devframe.ts in…
erkamyaman Nov 7, 2026
d251530
build(extension): rebuild extension/ui after the main merges
erkamyaman Nov 7, 2026
2f33a45
Merge remote-tracking branch 'origin/main' into feat/collectors-refac…
erkamyaman Nov 7, 2026
b112f54
fix(docs): label the Performance and Build inspectors in the agent to…
erkamyaman Nov 7, 2026
8ef013b
build(extension): rebuild extension/ui after the main merges
erkamyaman Nov 7, 2026
8a34baf
fix(http): keep the fetch collector out of production bundles
erkamyaman Nov 8, 2026
cca81a6
fix(hub): check lazy extraDevframes entries for the reserved id
erkamyaman Nov 8, 2026
3a4d606
fix(http): keep native fetch() calls out of cache misses and mocks
erkamyaman Nov 8, 2026
01d97cc
ci: add MCP e2e cases for the server errors, web vitals and build tools
erkamyaman Nov 8, 2026
7a2813c
fix(forms): read the default page in explain-field with only a path
erkamyaman Nov 8, 2026
61ac246
fix(router): refuse an unknown app in clear-router-navigations
erkamyaman Nov 8, 2026
93285dd
docs: list the new inspectors and fix the extra devframes and transfe…
erkamyaman Nov 8, 2026
d7d77a1
Merge remote-tracking branch 'origin/main' into feat/collectors-refac…
erkamyaman Nov 8, 2026
e44cec9
ci: record the line counts #490 and #491 added in the lint baseline
erkamyaman Nov 8, 2026
62c80da
ci: clear the setter warnings main brought and record its panel line …
erkamyaman Nov 8, 2026
9ee60ff
build(extension): rebuild extension/ui after the main merge
erkamyaman Nov 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .claude/skills/devtools-inspector/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Read `docs/contributing/coding-standards.md` ("Reading data from the page") befo

- Put collection logic in its own module (`<area>-collector.ts`) and keep `overlay.ts` changes to wiring: import, attach, push, `leave()` and the returned cleanup.
- Read Angular through the debug APIs on `window.ng` and verify each field against `node_modules/@angular/core/fesm2022` (or the library's fesm build). Known helpers:
- `debug-api.ts`: `NgDebugApi`, the typed `window.ng` with `@since` notes, and `pageDebugApi()`. Add a function there (and to `NG_DEBUG_FUNCTIONS`) before a collector reads it.
- `element-id.ts`: stable `WeakMap` ids for elements (`elementId`, `elementById`).
- `injector-tree.ts`: `className()` strips bundler `_` prefixes, `tokenName()`, `dependenciesOf()`, environment injector walk.
- `serialize.ts`: safe, size-limited serialization.
Expand All @@ -31,6 +32,8 @@ Read `docs/contributing/coding-standards.md` ("Reading data from the page") befo

## Server side (`devframe.ts`, `rpc/`)

- `devframe.ts` wires the setup. A new inspector goes in its own `rpc/<area>-register.ts` (see `rpc/errors-register.ts`), and agent tools of an existing area live in `devframe-<area>-tools.ts`, which take the setup values they read as an `env` object typed through `devframe-env.ts`. Keep `devframe.ts` under the line ratchet of `pnpm lint`.

- Keep a `Map<pageId, report>` with `reportedAt`, drop pages older than `ttl(pageId)` (from `visibility.ttl()`, so a page with a current heartbeat never expires) in the shared expiry interval, and write the combined value into the shared state.
- Page actions that the panel triggers (restore, highlight, run) go panel -> `request-<area>-action` -> broadcast `<area>-action` to the page -> `<area>-action-result`, keyed by `requestId`, with a timeout.
- Source scans in `rpc/` enrich or stand in for live data. They must return a `kind` for anything the Dashboard counts.
Expand All @@ -45,7 +48,7 @@ Read `docs/contributing/coding-standards.md` ("Reading data from the page") befo

- Describe what the tool returns, where the data comes from and what an empty answer means. Answer in markdown.
- Give each new tool or resource one entry in `AGENT_ENTRIES` (`packages/devtools/src/config.ts`): its inspector, safety tier, the `actions` it writes for and whether it needs a page. `AGENT_INSPECTOR`, `AGENT_SAFETY`, `ACTION_TOOLS`, `PAGE_AGENT_ENTRIES` and the tags derive from it. `packages/devtools/src/__tests__/agent-metadata.test.ts` fails on a registered tool without an entry; update `packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json` with it.
- Update descriptions in `devframe.ts` when data shapes change, and the tool list on the docs site (apps/docs/src/content/agents/tools.md). Use the `devtools-docs` skill for that edit.
- Update tool descriptions (in `devframe.ts`, `devframe-<area>-tools.ts` or the inspector's register module) when data shapes change, and the tool list on the docs site (apps/docs/src/content/agents/tools.md). Use the `devtools-docs` skill for that edit.

## Tests

Expand Down
1 change: 1 addition & 0 deletions .claude/skills/devtools-verify/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Run them in this order; all must pass:

```sh
pnpm format:check
pnpm lint
pnpm commit:check
pnpm skills:check
pnpm typecheck
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
- name: Check formatting
run: pnpm format:check

- name: Lint (no new oxlint warnings)
run: pnpm lint

- name: Validate agent skills and roles
run: pnpm skills:check

Expand Down
18 changes: 18 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "unicorn", "oxc"],
"categories": {
"correctness": "warn"
},
"rules": {
"typescript/no-explicit-any": "warn"
},
"ignorePatterns": [
"**/dist/**",
"**/.angular/**",
"**/node_modules/**",
"extension/ui/**",
"examples/nativescript/platforms/**",
"examples/nativescript/hooks/**"
]
}
23 changes: 12 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@ Pangular Inspector inspects a running Angular app and serves what it finds to a

### Commands

| Command | What it runs |
| ---------------------- | ------------------------------------------------------------------------- |
| `pnpm test:devtools` | Package tests |
| `pnpm test:panel` | Panel tests |
| `pnpm test:axe` | Builds a static report and runs axe on every panel view |
| `pnpm typecheck` | TypeScript and template checks for every project |
| `pnpm format:check` | Prettier, including the docs markdown |
| `pnpm skills:check` | Checks the skills and roles in `.claude` and the skills the package ships |
| `pnpm docs:build` | Builds the docs site with its link guards |
| `pnpm extension:build` | Rebuilds `extension/ui` from `app` |
| `pnpm commit:check` | Checks the commit messages on the branch |
| Command | What it runs |
| ---------------------- | --------------------------------------------------------------------------------------------------------------- |
| `pnpm test:devtools` | Package tests |
| `pnpm test:panel` | Panel tests |
| `pnpm test:axe` | Builds a static report and runs axe on every panel view |
| `pnpm typecheck` | TypeScript and template checks for every project |
| `pnpm format:check` | Prettier, including the docs markdown |
| `pnpm lint` | oxlint with a ratchet: no file gets more warnings than `lint-baseline.json`, and no file over 1,500 lines grows |
| `pnpm skills:check` | Checks the skills and roles in `.claude` and the skills the package ships |
| `pnpm docs:build` | Builds the docs site with its link guards |
| `pnpm extension:build` | Rebuilds `extension/ui` from `app` |
| `pnpm commit:check` | Checks the commit messages on the branch |

The words this project uses for its own concepts (overlay, collector, hub, devframe, agent tool and so on) are defined in `docs/CONTEXT.md`. Use them the same way in code, docs and issues.

Expand Down
8 changes: 6 additions & 2 deletions app/src/__tests__/app-shell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,9 @@ describe('App scroll memory', () => {
Object.defineProperty(main, 'scrollTop', {
configurable: true,
get: () => top,
set: (value: number) => (top = value),
set: (value: number) => {
top = value;
},
});
const buttons = () => [...host(fixture).querySelectorAll<HTMLButtonElement>('nav button')];
// An earlier test can leave another tab in the URL.
Expand Down Expand Up @@ -373,7 +375,9 @@ describe('App scroll memory while content loads', () => {
Object.defineProperty(main, 'scrollTop', {
configurable: true,
get: () => top,
set: (value: number) => (top = Math.max(0, Math.min(value, max))),
set: (value: number) => {
top = Math.max(0, Math.min(value, max));
},
});
const buttons = () => [...host(fixture).querySelectorAll<HTMLButtonElement>('nav button')];
buttons()[0].click();
Expand Down
8 changes: 6 additions & 2 deletions app/src/__tests__/network-live-sort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ describe('the network live controls', () => {
Object.defineProperty(wrap, 'scrollTop', {
configurable: true,
get: () => top,
set: (value: number) => (top = value),
set: (value: number) => {
top = value;
},
});
const follow = host().querySelector<HTMLInputElement>('app-live-controls input')!;
expect(follow.checked).toBe(false);
Expand Down Expand Up @@ -204,7 +206,9 @@ describe('Follow newest with SSR requests on the page', () => {
Object.defineProperty(wrap, 'scrollTop', {
configurable: true,
get: () => tops[i],
set: (value: number) => (tops[i] = value),
set: (value: number) => {
tops[i] = value;
},
}),
);
host().querySelector<HTMLInputElement>('app-live-controls input')!.click();
Expand Down
81 changes: 81 additions & 0 deletions app/src/__tests__/network-native-fetch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { TestBed, type ComponentFixture } from '@angular/core/testing';
import { afterEach, describe, expect, it } from 'vitest';
import { NetworkInspector } from '../pages/network-inspector';

let fixture: ComponentFixture<NetworkInspector>;

const call = (id: string, overrides: Record<string, unknown> = {}) => ({
id,
url: `/api/${id}`,
method: 'GET',
status: 200,
durationMs: 12,
side: 'client' as const,
cacheHit: false,
faulted: false,
at: 1000,
...overrides,
});

describe('NetworkInspector native fetch calls', () => {
afterEach(() => {
fixture?.destroy();
TestBed.resetTestingModule();
});

it('tags a native fetch() call and leaves HttpClient calls untagged', async () => {
fixture = TestBed.createComponent(NetworkInspector);
document.body.append(fixture.nativeElement);
fixture.componentInstance.pages.set([
{
pageId: 'p1',
url: '/trips',
title: 'Trips',
hydration: null,
reportedAt: 1000,
calls: [call('client'), call('native', { nativeFetch: true })],
},
]);
await fixture.whenStable();
const host = fixture.nativeElement as HTMLElement;
const rowOf = (id: string) =>
host.querySelector(`button[data-call-id="${id}"]`)!.closest('tr')!;
const tags = (id: string) =>
[...rowOf(id).querySelectorAll('.tag')].map((tag) => tag.textContent!.trim());
expect(tags('native')).toContain('fetch()');
expect(tags('client')).not.toContain('fetch()');
});

it('offers no mock for a native fetch() call and says why', async () => {
fixture = TestBed.createComponent(NetworkInspector);
document.body.append(fixture.nativeElement);
const inspector = fixture.componentInstance;
inspector.pages.set([
{
pageId: 'p1',
url: '/trips',
title: 'Trips',
hydration: null,
reportedAt: 1000,
calls: [call('client'), call('native', { nativeFetch: true })],
},
]);
await fixture.whenStable();
const host = fixture.nativeElement as HTMLElement;
const open = async (id: string) => {
host.querySelector<HTMLButtonElement>(`button[data-call-id="${id}"]`)!.click();
await fixture.whenStable();
return host.querySelector('#call-preview')!;
};
const mockButton = (preview: Element) =>
[...preview.querySelectorAll('button')].find((b) =>
b.textContent!.includes('Mock this request'),
);
let preview = await open('native');
expect(mockButton(preview)).toBeUndefined();
expect(preview.textContent).toContain('skips the HttpClient interceptor');
preview = await open('client');
expect(mockButton(preview)).toBeDefined();
expect(preview.textContent).not.toContain('skips the HttpClient interceptor');
});
});
2 changes: 2 additions & 0 deletions app/src/__tests__/network-ssr-requests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ describe('NetworkInspector SSR requests', () => {
calls: [
call({ id: 'b1', side: 'client', url: '/api/products', cacheHit: true, at: 1200 }),
call({ id: 'b2', side: 'client', url: '/api/quote', method: 'POST', at: 1200 }),
// A native fetch() to the same URL never reads the transfer cache: no cache miss.
call({ id: 'b3', side: 'client', url: '/api/products', at: 1300, nativeFetch: true }),
],
},
]);
Expand Down
Loading
Loading