mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(skills): add inspect-elements skill backed by element-source
Closes #1082
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
---
|
||||||
|
name: inspect-elements
|
||||||
|
description: Resolve on-screen 5chan DOM elements to React source files, line numbers, component names, and ownership stacks using the app's dev-only element-source helpers and playwright-cli. Use when Codex needs to inspect a page element, map a snapshot ref to source code, confirm which component rendered a node, or follow up after $profile-browsing finds a rerender hotspot and needs file-level attribution.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Inspect Elements
|
||||||
|
|
||||||
|
Use this skill to jump from a concrete DOM node in the running 5chan app to the React file and component stack that produced it.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Dev server running at `http://5chan.localhost:1355`
|
||||||
|
- `playwright-cli` installed
|
||||||
|
- Use the local dev app, not production. The element-source helpers are only exposed in dev mode.
|
||||||
|
|
||||||
|
## Quick workflow
|
||||||
|
|
||||||
|
1. Open the target route with `playwright-cli`.
|
||||||
|
2. Run `playwright-cli snapshot` and choose the relevant element ref.
|
||||||
|
3. Resolve that ref through the app helper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "async el => JSON.stringify(await window.__ELEMENT_SOURCE__.resolve(el))" e7
|
||||||
|
```
|
||||||
|
|
||||||
|
The result includes:
|
||||||
|
|
||||||
|
- `source`: the most useful file/line match for the element
|
||||||
|
- `componentName`: the nearest meaningful React component
|
||||||
|
- `stack`: ownership stack from the concrete node upward
|
||||||
|
- `tagName`: the underlying DOM tag
|
||||||
|
|
||||||
|
## Session setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect open http://5chan.localhost:1355
|
||||||
|
playwright-cli -s=inspect goto http://5chan.localhost:1355/all
|
||||||
|
playwright-cli -s=inspect eval "window.__ELEMENT_SOURCE__?.ready ?? false"
|
||||||
|
playwright-cli -s=inspect snapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
If `ready` is `false`, wait a moment and evaluate again. If `window.__ELEMENT_SOURCE__?.error` is set, report that error instead of continuing.
|
||||||
|
|
||||||
|
## Resolve strategies
|
||||||
|
|
||||||
|
Prefer snapshot refs because they target the exact live DOM node you just inspected.
|
||||||
|
|
||||||
|
### Snapshot ref
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "async el => JSON.stringify(await window.__ELEMENT_SOURCE__.resolve(el))" e7
|
||||||
|
```
|
||||||
|
|
||||||
|
### Selector
|
||||||
|
|
||||||
|
Use this only when the element is easy to target and a snapshot ref is not practical.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "JSON.stringify(await window.__ELEMENT_SOURCE__.resolveBySelector('[data-testid=\"composer\"]'))"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Screen coordinates
|
||||||
|
|
||||||
|
Useful when you have a screenshot or a visually obvious hotspot.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "JSON.stringify(await window.__ELEMENT_SOURCE__.resolveAtPoint(320, 420))"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Format the ownership stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "async el => { const info = await window.__ELEMENT_SOURCE__.resolve(el); return JSON.stringify({ ...info, formattedStack: window.__ELEMENT_SOURCE__.formatStack(info.stack, 5) }); }" e7
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `formattedStack` when you need a short, readable trace for the final report.
|
||||||
|
|
||||||
|
## Profiling follow-up
|
||||||
|
|
||||||
|
When `$profile-browsing` reports a hot route or rerender-heavy area:
|
||||||
|
|
||||||
|
1. Reopen the route in a fresh playwright session.
|
||||||
|
2. Snapshot the concrete list item, card, modal, or toolbar node that looks relevant.
|
||||||
|
3. Resolve it with `window.__ELEMENT_SOURCE__.resolve(...)`.
|
||||||
|
4. Use `source.filePath` as the direct edit target and `stack` to understand parent ownership.
|
||||||
|
|
||||||
|
This is a complement to `react-scan`, not a replacement. `react-scan` tells you which components rerender too often. `inspect-elements` tells you which exact source file produced the node you are looking at.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Prefer snapshot refs over brittle selectors.
|
||||||
|
- Inspect the actual node the user cares about, not a distant wrapper, unless wrappers are the suspected problem.
|
||||||
|
- If `source` is null but `stack` exists, use the first useful stack frame rather than guessing.
|
||||||
|
- If both `source` and `stack` are empty, report that the node could not be resolved and pick a nearby parent element instead.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Inspect Elements"
|
||||||
|
short_description: "Map DOM nodes to source files"
|
||||||
|
default_prompt: "Use $inspect-elements to map a 5chan page element to its React source file and component stack."
|
||||||
@@ -121,6 +121,16 @@ Collect structured output from each subagent and merge:
|
|||||||
| react-scan: component with >30 renders | Missing memoization or unstable references | `useMemo`/`useCallback`, check parent renders |
|
| react-scan: component with >30 renders | Missing memoization or unstable references | `useMemo`/`useCallback`, check parent renders |
|
||||||
| react-scan: component with >50ms time | Expensive render function | Split component, move work out of render |
|
| react-scan: component with >50ms time | Expensive render function | Split component, move work out of render |
|
||||||
|
|
||||||
|
## Element-source follow-up
|
||||||
|
|
||||||
|
When `react-scan` identifies a rerender hotspot but you still need the exact file behind a concrete DOM node, hand off to `$inspect-elements`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=prof-followup eval "async el => JSON.stringify(await window.__ELEMENT_SOURCE__.resolve(el))" e7
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `source.filePath` as the direct edit target and `stack` to understand which parent components own the node.
|
||||||
|
|
||||||
## Step 5: Cleanup
|
## Step 5: Cleanup
|
||||||
|
|
||||||
After profiling is complete and the report is delivered, verify no orphaned processes were left behind:
|
After profiling is complete and the report is delivered, verify no orphaned processes were left behind:
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
---
|
||||||
|
name: inspect-elements
|
||||||
|
description: Resolve on-screen 5chan DOM elements to React source files, line numbers, component names, and ownership stacks using the app's dev-only element-source helpers and playwright-cli. Use when Codex needs to inspect a page element, map a snapshot ref to source code, confirm which component rendered a node, or follow up after $profile-browsing finds a rerender hotspot and needs file-level attribution.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Inspect Elements
|
||||||
|
|
||||||
|
Use this skill to jump from a concrete DOM node in the running 5chan app to the React file and component stack that produced it.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Dev server running at `http://5chan.localhost:1355`
|
||||||
|
- `playwright-cli` installed
|
||||||
|
- Use the local dev app, not production. The element-source helpers are only exposed in dev mode.
|
||||||
|
|
||||||
|
## Quick workflow
|
||||||
|
|
||||||
|
1. Open the target route with `playwright-cli`.
|
||||||
|
2. Run `playwright-cli snapshot` and choose the relevant element ref.
|
||||||
|
3. Resolve that ref through the app helper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "async el => JSON.stringify(await window.__ELEMENT_SOURCE__.resolve(el))" e7
|
||||||
|
```
|
||||||
|
|
||||||
|
The result includes:
|
||||||
|
|
||||||
|
- `source`: the most useful file/line match for the element
|
||||||
|
- `componentName`: the nearest meaningful React component
|
||||||
|
- `stack`: ownership stack from the concrete node upward
|
||||||
|
- `tagName`: the underlying DOM tag
|
||||||
|
|
||||||
|
## Session setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect open http://5chan.localhost:1355
|
||||||
|
playwright-cli -s=inspect goto http://5chan.localhost:1355/all
|
||||||
|
playwright-cli -s=inspect eval "window.__ELEMENT_SOURCE__?.ready ?? false"
|
||||||
|
playwright-cli -s=inspect snapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
If `ready` is `false`, wait a moment and evaluate again. If `window.__ELEMENT_SOURCE__?.error` is set, report that error instead of continuing.
|
||||||
|
|
||||||
|
## Resolve strategies
|
||||||
|
|
||||||
|
Prefer snapshot refs because they target the exact live DOM node you just inspected.
|
||||||
|
|
||||||
|
### Snapshot ref
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "async el => JSON.stringify(await window.__ELEMENT_SOURCE__.resolve(el))" e7
|
||||||
|
```
|
||||||
|
|
||||||
|
### Selector
|
||||||
|
|
||||||
|
Use this only when the element is easy to target and a snapshot ref is not practical.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "JSON.stringify(await window.__ELEMENT_SOURCE__.resolveBySelector('[data-testid=\"composer\"]'))"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Screen coordinates
|
||||||
|
|
||||||
|
Useful when you have a screenshot or a visually obvious hotspot.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "JSON.stringify(await window.__ELEMENT_SOURCE__.resolveAtPoint(320, 420))"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Format the ownership stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=inspect eval "async el => { const info = await window.__ELEMENT_SOURCE__.resolve(el); return JSON.stringify({ ...info, formattedStack: window.__ELEMENT_SOURCE__.formatStack(info.stack, 5) }); }" e7
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `formattedStack` when you need a short, readable trace for the final report.
|
||||||
|
|
||||||
|
## Profiling follow-up
|
||||||
|
|
||||||
|
When `$profile-browsing` reports a hot route or rerender-heavy area:
|
||||||
|
|
||||||
|
1. Reopen the route in a fresh playwright session.
|
||||||
|
2. Snapshot the concrete list item, card, modal, or toolbar node that looks relevant.
|
||||||
|
3. Resolve it with `window.__ELEMENT_SOURCE__.resolve(...)`.
|
||||||
|
4. Use `source.filePath` as the direct edit target and `stack` to understand parent ownership.
|
||||||
|
|
||||||
|
This is a complement to `react-scan`, not a replacement. `react-scan` tells you which components rerender too often. `inspect-elements` tells you which exact source file produced the node you are looking at.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Prefer snapshot refs over brittle selectors.
|
||||||
|
- Inspect the actual node the user cares about, not a distant wrapper, unless wrappers are the suspected problem.
|
||||||
|
- If `source` is null but `stack` exists, use the first useful stack frame rather than guessing.
|
||||||
|
- If both `source` and `stack` are empty, report that the node could not be resolved and pick a nearby parent element instead.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Inspect Elements"
|
||||||
|
short_description: "Map DOM nodes to source files"
|
||||||
|
default_prompt: "Use $inspect-elements to map a 5chan page element to its React source file and component stack."
|
||||||
@@ -121,6 +121,16 @@ Collect structured output from each subagent and merge:
|
|||||||
| react-scan: component with >30 renders | Missing memoization or unstable references | `useMemo`/`useCallback`, check parent renders |
|
| react-scan: component with >30 renders | Missing memoization or unstable references | `useMemo`/`useCallback`, check parent renders |
|
||||||
| react-scan: component with >50ms time | Expensive render function | Split component, move work out of render |
|
| react-scan: component with >50ms time | Expensive render function | Split component, move work out of render |
|
||||||
|
|
||||||
|
## Element-source follow-up
|
||||||
|
|
||||||
|
When `react-scan` identifies a rerender hotspot but you still need the exact file behind a concrete DOM node, hand off to `$inspect-elements`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
playwright-cli -s=prof-followup eval "async el => JSON.stringify(await window.__ELEMENT_SOURCE__.resolve(el))" e7
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `source.filePath` as the direct edit target and `stack` to understand which parent components own the node.
|
||||||
|
|
||||||
## Step 5: Cleanup
|
## Step 5: Cleanup
|
||||||
|
|
||||||
After profiling is complete and the report is delivered, verify no orphaned processes were left behind:
|
After profiling is complete and the report is delivered, verify no orphaned processes were left behind:
|
||||||
|
|||||||
@@ -137,6 +137,7 @@
|
|||||||
"cz-conventional-changelog": "3.3.0",
|
"cz-conventional-changelog": "3.3.0",
|
||||||
"decompress": "4.2.1",
|
"decompress": "4.2.1",
|
||||||
"electron": "36.9.5",
|
"electron": "36.9.5",
|
||||||
|
"element-source": "0.0.4",
|
||||||
"husky": "4.3.8",
|
"husky": "4.3.8",
|
||||||
"isomorphic-fetch": "3.0.0",
|
"isomorphic-fetch": "3.0.0",
|
||||||
"jsdom": "28.1.0",
|
"jsdom": "28.1.0",
|
||||||
|
|||||||
@@ -4,6 +4,75 @@ if (import.meta.env.DEV) {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
showToolbar: !(window as any).__PROFILING__,
|
showToolbar: !(window as any).__PROFILING__,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const notReady = async () => ({
|
||||||
|
error: 'element-source is not ready yet.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const elementSourceApi: any = {
|
||||||
|
ready: false,
|
||||||
|
error: null,
|
||||||
|
resolve: notReady,
|
||||||
|
resolveBySelector: async () => ({
|
||||||
|
error: 'element-source is not ready yet.',
|
||||||
|
}),
|
||||||
|
resolveAtPoint: async () => ({
|
||||||
|
error: 'element-source is not ready yet.',
|
||||||
|
}),
|
||||||
|
formatStack: () => '',
|
||||||
|
};
|
||||||
|
|
||||||
(window as any).__getReactScanReport = getReport;
|
(window as any).__getReactScanReport = getReport;
|
||||||
|
(window as any).__ELEMENT_SOURCE__ = elementSourceApi;
|
||||||
|
|
||||||
|
import('element-source')
|
||||||
|
.then(({ formatStack, resolveElementInfo }) => {
|
||||||
|
const resolve = async (node: unknown) => {
|
||||||
|
if (!(node instanceof Element)) {
|
||||||
|
return {
|
||||||
|
error: 'Expected a DOM Element.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const info = await resolveElementInfo(node);
|
||||||
|
return {
|
||||||
|
...info,
|
||||||
|
available: Boolean(info.source || info.stack.length || info.componentName),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(elementSourceApi, {
|
||||||
|
ready: true,
|
||||||
|
resolve,
|
||||||
|
resolveBySelector: async (selector: string) => {
|
||||||
|
const element = document.querySelector(selector);
|
||||||
|
if (!(element instanceof Element)) {
|
||||||
|
return {
|
||||||
|
error: `No element matched selector: ${selector}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return resolve(element);
|
||||||
|
},
|
||||||
|
resolveAtPoint: async (x: number, y: number) => {
|
||||||
|
const element = document.elementFromPoint(x, y);
|
||||||
|
if (!(element instanceof Element)) {
|
||||||
|
return {
|
||||||
|
error: `No element found at point (${x}, ${y})`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return resolve(element);
|
||||||
|
},
|
||||||
|
formatStack: (stack: unknown, maxLines = 3) => (Array.isArray(stack) ? formatStack(stack as any, maxLines) : ''),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
elementSourceApi.error = error instanceof Error ? error.message : String(error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5671,6 +5671,13 @@ bippy@^0.5.28:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react-reconciler" "^0.28.9"
|
"@types/react-reconciler" "^0.28.9"
|
||||||
|
|
||||||
|
bippy@^0.5.32:
|
||||||
|
version "0.5.32"
|
||||||
|
resolved "https://registry.yarnpkg.com/bippy/-/bippy-0.5.32.tgz#60cbed767572e7cecda2f7b7d860f7f0ae6b0874"
|
||||||
|
integrity sha512-yt1mC8eReTxjfg41YBZdN4PvsDwHFWxltoiQX0Q+Htlbf41aSniopb7ECZits01HwNAvXEh69RGk/ImlswDTEw==
|
||||||
|
dependencies:
|
||||||
|
"@types/react-reconciler" "^0.28.9"
|
||||||
|
|
||||||
bl@^1.0.0:
|
bl@^1.0.0:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7"
|
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7"
|
||||||
@@ -7330,6 +7337,13 @@ electron@36.9.5:
|
|||||||
"@types/node" "^22.7.7"
|
"@types/node" "^22.7.7"
|
||||||
extract-zip "^2.0.1"
|
extract-zip "^2.0.1"
|
||||||
|
|
||||||
|
element-source@0.0.4:
|
||||||
|
version "0.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/element-source/-/element-source-0.0.4.tgz#6594b5bce4cedc0ed99213aa47b532a8803110f8"
|
||||||
|
integrity sha512-4feZB3FhezD6UBGj6h8OO8axyPaz8kcJrpJzGTCWyLeZ0ldHgiE2z6i7Th0d5V2IlHUFNoXR7Tb2dfbZIgih0A==
|
||||||
|
dependencies:
|
||||||
|
bippy "^0.5.32"
|
||||||
|
|
||||||
elementtree@^0.1.7:
|
elementtree@^0.1.7:
|
||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/elementtree/-/elementtree-0.1.7.tgz#9ac91be6e52fb6e6244c4e54a4ac3ed8ae8e29c0"
|
resolved "https://registry.yarnpkg.com/elementtree/-/elementtree-0.1.7.tgz#9ac91be6e52fb6e6244c4e54a4ac3ed8ae8e29c0"
|
||||||
|
|||||||
Reference in New Issue
Block a user