From 89e7e8c2fb76140c9df1da20249bfde7d511a518 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:22:54 +0300 Subject: [PATCH 01/15] docs: describe reads after change detection instead of polling Since the overlay follows change detection on Angular 20 and later, the signals page and the voice example in the writing guide still spoke of a fixed poll every 3 seconds. Say the overlay reads the page instead. --- apps/docs/src/content/contributing/writing-docs.md | 2 +- apps/docs/src/content/inspectors/signals.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/src/content/contributing/writing-docs.md b/apps/docs/src/content/contributing/writing-docs.md index 4888139..96614e0 100644 --- a/apps/docs/src/content/contributing/writing-docs.md +++ b/apps/docs/src/content/contributing/writing-docs.md @@ -29,7 +29,7 @@ Orient every page around what the reader is trying to do. Ask: _what does the de - Use second person and the imperative: "Open the Router tab", not "We can open the Router tab". - Use present tense: "The tab shows", not "The tab will show". -- Use active voice: "The overlay reads the page every 3 seconds", not "The page is read every 3 seconds". +- Use active voice: "The overlay reads the page after change detection", not "The page is read after change detection". - One idea per sentence. Keep sentences short and plain. - Put the condition first: "If the tab is empty, check that the app runs in development mode." - Use sentence case for headings. Capitalize only the first word and proper nouns. diff --git a/apps/docs/src/content/inspectors/signals.md b/apps/docs/src/content/inspectors/signals.md index 3739add..30152c6 100644 --- a/apps/docs/src/content/inspectors/signals.md +++ b/apps/docs/src/content/inspectors/signals.md @@ -71,7 +71,7 @@ The live graph reads `ng.ɵgetSignalGraph` with the component's injector, from ` ### Exact and sampled values -Exact **set** entries come from a hook on signal writes. The overlay matches a write to a node by its label, so only signals with a `debugName` get exact entries. It samples everything else on each poll, as **sampled** entries. +Exact **set** entries come from a hook on signal writes. The overlay matches a write to a node by its label, so only signals with a `debugName` get exact entries. It samples everything else each time it reads the page, as **sampled** entries. Pass a debugName to signal() to get exact history entries and a readable label on the card. @@ -143,7 +143,7 @@ When the picked component is gone or has no graph, a notice appears and the tab The default follows the deepest component in the primary router outlet. It skips named outlets. Pick the component yourself to pin it. - Exact entries need a debugName on the signal. Without one, the overlay samples values on each poll. + Exact entries need a debugName on the signal. Without one, the overlay samples values each time it reads the page. A computed that nothing has read yet has no value. It fills in after its first read. From 94afc969c4c39a6e422d48e0231e703d493a2f95 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:23:35 +0300 Subject: [PATCH 02/15] docs: say when the MCP endpoint answers other addresses The MCP route only requires a loopback peer when it has no bearer token. With the Express hub's token, a request from another address passes if it sends the token and a loopback Origin, which any client can forge. The Security and MCP server pages said every request had to come from a loopback address. --- apps/docs/src/content/agents/mcp-server.md | 2 +- apps/docs/src/content/security.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/docs/src/content/agents/mcp-server.md b/apps/docs/src/content/agents/mcp-server.md index 706616b..8aacce4 100644 --- a/apps/docs/src/content/agents/mcp-server.md +++ b/apps/docs/src/content/agents/mcp-server.md @@ -99,7 +99,7 @@ If you mount the devtools panel without the hub, at `/__ng-devtools/`, the endpo ### Send an Origin header - The HTTP endpoint only answers requests from this machine that carry a local Origin header, such as http://localhost:4000. Requests without one get 403 Forbidden. If your MCP client does not send an Origin header, add it in the client config. + The HTTP endpoint only answers requests that carry a local Origin header, such as http://localhost:4000. Requests without one get 403 Forbidden. If your MCP client does not send an Origin header, add it in the client config. The header value is the origin of your dev server. Every example below sets it. diff --git a/apps/docs/src/content/security.md b/apps/docs/src/content/security.md index 6d529de..fafc606 100644 --- a/apps/docs/src/content/security.md +++ b/apps/docs/src/content/security.md @@ -109,10 +109,12 @@ The CLI server binds to `localhost` and asks for a one-time code. `--host` chang ### MCP endpoint -The HTTP MCP endpoint answers only requests from a loopback address that carry a loopback `Origin` header. +The HTTP MCP endpoint answers only requests that carry a loopback `Origin` header. In the Vite plugin, the request must also come from a loopback address, like every devtools request. While the one-time code is on, the endpoint also asks for a bearer token. That is the Express hub by default, and the Vite plugin when its code is on. The hub prints a generated token when it starts. Set `NG_DEVTOOLS_MCP_TOKEN` to choose the token yourself. Requests without the right `Authorization: Bearer ` header get `401`. The stdio server needs no token. See [Send a token](/agents/mcp-server#send-a-token). +Without a token, the Express hub answers only requests from a loopback address. With a token, it also answers other addresses that send the right token and a loopback `Origin`. Any client can set that header, so treat the token like a password. + ### Chrome extension The extension has host permissions for loopback hosts only: `localhost` and its subdomains, `127.0.0.1` and `[::1]`, over HTTP and HTTPS. On those hosts, the panel looks for the devtools server as soon as it opens. From edd790c317959ba6105e9b00d0f89765729584ac Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:23:55 +0300 Subject: [PATCH 03/15] docs: link auth, the MCP token and the config options from each setup The configuration, tunnel auth and MCP token changes each landed on their own pages. The Express and Vite option tables never mentioned the devtools options, the Analog guide listed three plugin options without auth, the Vite auth section didn't say the code also turns on the MCP token, and the MCP server page didn't say agent.readOnly drops the action tools. --- apps/docs/src/content/agents/mcp-server.md | 2 +- apps/docs/src/content/getting-started/express.md | 2 ++ apps/docs/src/content/getting-started/vite.md | 4 ++++ apps/docs/src/content/guides/analog.md | 15 +++++++++------ 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/docs/src/content/agents/mcp-server.md b/apps/docs/src/content/agents/mcp-server.md index 8aacce4..5a0c635 100644 --- a/apps/docs/src/content/agents/mcp-server.md +++ b/apps/docs/src/content/agents/mcp-server.md @@ -212,7 +212,7 @@ The server marks read-only tools as read-only for your client. Five tools act on | `fill-form` | [Act on a form](/agents/tools#act-on-a-form) | | `analog-call-api` | [Call a server route](/agents/tools#call-a-server-route) | -Your client can ask you before it runs them. +Your client can ask you before it runs them. To drop them from the server, set `agent.readOnly`. See [Inspectors and agent tools](/getting-started/configuration#inspectors-and-agent-tools). ### Pages and tabs diff --git a/apps/docs/src/content/getting-started/express.md b/apps/docs/src/content/getting-started/express.md index 9216f08..96f0db0 100644 --- a/apps/docs/src/content/getting-started/express.md +++ b/apps/docs/src/content/getting-started/express.md @@ -99,6 +99,8 @@ With `ws: {sidecar: true}`, the WebSocket runs on its own port, picked automatic | `allowedOrigins` | loopback origins and the Chrome extension | Extra origins allowed to open the WebSocket. A list replaces the Chrome extension default. `false` turns the origin check off. | | `mcp` | a bearer token | Mounts the MCP endpoint at `__mcp` and asks for a bearer token. With `auth: false` the default is `'auto'`: it mounts once agent tools exist and asks for no token. See [Send a token](/agents/mcp-server#send-a-token). | +The hub also takes the devtools options, such as `inspectors`, `agent`, `actions`, `redaction` and `limits`. See [Configuration](/getting-started/configuration). + ### Access control The hub protects its connection with a one-time code by default. The server prints the code, and a browser can read data only after it exchanges that code. On a machine only you use, pass `auth: false` to turn the gate off. diff --git a/apps/docs/src/content/getting-started/vite.md b/apps/docs/src/content/getting-started/vite.md index 9c25834..15318fd 100644 --- a/apps/docs/src/content/getting-started/vite.md +++ b/apps/docs/src/content/getting-started/vite.md @@ -106,6 +106,8 @@ ngDevtools({ | `allowedOrigins` | none | Extra exact origins allowed to reach the devtools, for example a tunnel. | | `auth` | on if a non-loopback host or origin is allowed, otherwise off | Whether the devtools ask for the one-time code. | +The plugin also takes the devtools options, such as `inspectors`, `agent`, `actions`, `redaction` and `limits`. See [Configuration](/getting-started/configuration). + ### `base` Change `base` if `/__devframes/` clashes with a route of your own. The overlay looks for `/__devframes/ng-devtools/` and `/__ng-devtools/` by default, so a custom base also needs a custom overlay path. See [A custom mount path](/getting-started/overlay#a-custom-mount-path). @@ -128,6 +130,8 @@ A tunnel forwards other people's requests to your machine, and those requests ar | `true` | The code is always on. | | `false` | The code is always off. The loopback and origin checks still apply. | +While the code is on, the HTTP MCP endpoint also asks for a bearer token. See [Send a token](/agents/mcp-server#send-a-token). + If your tunnel rewrites the `Host` header to `localhost`, you don't list it in `server.allowedHosts`, so the plugin leaves the code off. Pass `auth: true`: ```ts diff --git a/apps/docs/src/content/guides/analog.md b/apps/docs/src/content/guides/analog.md index 0df7fe4..8076909 100644 --- a/apps/docs/src/content/guides/analog.md +++ b/apps/docs/src/content/guides/analog.md @@ -82,13 +82,16 @@ The plugin runs on the dev server only (`apply: 'serve'`). Production builds do ### Plugin options -All three are optional. +All four are optional. -| Option | Default | What it does | -| ---------------- | ---------------------------- | ------------------------------------------------------ | -| `base` | `/__devframes/` | Where the hub is mounted. | -| `apiPrefix` | Read from your Analog config | The API prefix used to tell API calls from page calls. | -| `allowedOrigins` | none | Extra page origins accepted next to localhost. | +| Option | Default | What it does | +| ---------------- | ---------------------------------------------- | ------------------------------------------------------ | +| `base` | `/__devframes/` | Where the hub is mounted. | +| `apiPrefix` | Read from your Analog config | The API prefix used to tell API calls from page calls. | +| `allowedOrigins` | none | Extra page origins accepted next to localhost. | +| `auth` | on if a non-loopback host or origin is allowed | Whether the devtools ask for the one-time code. | + +The plugin also takes the devtools options. See [Vite and Analog](/getting-started/vite#options) and [Configuration](/getting-started/configuration). ### Custom hostnames From 276b3f3ce78c21a34cf9c1fefd8d992e5f805102 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:24:03 +0300 Subject: [PATCH 04/15] docs: point the popup troubleshooting at disposeOverlay The popup page only offered moving the button, though disposeOverlay removes it along with the overlay. Link the Stop the overlay section from the troubleshooting entry. --- apps/docs/src/content/getting-started/popup-and-hub.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/src/content/getting-started/popup-and-hub.md b/apps/docs/src/content/getting-started/popup-and-hub.md index 4154eb4..a99cff1 100644 --- a/apps/docs/src/content/getting-started/popup-and-hub.md +++ b/apps/docs/src/content/getting-started/popup-and-hub.md @@ -141,7 +141,7 @@ If the panel cannot reach the server, check that the dev server is running, then - Drag it somewhere else, or focus it and use the arrow keys. Double-click it to reset its position. + Drag it somewhere else, or focus it and use the arrow keys. Double-click it to reset its position. To remove it, stop the overlay with disposeOverlay. Remove the ng-devtools-popup key from localStorage and reload. From 003740c0f0fbd7a5623d2de70d26516c98ec6ee1 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:24:09 +0300 Subject: [PATCH 05/15] docs: list the config entry point on the publishing page The config options added a ./config export that ships as dist/config.mjs, but the What ships table on the Publishing page still listed only the older entry points. --- apps/docs/src/content/contributing/publishing.md | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/src/content/contributing/publishing.md b/apps/docs/src/content/contributing/publishing.md index 711a013..7bebea4 100644 --- a/apps/docs/src/content/contributing/publishing.md +++ b/apps/docs/src/content/contributing/publishing.md @@ -19,6 +19,7 @@ The package publishes `dist/` and `bin.mjs`. On publish, `publishConfig.exports` | --------------------------------------- | ------------------- | | `@santoshyadavdev/ng-devtools` | `dist/devframe.mjs` | | `@santoshyadavdev/ng-devtools/devframe` | `dist/devframe.mjs` | +| `@santoshyadavdev/ng-devtools/config` | `dist/config.mjs` | | `@santoshyadavdev/ng-devtools/overlay` | `dist/overlay.mjs` | | `@santoshyadavdev/ng-devtools/popup` | `dist/popup.mjs` | | `@santoshyadavdev/ng-devtools/http` | `dist/http.mjs` | From c3efa8607d695081636cb7f881578cbbb8326d71 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:24:22 +0300 Subject: [PATCH 06/15] docs: add the changelog to the release commit steps A release commit now also adds a section to packages/ng-devtools/CHANGELOG.md, so the Publishing page can no longer say release commits change only the version line. --- apps/docs/src/content/contributing/publishing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/src/content/contributing/publishing.md b/apps/docs/src/content/contributing/publishing.md index 7bebea4..cbcbd67 100644 --- a/apps/docs/src/content/contributing/publishing.md +++ b/apps/docs/src/content/contributing/publishing.md @@ -47,7 +47,7 @@ The package's `build` script runs two steps: ### 1. Bump the version -Update `version` in `packages/ng-devtools/package.json`. Release commits change only that line, with a message like `chore(release): ng-devtools 0.0.5`. +Update `version` in `packages/ng-devtools/package.json`. In the same commit, add a section for the version to `packages/ng-devtools/CHANGELOG.md`. The changelog follows [Keep a Changelog](https://keepachangelog.com), with entries grouped as Upgrade notes, Security fixes, Features and Documentation. Use a message like `chore(release): ng-devtools 0.0.5`. ### 2. Check the build From 89cc75ef775c266135d0a13ab5eb343dab4a9637 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:24:46 +0300 Subject: [PATCH 07/15] docs: map new RPC functions and agent tools to their inspector Since the config options, an RPC function or tool missing from RPC_INSPECTOR or AGENT_INSPECTOR in config.ts stays on when its inspector or its agent tools are turned off. The contributor steps for adding one did not mention it. --- apps/docs/src/content/contributing/development.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/docs/src/content/contributing/development.md b/apps/docs/src/content/contributing/development.md index d9cecd7..f29873f 100644 --- a/apps/docs/src/content/contributing/development.md +++ b/apps/docs/src/content/contributing/development.md @@ -183,7 +183,8 @@ If a code change needs no docs change, add the `no-docs` label to the pull reque 1. Create the function in `packages/ng-devtools/src/rpc/`. 2. Register it in `packages/ng-devtools/src/devframe.ts`. -3. Call it from the UI in `app/src/pages/`. +3. Map it to its inspector in `RPC_INSPECTOR` in `packages/ng-devtools/src/config.ts`, so turning the inspector off removes it. +4. Call it from the UI in `app/src/pages/`. ### Add a tab @@ -195,6 +196,8 @@ If a code change needs no docs change, add the `no-docs` label to the pull reque Add `agent: { description }` to an RPC function, or call `ctx.agent.registerTool()` in the devframe setup. List the tool on the [Tools](/agents/tools) page. +Map a registered tool to its inspector in `AGENT_INSPECTOR` in `packages/ng-devtools/src/config.ts`, so `inspectors` and `agent.tools` can hide it. A tool that acts on the page sets `safety: 'action'`, so `agent.readOnly` drops it. See [Configuration](/getting-started/configuration). + Run pnpm extension:build and commit extension/ui. CI fails when it is stale. See Build the extension. From 60c1c2331f1e0eefb95022945fbd33edf5f11497 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:24:47 +0300 Subject: [PATCH 08/15] docs: list config.ts as a source of truth for setup pages The Configuration page documents config.ts, but the claim-checking tables in the writing guide and the devtools-docs skill did not name it. --- .claude/skills/devtools-docs/SKILL.md | 14 +++++++------- apps/docs/src/content/contributing/writing-docs.md | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.claude/skills/devtools-docs/SKILL.md b/.claude/skills/devtools-docs/SKILL.md index d577af5..a028356 100644 --- a/.claude/skills/devtools-docs/SKILL.md +++ b/.claude/skills/devtools-docs/SKILL.md @@ -123,13 +123,13 @@ Rules: ## 7. Check claims against the code -| Page | Source of truth | -| ------------------ | ------------------------------------------------------------------------------------------------- | -| inspectors/\* | `app/src/pages/*.ts` (the tab) and `packages/ng-devtools/src/*` (collectors, actions) | -| agents/\* | `packages/ng-devtools/src/devframe.ts`, `rpc/*.ts`, `rpc/analog-register.ts`, Devframe built-ins | -| getting-started/\* | `packages/ng-devtools/package.json` exports, `hub.ts`, `vite.ts`, `overlay.ts`, `popup.ts`, demos | -| security | `hub.ts`, `vite.ts`, `forms-privacy.ts`, `forms-actions.ts`, router and Analog redaction | -| contributing/\* | root `package.json`, `nx.json`, `project.json` files, `.github/workflows`, `extension/` | +| Page | Source of truth | +| ------------------ | -------------------------------------------------------------------------------------------------------------- | +| inspectors/\* | `app/src/pages/*.ts` (the tab) and `packages/ng-devtools/src/*` (collectors, actions) | +| agents/\* | `packages/ng-devtools/src/devframe.ts`, `rpc/*.ts`, `rpc/analog-register.ts`, Devframe built-ins | +| getting-started/\* | `packages/ng-devtools/package.json` exports, `hub.ts`, `vite.ts`, `config.ts`, `overlay.ts`, `popup.ts`, demos | +| security | `hub.ts`, `vite.ts`, `forms-privacy.ts`, `forms-actions.ts`, router and Analog redaction | +| contributing/\* | root `package.json`, `nx.json`, `project.json` files, `.github/workflows`, `extension/` | Check names exactly. When code changes, update the docs in the same PR. When unsure, say less rather than guess. diff --git a/apps/docs/src/content/contributing/writing-docs.md b/apps/docs/src/content/contributing/writing-docs.md index 96614e0..a67524a 100644 --- a/apps/docs/src/content/contributing/writing-docs.md +++ b/apps/docs/src/content/contributing/writing-docs.md @@ -192,13 +192,13 @@ npm install @santoshyadavdev/ng-devtools devframe The docs describe what the code does today. Before you write a claim, find it in the source. -| Page | Source of truth | -| ------------------------- | -------------------------------------------------------------------------------------------------------- | -| Inspector pages | The tab in `app/src/pages/` and its collector in `packages/ng-devtools/src/` | -| Agent tools and resources | `packages/ng-devtools/src/devframe.ts`, `rpc/*.ts` and `rpc/analog-register.ts` | -| Setup pages | `packages/ng-devtools/package.json` exports, `hub.ts`, `vite.ts`, `overlay.ts`, `popup.ts` and the demos | -| Security | `hub.ts`, `vite.ts` and the redaction code, such as `forms-privacy.ts` | -| Contributing | Root `package.json` scripts, `nx.json`, `project.json` files and `.github/workflows` | +| Page | Source of truth | +| ------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| Inspector pages | The tab in `app/src/pages/` and its collector in `packages/ng-devtools/src/` | +| Agent tools and resources | `packages/ng-devtools/src/devframe.ts`, `rpc/*.ts` and `rpc/analog-register.ts` | +| Setup pages | `packages/ng-devtools/package.json` exports, `hub.ts`, `vite.ts`, `config.ts`, `overlay.ts`, `popup.ts` and the demos | +| Security | `hub.ts`, `vite.ts` and the redaction code, such as `forms-privacy.ts` | +| Contributing | Root `package.json` scripts, `nx.json`, `project.json` files and `.github/workflows` | Check names exactly: labels, buttons, tool names, arguments, option names and defaults. When the code changes, update the page in the same pull request. From e9b7a3430dae5de3705b41e3ef3bb7043f6c312e Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:24:55 +0300 Subject: [PATCH 09/15] docs: mark the overlay and MCP server pages as updated Since the docs site landed, the overlay gained disposeOverlay and change-detection refresh, and the HTTP MCP endpoint started asking for a bearer token. Both pages changed in ways existing users need to see, but the sidebar showed no badge for them. --- apps/docs/src/ngmd.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/src/ngmd.config.ts b/apps/docs/src/ngmd.config.ts index 8dee518..2448c0a 100644 --- a/apps/docs/src/ngmd.config.ts +++ b/apps/docs/src/ngmd.config.ts @@ -178,7 +178,7 @@ const config: NgmdConfig = { {label: 'Standalone CLI', href: '/getting-started/cli'}, {label: 'Configuration', href: '/getting-started/configuration', status: 'new'}, {label: 'Popup and hub', href: '/getting-started/popup-and-hub', status: 'new'}, - {label: 'Browser overlay', href: '/getting-started/overlay'}, + {label: 'Browser overlay', href: '/getting-started/overlay', status: 'updated'}, {label: 'Chrome extension', href: '/getting-started/chrome-extension'}, ], }, @@ -200,7 +200,7 @@ const config: NgmdConfig = { { label: 'Agent Tools', items: [ - {label: 'MCP server', href: '/agents/mcp-server'}, + {label: 'MCP server', href: '/agents/mcp-server', status: 'updated'}, {label: 'Tools', href: '/agents/tools'}, {label: 'Resources', href: '/agents/resources'}, ], From 341d6e76fcf2f607d26065c9a36dcee0a9dcd3c4 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:40:30 +0300 Subject: [PATCH 10/15] docs: list the target and merge labels in CONTRIBUTING --- CONTRIBUTING.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c99716e..92b4935 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,16 +33,18 @@ By taking part you agree to the [Code of Conduct](./CODE_OF_CONDUCT.md). Report The labels follow the Angular repository. -| Label | Use | -| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `bug`, `feature`, `breaking changes` | What kind of change it is. Issue forms add `bug` or `feature` with `needs triage`. | -| `P0` to `P3` | Priority, from broken for most users (`P0`) to not urgent (`P3`). | -| `area: *` | The part of the repository: panel, package, agents, extension, demo, docs, security, performance, accessibility, ci. Pull requests get these from the paths they change. | -| `needs triage`, `needs reproduction`, `needs: clarification`, `needs: discussion` | What an issue is waiting for. | -| `state: confirmed`, `state: has PR`, `state: blocked`, `state: WIP` | Where an issue stands. | -| `action: review`, `action: cleanup`, `action: merge`, `action: discuss` | What a pull request needs next. | -| `good first issue`, `help wanted` | Issues open to new contributors. | -| `no-docs`, `release: skip` | No docs change needed; leave out of the release notes. | +| Label | Use | +| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `bug`, `feature`, `breaking changes` | What kind of change it is. Issue forms add `bug` or `feature` with `needs triage`. | +| `P0` to `P3` | Priority, from broken for most users (`P0`) to not urgent (`P3`). | +| `area: *` | The part of the repository: panel, package, agents, extension, demo, docs, security, performance, accessibility, ci. Pull requests get these from the paths they change. | +| `needs triage`, `needs reproduction`, `needs: clarification`, `needs: discussion` | What an issue is waiting for. | +| `state: confirmed`, `state: has PR`, `state: blocked`, `state: WIP` | Where an issue stands. | +| `action: review`, `action: cleanup`, `action: merge`, `action: discuss` | What a pull request needs next. | +| `target: patch`, `target: minor`, `target: major` | Which release a pull request goes into. | +| `merge: squash commits`, `merge: preserve commits`, `merge: caretaker note`, `merge: fix commit message` | How to merge: squash (the default) or keep each commit, read the note in the description first, or fix the commit message when merging. | +| `good first issue`, `help wanted` | Issues open to new contributors. | +| `no-docs`, `release: skip` | No docs change needed; leave out of the release notes. | ## Run the checks From 603d6c218afb769f20cb8168891253576932e113 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:44:12 +0300 Subject: [PATCH 11/15] docs: note that pull requests are squash merged by default --- CONTRIBUTING.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92b4935..6da329c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,18 +33,18 @@ By taking part you agree to the [Code of Conduct](./CODE_OF_CONDUCT.md). Report The labels follow the Angular repository. -| Label | Use | -| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `bug`, `feature`, `breaking changes` | What kind of change it is. Issue forms add `bug` or `feature` with `needs triage`. | -| `P0` to `P3` | Priority, from broken for most users (`P0`) to not urgent (`P3`). | -| `area: *` | The part of the repository: panel, package, agents, extension, demo, docs, security, performance, accessibility, ci. Pull requests get these from the paths they change. | -| `needs triage`, `needs reproduction`, `needs: clarification`, `needs: discussion` | What an issue is waiting for. | -| `state: confirmed`, `state: has PR`, `state: blocked`, `state: WIP` | Where an issue stands. | -| `action: review`, `action: cleanup`, `action: merge`, `action: discuss` | What a pull request needs next. | -| `target: patch`, `target: minor`, `target: major` | Which release a pull request goes into. | -| `merge: squash commits`, `merge: preserve commits`, `merge: caretaker note`, `merge: fix commit message` | How to merge: squash (the default) or keep each commit, read the note in the description first, or fix the commit message when merging. | -| `good first issue`, `help wanted` | Issues open to new contributors. | -| `no-docs`, `release: skip` | No docs change needed; leave out of the release notes. | +| Label | Use | +| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `bug`, `feature`, `breaking changes` | What kind of change it is. Issue forms add `bug` or `feature` with `needs triage`. | +| `P0` to `P3` | Priority, from broken for most users (`P0`) to not urgent (`P3`). | +| `area: *` | The part of the repository: panel, package, agents, extension, demo, docs, security, performance, accessibility, ci. Pull requests get these from the paths they change. | +| `needs triage`, `needs reproduction`, `needs: clarification`, `needs: discussion` | What an issue is waiting for. | +| `state: confirmed`, `state: has PR`, `state: blocked`, `state: WIP` | Where an issue stands. | +| `action: review`, `action: cleanup`, `action: merge`, `action: discuss` | What a pull request needs next. | +| `target: patch`, `target: minor`, `target: major` | Which release a pull request goes into. | +| `merge: preserve commits`, `merge: caretaker note`, `merge: fix commit message` | Pull requests are squash merged. These mark the exceptions: keep each commit, read the note in the description first, or fix the commit message when merging. | +| `good first issue`, `help wanted` | Issues open to new contributors. | +| `no-docs`, `release: skip` | No docs change needed; leave out of the release notes. | ## Run the checks From c6c34f9a8f69c6311b8bd58a7f3e19a45b34e5e1 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 17:58:21 +0300 Subject: [PATCH 12/15] docs: note the Vite loopback check on MCP and qualify the voice example --- apps/docs/src/content/agents/mcp-server.md | 2 +- apps/docs/src/content/contributing/writing-docs.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/src/content/agents/mcp-server.md b/apps/docs/src/content/agents/mcp-server.md index 5a0c635..ac40954 100644 --- a/apps/docs/src/content/agents/mcp-server.md +++ b/apps/docs/src/content/agents/mcp-server.md @@ -99,7 +99,7 @@ If you mount the devtools panel without the hub, at `/__ng-devtools/`, the endpo ### Send an Origin header - The HTTP endpoint only answers requests that carry a local Origin header, such as http://localhost:4000. Requests without one get 403 Forbidden. If your MCP client does not send an Origin header, add it in the client config. + The HTTP endpoint only answers requests that carry a local Origin header, such as http://localhost:4000. Requests without one get 403 Forbidden. With the Vite plugin, the request must also come from a loopback address. If your MCP client does not send an Origin header, add it in the client config. The header value is the origin of your dev server. Every example below sets it. diff --git a/apps/docs/src/content/contributing/writing-docs.md b/apps/docs/src/content/contributing/writing-docs.md index a67524a..f2c7e7d 100644 --- a/apps/docs/src/content/contributing/writing-docs.md +++ b/apps/docs/src/content/contributing/writing-docs.md @@ -29,7 +29,7 @@ Orient every page around what the reader is trying to do. Ask: _what does the de - Use second person and the imperative: "Open the Router tab", not "We can open the Router tab". - Use present tense: "The tab shows", not "The tab will show". -- Use active voice: "The overlay reads the page after change detection", not "The page is read after change detection". +- Use active voice: "On Angular 20 and later, the overlay reads the page after change detection", not "The page is read after change detection". - One idea per sentence. Keep sentences short and plain. - Put the condition first: "If the tab is empty, check that the app runs in development mode." - Use sentence case for headings. Capitalize only the first word and proper nouns. From 89d84e400e2d6fd484debf5021907c8d47b82ab5 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 19:17:26 +0300 Subject: [PATCH 13/15] docs: link between pages with relative .md paths so they work on GitHub The README sends readers to the markdown files under apps/docs/src/content, but those pages linked to each other with site routes such as /getting-started/configuration. The site isn't deployed, so on GitHub every one of those links returned 404. Pages now link to each other by the relative path of the .md file (./configuration.md, ../inspectors/router.md#agent-tools), in markdown links and in tags. A new md-links Vite plugin rewrites these hrefs to routes in each page's rendered content module, where the file path is known, so the site keeps its routes, fragments and client-side navigation. The link guard resolves relative .md links the same way and fails the build on missing pages and anchors. The writing guide, the docs skill and apps/docs/README.md describe the new rule. --- .claude/skills/devtools-docs/SKILL.md | 2 + apps/docs/README.md | 1 + apps/docs/build-plugins.spec.ts | 15 ++++ apps/docs/link-guard.plugin.ts | 29 +++++-- apps/docs/md-links.plugin.spec.ts | 81 +++++++++++++++++++ apps/docs/md-links.plugin.ts | 52 ++++++++++++ apps/docs/src/content/agents/mcp-server.md | 34 ++++---- apps/docs/src/content/agents/resources.md | 2 +- apps/docs/src/content/agents/tools.md | 12 +-- .../src/content/contributing/demo-apps.md | 2 +- .../src/content/contributing/development.md | 4 +- .../src/content/contributing/kitchen-sink.md | 6 +- .../src/content/contributing/publishing.md | 4 +- .../src/content/contributing/writing-docs.md | 8 ++ .../getting-started/chrome-extension.md | 10 +-- apps/docs/src/content/getting-started/cli.md | 18 ++--- .../content/getting-started/configuration.md | 14 ++-- .../src/content/getting-started/express.md | 28 +++---- .../content/getting-started/installation.md | 28 +++---- .../content/getting-started/introduction.md | 32 ++++---- .../src/content/getting-started/overlay.md | 10 +-- .../content/getting-started/popup-and-hub.md | 2 +- apps/docs/src/content/getting-started/vite.md | 12 +-- apps/docs/src/content/guides/analog.md | 8 +- .../content/guides/ngrx-signals-restore.md | 2 +- apps/docs/src/content/guides/ssr-http.md | 8 +- apps/docs/src/content/inspectors/analog.md | 8 +- .../docs/src/content/inspectors/components.md | 10 +-- apps/docs/src/content/inspectors/dashboard.md | 24 +++--- apps/docs/src/content/inspectors/forms.md | 8 +- apps/docs/src/content/inspectors/injectors.md | 2 +- .../docs/src/content/inspectors/ngrx-store.md | 6 +- apps/docs/src/content/inspectors/pipes.md | 4 +- apps/docs/src/content/inspectors/router.md | 6 +- apps/docs/src/content/inspectors/signals.md | 2 +- apps/docs/src/content/inspectors/ssr-http.md | 8 +- apps/docs/src/content/security.md | 12 +-- apps/docs/vite.config.ts | 2 + 38 files changed, 347 insertions(+), 169 deletions(-) create mode 100644 apps/docs/md-links.plugin.spec.ts create mode 100644 apps/docs/md-links.plugin.ts diff --git a/.claude/skills/devtools-docs/SKILL.md b/.claude/skills/devtools-docs/SKILL.md index a028356..3a48308 100644 --- a/.claude/skills/devtools-docs/SKILL.md +++ b/.claude/skills/devtools-docs/SKILL.md @@ -77,6 +77,8 @@ Short intro. - Headings are unique within a page. - Before renaming a heading, grep `apps/docs/src/content` for its anchor. Anchors are the slug of the heading text (lowercase, non-alphanumerics to `-`), badges excluded. - Add new pages to `nav` in `ngmd.config.ts`. +- Link to other pages by the relative path of the `.md` file, in markdown links and `` tags: `[Configuration](./configuration.md)`, ``. The site resolves them to routes and they also work on GitHub. Never write `/getting-started/...` routes in markdown links or ``. +- ``, `` and pages without a `.md` file (`/sponsors`) use the site route. - `` only on pages about one external tool (NgRx, Analog, Vite, Express, Chrome, MCP, Nx). ## 5. Components diff --git a/apps/docs/README.md b/apps/docs/README.md index df0a357..55e4d63 100644 --- a/apps/docs/README.md +++ b/apps/docs/README.md @@ -17,6 +17,7 @@ With Nx: `pnpm nx serve angular-devtools-docs`, `pnpm nx build angular-devtools- ## Edit - Pages are markdown files in `src/content`. The path becomes the URL: `src/content/inspectors/signals.md` is served at `/inspectors/signals`. +- Link between pages with relative `.md` paths, such as `[Router](../inspectors/router.md)`, so the links also work on GitHub. - The sidebar, site name, links and site URL live in `src/ngmd.config.ts`. - Brand colors are CSS variables in `src/styles.css`. - The landing page is `src/app/pages/index.page.ts`. diff --git a/apps/docs/build-plugins.spec.ts b/apps/docs/build-plugins.spec.ts index fd744f9..23aac7f 100644 --- a/apps/docs/build-plugins.spec.ts +++ b/apps/docs/build-plugins.spec.ts @@ -85,6 +85,21 @@ describe('internalLinkGuard', () => { expect(warning).toContain('"/missing.png" is not a known route or file in public/'); expect(warning).toContain('"/guide#nope"'); }); + + it('resolves relative .md links from the linking file', () => { + expect( + check( + '[a](./guide/index.md#setup-1) [b](guide/index.md) [c](./some%20page.md) d ![e](./logo.png)', + ), + ).toEqual([]); + const [warning] = check( + '[a](./missing.md) [b](../README.md) [c](./guide/index.md#nope) [d](./guide/index.md?x=1)', + ); + expect(warning).toContain('"/missing" is not a known route'); + expect(warning).toContain('"../README.md" is not a page in src/content'); + expect(warning).toContain('"/guide#nope"'); + expect(warning).toContain('"./guide/index.md?x=1" is not a page in src/content'); + }); }); describe('searchIndexPlugin', () => { diff --git a/apps/docs/link-guard.plugin.ts b/apps/docs/link-guard.plugin.ts index d46a47f..02b3718 100644 --- a/apps/docs/link-guard.plugin.ts +++ b/apps/docs/link-guard.plugin.ts @@ -12,19 +12,23 @@ import { walkPageFiles, withoutCode, } from './plugin-utils.ts'; +import {resolveMdHref} from './md-links.plugin.ts'; /** * Build-time guard that errors on broken internal links inside markdown files. * - * Validates three cases: + * Validates four cases: * - `[text](#fragment)` — fragment must be a real heading slug in the same file * - `[text](/path)` — `/path` must be a known route * - `[text](/path#fragment)` — both the route and the heading slug must exist + * - `[text](../dir/page.md#fragment)` — resolved from this file, then checked + * like `/dir/page#fragment` * * Routes are discovered by walking `src/content/**\/*.md` (each markdown * file's path under content/ becomes its route) and `src/app/pages/**\/*.page.ts`. - * External (`http(s)://`), mail (`mailto:`), and relative (`./foo`) links are - * skipped; the existing externalLinkGuard covers raw HTML external anchors. + * External (`http(s)://`), mail (`mailto:`), and relative links that don't + * end in `.md` (`./foo.png`) are skipped; the existing externalLinkGuard + * covers raw HTML external anchors. * * Heading slugs are computed with the same algorithm the rendered TOC uses * (see `plugin-utils.slugify`), so dev-time and runtime stay in sync. @@ -119,10 +123,23 @@ export function internalLinkGuard(): Plugin { const content = readFileSync(file, 'utf8'); const ownSlugs = extractHeadings(content); const issues: string[] = []; + const pageFile = + '/src/content/' + relative(join(root, 'src/content'), file).replace(/\\/g, '/'); - const validate = (href: string, label: string) => { - if (!href) return; - // external / mail / relative — skip + const validate = (link: string, label: string) => { + if (!link) return; + let href = link; + if (!/^([a-z][a-z0-9+.-]*:|\/|#)/i.test(href)) { + const route = resolveMdHref(href, pageFile); + if (route === null) { + if (/\.md([#?]|$)/.test(href)) { + issues.push(` ${label} → "${href}" is not a page in src/content`); + } + return; + } + href = route; + } + // external / mail — skip if (!href.startsWith('#') && (!href.startsWith('/') || href.startsWith('//'))) return; const hashAt = href.indexOf('#'); diff --git a/apps/docs/md-links.plugin.spec.ts b/apps/docs/md-links.plugin.spec.ts new file mode 100644 index 0000000..4af049b --- /dev/null +++ b/apps/docs/md-links.plugin.spec.ts @@ -0,0 +1,81 @@ +import {mdLinksPlugin, resolveDocLinks, resolveMdHref} from './md-links.plugin'; + +describe('resolveMdHref', () => { + const page = '/src/content/getting-started/installation'; + + it('resolves sibling, parent and nested .md links to routes and keeps the fragment', () => { + expect(resolveMdHref('./configuration.md', page)).toBe('/getting-started/configuration'); + expect(resolveMdHref('vite.md#analog', page)).toBe('/getting-started/vite#analog'); + expect(resolveMdHref('../inspectors/router.md#tools', page)).toBe('/inspectors/router#tools'); + expect(resolveMdHref('../security.md', page)).toBe('/security'); + expect(resolveMdHref('./guide/index.md', '/src/content/intro.md')).toBe('/guide'); + expect(resolveMdHref('../index.md', page)).toBe('/'); + }); + + it('resolves against the directory of an index page', () => { + expect(resolveMdHref('./setup.md', '/src/content/guide/index')).toBe('/guide/setup'); + }); + + it('ignores routes, fragments, external links, other files and paths outside content', () => { + for (const href of [ + '/getting-started/vite', + '#local', + 'https://angular.dev/guide.md', + 'mailto:a@b.md', + './diagram.png', + './configuration', + '../../../../README.md', + ]) { + expect(resolveMdHref(href, page)).toBeNull(); + } + }); +}); + +describe('resolveDocLinks', () => { + it('rewrites relative .md hrefs in HTML and leaves the rest alone', () => { + const html = + 'a b ' + + 'c d'; + expect(resolveDocLinks(html, '/src/content/getting-started/installation')).toBe( + 'a b ' + + 'c d', + ); + }); +}); + +describe('mdLinksPlugin', () => { + type Hook = (this: unknown, ...args: unknown[]) => unknown; + const plugin = mdLinksPlugin(); + (plugin.configResolved as Hook).call(undefined, {root: '/site'}); + const transform = (code: string, id: string) => + (plugin.transform as Hook).call( + { + error: (m: string) => { + throw new Error(m); + }, + }, + code, + id, + ); + + it('rewrites the rendered content module of a page', () => { + const html = '---\ntitle: Vite\n---\n\nCLI'; + expect( + transform( + `export default ${JSON.stringify(html)}`, + '/site/src/content/getting-started/vite.md?analog-content-file=true', + ), + ).toEqual({ + code: `export default ${JSON.stringify(html.replace('./cli.md', '/getting-started/cli'))}`, + map: null, + }); + }); + + it('skips other modules and fails on an unexpected module shape', () => { + expect(transform('x', '/site/src/content/vite.md?raw')).toBeNull(); + expect(transform('x', '/site/README.md?analog-content-file=true')).toBeNull(); + expect(() => + transform('export const x = 1', '/site/src/content/vite.md?analog-content-file=true'), + ).toThrow('Unexpected content module shape'); + }); +}); diff --git a/apps/docs/md-links.plugin.ts b/apps/docs/md-links.plugin.ts new file mode 100644 index 0000000..346f57d --- /dev/null +++ b/apps/docs/md-links.plugin.ts @@ -0,0 +1,52 @@ +import {join, relative} from 'node:path'; +import type {Plugin} from 'vite'; + +export function mdLinksPlugin(): Plugin { + let contentDir = join(process.cwd(), 'src/content'); + return { + name: 'ngmd-md-links', + configResolved(cfg) { + contentDir = join(cfg.root, 'src/content'); + }, + transform(code, id) { + const [file, query = ''] = id.split('?'); + if (!query.includes('analog-content-file=true')) return null; + const rel = relative(contentDir, file).replace(/\\/g, '/'); + if (rel.startsWith('..')) return null; + const match = /^export default (".*");?\s*$/s.exec(code); + if (!match) { + return this.error( + `[ngmd] Unexpected content module shape for ${rel}, can't resolve .md links.`, + ); + } + const html = resolveDocLinks(JSON.parse(match[1]) as string, `/src/content/${rel}`); + return {code: `export default ${JSON.stringify(html)}`, map: null}; + }, + }; +} + +const CONTENT_ROOT = '/src/content/'; + +export function resolveMdHref(href: string, pageFile: string): string | null { + if (/^([a-z][a-z0-9+.-]*:|\/|#|\?)/i.test(href)) return null; + const hashAt = href.indexOf('#'); + const path = hashAt === -1 ? href : href.slice(0, hashAt); + if (!path.endsWith('.md')) return null; + const {pathname} = new URL(path, `http://docs${pageFile}`); + if (!pathname.startsWith(CONTENT_ROOT)) return null; + const route = pathname + .slice(CONTENT_ROOT.length - 1, -'.md'.length) + .replace(/(^|\/)index$/, '$1') + .replace(/(.)\/$/, '$1'); + return route + (hashAt === -1 ? '' : href.slice(hashAt)); +} + +export function resolveDocLinks(html: string, pageFile: string): string { + return html.replace( + /(\shref=)(["'])([^"']*)\2/g, + (match, attr: string, quote: string, href: string) => { + const route = resolveMdHref(href, pageFile); + return route === null ? match : `${attr}${quote}${route}${quote}`; + }, + ); +} diff --git a/apps/docs/src/content/agents/mcp-server.md b/apps/docs/src/content/agents/mcp-server.md index ac40954..c604953 100644 --- a/apps/docs/src/content/agents/mcp-server.md +++ b/apps/docs/src/content/agents/mcp-server.md @@ -86,11 +86,11 @@ When the devtools are embedded in your app's server, the same tools are served o The path depends on how you mount the devtools. Use the port your server actually runs on. -| Setup | Endpoint | -| --------------------------------------- | ----------------------------------------- | -| [Express hub](/getting-started/express) | `http://localhost:4000/__devframes/__mcp` | -| [Vite plugin](/getting-started/vite) | `http://localhost:5173/__devframes/__mcp` | -| [Standalone CLI](/getting-started/cli) | `http://localhost:9999/__mcp` | +| Setup | Endpoint | +| -------------------------------------------- | ----------------------------------------- | +| [Express hub](../getting-started/express.md) | `http://localhost:4000/__devframes/__mcp` | +| [Vite plugin](../getting-started/vite.md) | `http://localhost:5173/__devframes/__mcp` | +| [Standalone CLI](../getting-started/cli.md) | `http://localhost:9999/__mcp` | The standalone CLI uses port 9999 by default. If that port is taken and you did not pass `--port`, it picks a free port. Use the URL it prints. @@ -108,10 +108,10 @@ The header value is the origin of your dev server. Every example below sets it. If the hub asks for the one-time code, the HTTP endpoint also asks for a bearer token. Requests without the right token get `401`. -| Setup | Token required | -| --------------------------------------- | ------------------------------------------------------------------------------------------------ | -| [Express hub](/getting-started/express) | Yes, unless you pass `auth: false` or your own `mcp` option. | -| [Vite plugin](/getting-started/vite) | Only when the one-time code is on. See the plugin's [`auth` option](/getting-started/vite#auth). | +| Setup | Token required | +| -------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| [Express hub](../getting-started/express.md) | Yes, unless you pass `auth: false` or your own `mcp` option. | +| [Vite plugin](../getting-started/vite.md) | Only when the one-time code is on. See the plugin's [`auth` option](../getting-started/vite.md#auth). | The hub prints a generated token in the terminal when it starts. The token changes on every restart. To keep the same token across restarts, set `NG_DEVTOOLS_MCP_TOKEN` in the environment of the server. The hub then uses that value and prints nothing. @@ -187,7 +187,7 @@ The live tools read what the page reports. Without an open page, they have nothi Run the server that mounts the devtools: your Express SSR server, the Vite dev server, or ng-devtools dev. - Load the app with the overlay. The page connects to the devtools and starts reporting. + Load the app with the overlay. The page connects to the devtools and starts reporting. Ask your agent something the page knows, like "why is the checkout form invalid?". It calls explain-form-invalid on the connected page. @@ -204,13 +204,13 @@ The server registers tools with a colon, as `ng-devtools:get-routes`. MCP client The server marks read-only tools as read-only for your client. Five tools act on the app, so the server does not mark them: -| Tool | Reference | -| ----------------- | --------------------------------------------------------------------- | -| `highlight` | [Components, signals and DI](/agents/tools#components-signals-and-di) | -| `navigate` | [Act on the router](/agents/tools#act-on-the-router) | -| `form-action` | [Act on a form](/agents/tools#act-on-a-form) | -| `fill-form` | [Act on a form](/agents/tools#act-on-a-form) | -| `analog-call-api` | [Call a server route](/agents/tools#call-a-server-route) | +| Tool | Reference | +| ----------------- | ------------------------------------------------------------------ | +| `highlight` | [Components, signals and DI](./tools.md#components-signals-and-di) | +| `navigate` | [Act on the router](./tools.md#act-on-the-router) | +| `form-action` | [Act on a form](./tools.md#act-on-a-form) | +| `fill-form` | [Act on a form](./tools.md#act-on-a-form) | +| `analog-call-api` | [Call a server route](./tools.md#call-a-server-route) | Your client can ask you before it runs them. To drop them from the server, set `agent.readOnly`. See [Inspectors and agent tools](/getting-started/configuration#inspectors-and-agent-tools). diff --git a/apps/docs/src/content/agents/resources.md b/apps/docs/src/content/agents/resources.md index d2a6c05..0e18695 100644 --- a/apps/docs/src/content/agents/resources.md +++ b/apps/docs/src/content/agents/resources.md @@ -15,7 +15,7 @@ Resources hold the live data the connected pages reported. An agent reads them w ### Connect over HTTP -Resources are empty when no page is connected. Read them through the [HTTP endpoint](/agents/mcp-server#connect-over-http), with the app open in a browser. +Resources are empty when no page is connected. Read them through the [HTTP endpoint](./mcp-server.md#connect-over-http), with the app open in a browser. Over stdio, no page ever connects. Every resource stays empty. diff --git a/apps/docs/src/content/agents/tools.md b/apps/docs/src/content/agents/tools.md index 810c1d3..1f7aaf6 100644 --- a/apps/docs/src/content/agents/tools.md +++ b/apps/docs/src/content/agents/tools.md @@ -9,7 +9,7 @@ description: Every agent tool the devtools expose, grouped by inspector, with wh # Tools -This page lists every tool the [MCP server](/agents/mcp-server) exposes. Each group matches an inspector in the panel. +This page lists every tool the [MCP server](./mcp-server.md) exposes. Each group matches an inspector in the panel. ## Before you call a tool @@ -45,7 +45,7 @@ Most page tools take an optional `page` argument to pick a browser tab. It defau ### Turn tools off -The server decides which tools exist. Set `agent.readOnly` to drop the five action tools. Set `agent.tools.` to `false` to hide one inspector's tools and resources, and keep its tab. Turning an inspector off with `inspectors`, or blocking an action with `actions`, drops the matching tools too. See [Inspectors and agent tools](/getting-started/configuration#inspectors-and-agent-tools). +The server decides which tools exist. Set `agent.readOnly` to drop the five action tools. Set `agent.tools.` to `false` to hide one inspector's tools and resources, and keep its tab. Turning an inspector off with `inspectors`, or blocking an action with `actions`, drops the matching tools too. See [Inspectors and agent tools](../getting-started/configuration.md#inspectors-and-agent-tools). ## Source scan @@ -178,7 +178,7 @@ Markers let an agent check its own work: read the marker, act, then call `form-d ### Act on a form -Both tools are action tools and need a development build. They don't write secret fields unless you unmask them. See [Opt fields in or out](/security#opt-fields-in-or-out). For Signal Forms, they don't write hidden, readonly or disabled fields either. +Both tools are action tools and need a development build. They don't write secret fields unless you unmask them. See [Opt fields in or out](../security.md#opt-fields-in-or-out). For Signal Forms, they don't write hidden, readonly or disabled fields either. | Tool | What it does | Arguments | | ------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | @@ -207,7 +207,7 @@ It finds impure pipes used inside `@for`, `| json` left in templates, and pure p | -------- | -------- | ----------------------------------------------- | | `name` | yes | The pipe name as used after `\|` in a template. | -Live counts, input and output appear when recording is on in the [Pipes inspector](/inspectors/pipes). +Live counts, input and output appear when recording is on in the [Pipes inspector](../inspectors/pipes.md). ## Analog @@ -252,14 +252,14 @@ These tools cover *Analog apps. Most read your source. Two read what the Vite pl `analog-lint` finds Analog mistakes: two files for one URL, a missing default export, a layout without `router-outlet`, bad API method suffixes, prerender entries that match nothing, and frontmatter errors. It also reports live problems, like a `load()` fetched twice or a restart needed. No arguments. - analog-server-calls and analog-call-api need the Vite plugin. The plugin records the calls and knows the dev server address. + analog-server-calls and analog-call-api need the Vite plugin. The plugin records the calls and knows the dev server address. ## Shared state `devframe_state_read` reads the devtools' live shared state. Call it without arguments to list the keys, then with `key` to read a value as JSON. -Use it for data that has no dedicated tool, such as the SSR & HTTP timeline (`ng-devtools:http`) or live pipe usage (`ng-devtools:pipe-usage`). See [Resources](/agents/resources) for every key. +Use it for data that has no dedicated tool, such as the SSR & HTTP timeline (`ng-devtools:http`) or live pipe usage (`ng-devtools:pipe-usage`). See [Resources](./resources.md) for every key. ## Where to next diff --git a/apps/docs/src/content/contributing/demo-apps.md b/apps/docs/src/content/contributing/demo-apps.md index 86f6c2c..d3f34fd 100644 --- a/apps/docs/src/content/contributing/demo-apps.md +++ b/apps/docs/src/content/contributing/demo-apps.md @@ -82,7 +82,7 @@ It listens on port 4000, or on `PORT` when set. ## Analog demo -`examples/analog` is an *Analog app wired with the [Vite plugin](/getting-started/vite). Its project name is `analog-demo`. +`examples/analog` is an *Analog app wired with the [Vite plugin](../getting-started/vite.md). Its project name is `analog-demo`. ### What's in the Analog demo diff --git a/apps/docs/src/content/contributing/development.md b/apps/docs/src/content/contributing/development.md index f29873f..f81cdfe 100644 --- a/apps/docs/src/content/contributing/development.md +++ b/apps/docs/src/content/contributing/development.md @@ -194,12 +194,12 @@ If a code change needs no docs change, add the `no-docs` label to the pull reque ### Add an agent tool -Add `agent: { description }` to an RPC function, or call `ctx.agent.registerTool()` in the devframe setup. List the tool on the [Tools](/agents/tools) page. +Add `agent: { description }` to an RPC function, or call `ctx.agent.registerTool()` in the devframe setup. List the tool on the [Tools](../agents/tools.md) page. Map a registered tool to its inspector in `AGENT_INSPECTOR` in `packages/ng-devtools/src/config.ts`, so `inspectors` and `agent.tools` can hide it. A tool that acts on the page sets `safety: 'action'`, so `agent.readOnly` drops it. See [Configuration](/getting-started/configuration). - Run pnpm extension:build and commit extension/ui. CI fails when it is stale. See Build the extension. + Run pnpm extension:build and commit extension/ui. CI fails when it is stale. See Build the extension. ## Work on the docs diff --git a/apps/docs/src/content/contributing/kitchen-sink.md b/apps/docs/src/content/contributing/kitchen-sink.md index df0a345..0b3cc32 100644 --- a/apps/docs/src/content/contributing/kitchen-sink.md +++ b/apps/docs/src/content/contributing/kitchen-sink.md @@ -10,13 +10,13 @@ noIndex: true # Kitchen sink -This page follows the [writing guide](/contributing/writing-docs). Each section shows one feature with its common options. +This page follows the [writing guide](./writing-docs.md). Each section shows one feature with its common options. ## Text ### Inline formatting -Plain text, **bold**, _italic_, `inline code`, ~~strikethrough~~ and a **UI label** like **Record**. A line with a [site link](/inspectors/router), an [anchor link](#tables), an [external link](https://angular.dev) and keyword links: *Angular, *Analog, *Devframe, *NgRx, *MCP and *Vite. +Plain text, **bold**, _italic_, `inline code`, ~~strikethrough~~ and a **UI label** like **Record**. A line with a [site link](../inspectors/router.md), an [anchor link](#tables), an [external link](https://angular.dev) and keyword links: *Angular, *Analog, *Devframe, *NgRx, *MCP and *Vite. ### Lists @@ -128,7 +128,7 @@ bun add @santoshyadavdev/ng-devtools devframe ## Callouts - Context the reader may need, with code and a link. + Context the reader may need, with code and a link. Callouts are never adjacent on real pages. The text between them here keeps that rule. diff --git a/apps/docs/src/content/contributing/publishing.md b/apps/docs/src/content/contributing/publishing.md index cbcbd67..9f043d1 100644 --- a/apps/docs/src/content/contributing/publishing.md +++ b/apps/docs/src/content/contributing/publishing.md @@ -51,7 +51,7 @@ Update `version` in `packages/ng-devtools/package.json`. In the same commit, add ### 2. Check the build -Run the checks from [Development setup](/contributing/development), then build the package without publishing: +Run the checks from [Development setup](./development.md), then build the package without publishing: ```bash pnpm devtools:build-pkg @@ -82,7 +82,7 @@ The extension has its own version, in `extension/manifest.json`. It does not fol 3. Commit `extension/ui` and the manifest. 4. Upload `dist/ng-devtools-extension.zip`. -See [Build the extension](/contributing/chrome-extension) for the upload steps. +See [Build the extension](./chrome-extension.md) for the upload steps. ## Where to next diff --git a/apps/docs/src/content/contributing/writing-docs.md b/apps/docs/src/content/contributing/writing-docs.md index f2c7e7d..dae94ac 100644 --- a/apps/docs/src/content/contributing/writing-docs.md +++ b/apps/docs/src/content/contributing/writing-docs.md @@ -113,6 +113,14 @@ A short intro: what the tab is and when you open it. Add an entry to `nav` in `apps/docs/src/ngmd.config.ts`. Pages that aren't listed still build, but readers can't find them. Use `status: 'new'` or `status: 'updated'` for a sidebar badge instead of saying "new" in the text. +### Link to other pages + +Link to another page by the relative path of its `.md` file, in markdown links and in `` tags: `[Configuration](./configuration.md)`, ``. The site turns these into routes, and the same links work when the page is read on GitHub. + +- The build fails if the file doesn't exist or the anchor doesn't match a heading. +- `` and `` take the site route, such as `/agents/tools`. They only render on the site. +- Pages without a `.md` file, such as `/sponsors`, use the site route. + ## Components The site uses NgMd's authoring components. Write them as raw HTML inside the markdown. The full reference is the components page of the [NgMd documentation](https://ngmd.netlify.app/concepts/components). diff --git a/apps/docs/src/content/getting-started/chrome-extension.md b/apps/docs/src/content/getting-started/chrome-extension.md index 680a1d0..817812f 100644 --- a/apps/docs/src/content/getting-started/chrome-extension.md +++ b/apps/docs/src/content/getting-started/chrome-extension.md @@ -12,7 +12,7 @@ description: Open the devtools as a panel inside Chrome DevTools. The Chrome extension adds a panel named **Angular DevTools** to Chrome DevTools. The panel loads the devtools UI and connects it to the dev server of the page you are inspecting. - The page still needs the devtools mounted on its server and the overlay loaded. The extension is one more way to open the devtools. It does not replace the setup. Start with Angular CLI and Express or Vite and Analog. + The page still needs the devtools mounted on its server and the overlay loaded. The extension is one more way to open the devtools. It does not replace the setup. Start with Angular CLI and Express or Vite and Analog. ## Before you start @@ -60,7 +60,7 @@ pnpm install pnpm extension:build ``` -[Build the extension](/contributing/chrome-extension) covers the build and the store package in detail. +[Build the extension](../contributing/chrome-extension.md) covers the build and the store package in detail. ## How it works @@ -99,7 +99,7 @@ When the inspected page navigates, the panel shows "Detecting Angular app…", l ### Elements panel -While the **Components** tab is open, select an element in the Chrome **Elements** panel. The Components tab selects the component that hosts that element (the element itself, or the nearest ancestor that is a component host). It expands the parent rows, clears the filter if it hides the row, and scrolls the row into view. On other tabs, the Elements selection does nothing. It also does nothing when `inspectors.components` is `false` in the [configuration](/getting-started/configuration). +While the **Components** tab is open, select an element in the Chrome **Elements** panel. The Components tab selects the component that hosts that element (the element itself, or the nearest ancestor that is a component host). It expands the parent rows, clears the filter if it hides the row, and scrolls the row into view. On other tabs, the Elements selection does nothing. It also does nothing when `inspectors.components` is `false` in the [configuration](./configuration.md). This needs the overlay on the page, since the overlay answers which component hosts the element. @@ -117,7 +117,7 @@ The manifest asks for no `permissions`. Its host permissions cover loopback host Other hosts are optional host permissions. **Allow access** asks Chrome for the host of the inspected page only, on the scheme of that page (`http` or `https`) and on any port. The extension never asks for all hosts at once. -Granting the extension a host doesn't change what the devtools server accepts. The server still applies its own checks. The Vite plugin, for example, only answers requests from a loopback address. See [Access and redaction](/security). +Granting the extension a host doesn't change what the devtools server accepts. The server still applies its own checks. The Vite plugin, for example, only answers requests from a loopback address. See [Access and redaction](../security.md). The Vite plugin and the Express hub accept the extension's `chrome-extension://` origin by default. If your Express hub passes its own `allowedOrigins` list, add `chrome-extension://` to it. The ID is on the extension card in `chrome://extensions`. @@ -135,7 +135,7 @@ The content scripts are wider. Two of them run on every page. They check for an The page is not on a loopback host. Click Allow access to let the extension reach that host. Chrome asks you to confirm. - None of them served a connection file. Check that the server of the page mounts the devtools and that the server accepts the request. See Access and redaction. + None of them served a connection file. Check that the server of the page mounts the devtools and that the server accepts the request. See Access and redaction. Open the Components tab first, and check that the overlay is loaded. Elements outside any component select nothing. diff --git a/apps/docs/src/content/getting-started/cli.md b/apps/docs/src/content/getting-started/cli.md index bcda20e..07050b6 100644 --- a/apps/docs/src/content/getting-started/cli.md +++ b/apps/docs/src/content/getting-started/cli.md @@ -68,22 +68,22 @@ npx @santoshyadavdev/ng-devtools dev --port 9999 --open | `--mcp`, `--no-mcp` | Mount the MCP endpoint at `/__mcp`, or not. It is on by default. | - The server binds to localhost by default and asks for a one-time code. Changing --host or passing --no-auth widens who can reach it. See Access and redaction. + The server binds to localhost by default and asks for a one-time code. Changing --host or passing --no-auth widens who can reach it. See Access and redaction. ### What it shows No page is connected to the CLI server. The tabs show what your source declares: -- [Components](/inspectors/components) -- [Routes](/inspectors/router) -- [Signals](/inspectors/signals) -- [Providers](/inspectors/injectors) -- [NgRx declarations](/inspectors/ngrx-store) -- [Pipes](/inspectors/pipes) +- [Components](../inspectors/components.md) +- [Routes](../inspectors/router.md) +- [Signals](../inspectors/signals.md) +- [Providers](../inspectors/injectors.md) +- [NgRx declarations](../inspectors/ngrx-store.md) +- [Pipes](../inspectors/pipes.md) - For live data, mount the devtools in your app's own server. See Angular CLI and Express or Vite and Analog. + For live data, mount the devtools in your app's own server. See Angular CLI and Express or Vite and Analog. ## Static report @@ -117,7 +117,7 @@ The output is static files. Open it offline or host it on any static file server npx @santoshyadavdev/ng-devtools mcp ``` -Your agent client runs this command for you. [MCP server](/agents/mcp-server) covers client setup. +Your agent client runs this command for you. [MCP server](../agents/mcp-server.md) covers client setup. ### Source scan only diff --git a/apps/docs/src/content/getting-started/configuration.md b/apps/docs/src/content/getting-started/configuration.md index 0b4206f..3f2f1e8 100644 --- a/apps/docs/src/content/getting-started/configuration.md +++ b/apps/docs/src/content/getting-started/configuration.md @@ -17,13 +17,13 @@ These three functions take the same options: | Function | Import | Setup | | --------------------- | --------------------------------------- | --------------------------------------------------------------- | -| `initNgDevtoolsHub()` | `@santoshyadavdev/ng-devtools/hub` | [Angular CLI and Express](/getting-started/express) | -| `ngDevtools()` | `@santoshyadavdev/ng-devtools/vite` | [Vite and Analog](/getting-started/vite) | +| `initNgDevtoolsHub()` | `@santoshyadavdev/ng-devtools/hub` | [Angular CLI and Express](./express.md) | +| `ngDevtools()` | `@santoshyadavdev/ng-devtools/vite` | [Vite and Analog](./vite.md) | | `createNgDevtools()` | `@santoshyadavdev/ng-devtools/devframe` | A custom devframe host, such as `initDevframe()` without a hub. | ### Express hub -Pass the options next to the [access options](/security#express-hub) `auth`, `allowedOrigins` and `mcp`: +Pass the options next to the [access options](../security.md#express-hub) `auth`, `allowedOrigins` and `mcp`: ```ts {8-10} // src/server.ts @@ -42,7 +42,7 @@ app.use(devtools.nodeMiddleware); ### Vite plugin -Pass them to `ngDevtools()`, next to the [access options](/security#vite-plugin) `auth` and `allowedOrigins`: +Pass them to `ngDevtools()`, next to the [access options](../security.md#vite-plugin) `auth` and `allowedOrigins`: ```ts {9-12} // vite.config.ts @@ -124,7 +124,7 @@ interface NgDevtoolsConfig { `agent.tools` has the same keys as `inspectors`. An inspector that is off has no agent tools, whatever `agent.tools` says. -If you open a tab that is turned off, the panel says so and names the `inspectors` option. With `inspectors.components` set to `false`, the [Chrome extension](/getting-started/chrome-extension) does not follow the **Elements** panel. +If you open a tab that is turned off, the panel says so and names the `inspectors` option. With `inspectors.components` set to `false`, the [Chrome extension](./chrome-extension.md) does not follow the **Elements** panel. ### Actions @@ -150,7 +150,7 @@ The server refuses a blocked action. The panel disables its controls and shows a | `redaction.secretNames` | `[]` | Extra field names to treat as secret, on top of the built-in list. They match by words, like the built-in list, so `passport` also covers `passportNumber`. | | `redaction.unmask` | `[]` | Field names to show even when they look secret. They join the `unmask` list of `window.__NG_DEVTOOLS_FORMS__`. | -Forms, the router, component inputs, signals, NgRx and Analog previews use the extra secret names. Each list keeps up to 100 names. See [Access and redaction](/security#what-is-redacted) for what is redacted and what unmasking allows. +Forms, the router, component inputs, signals, NgRx and Analog previews use the extra secret names. Each list keeps up to 100 names. See [Access and redaction](../security.md#what-is-redacted) for what is redacted and what unmasking allows. ### Limits @@ -168,7 +168,7 @@ On Angular 20 and later, the page reports about 250 ms after Angular runs change ## Check the active configuration -The [Dashboard](/inspectors/dashboard#configuration-block) has a **Configuration** block. It lists the options that differ from the defaults, or says **Defaults** when nothing is changed. +The [Dashboard](../inspectors/dashboard.md#configuration-block) has a **Configuration** block. It lists the options that differ from the defaults, or says **Defaults** when nothing is changed. | Row | Lists | | ------------------------- | ---------------------------------------------- | diff --git a/apps/docs/src/content/getting-started/express.md b/apps/docs/src/content/getting-started/express.md index 96f0db0..6ca488e 100644 --- a/apps/docs/src/content/getting-started/express.md +++ b/apps/docs/src/content/getting-started/express.md @@ -12,14 +12,14 @@ description: Mount the devtools hub in the Express server of an Angular SSR app. In an *Angular app with server-side rendering, the devtools run inside your Express server. You add a middleware on the server and load the overlay in the browser. - This setup mounts the devtools in the Express server.ts that Angular SSR generates. For an Analog app, follow Vite and Analog instead. + This setup mounts the devtools in the Express server.ts that Angular SSR generates. For an Analog app, follow Vite and Analog instead. ## Setup at a glance - Add @santoshyadavdev/ng-devtools and devframe. See Installation. + Add @santoshyadavdev/ng-devtools and devframe. See Installation. Add initNgDevtoolsHub() to server.ts, before your other routes. @@ -91,13 +91,13 @@ With `ws: {sidecar: true}`, the WebSocket runs on its own port, picked automatic `initNgDevtoolsHub()` accepts the options of `initHub()` from `@devframes/hub`, apart from `devframes` and `ui`. These are the ones you are most likely to set: -| Option | Default | What it does | -| ---------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `base` | `'/__devframes/'` | Where the hub is mounted. The devtools panel lives at `ng-devtools/`. | -| `ws` | | `false` uses server-sent events only. `{ sidecar: true }` runs the WebSocket on its own port. | -| `auth` | on | `false` turns off the one-time code. | -| `allowedOrigins` | loopback origins and the Chrome extension | Extra origins allowed to open the WebSocket. A list replaces the Chrome extension default. `false` turns the origin check off. | -| `mcp` | a bearer token | Mounts the MCP endpoint at `__mcp` and asks for a bearer token. With `auth: false` the default is `'auto'`: it mounts once agent tools exist and asks for no token. See [Send a token](/agents/mcp-server#send-a-token). | +| Option | Default | What it does | +| ---------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `base` | `'/__devframes/'` | Where the hub is mounted. The devtools panel lives at `ng-devtools/`. | +| `ws` | | `false` uses server-sent events only. `{ sidecar: true }` runs the WebSocket on its own port. | +| `auth` | on | `false` turns off the one-time code. | +| `allowedOrigins` | loopback origins and the Chrome extension | Extra origins allowed to open the WebSocket. A list replaces the Chrome extension default. `false` turns the origin check off. | +| `mcp` | a bearer token | Mounts the MCP endpoint at `__mcp` and asks for a bearer token. With `auth: false` the default is `'auto'`: it mounts once agent tools exist and asks for no token. See [Send a token](../agents/mcp-server.md#send-a-token). | The hub also takes the devtools options, such as `inspectors`, `agent`, `actions`, `redaction` and `limits`. See [Configuration](/getting-started/configuration). @@ -105,7 +105,7 @@ The hub also takes the devtools options, such as `inspectors`, `agent`, `actions The hub protects its connection with a one-time code by default. The server prints the code, and a browser can read data only after it exchanges that code. On a machine only you use, pass `auth: false` to turn the gate off. -The origin check is on by default too. Only loopback origins and the [Chrome extension](/getting-started/chrome-extension) can open the WebSocket. If you pass your own `allowedOrigins` list, it keeps loopback origins but drops the extension. Add `chrome-extension://` to the list, with the ID from `chrome://extensions`: +The origin check is on by default too. Only loopback origins and the [Chrome extension](./chrome-extension.md) can open the WebSocket. If you pass your own `allowedOrigins` list, it keeps loopback origins but drops the extension. Add `chrome-extension://` to the list, with the ID from `chrome://extensions`: ```ts // src/server.ts @@ -116,7 +116,7 @@ const devtools = initNgDevtoolsHub({ }); ``` -[Access and redaction](/security) covers both checks. +[Access and redaction](../security.md) covers both checks. The demo app in this repository mounts the hub like this: @@ -140,7 +140,7 @@ It turns the one-time code off unless `NG_DEVTOOLS_AUTH` is `true`. Don't copy t ### Import it in development -The [overlay](/getting-started/overlay) collects live data from the page. Import it after bootstrap, in development only: +The [overlay](./overlay.md) collects live data from the page. Import it after bootstrap, in development only: ```ts {8-10} // src/main.ts @@ -172,7 +172,7 @@ A floating button appears on your page. It opens the devtools with one dock entr | NativeScript | A **Coming Soon** placeholder | | Capacitor | A **Coming Soon** placeholder | -[Popup and hub](/getting-started/popup-and-hub) covers the panel, its dock modes and deep links. +[Popup and hub](./popup-and-hub.md) covers the panel, its dock modes and deep links. ## Run the app @@ -231,7 +231,7 @@ Register `withNgDevtools()` before your own interceptors, for example `provideHt SSR and the devtools middleware must run in the same Express process. Otherwise the server-side calls never reach the tab. -The [SSR & HTTP guide](/guides/ssr-http) covers interceptor order and fault injection in detail. +The [SSR & HTTP guide](../guides/ssr-http.md) covers interceptor order and fault injection in detail. ## Mount only the panel diff --git a/apps/docs/src/content/getting-started/installation.md b/apps/docs/src/content/getting-started/installation.md index 73bbdeb..8fe5a4f 100644 --- a/apps/docs/src/content/getting-started/installation.md +++ b/apps/docs/src/content/getting-started/installation.md @@ -51,26 +51,26 @@ MCP agent support (`@devframes/agentic`) is included. You don't install it separ ### Entry points -| Import | Use it for | -| --------------------------------------- | -------------------------------------------------------------------------------------------------- | -| `@santoshyadavdev/ng-devtools/hub` | `initNgDevtoolsHub()`, the server middleware for an Express app. | -| `@santoshyadavdev/ng-devtools/vite` | The Vite plugin for Analog apps. | -| `@santoshyadavdev/ng-devtools/overlay` | The browser script that collects live data from your page. | -| `@santoshyadavdev/ng-devtools/popup` | The floating button and panel on your page. | -| `@santoshyadavdev/ng-devtools/http` | The HTTP interceptor and hydration hooks for the SSR & HTTP tab. | -| `@santoshyadavdev/ng-devtools/config` | The `NgDevtoolsConfig` type and its defaults. See [Configuration](/getting-started/configuration). | -| `@santoshyadavdev/ng-devtools/devframe` | The devframe definition, for custom hosts. | +| Import | Use it for | +| --------------------------------------- | -------------------------------------------------------------------------------------- | +| `@santoshyadavdev/ng-devtools/hub` | `initNgDevtoolsHub()`, the server middleware for an Express app. | +| `@santoshyadavdev/ng-devtools/vite` | The Vite plugin for Analog apps. | +| `@santoshyadavdev/ng-devtools/overlay` | The browser script that collects live data from your page. | +| `@santoshyadavdev/ng-devtools/popup` | The floating button and panel on your page. | +| `@santoshyadavdev/ng-devtools/http` | The HTTP interceptor and hydration hooks for the SSR & HTTP tab. | +| `@santoshyadavdev/ng-devtools/config` | The `NgDevtoolsConfig` type and its defaults. See [Configuration](./configuration.md). | +| `@santoshyadavdev/ng-devtools/devframe` | The devframe definition, for custom hosts. | ### The CLI binary -The package also installs an `ng-devtools` binary. It runs the devtools without your app: a local server, a static report or an MCP server. See [Standalone CLI](/getting-started/cli). +The package also installs an `ng-devtools` binary. It runs the devtools without your app: a local server, a static report or an MCP server. See [Standalone CLI](./cli.md). ## Pick a setup Every setup has two parts: - **Server part**: serves the devtools UI and receives data. -- **Browser part**: the [overlay](/getting-started/overlay). It runs in your page and sends live data to the server. +- **Browser part**: the [overlay](./overlay.md). It runs in your page and sends live data to the server. ### Server part @@ -137,11 +137,11 @@ The standalone CLI has no page connected, so it needs no browser part. ### Configure the devtools -Everything is on by default. To turn inspectors, agent tools or actions off, or to change redaction and limits, pass options to the server part. See [Configuration](/getting-started/configuration). +Everything is on by default. To turn inspectors, agent tools or actions off, or to change redaction and limits, pass options to the server part. See [Configuration](./configuration.md). ### Add the Chrome extension -The [Chrome extension](/getting-started/chrome-extension) adds a panel to Chrome DevTools. It sits on top of the Express or Vite setup. It does not replace the server part or the overlay. +The [Chrome extension](./chrome-extension.md) adds a panel to Chrome DevTools. It sits on top of the Express or Vite setup. It does not replace the server part or the overlay. ## Check that it works @@ -164,7 +164,7 @@ The [Chrome extension](/getting-started/chrome-extension) adds a panel to Chrome - The devtools are built on Devframe. Some setups import from devframe directly, for example initDevframe from devframe/initiate to mount only the panel. Package managers like pnpm only resolve imports of direct dependencies. + The devtools are built on Devframe. Some setups import from devframe directly, for example initDevframe from devframe/initiate to mount only the panel. Package managers like pnpm only resolve imports of direct dependencies. Wherever your server part runs. An Express app imports the hub in server.ts, so the package must be installed where that server starts. The overlay import in main.ts only runs in development builds. diff --git a/apps/docs/src/content/getting-started/introduction.md b/apps/docs/src/content/getting-started/introduction.md index c52074a..62375a4 100644 --- a/apps/docs/src/content/getting-started/introduction.md +++ b/apps/docs/src/content/getting-started/introduction.md @@ -14,7 +14,7 @@ The devtools inspect a running *Angular app. They read components, signals, inje The same tool runs in several places. It is built with *Devframe, so one definition powers every mode. - Everything ships in @santoshyadavdev/ng-devtools: the server side, the browser overlay, the in-page popup, the CLI and the built UI. See Installation. + Everything ships in @santoshyadavdev/ng-devtools: the server side, the browser overlay, the in-page popup, the CLI and the built UI. See Installation. ## What it inspects @@ -52,18 +52,18 @@ These tabs read the running page through Angular's debug API. They need a develo ### Project overview -| Tab | What it shows | -| ---------------------------------- | ------------------------------------------------------------------------------- | -| [Dashboard](/inspectors/dashboard) | The Angular and TypeScript versions, SSR status and a count for each inspector. | -| [Analog](/inspectors/analog) | File routes, server calls, render modes, content and lint for *Analog apps. | +| Tab | What it shows | +| --------------------------------------- | ------------------------------------------------------------------------------- | +| [Dashboard](../inspectors/dashboard.md) | The Angular and TypeScript versions, SSR status and a count for each inspector. | +| [Analog](../inspectors/analog.md) | File routes, server calls, render modes, content and lint for *Analog apps. | ### Source scan -The devtools also read your source files. Components, routes, signals, providers, NgRx declarations and pipes show up even with no page connected. The [standalone CLI](/getting-started/cli) and the static report run on the source scan alone. +The devtools also read your source files. Components, routes, signals, providers, NgRx declarations and pipes show up even with no page connected. The [standalone CLI](./cli.md) and the static report run on the source scan alone. ### Agent tools -The inspectors are exposed as *MCP tools and resources, so a coding agent can read and act on the running app. See [MCP server](/agents/mcp-server). +The inspectors are exposed as *MCP tools and resources, so a coding agent can read and act on the running app. See [MCP server](../agents/mcp-server.md). ## Ways to run it @@ -71,11 +71,11 @@ The inspectors are exposed as *MCP tools and resources, so a coding agent can re Your app's server hosts the devtools, and a script in the page sends live data to it. A floating button on the page opens the panel next to your app. -| Setup | Server part | Guide | -| ----------------------- | --------------------------- | ----------------------------------------------------- | -| Angular CLI with SSR | `initNgDevtoolsHub()` | [Angular CLI and Express](/getting-started/express) | -| Analog | The Vite plugin | [Vite and Analog](/getting-started/vite) | -| Chrome DevTools (extra) | One of the two setups above | [Chrome extension](/getting-started/chrome-extension) | +| Setup | Server part | Guide | +| ----------------------- | --------------------------- | ----------------------------------------- | +| Angular CLI with SSR | `initNgDevtoolsHub()` | [Angular CLI and Express](./express.md) | +| Analog | The Vite plugin | [Vite and Analog](./vite.md) | +| Chrome DevTools (extra) | One of the two setups above | [Chrome extension](./chrome-extension.md) | ### Outside your app @@ -85,7 +85,7 @@ Your app's server hosts the devtools, and a script in the page sends live data t | Static report | An offline HTML build of the source scan. | | MCP server | Every inspector exposed to coding agents over stdio. | -All three come from the `ng-devtools` binary. See [Standalone CLI](/getting-started/cli). +All three come from the `ng-devtools` binary. See [Standalone CLI](./cli.md). ## Built on Devframe @@ -130,16 +130,16 @@ The devtools are a - No. The overlay adds a floating button to your page and opens the devtools in a panel. The Chrome extension is optional. It adds the same UI as a panel in Chrome DevTools. + No. The overlay adds a floating button to your page and opens the devtools in a panel. The Chrome extension is optional. It adds the same UI as a panel in Chrome DevTools. - The devtools need a server part. An Angular CLI app mounts it in its Express server.ts. An Analog app gets it from the Vite plugin. Without either, the standalone CLI serves the source scan. + The devtools need a server part. An Angular CLI app mounts it in its Express server.ts. An Analog app gets it from the Vite plugin. Without either, the standalone CLI serves the source scan. Not if you follow the setup guides. They load the overlay with a dynamic import that only runs in development builds. - By default, no. The Vite plugin only answers requests from your machine, and the Express hub asks for a one-time code. See Access and redaction. + By default, no. The Vite plugin only answers requests from your machine, and the Express hub asks for a one-time code. See Access and redaction. diff --git a/apps/docs/src/content/getting-started/overlay.md b/apps/docs/src/content/getting-started/overlay.md index f313382..21be006 100644 --- a/apps/docs/src/content/getting-started/overlay.md +++ b/apps/docs/src/content/getting-started/overlay.md @@ -80,11 +80,11 @@ The overlay looks for the devframe connection next to the page first. Then it tr 1. `/__ng-devtools/` 2. `/__devframes/ng-devtools/` -It also adds the [floating button](/getting-started/popup-and-hub). With the hub mounted, the button opens the whole hub, with every dock in a side rail. +It also adds the [floating button](./popup-and-hub.md). With the hub mounted, the button opens the whole hub, with every dock in a side rail. ### Snapshots and events -On Angular 20 and later, the overlay reads the page about 250 ms after Angular runs change detection. It also reads it every 4 seconds as a heartbeat. On older versions, it reads the page every 3 seconds instead. Change that interval with [`limits.refreshMs`](/getting-started/configuration#limits). +On Angular 20 and later, the overlay reads the page about 250 ms after Angular runs change detection. It also reads it every 4 seconds as a heartbeat. On older versions, it reads the page every 3 seconds instead. Change that interval with [`limits.refreshMs`](./configuration.md#limits). Each read skips data that did not change. Router events are sent as they happen. @@ -156,7 +156,7 @@ bootstrapApplication(App, appConfig).then(() => { }); ``` -See [Restore NgRx signal state](/guides/ngrx-signals-restore). +See [Restore NgRx signal state](../guides/ngrx-signals-restore.md). ## Highlighting @@ -166,13 +166,13 @@ When you hover a component in the devtools, the overlay draws an amber box aroun - No. The overlay adds the floating button itself. See Popup and hub. + No. The overlay adds the floating button itself. See Popup and hub. It reads the page after change detection, at most once every 250 ms, plus a heartbeat every 4 seconds. It only sends data that changed. With the dynamic import above, it never loads in production builds. - Live values are sent to the devtools server. Secret-looking values are redacted first. See Access and redaction. + Live values are sent to the devtools server. Secret-looking values are redacted first. See Access and redaction. diff --git a/apps/docs/src/content/getting-started/popup-and-hub.md b/apps/docs/src/content/getting-started/popup-and-hub.md index a99cff1..56781d3 100644 --- a/apps/docs/src/content/getting-started/popup-and-hub.md +++ b/apps/docs/src/content/getting-started/popup-and-hub.md @@ -15,7 +15,7 @@ When the overlay loads, a floating button appears in the bottom-right corner of ### Where it comes from -Importing the [overlay](/getting-started/overlay) adds the button. The overlay first checks whether the page's server mounts the hub at `/__devframes/`. If it does, the button opens the whole hub. If not, it opens the devtools panel on its own. +Importing the [overlay](./overlay.md) adds the button. The overlay first checks whether the page's server mounts the hub at `/__devframes/`. If it does, the button opens the whole hub. If not, it opens the devtools panel on its own. ### Create it yourself diff --git a/apps/docs/src/content/getting-started/vite.md b/apps/docs/src/content/getting-started/vite.md index 15318fd..785c2a4 100644 --- a/apps/docs/src/content/getting-started/vite.md +++ b/apps/docs/src/content/getting-started/vite.md @@ -15,7 +15,7 @@ For *Analog apps, add the *Vite plugin next to `analog()` and load the overlay i - Add @santoshyadavdev/ng-devtools and devframe. See Installation. + Add @santoshyadavdev/ng-devtools and devframe. See Installation. Register ngDevtools() after analog() in vite.config.ts. @@ -80,13 +80,13 @@ It mounts the devtools hub on the Vite dev server. The WebSocket shares Vite's H ### Records Analog server activity -It records Analog page renders, `load()` fetches, server functions and API calls for the [Analog inspector](/inspectors/analog). The `apiPrefix` option tells it which requests are API calls. +It records Analog page renders, `load()` fetches, server functions and API calls for the [Analog inspector](../inspectors/analog.md). The `apiPrefix` option tells it which requests are API calls. ### Answers only your machine The plugin only answers requests from a loopback address (any `127.x.x.x` address or `::1`). Other requests to the devtools get `403` with the message "ng-devtools only answers requests from this machine." WebSocket upgrades follow the same rules. -By default the plugin leaves the one-time code off, and the loopback and origin checks take its place. If `server.allowedHosts` or `allowedOrigins` allows a host that is not a loopback host, the plugin also asks for the code. See [`auth`](#auth). [Access and redaction](/security) covers every check. +By default the plugin leaves the one-time code off, and the loopback and origin checks take its place. If `server.allowedHosts` or `allowedOrigins` allows a host that is not a loopback host, the plugin also asks for the code. See [`auth`](#auth). [Access and redaction](../security.md) covers every check. ## Options @@ -110,7 +110,7 @@ The plugin also takes the devtools options, such as `inspectors`, `agent`, `acti ### `base` -Change `base` if `/__devframes/` clashes with a route of your own. The overlay looks for `/__devframes/ng-devtools/` and `/__ng-devtools/` by default, so a custom base also needs a custom overlay path. See [A custom mount path](/getting-started/overlay#a-custom-mount-path). +Change `base` if `/__devframes/` clashes with a route of your own. The overlay looks for `/__devframes/ng-devtools/` and `/__ng-devtools/` by default, so a custom base also needs a custom overlay path. See [A custom mount path](./overlay.md#a-custom-mount-path). ### `apiPrefix` @@ -171,7 +171,7 @@ A non-loopback entry in `server.allowedHosts` or `allowedOrigins` turns the one- ## Angular CLI apps - The Angular CLI dev server does not accept Vite plugins. For an Angular CLI app, mount the hub in your Express server instead. See Angular CLI and Express. + The Angular CLI dev server does not accept Vite plugins. For an Angular CLI app, mount the hub in your Express server instead. See Angular CLI and Express. ## FAQ @@ -184,7 +184,7 @@ A non-loopback entry in `server.allowedHosts` or `allowedOrigins` turns the one- The request did not come from your machine, or its origin is not trusted. Open the app on localhost, list your hostname in server.allowedHosts, or add the origin to allowedOrigins. - The plugin records server calls made through the Vite dev server. Check that the plugin is registered and that apiPrefix matches your server routes. The Analog guide walks through a full setup. + The plugin records server calls made through the Vite dev server. Check that the plugin is registered and that apiPrefix matches your server routes. The Analog guide walks through a full setup. diff --git a/apps/docs/src/content/guides/analog.md b/apps/docs/src/content/guides/analog.md index 8076909..1b40523 100644 --- a/apps/docs/src/content/guides/analog.md +++ b/apps/docs/src/content/guides/analog.md @@ -95,7 +95,7 @@ The plugin also takes the devtools options. See [Vite and Analog](/getting-start ### Custom hostnames -The devtools only answer requests from this machine. If you open the dev server through another hostname, add it to Vite's `server.allowedHosts`. See [Security](/security). +The devtools only answer requests from this machine. If you open the dev server through another hostname, add it to Vite's `server.allowedHosts`. See [Security](../security.md). ## Step 3: Load the overlay @@ -122,10 +122,10 @@ Start the dev server as usual. Then: | Full viewer | `/__devframes/` on the Vite dev server | | MCP endpoint | `/__devframes/__mcp` on the Vite dev server | -Open the **Analog** dock to see file routes, server calls, render modes, content and lint. See [the Analog inspector](/inspectors/analog) for each view. +Open the **Analog** dock to see file routes, server calls, render modes, content and lint. See [the Analog inspector](../inspectors/analog.md) for each view. - Point your MCP client at http://localhost:5173/__devframes/__mcp with an Origin header. See MCP server. The analog-server-calls and analog-call-api tools only work through the Vite plugin. + Point your MCP client at http://localhost:5173/__devframes/__mcp with an Origin header. See MCP server. The analog-server-calls and analog-call-api tools only work through the Vite plugin. ## Optional: record HttpClient calls @@ -148,7 +148,7 @@ export const appConfig: ApplicationConfig = { }; ``` -See [Set up SSR & HTTP](/guides/ssr-http) for the interceptor order. +See [Set up SSR & HTTP](./ssr-http.md) for the interceptor order. ## Try the demo diff --git a/apps/docs/src/content/guides/ngrx-signals-restore.md b/apps/docs/src/content/guides/ngrx-signals-restore.md index a59cd9b..c7224af 100644 --- a/apps/docs/src/content/guides/ngrx-signals-restore.md +++ b/apps/docs/src/content/guides/ngrx-signals-restore.md @@ -9,7 +9,7 @@ description: Register patchState so that restoring a signal store also notifies # Restore NgRx signal state -The [NgRx Store tab](/inspectors/ngrx-store) can put a signal store back to its state after any change in the log. By default it writes the state signals directly. That updates your components, but `watchState` listeners do not run. +The [NgRx Store tab](../inspectors/ngrx-store.md) can put a signal store back to its state after any change in the log. By default it writes the state signals directly. That updates your components, but `watchState` listeners do not run. Register `patchState` once, and restore goes through it instead. Then `watchState` listeners run as they would for any other change. diff --git a/apps/docs/src/content/guides/ssr-http.md b/apps/docs/src/content/guides/ssr-http.md index b84c6a2..9f8225c 100644 --- a/apps/docs/src/content/guides/ssr-http.md +++ b/apps/docs/src/content/guides/ssr-http.md @@ -9,7 +9,7 @@ description: Add the interceptor and hydration hooks, in the right order, to fil # Set up SSR & HTTP -The [SSR & HTTP tab](/inspectors/ssr-http) records every `HttpClient` call during server rendering and in the browser. It needs three things: an interceptor, a hydration hook, and SSR running next to the devtools. +The [SSR & HTTP tab](../inspectors/ssr-http.md) records every `HttpClient` call during server rendering and in the browser. It needs three things: an interceptor, a hydration hook, and SSR running next to the devtools. ## What you set up @@ -122,7 +122,7 @@ app.use(devtools.nodeMiddleware); export const reqHandler = createNodeRequestHandler(app); ``` -This is adapted from the demo app's `src/server.ts`. It keeps the one-time code and the origin check on, which are the defaults. See [Angular CLI and Express](/getting-started/express) for every option. +This is adapted from the demo app's `src/server.ts`. It keeps the one-time code and the origin check on, which are the defaults. See [Angular CLI and Express](../getting-started/express.md) for every option. ## Step 4: Render the pages you test on the server @@ -139,7 +139,7 @@ export const serverRoutes: ServerRoute[] = [ ``` - The explain-render-mode agent tool tells you which ServerRoute and render mode a URL gets. See Tools. + The explain-render-mode agent tool tells you which ServerRoute and render mode a URL gets. See Tools. ## Step 5: Inject a fault @@ -180,7 +180,7 @@ pnpm build --configuration development node dist/angular-devtools/server/server.mjs ``` -Open `http://localhost:4000/examples/http`. See [Demo apps](/contributing/demo-apps) for the rest. +Open `http://localhost:4000/examples/http`. See [Demo apps](../contributing/demo-apps.md) for the rest. ## Where to next diff --git a/apps/docs/src/content/inspectors/analog.md b/apps/docs/src/content/inspectors/analog.md index f59afb2..0afe73b 100644 --- a/apps/docs/src/content/inspectors/analog.md +++ b/apps/docs/src/content/inspectors/analog.md @@ -17,7 +17,7 @@ The Analog dock is always in the rail. In other apps it shows a **This app doesn ### Add the plugin -Add the Vite plugin next to `analog()` and load the overlay. See [Vite and Analog](/getting-started/vite) and the [Analog guide](/guides/analog). +Add the Vite plugin next to `analog()` and load the overlay. See [Vite and Analog](../getting-started/vite.md) and the [Analog guide](../guides/analog.md). ```ts {3,7} // vite.config.ts @@ -122,7 +122,7 @@ A page is **Client only** when `routeRules` or the `ssr` option turns SSR off fo A warning at the top names the route. - Open the SSR & HTTP tab and look for the Analog entry in the payload. + Open the SSR & HTTP tab and look for the Analog entry in the payload. After the fix, the browser should not fetch the route's load() again. @@ -158,7 +158,7 @@ A page is **Client only** when `routeRules` or the `ssr` option turns SSR off fo | `ng-devtools:analog-content` | `filter` | Markdown files with slug, frontmatter, route and parse errors. | | `ng-devtools:analog-lint` | | The Analog checks. | -`analog-current-page` is the only place that shows the `load()` data a page received. See [Tools](/agents/tools). +`analog-current-page` is the only place that shows the `load()` data a page received. See [Tools](../agents/tools.md). ## Limits and gotchas @@ -168,7 +168,7 @@ It sends a real request to your dev server. Methods other than GET, HEAD and OPT ### Redaction -Response previews and `load()` data redact secret-looking keys, tokens, `Bearer` values and secret query parameters. See [what the devtools redact](/security). +Response previews and `load()` data redact secret-looking keys, tokens, `Bearer` values and secret query parameters. See [what the devtools redact](../security.md). ### Call history size diff --git a/apps/docs/src/content/inspectors/components.md b/apps/docs/src/content/inspectors/components.md index 24f4f50..1e65571 100644 --- a/apps/docs/src/content/inspectors/components.md +++ b/apps/docs/src/content/inspectors/components.md @@ -28,7 +28,7 @@ The tree shows up to 2000 components, and walks up to 256 levels of DOM nesting. The header of the selected instance shows the class name, the host tag, and the source file and line. The file and line come from the source scan, matched by class name. They are missing when the scan has no match. -When a form exists in the same source file, a **Show … in Forms** button opens it in the [Forms tab](/inspectors/forms). +When a form exists in the same source file, a **Show … in Forms** button opens it in the [Forms tab](./forms.md). ### Facts @@ -48,7 +48,7 @@ A fact shows **Unknown** when Angular does not report it. ### Injected services -**Injected** lists each token the component class injects, with its flags and the injector that provided it. The block marks a token nobody provides as **not provided**. The block leaves out tokens that host directives inject. Use the [Injectors tab](/inspectors/injectors) for those. +**Injected** lists each token the component class injects, with its flags and the injector that provided it. The block marks a token nobody provides as **not provided**. The block leaves out tokens that host directives inject. Use the [Injectors tab](./injectors.md) for those. ### Source mode @@ -103,7 +103,7 @@ On Angular 20 and later, the page reads the tree about 250 ms after Angular runs ### Start from the Elements panel -If you use the [Chrome extension](/getting-started/chrome-extension), open the **Components** tab in its panel. Then select an element in the Chrome **Elements** panel. The tab selects the component that hosts that element and scrolls its row into view. +If you use the [Chrome extension](../getting-started/chrome-extension.md), open the **Components** tab in its panel. Then select an element in the Chrome **Elements** panel. The tab selects the component that hosts that element and scrolls its row into view. ### Check why an output does nothing @@ -142,7 +142,7 @@ Use the arrow keys, Home and End to move through the tree. The right arrow expan | `ng-devtools:highlight` | tool | Highlights a component in the page. Takes an instance id, class name, host tag or CSS selector. Also retargets the Signals graph. | | `ng-devtools:component-tree` | resource | The live tree per page, with the detail of the selected instance. | -See [Tools](/agents/tools) and [Resources](/agents/resources). +See [Tools](../agents/tools.md) and [Resources](../agents/resources.md). ## Limits and gotchas @@ -156,7 +156,7 @@ Input values stop at 3 levels of nesting, 30 keys or items, and 300 characters. ### Secrets are redacted -The devtools replace inputs with secret-looking names with `[redacted]`. They also redact JWTs and `Bearer` values inside strings. See [what the devtools redact](/security). +The devtools replace inputs with secret-looking names with `[redacted]`. They also redact JWTs and `Bearer` values inside strings. See [what the devtools redact](../security.md). ### Instance ids change on reload diff --git a/apps/docs/src/content/inspectors/dashboard.md b/apps/docs/src/content/inspectors/dashboard.md index e0bc7f7..242c553 100644 --- a/apps/docs/src/content/inspectors/dashboard.md +++ b/apps/docs/src/content/inspectors/dashboard.md @@ -28,16 +28,16 @@ The top block shows the project name and a chip for each of these: Each card counts what one inspector found. Click a card to open its tab. When the hub is mounted, the NgRx card opens the **NgRx** dock. -| Card | Counts | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| [Components](/inspectors/components) | Components in source, plus the number of directives. | -| [Routes](/inspectors/router) | Navigable page paths in source, plus the number of redirects. | -| [Signals](/inspectors/signals) | Nodes in the live signal graph, plus the declarations in source. Without a page, the declarations in source. | -| [Injectors](/inspectors/injectors) | Live injectors on the page, plus their providers. Without a page, the provider declarations in source. | -| [NgRx declarations](/inspectors/ngrx-store) | NgRx declarations in source, broken down by kind. | -| [Pipes](/inspectors/pipes) | Custom pipes in source, plus the built-in pipes in use. | +| Card | Counts | +| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ | +| [Components](./components.md) | Components in source, plus the number of directives. | +| [Routes](./router.md) | Navigable page paths in source, plus the number of redirects. | +| [Signals](./signals.md) | Nodes in the live signal graph, plus the declarations in source. Without a page, the declarations in source. | +| [Injectors](./injectors.md) | Live injectors on the page, plus their providers. Without a page, the provider declarations in source. | +| [NgRx declarations](./ngrx-store.md) | NgRx declarations in source, broken down by kind. | +| [Pipes](./pipes.md) | Custom pipes in source, plus the built-in pipes in use. | -Cards of inspectors turned off in the [configuration](/getting-started/configuration) are hidden. +Cards of inspectors turned off in the [configuration](../getting-started/configuration.md) are hidden. ### Card states @@ -45,7 +45,7 @@ A card shows **Counting…** while it loads. It shows **Count unavailable** when ### Configuration block -Below the cards, the **Configuration** block lists the devtools options that differ from the defaults, such as **Inspectors off**, **Blocked actions** and **Limits**. It says **Defaults** when nothing is changed. See [Configuration](/getting-started/configuration#check-the-active-configuration). +Below the cards, the **Configuration** block lists the devtools options that differ from the defaults, such as **Inspectors off**, **Blocked actions** and **Limits**. It says **Defaults** when nothing is changed. See [Configuration](../getting-started/configuration.md#check-the-active-configuration). ## Where the data comes from @@ -63,7 +63,7 @@ SSR is **On** when the build options set `ssr` or `server`. For *Analog apps, SS ### Counts -The Components, Routes, NgRx and Pipes cards count the source scan. The Signals and Injectors cards use the live page when one is connected, and the source scan otherwise. The live Signals count covers the graph of the one component the [Signals tab](/inspectors/signals) shows, and counts its signals, computeds, linked signals and effects. +The Components, Routes, NgRx and Pipes cards count the source scan. The Signals and Injectors cards use the live page when one is connected, and the source scan otherwise. The live Signals count covers the graph of the one component the [Signals tab](./signals.md) shows, and counts its signals, computeds, linked signals and effects. ## How to use it @@ -85,7 +85,7 @@ The Components, Routes, NgRx and Pipes cards count the source scan. The Signals | ------------------------ | ------------------------------------------------------------------------------------------------------ | | `ng-devtools:build-meta` | Angular and TypeScript versions, the project name, SSR status and, in Analog apps, the Analog version. | -[Static reports](/getting-started/cli) include the same data. See [Tools](/agents/tools) for every tool. +[Static reports](../getting-started/cli.md) include the same data. See [Tools](../agents/tools.md) for every tool. ## Limits and gotchas diff --git a/apps/docs/src/content/inspectors/forms.md b/apps/docs/src/content/inspectors/forms.md index 672bd30..60bcc7e 100644 --- a/apps/docs/src/content/inspectors/forms.md +++ b/apps/docs/src/content/inspectors/forms.md @@ -142,7 +142,7 @@ The devtools never run async validators. The probe emits no form events, so it d -You can also open a form from its component in the [Components tab](/inspectors/components). +You can also open a form from its component in the [Components tab](./components.md). ## Agent tools @@ -171,12 +171,12 @@ You can also open a form from its component in the [Components tab](/inspectors/ | `ng-devtools:form-action` | Set, touch, revalidate, reset, submit, focus, snapshot, restore and more. | | `ng-devtools:fill-form` | Fills several fields through the inputs, like a user would. Can submit afterwards. | -Agents can loop: inspect, act, `wait-for-form`, then `form-diff` from the marker they had. The `ng-devtools:forms` resource holds every form and recent changes. See [Tools](/agents/tools). +Agents can loop: inspect, act, `wait-for-form`, then `form-diff` from the marker they had. The `ng-devtools:forms` resource holds every form and recent changes. See [Tools](../agents/tools.md). ## Limits and gotchas - The devtools send values to the devtools server, show them in the tab and return them to agents. They replace password fields and fields with secret-looking names with [redacted]. To mask or unmask a field, see Security. + The devtools send values to the devtools server, show them in the tab and return them to agents. They replace password fields and fields with secret-looking names with [redacted]. To mask or unmask a field, see Security. ### Reset, submit and restore ask first @@ -185,7 +185,7 @@ In the tab, the button turns into **Confirm reset**, **Confirm submit** or **Con ### Fields that are not written -The actions don't write secret fields unless you unmask them. See [Access and redaction](/security#opt-fields-in-or-out). For Signal Forms, they skip hidden, readonly and rule-disabled fields too. They write disabled reactive fields only with `force`. +The actions don't write secret fields unless you unmask them. See [Access and redaction](../security.md#opt-fields-in-or-out). For Signal Forms, they skip hidden, readonly and rule-disabled fields too. They write disabled reactive fields only with `force`. ### Snapshot limits diff --git a/apps/docs/src/content/inspectors/injectors.md b/apps/docs/src/content/inspectors/injectors.md index 9efb9b7..16ecc06 100644 --- a/apps/docs/src/content/inspectors/injectors.md +++ b/apps/docs/src/content/inspectors/injectors.md @@ -113,7 +113,7 @@ Arrow keys, Home and End move the selection through the tree. The right arrow ex | `ng-devtools:inspect-providers` | tool | The injector tree a page reported. `pageId` picks a tab. `selector` only labels the answer. | | `ng-devtools:injector-tree` | resource | The live tree last reported by a page. | -See [Tools](/agents/tools) and [Resources](/agents/resources). +See [Tools](../agents/tools.md) and [Resources](../agents/resources.md). ## Limits and gotchas diff --git a/apps/docs/src/content/inspectors/ngrx-store.md b/apps/docs/src/content/inspectors/ngrx-store.md index a303fd9..835b0df 100644 --- a/apps/docs/src/content/inspectors/ngrx-store.md +++ b/apps/docs/src/content/inspectors/ngrx-store.md @@ -120,13 +120,13 @@ The overlay finds stores through Angular's debug API, so the live section needs | `ng-devtools:get-ngrx-store` | tool | NgRx declarations from source, with the members of each `signalStore`. | | `ng-devtools:ngrx-store` | resource | The live stores per page, with state, computeds, methods, references and the change log. | -Agent access is read-only. No tool can restore a state. See [Tools](/agents/tools) and [Resources](/agents/resources). +Agent access is read-only. No tool can restore a state. See [Tools](../agents/tools.md) and [Resources](../agents/resources.md). ## Limits and gotchas ### `watchState` needs `registerNgrxSignals` -Without it, restore writes the state signals directly. Components update, but `watchState` listeners do not run, and the log entry says so. Call `registerNgrxSignals({ patchState })` from `@santoshyadavdev/ng-devtools/overlay` once, and restore goes through `patchState`. This applies to `signalStore` only. A `signalState` restore always writes directly. See [Restore NgRx signal state](/guides/ngrx-signals-restore). +Without it, restore writes the state signals directly. Components update, but `watchState` listeners do not run, and the log entry says so. Call `registerNgrxSignals({ patchState })` from `@santoshyadavdev/ng-devtools/overlay` once, and restore goes through `patchState`. This applies to `signalStore` only. A `signalState` restore always writes directly. See [Restore NgRx signal state](../guides/ngrx-signals-restore.md). ### Stores appear when they are created @@ -138,7 +138,7 @@ Use `provideStore()` or `StoreModule.forRoot()`. The overlay stops looking after ### Redaction -The devtools replace state keys with secret-looking names with `[redacted]`, at any depth. See [what the devtools redact](/security). +The devtools replace state keys with secret-looking names with `[redacted]`, at any depth. See [what the devtools redact](../security.md). ### Log size diff --git a/apps/docs/src/content/inspectors/pipes.md b/apps/docs/src/content/inspectors/pipes.md index 9cad3ad..de9b5ee 100644 --- a/apps/docs/src/content/inspectors/pipes.md +++ b/apps/docs/src/content/inspectors/pipes.md @@ -121,12 +121,12 @@ Open **Async subscriptions** and look for **duplicate subscription** rows. Subsc | `ng-devtools:lint-pipes` | no | Runs the lint rules above. | | `ng-devtools:explain-pipe` | partly | One pipe by `name`: where it is declared or used, purity, live counts, last input and output, the stale warning and the lint findings. | -Agents can't turn recording on. To give `explain-pipe` call data, click **Record calls** in the panel first. See [Tools](/agents/tools). +Agents can't turn recording on. To give `explain-pipe` call data, click **Record calls** in the panel first. See [Tools](../agents/tools.md). ## Limits and gotchas - The devtools send pipe inputs, outputs and async values as they are, cut to 200 characters. Keep the dev server on localhost. See Security. + The devtools send pipe inputs, outputs and async values as they are, cut to 200 characters. Keep the dev server on localhost. See Security. ### The stale warning is experimental diff --git a/apps/docs/src/content/inspectors/router.md b/apps/docs/src/content/inspectors/router.md index b8a9a85..8c5f6a1 100644 --- a/apps/docs/src/content/inspectors/router.md +++ b/apps/docs/src/content/inspectors/router.md @@ -82,7 +82,7 @@ Each finding says whether Angular throws, warns or does not warn. The lint skips The routes declared in your files: `*.routes.ts` and `*routing.module.ts` files, the files they lazy load, and Analog pages. Each row shows the path, the component or target, guards and resolvers, the title and the declaring file. Once the live config is available, the tab collapses this table. **Show table** opens it. -Components rendered by the router show their route and outlet in the [Components tab](/inspectors/components). +Components rendered by the router show their route and outlet in the [Components tab](./components.md). ## Where the data comes from @@ -173,7 +173,7 @@ Without that recording, the guards listed for a navigation are candidates: the ` | `ng-devtools:navigate` | Acts on the router: `navigate`, `abort`, `replay`, `probe`, `instrument` and `resolve-lazy`. | | `ng-devtools:router` (resource) | The active route tree and recent navigations of each page. | -`navigate` only accepts same-origin relative URLs that start with `/`. `resolve-lazy` needs a `routeId`. See [Tools](/agents/tools). +`navigate` only accepts same-origin relative URLs that start with `/`. `resolve-lazy` needs a `routeId`. See [Tools](../agents/tools.md). ## Limits and gotchas @@ -191,7 +191,7 @@ The tab lists only the last one, marked **before DevTools connected**, without t ### Redaction -The devtools replace query, matrix and fragment values with secret-looking keys with `[redacted]`. They also redact tokens, `Bearer` values, and route params with secret-looking names such as `:token`. You can't replay a navigation with a redacted URL. See [what the devtools redact](/security). +The devtools replace query, matrix and fragment values with secret-looking keys with `[redacted]`. They also redact tokens, `Bearer` values, and route params with secret-looking names such as `:token`. You can't replay a navigation with a redacted URL. See [what the devtools redact](../security.md). ### History and config caps diff --git a/apps/docs/src/content/inspectors/signals.md b/apps/docs/src/content/inspectors/signals.md index 30152c6..1c4df22 100644 --- a/apps/docs/src/content/inspectors/signals.md +++ b/apps/docs/src/content/inspectors/signals.md @@ -120,7 +120,7 @@ The `ng-devtools:highlight` tool also switches the graph to the component it hig | `ng-devtools:highlight` | tool | Highlights a component and makes it the target of the graph. | | `ng-devtools:signal-graph` | resource | The live graph per page. | -`inspect-signals` returns the graph of the chosen component. Call `highlight` first to switch it. See [Tools](/agents/tools). +`inspect-signals` returns the graph of the chosen component. Call `highlight` first to switch it. See [Tools](../agents/tools.md). ## Limits and gotchas diff --git a/apps/docs/src/content/inspectors/ssr-http.md b/apps/docs/src/content/inspectors/ssr-http.md index 5c55489..ec36337 100644 --- a/apps/docs/src/content/inspectors/ssr-http.md +++ b/apps/docs/src/content/inspectors/ssr-http.md @@ -31,10 +31,10 @@ export const appConfig: ApplicationConfig = { ``` - Put withNgDevtools() before your own interceptors. Then it records requests as the app makes them, and fault rules apply before anything else. The full setup is in the SSR & HTTP guide. + Put withNgDevtools() before your own interceptors. Then it records requests as the app makes them, and fault rules apply before anything else. The full setup is in the SSR & HTTP guide. -SSR must run in the same Node process as the devtools server, such as the Express server with the hub mounted, or the Vite dev server with the plugin. The [overlay](/getting-started/overlay) must be loaded, because client calls, hydration and the payload reach the tab through it. +SSR must run in the same Node process as the devtools server, such as the Express server with the hub mounted, or the Vite dev server with the plugin. The [overlay](../getting-started/overlay.md) must be loaded, because client calls, hydration and the payload reach the tab through it. ## What it shows @@ -144,7 +144,7 @@ The interceptor works in development builds only. In production it passes reques ## Agent tools -There is no dedicated tool for this tab. Agents read its data with the `devframe_state_read` tool and the `ng-devtools:http` key. See [Resources](/agents/resources). +There is no dedicated tool for this tab. Agents read its data with the `devframe_state_read` tool and the `ng-devtools:http` key. See [Resources](../agents/resources.md). Two router tools cover related ground: @@ -156,7 +156,7 @@ Two router tools cover related ground: ## Limits and gotchas - The devtools send response previews and TransferState values to the devtools server as they are. Don't expose the dev server beyond localhost. See Security. + The devtools send response previews and TransferState values to the devtools server as they are. Don't expose the dev server beyond localhost. See Security. ### Prerendered routes make no requests diff --git a/apps/docs/src/content/security.md b/apps/docs/src/content/security.md index fafc606..4ec456e 100644 --- a/apps/docs/src/content/security.md +++ b/apps/docs/src/content/security.md @@ -105,13 +105,13 @@ A list keeps loopback origins but replaces the Chrome extension default. If you ### Standalone CLI -The CLI server binds to `localhost` and asks for a one-time code. `--host` changes the bind address and `--no-auth` turns the code off. See [Standalone CLI](/getting-started/cli). +The CLI server binds to `localhost` and asks for a one-time code. `--host` changes the bind address and `--no-auth` turns the code off. See [Standalone CLI](./getting-started/cli.md). ### MCP endpoint The HTTP MCP endpoint answers only requests that carry a loopback `Origin` header. In the Vite plugin, the request must also come from a loopback address, like every devtools request. -While the one-time code is on, the endpoint also asks for a bearer token. That is the Express hub by default, and the Vite plugin when its code is on. The hub prints a generated token when it starts. Set `NG_DEVTOOLS_MCP_TOKEN` to choose the token yourself. Requests without the right `Authorization: Bearer ` header get `401`. The stdio server needs no token. See [Send a token](/agents/mcp-server#send-a-token). +While the one-time code is on, the endpoint also asks for a bearer token. That is the Express hub by default, and the Vite plugin when its code is on. The hub prints a generated token when it starts. Set `NG_DEVTOOLS_MCP_TOKEN` to choose the token yourself. Requests without the right `Authorization: Bearer ` header get `401`. The stdio server needs no token. See [Send a token](./agents/mcp-server.md#send-a-token). Without a token, the Express hub answers only requests from a loopback address. With a token, it also answers other addresses that send the right token and a loopback `Origin`. Any client can set that header, so treat the token like a password. @@ -121,7 +121,7 @@ The extension has host permissions for loopback hosts only: `localhost` and its On any other host, the panel doesn't send a request until you click **Allow access**. Chrome then asks you to grant the extension that one host, on the scheme of the page and any port. The extension never asks for all hosts at once. -Granting the extension a host doesn't change what the devtools server accepts. The server still applies the checks on this page. Both the Vite plugin and the Express hub accept the extension's `chrome-extension://` origin by default. An Express hub with its own `allowedOrigins` list needs the extension origin in that list. See [Chrome extension](/getting-started/chrome-extension#host-access). +Granting the extension a host doesn't change what the devtools server accepts. The server still applies the checks on this page. Both the Vite plugin and the Express hub accept the extension's `chrome-extension://` origin by default. An Express hub with its own `allowedOrigins` list needs the extension origin in that list. See [Chrome extension](./getting-started/chrome-extension.md#host-access). ## What is redacted @@ -153,7 +153,7 @@ window.__NG_DEVTOOLS_FORMS__ = {mask: ['iban'], unmask: ['passport']}; `[data-ng-devtools="unmask"]` opts a field back in. The `window` setting does the same by key. -You can also name secret and unmasked fields on the server, with the `redaction` option. `redaction.secretNames` adds secret names for forms, the router, components, signals, NgRx and Analog, and `redaction.unmask` joins the `window` list. See [Redaction options](/getting-started/configuration#redaction). +You can also name secret and unmasked fields on the server, with the `redaction` option. `redaction.secretNames` adds secret names for forms, the router, components, signals, NgRx and Analog, and `redaction.unmask` joins the `window` list. See [Redaction options](./getting-started/configuration.md#redaction). Unmasking also changes what the devtools can write. A key listed in `unmask` on `window` can be written. The element marker only lifts the checks that come from the element (password type, `autocomplete` and mask markers), so a field with a secret-looking name is still not written. @@ -187,7 +187,7 @@ Server call previews and URLs are redacted: secret-looking keys in JSON bodies, ### Not redacted -Response previews and TransferState values in the [SSR & HTTP tab](/inspectors/ssr-http) are not redacted. They reach the devtools server unchanged, so don't expose the dev server beyond localhost. +Response previews and TransferState values in the [SSR & HTTP tab](./inspectors/ssr-http.md) are not redacted. They reach the devtools server unchanged, so don't expose the dev server beyond localhost. ## Checklist @@ -205,7 +205,7 @@ Response previews and TransferState values in the [SSR & HTTP tab](/inspectors/s Use data-ng-devtools="mask", window.__NG_DEVTOOLS_FORMS__ or redaction.secretNames for fields the secret words miss. - Set agent.readOnly or turn off actions to stop the panel and agents from writing to your app. See Configuration. + Set agent.readOnly or turn off actions to stop the panel and agents from writing to your app. See Configuration. diff --git a/apps/docs/vite.config.ts b/apps/docs/vite.config.ts index 5288e16..b5b3090 100644 --- a/apps/docs/vite.config.ts +++ b/apps/docs/vite.config.ts @@ -7,6 +7,7 @@ import {readFileSync} from 'node:fs'; import {getBuildExtensions} from './src/marked-extensions/index.ts'; import {pageMetaPlugin} from './page-meta.plugin.ts'; import {internalLinkGuard} from './link-guard.plugin.ts'; +import {mdLinksPlugin} from './md-links.plugin.ts'; import {sitemapPlugin} from './sitemap.plugin.ts'; import {searchIndexPlugin} from './search-index.plugin.ts'; import {rawMdPlugin} from './raw-md.plugin.ts'; @@ -78,6 +79,7 @@ export default defineConfig(async () => ({ varsPlugin(), externalLinkGuard(), internalLinkGuard(), + mdLinksPlugin(), pageMetaPlugin({ repoUrl: config.site.githubUrl, branch: config.site.githubBranch ?? 'main', From c997e8b96f5001682270348ef19427602389d833 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 19:19:31 +0300 Subject: [PATCH 14/15] docs: use relative .md links in the pages added after the link change --- apps/docs/src/content/agents/mcp-server.md | 2 +- apps/docs/src/content/contributing/development.md | 2 +- apps/docs/src/content/getting-started/express.md | 2 +- apps/docs/src/content/getting-started/vite.md | 4 ++-- apps/docs/src/content/guides/analog.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/docs/src/content/agents/mcp-server.md b/apps/docs/src/content/agents/mcp-server.md index c604953..833d242 100644 --- a/apps/docs/src/content/agents/mcp-server.md +++ b/apps/docs/src/content/agents/mcp-server.md @@ -212,7 +212,7 @@ The server marks read-only tools as read-only for your client. Five tools act on | `fill-form` | [Act on a form](./tools.md#act-on-a-form) | | `analog-call-api` | [Call a server route](./tools.md#call-a-server-route) | -Your client can ask you before it runs them. To drop them from the server, set `agent.readOnly`. See [Inspectors and agent tools](/getting-started/configuration#inspectors-and-agent-tools). +Your client can ask you before it runs them. To drop them from the server, set `agent.readOnly`. See [Inspectors and agent tools](../getting-started/configuration.md#inspectors-and-agent-tools). ### Pages and tabs diff --git a/apps/docs/src/content/contributing/development.md b/apps/docs/src/content/contributing/development.md index f81cdfe..034c9c7 100644 --- a/apps/docs/src/content/contributing/development.md +++ b/apps/docs/src/content/contributing/development.md @@ -196,7 +196,7 @@ If a code change needs no docs change, add the `no-docs` label to the pull reque Add `agent: { description }` to an RPC function, or call `ctx.agent.registerTool()` in the devframe setup. List the tool on the [Tools](../agents/tools.md) page. -Map a registered tool to its inspector in `AGENT_INSPECTOR` in `packages/ng-devtools/src/config.ts`, so `inspectors` and `agent.tools` can hide it. A tool that acts on the page sets `safety: 'action'`, so `agent.readOnly` drops it. See [Configuration](/getting-started/configuration). +Map a registered tool to its inspector in `AGENT_INSPECTOR` in `packages/ng-devtools/src/config.ts`, so `inspectors` and `agent.tools` can hide it. A tool that acts on the page sets `safety: 'action'`, so `agent.readOnly` drops it. See [Configuration](../getting-started/configuration.md). Run pnpm extension:build and commit extension/ui. CI fails when it is stale. See Build the extension. diff --git a/apps/docs/src/content/getting-started/express.md b/apps/docs/src/content/getting-started/express.md index 6ca488e..a6b36a7 100644 --- a/apps/docs/src/content/getting-started/express.md +++ b/apps/docs/src/content/getting-started/express.md @@ -99,7 +99,7 @@ With `ws: {sidecar: true}`, the WebSocket runs on its own port, picked automatic | `allowedOrigins` | loopback origins and the Chrome extension | Extra origins allowed to open the WebSocket. A list replaces the Chrome extension default. `false` turns the origin check off. | | `mcp` | a bearer token | Mounts the MCP endpoint at `__mcp` and asks for a bearer token. With `auth: false` the default is `'auto'`: it mounts once agent tools exist and asks for no token. See [Send a token](../agents/mcp-server.md#send-a-token). | -The hub also takes the devtools options, such as `inspectors`, `agent`, `actions`, `redaction` and `limits`. See [Configuration](/getting-started/configuration). +The hub also takes the devtools options, such as `inspectors`, `agent`, `actions`, `redaction` and `limits`. See [Configuration](./configuration.md). ### Access control diff --git a/apps/docs/src/content/getting-started/vite.md b/apps/docs/src/content/getting-started/vite.md index 785c2a4..a9549b1 100644 --- a/apps/docs/src/content/getting-started/vite.md +++ b/apps/docs/src/content/getting-started/vite.md @@ -106,7 +106,7 @@ ngDevtools({ | `allowedOrigins` | none | Extra exact origins allowed to reach the devtools, for example a tunnel. | | `auth` | on if a non-loopback host or origin is allowed, otherwise off | Whether the devtools ask for the one-time code. | -The plugin also takes the devtools options, such as `inspectors`, `agent`, `actions`, `redaction` and `limits`. See [Configuration](/getting-started/configuration). +The plugin also takes the devtools options, such as `inspectors`, `agent`, `actions`, `redaction` and `limits`. See [Configuration](./configuration.md). ### `base` @@ -130,7 +130,7 @@ A tunnel forwards other people's requests to your machine, and those requests ar | `true` | The code is always on. | | `false` | The code is always off. The loopback and origin checks still apply. | -While the code is on, the HTTP MCP endpoint also asks for a bearer token. See [Send a token](/agents/mcp-server#send-a-token). +While the code is on, the HTTP MCP endpoint also asks for a bearer token. See [Send a token](../agents/mcp-server.md#send-a-token). If your tunnel rewrites the `Host` header to `localhost`, you don't list it in `server.allowedHosts`, so the plugin leaves the code off. Pass `auth: true`: diff --git a/apps/docs/src/content/guides/analog.md b/apps/docs/src/content/guides/analog.md index 1b40523..cb8fe1b 100644 --- a/apps/docs/src/content/guides/analog.md +++ b/apps/docs/src/content/guides/analog.md @@ -91,7 +91,7 @@ All four are optional. | `allowedOrigins` | none | Extra page origins accepted next to localhost. | | `auth` | on if a non-loopback host or origin is allowed | Whether the devtools ask for the one-time code. | -The plugin also takes the devtools options. See [Vite and Analog](/getting-started/vite#options) and [Configuration](/getting-started/configuration). +The plugin also takes the devtools options. See [Vite and Analog](../getting-started/vite.md#options) and [Configuration](../getting-started/configuration.md). ### Custom hostnames From 1426cb32f9fc49c41d479dd05f21956289b455b8 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 30 Sep 2026 19:23:42 +0300 Subject: [PATCH 15/15] fix: skip code examples when the skills check validates links --- scripts/validate-skills.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/validate-skills.mjs b/scripts/validate-skills.mjs index 7371b11..bcae7e5 100644 --- a/scripts/validate-skills.mjs +++ b/scripts/validate-skills.mjs @@ -22,7 +22,8 @@ function frontmatter(file) { } function checkLinks(file, body) { - for (const [, link] of body.matchAll(/\]\(([^)\s]+)\)/g)) { + const prose = body.replace(/^```[\s\S]*?^```/gm, '').replace(/`[^`\n]*`/g, ''); + for (const [, link] of prose.matchAll(/\]\(([^)\s]+)\)/g)) { if (/^(https?:|#|mailto:)/.test(link)) continue; const target = normalize(join(dirname(file), link.split('#')[0])); if (!existsSync(target)) errors.push(`${file}: broken link ${link}`);