Skip to content
Open
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
9 changes: 9 additions & 0 deletions .changeset/issues-panel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@solidjs/start-devtools': minor
---

Add an Issues panel that lists the issues the Solid runtime reports, such as a signal read outside a tracking scope. Each one is an error, a warning or info.

- Repeated findings are grouped with a count, and the tab shows the total.
- A finding links to its owner in the ownership tree, its node in the reactivity graph, and its section of the repair guide.
- The copy button puts a report of the finding on the clipboard for an agent.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Development error and server-function tooling for Solid Start mode.
`@solidjs/start-devtools` provides the toolbar used by the Solid Vite plugin in development.

- Runtime error inspection with source-mapped stack frames.
- The issues the runtime reports, such as a signal read outside a tracking scope.
- Server-function request and response inspection.
- A reactivity graph of the live signals, memos and effects in the app.
- An ownership tree of the components and scopes the app created.
Expand Down Expand Up @@ -47,6 +48,22 @@ pnpm demo
pnpm demo:explorer
```

## Issues

The issues panel lists what the Solid runtime reports on its diagnostics channel, such as
a signal read outside a tracking scope or a write inside a component. The console prints
the same findings, mixed with everything else.

- A finding that repeats, such as one in each row of a list, is one row with a count.
- Filter by severity (errors, warnings, info) or by code, message and owner.
- Select a row to see its kind, owner path, node and when it was seen.
- **Show in the ownership tree** and **Show in the reactivity graph** select the owner or node
the finding is about.
- **Open the repair guide** opens the section for its code.

The panel listens from the moment the toolbar mounts, so findings from the first render
are there when you open it. Issues need a development build of `solid-js`.

## Reactivity graph

The graph panel maps the running reactive graph. Signals, memos and effects are nodes,
Expand Down
19 changes: 19 additions & 0 deletions src/dev-toolbar/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -721,3 +721,22 @@ export function CheckIcon(props: JSX.IntrinsicElements['svg'] & { title: string
</svg>
);
}

export function IssueIcon(props: JSX.IntrinsicElements['svg'] & { title: string }): JSX.Element {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
{...props}
>
<title>{props.title}</title>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
);
}
5 changes: 5 additions & 0 deletions src/dev-toolbar/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,11 @@
color: oklch(0.78 0.14 25);
}

[data-solid-dev-toolbar-tab-badge='warning'] {
background: oklch(0.8 0.15 80 / 0.18);
color: oklch(0.85 0.12 80);
}

[data-solid-dev-toolbar-body] {
flex: 1;
min-width: 0;
Expand Down
44 changes: 41 additions & 3 deletions src/dev-toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,38 @@ import {
For,
getOwner,
OBSERVE,
onCleanup,
onSettled,
Show,
} from 'solid-js';
import version from '../version.js';
import IconButton from '../ui/IconButton.js';
import { type ServerFunctionInstance, ServerFunctionViewer } from './functions/index.js';
import { captureServerFunctionCall } from './functions/tracker.js';
import { ErrorIcon, FunctionIcon, GraphIcon, MinimizeIcon, SolidIcon, TreeIcon } from './icons.js';
import {
ErrorIcon,
FunctionIcon,
GraphIcon,
MinimizeIcon,
SolidIcon,
TreeIcon,
IssueIcon,
} from './icons.js';
import { excludeOwner, includeOwner } from './ownership/registry.js';
import { excludeReactiveOwner, includeReactiveOwner } from './reactivity/registry.js';
import { addIssues, captureIssues, type IssueEntry } from './issues/tracker.js';
import './index.css';

const ErrorViewer = clientOnly(() => import('./error-viewer/index.js'), { lazy: true });
const ReactivityViewer = clientOnly(() => import('./reactivity/index.js'), { lazy: true });
const OwnershipViewer = clientOnly(() => import('./ownership/index.js'), { lazy: true });
const IssuesViewer = clientOnly(() => import('./issues/index.js'), { lazy: true });

type Tab = 'err' | 'fn' | 'rx' | 'own';
type Tab = 'err' | 'issues' | 'fn' | 'rx' | 'own';

const TABS: { value: Tab; label: string; icon: typeof ErrorIcon }[] = [
{ value: 'err', label: 'Errors', icon: ErrorIcon },
{ value: 'issues', label: 'Issues', icon: IssueIcon },
{ value: 'fn', label: 'Server functions', icon: FunctionIcon },
{ value: 'rx', label: 'Reactivity', icon: GraphIcon },
{ value: 'own', label: 'Ownership', icon: TreeIcon },
Expand Down Expand Up @@ -301,6 +313,14 @@ export function DevToolbar(props: DevToolbarProps) {
};
});

// Findings arrive while the app renders, before the toolbar settles, so the
// channel is joined during setup instead of in `onSettled`.
const [issues, setIssues] = createSignal<IssueEntry[]>([]);
if (!isServer) {
onCleanup(captureIssues((added) => setIssues((current) => addIssues(current, added))));
}
const issueCount = createMemo(() => issues().reduce((total, entry) => total + entry.count, 0));

const [instances, setInstances] = createSignal<
Record<string, ServerFunctionInstance | undefined>
>({});
Expand All @@ -323,6 +343,7 @@ export function DevToolbar(props: DevToolbarProps) {

const badge = (value: Tab): number => {
if (value === 'err') return errors().length;
if (value === 'issues') return issueCount();
if (value === 'fn') return callCount();
return 0;
};
Expand Down Expand Up @@ -428,7 +449,11 @@ export function DevToolbar(props: DevToolbarProps) {
<span
id={`solid-dev-toolbar-tab-${entry.value}-count`}
data-solid-dev-toolbar-tab-badge={
entry.value === 'err' ? 'failure' : undefined
entry.value === 'err'
? 'failure'
: entry.value === 'issues'
? 'warning'
: undefined
}
>
{count()}
Expand All @@ -447,6 +472,19 @@ export function DevToolbar(props: DevToolbarProps) {
aria-labelledby={`solid-dev-toolbar-tab-${tab()}`}
>
<ErrorViewer show={visible('err')} errors={errors()} resetError={resetError} />
<IssuesViewer
show={visible('issues')}
entries={issues()}
onClear={() => setIssues([])}
onViewOwner={(owner) => {
setOwnershipFocus({ owner });
show('own');
}}
onViewInGraph={(node) => {
setGraphFocus({ node });
show('rx');
}}
/>
<ReactivityViewer
show={visible('rx')}
focus={graphFocus()}
Expand Down
Loading
Loading