Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/devtools-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Rules:
| Page | Source of truth |
| ------------------ | ----------------------------------------------------------------------------------------------------------- |
| inspectors/\* | `app/src/pages/*.ts` (the tab) and `packages/devtools/src/*` (collectors, actions) |
| agents/\* | `packages/devtools/src/devframe.ts`, `rpc/*.ts`, `rpc/analog-register.ts`, Devframe built-ins |
| agents/\* | `packages/devtools/src/devframe.ts`, `devframe-*.ts`, `rpc/*.ts`, `rpc/analog-register.ts`, Devframe built-ins |
| getting-started/\* | `packages/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/` |
Expand Down
6 changes: 3 additions & 3 deletions .claude/skills/devtools-inspector/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Every inspector follows the same path:
```
app page (overlay.ts + <area>-collector.ts)
-> rpc.call('push-<area>', { pageId, ... })
-> devframe.ts: per-page Map, shared state '<area>', expiry, forget-<area>-page
-> devframe.ts and devframe-<area>.ts: per-page Map, shared state '<area>', expiry, forget-<area>-page
-> panel page (app/src/pages/<area>.ts) via rpc.sharedState('<area>')
-> agent tools (ctx.agent.registerTool) and MCP resources
```
Expand All @@ -32,7 +32,7 @@ Read `docs/contributing/coding-standards.md` ("Reading data from the page") befo

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

- `devframe.ts` wires the setup. A new inspector goes in its own `rpc/<area>-register.ts` (see `rpc/errors-register.ts`), and agent tools of an existing area live in `devframe-<area>-tools.ts`, which take the setup values they read as an `env` object typed through `devframe-env.ts`. Keep `devframe.ts` under the line ratchet of `pnpm lint`.
- `devframe.ts` wires the setup. A new inspector goes in its own `rpc/<area>-register.ts` (see `rpc/errors-register.ts`). Existing inspectors follow one of two layouts: the ones registered from `devframe-inspectors.ts` (errors, perf, build and the like) keep their state, RPC functions and tools in their `rpc/<area>-register.ts`; the older areas keep their state and RPC functions in `devframe-<area>.ts`. Change an inspector in the module it already lives in. An older area's agent tools are in that module or in a separate `devframe-*-tools.ts` module. These modules take the setup values they read as an `env` object typed through `devframe-env.ts`. The page registry (owners, heartbeats, lease) is `devframe-pages.ts` and the registrars are `devframe-registrars.ts`. Keep `devframe.ts` under the line ratchet of `pnpm lint`.

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

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

## Tests

Expand Down
3 changes: 2 additions & 1 deletion apps/docs/src/content/contributing/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ app/ # Devtools UI SPA (Angular + Vite)
packages/
devtools/ # Publishable npm package
src/devframe.ts # defineDevframe(): the tool definition
src/devframe-*.ts # Its parts: shared types, registrars, the page registry, and the state, RPC functions and agent tools of the areas kept here (other inspectors live in `src/rpc/<area>-register.ts`)
src/overlay.ts # Client script running in the user's page (the overlay-manual entry)
src/overlay-auto.ts # The overlay entry: starts the overlay and the floating button on import
src/rpc/ # Node-side RPC functions and agent tools
Expand Down Expand Up @@ -269,7 +270,7 @@ If a code change needs no docs change, add the `no-docs` label to the pull reque
### Add an RPC function

1. Create the function in `packages/devtools/src/rpc/`.
2. Register it in `packages/devtools/src/devframe.ts`.
2. Register it in `packages/devtools/src/devframe.ts`, or in the `devframe-<area>.ts` module of its inspector.
3. Map it to its inspector in `RPC_INSPECTOR` in `packages/devtools/src/config.ts`, so turning the inspector off removes it.
4. Call it from the UI in `app/src/pages/`.

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/content/contributing/writing-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ The docs describe what the code does today. Before you write a claim, find it in
| Page | Source of truth |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Inspector pages | The tab in `app/src/pages/` and its collector in `packages/devtools/src/` |
| Agent tools and resources | `packages/devtools/src/devframe.ts`, `rpc/*.ts` and `rpc/analog-register.ts` |
| Agent tools and resources | `packages/devtools/src/devframe.ts`, `devframe-*.ts`, `rpc/*.ts` and `rpc/analog-register.ts` |
| Setup pages | `packages/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` |
Expand Down
2 changes: 1 addition & 1 deletion docs/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ A library's own view in the panel and its own agent tools, through the public pl
_Avoid_: extension (the Chrome extension), add-on, inspector

**Devframe**:
The framework the devtools are built on (`devframe`). One definition, `packages/devtools/src/devframe.ts` (with the `devframe-*-tools.ts` and `rpc/*-register.ts` modules it calls), declares the RPC functions, shared state and agent tools, and Devframe serves it as the embedded panel, the standalone CLI, the static report and the MCP server.
The framework the devtools are built on (`devframe`). One definition, `packages/devtools/src/devframe.ts` (with the `devframe-*.ts` and `rpc/*-register.ts` modules it calls), declares the RPC functions, shared state and agent tools, and Devframe serves it as the embedded panel, the standalone CLI, the static report and the MCP server.
_Avoid_: framework, runtime, backend

**Hub**:
Expand Down
3 changes: 1 addition & 2 deletions lint-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,7 @@
"packages/devtools/src/console-tap.ts": {
"unicorn(no-useless-spread)": 1
},
"packages/devtools/src/devframe.ts": {
"pangular(max-lines)": 3236,
"packages/devtools/src/devframe-components.ts": {
"unicorn(no-useless-spread)": 1
},
"packages/devtools/src/dom-diff.ts": {
Expand Down
72 changes: 72 additions & 0 deletions packages/devtools/src/devframe-capacitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// The Capacitor inspector on the devframe server (`devframe.ts`): the
// `capacitor` shared state with patches, and its push and forget.

import { createSharedState } from 'devframe/utils/shared-state';
import type { ResolvedPangularConfig } from './config.ts';
import {
mergeCapacitorReport,
patchCapacitorState,
syncCapacitorState,
toCapacitorReport,
type CapacitorPage,
type CapacitorState,
} from './rpc/capacitor-tools.ts';
import { trackPageSessions } from './rpc/page-sessions.ts';
import type { DevframeNodeContext, Owns, PangularScope, Register } from './devframe-env.ts';

export async function registerCapacitor(env: {
ctx: DevframeNodeContext;
my: PangularScope;
config: ResolvedPangularConfig;
register: Register;
owns: Owns;
mayForget: Owns;
}) {
const { ctx, my, config, register, owns, mayForget } = env;
const limits = config.limits;
const capacitorPages = new Map<string, CapacitorPage>();
// With patches on, a push sends the panel what changed, not every page:
// devframe's own shared states send the whole value on each change.
const capacitorShared = await my.rpc.sharedState('capacitor', {
sharedState: createSharedState<CapacitorState>({
initialValue: { pages: {} },
enablePatches: true,
}),
});
const applyCapacitor = () =>
capacitorShared.mutate((draft) => syncCapacitorState(draft, capacitorPages));
const capacitorSessions = trackPageSessions(ctx.rpc, (pageIds) => {
let removed = false;
for (const pageId of pageIds) removed = capacitorPages.delete(pageId) || removed;
if (removed) applyCapacitor();
});

register({
name: 'push-capacitor',
type: 'action',
jsonSerializable: true,
handler: (message: unknown) => {
const report = toCapacitorReport(message);
if (!report || !owns(report.pageId)) return { known: true };
capacitorSessions.bind(report.pageId);
const answer = mergeCapacitorReport(capacitorPages, report, limits.capacitorCalls);
if (answer.known) {
capacitorShared.mutate((draft) => patchCapacitorState(draft, capacitorPages, report));
}
return answer;
},
});

register({
name: 'forget-capacitor-page',
type: 'action',
jsonSerializable: true,
handler: (pageId: string) => {
if (!mayForget(pageId)) return;
capacitorSessions.unbind(pageId);
if (capacitorPages.delete(pageId)) applyCapacitor();
},
});

return { capacitorPages, capacitorShared, applyCapacitor };
}
Loading
Loading