-
-
Notifications
You must be signed in to change notification settings - Fork 71
feat: logs system #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: logs system #193
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
da86e0d
feat: logs system
antfu fd2b44d
feat: add a basic a11y checker
antfu 9483bf8
feat: fix build
antfu e9b2be3
feat: toast style
antfu dbf92e1
feat: logs api
antfu 36f1a1c
feat: improve ui
antfu ddd655d
chore: update ui
antfu 7b1b309
perf: more efficient logs listing
antfu 52ad731
feat: unify logs api
antfu 1015746
Merge remote-tracking branch 'origin/main' into antfu/logs-system
antfu 9133ba8
feat: improve ui
antfu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| # Logs & Notifications | ||
|
|
||
| The Logs system allows plugins to emit structured log entries from both the server (Node.js) and client (browser) contexts. Logs are displayed in the built-in **Logs** panel in the DevTools dock, and can optionally appear as toast notifications. | ||
|
|
||
| ## Use Cases | ||
|
|
||
| - **Accessibility audits** — Run a11y checks or similar tools on the client side, report warnings with element positions | ||
| - **Runtime errors** — Capture and display errors with stack traces | ||
| - **Linting & testing** — Run ESLint or test runners alongside the dev server and surface results with file positions | ||
| - **Notifications** — Short-lived messages like "URL copied" that auto-dismiss | ||
|
|
||
| ## Log Entry Fields | ||
|
|
||
| | Field | Type | Required | Description | | ||
| |-------|------|----------|-------------| | ||
| | `message` | `string` | Yes | Short title or summary | | ||
| | `level` | `'info' \| 'warn' \| 'error' \| 'success' \| 'debug'` | Yes | Severity level, determines color and icon | | ||
| | `description` | `string` | No | Detailed description or explanation | | ||
| | `stacktrace` | `string` | No | Stack trace string | | ||
| | `filePosition` | `{ file, line?, column? }` | No | Source file location (clickable in the panel) | | ||
| | `elementPosition` | `{ selector?, boundingBox?, description? }` | No | DOM element position info | | ||
| | `notify` | `boolean` | No | Show as a toast notification | | ||
| | `category` | `string` | No | Grouping category (e.g., `'a11y'`, `'lint'`) | | ||
| | `labels` | `string[]` | No | Tags for filtering | | ||
| | `autoDismiss` | `number` | No | Time in ms to auto-dismiss the toast (default: 5000) | | ||
| | `autoDelete` | `number` | No | Time in ms to auto-delete the log entry | | ||
| | `status` | `'loading' \| 'idle'` | No | Status indicator (shows spinner when `'loading'`) | | ||
| | `id` | `string` | No | Explicit id for deduplication — re-adding with the same id updates the existing entry | | ||
|
|
||
| The `source` field is automatically set to `'server'` or `'client'` depending on where the log was emitted. | ||
|
|
||
| ## Usage | ||
|
|
||
| Both server-side and client-side share the same `context.logs` API. All methods return Promises, but you don't need to `await` them for fire-and-forget usage. | ||
|
|
||
| ### Fire-and-Forget | ||
|
|
||
| ```ts | ||
| // No await needed — just emit the log | ||
| context.logs.add({ | ||
| message: 'Plugin initialized', | ||
| level: 'info', | ||
| }) | ||
| ``` | ||
|
|
||
| ### With Handle | ||
|
|
||
| `await` the `add()` call to get a `DevToolsLogHandle` for subsequent updates: | ||
|
|
||
| ```ts | ||
| // Await to get a handle for later updates | ||
| const handle = await context.logs.add({ | ||
| id: 'my-build', | ||
| message: 'Building...', | ||
| level: 'info', | ||
| status: 'loading', | ||
| }) | ||
|
|
||
| // Later, update via the handle | ||
| await handle.update({ | ||
| message: 'Build complete', | ||
| level: 'success', | ||
| status: 'idle', | ||
| }) | ||
|
|
||
| // Or dismiss it | ||
| await handle.dismiss() | ||
| ``` | ||
|
|
||
| ### Server-Side Example | ||
|
|
||
| ```ts | ||
| export function myPlugin() { | ||
| return { | ||
| name: 'my-plugin', | ||
| devtools: { | ||
| setup(context) { | ||
| // Fire-and-forget | ||
| context.logs.add({ | ||
| message: 'Plugin initialized', | ||
| level: 'info', | ||
| }) | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Client-Side Example | ||
|
|
||
| ```ts | ||
| import type { DockClientScriptContext } from '@vitejs/devtools-kit/client' | ||
|
|
||
| export default async function (context: DockClientScriptContext) { | ||
| // Await to get the handle | ||
| const log = await context.logs.add({ | ||
| message: 'Running audit...', | ||
| level: 'info', | ||
| status: 'loading', | ||
| notify: true, | ||
| }) | ||
|
|
||
| // ... do work ... | ||
|
|
||
| // Update via handle — can also be fire-and-forget | ||
| log.update({ | ||
| message: 'Audit complete — 3 issues found', | ||
| level: 'warn', | ||
| status: 'idle', | ||
| }) | ||
| } | ||
| ``` | ||
|
|
||
| ## Log Handle | ||
|
|
||
| `context.logs.add()` returns a `Promise<DevToolsLogHandle>` with: | ||
|
|
||
| | Property/Method | Description | | ||
| |-----------------|-------------| | ||
| | `handle.id` | The log entry id | | ||
| | `handle.entry` | The current `DevToolsLogEntry` data | | ||
| | `handle.update(patch)` | Partially update the log entry (returns `Promise`) | | ||
| | `handle.dismiss()` | Remove the log entry (returns `Promise`) | | ||
|
|
||
| Both `handle.update()` and `handle.dismiss()` return Promises but can be used without `await` for fire-and-forget. | ||
|
|
||
| ## Deduplication | ||
|
|
||
| When you call `add()` with an explicit `id` that already exists, the existing entry is **updated** instead of duplicated. This is useful for logs that represent ongoing operations: | ||
|
|
||
| ```ts | ||
| // First call creates the entry | ||
| context.logs.add({ id: 'my-scan', message: 'Scanning...', level: 'info', status: 'loading' }) | ||
|
|
||
| // Second call with same id updates it | ||
| context.logs.add({ id: 'my-scan', message: 'Scan complete', level: 'success', status: 'idle' }) | ||
| ``` | ||
|
|
||
| ## Toast Notifications | ||
|
|
||
| Set `notify: true` to show the log entry as a toast notification overlay. Toasts appear regardless of whether the Logs panel is open. | ||
|
|
||
| ```ts | ||
| context.logs.add({ | ||
| message: 'URL copied to clipboard', | ||
| level: 'success', | ||
| notify: true, | ||
| autoDismiss: 2000, // disappear after 2 seconds | ||
| }) | ||
| ``` | ||
|
|
||
| The default auto-dismiss time for toasts is 5 seconds. | ||
|
|
||
| ## Managing Logs | ||
|
|
||
| ```ts | ||
| // Remove a specific log by id | ||
| context.logs.remove(entryId) | ||
|
|
||
| // Clear all logs | ||
| context.logs.clear() | ||
| ``` | ||
|
|
||
| Logs have a maximum capacity of 1000 entries. When the limit is reached, the oldest entries are automatically removed. | ||
|
|
||
| ## Dock Badge | ||
|
|
||
| The Logs dock icon automatically shows a badge with the total log count. The icon is hidden when there are no logs. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "name": "example-plugin-a11y-checker", | ||
| "type": "module", | ||
| "version": "0.0.0-alpha.34", | ||
| "private": true, | ||
| "exports": { | ||
| ".": "./dist/index.mjs", | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "types": "./dist/index.d.mts", | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "scripts": { | ||
| "build:node": "tsdown --config-loader=tsx", | ||
| "build": "pnpm run build:node", | ||
| "play:dev": "pnpm run build && cd playground && DEBUG='vite:devtools:*' vite" | ||
| }, | ||
| "peerDependencies": { | ||
| "vite": "*" | ||
| }, | ||
| "dependencies": { | ||
| "@vitejs/devtools": "workspace:*", | ||
| "@vitejs/devtools-kit": "workspace:*", | ||
| "axe-core": "catalog:devtools" | ||
| }, | ||
| "devDependencies": { | ||
| "solid-js": "catalog:devtools", | ||
| "tsdown": "catalog:build", | ||
| "unocss": "catalog:build", | ||
| "vite": "catalog:build", | ||
| "vite-plugin-solid": "catalog:devtools" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>A11y Checker Playground</title> | ||
| </head> | ||
| <body> | ||
| <div id="app"></div> | ||
| <script type="module" src="/src/main.tsx"></script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| // @unocss-include | ||
|
|
||
| /** | ||
| * This component has intentional accessibility issues | ||
| * for testing the A11y Checker plugin. | ||
| */ | ||
| export default function App() { | ||
| return ( | ||
| <div class="min-h-screen bg-gradient-to-br from-slate-100 via-blue-50 to-cyan-100 text-slate-800 dark:from-slate-950 dark:via-slate-900 dark:to-slate-800 dark:text-slate-100"> | ||
| <main class="mx-auto max-w-4xl px-6 py-10"> | ||
| <section class="rounded-2xl border border-slate-200 bg-white/80 p-6 shadow-xl shadow-slate-300/20 backdrop-blur dark:border-slate-700 dark:bg-slate-900/70 dark:shadow-black/25"> | ||
| <h1 class="m-0 text-3xl font-semibold tracking-tight">A11y Checker Playground</h1> | ||
| <p class="mt-3 leading-7 text-slate-700 dark:text-slate-300"> | ||
| Open Vite DevTools and click the | ||
| {' '} | ||
| <strong>A11y Checker</strong> | ||
| {' '} | ||
| icon | ||
| (wheelchair) to run an accessibility audit on this page. | ||
| The results will appear in the | ||
| {' '} | ||
| <strong>Logs</strong> | ||
| {' '} | ||
| panel. | ||
| </p> | ||
| </section> | ||
|
|
||
| {/* Intentional a11y issues below */} | ||
|
|
||
| <section class="mt-6 rounded-2xl border border-slate-200 bg-white/80 p-6 shadow-xl dark:border-slate-700 dark:bg-slate-900/70"> | ||
| <h2 class="text-xl font-semibold mb-4">Test Cases</h2> | ||
|
|
||
| {/* Issue: image without alt */} | ||
| <div class="mb-4"> | ||
| <h3 class="text-sm font-medium mb-2 op50">Image without alt text</h3> | ||
| <img src="https://placehold.co/200x100" width="200" height="100" /> | ||
| </div> | ||
|
|
||
| {/* Issue: button with no accessible name */} | ||
| <div class="mb-4"> | ||
| <h3 class="text-sm font-medium mb-2 op50">Button without label</h3> | ||
| <button class="px-3 py-1 rounded bg-blue-500 text-white" /> | ||
| </div> | ||
|
|
||
| {/* Issue: low contrast text */} | ||
| <div class="mb-4"> | ||
| <h3 class="text-sm font-medium mb-2 op50">Low contrast text</h3> | ||
| <p style={{ 'color': '#ccc', 'background-color': '#fff' }}> | ||
| This text has very low contrast and is hard to read. | ||
| </p> | ||
| </div> | ||
|
|
||
| {/* Issue: form input without label */} | ||
| <div class="mb-4"> | ||
| <h3 class="text-sm font-medium mb-2 op50">Input without label</h3> | ||
| <input type="text" placeholder="Enter something..." class="border rounded px-2 py-1" /> | ||
| </div> | ||
|
|
||
| {/* Issue: clickable div without role */} | ||
| <div class="mb-4"> | ||
| <h3 class="text-sm font-medium mb-2 op50">Clickable div without role</h3> | ||
| <div | ||
| onClick={() => {}} | ||
| class="cursor-pointer bg-purple-100 dark:bg-purple-900 rounded px-3 py-2 inline-block" | ||
| > | ||
| Click me (I'm a div, not a button) | ||
| </div> | ||
| </div> | ||
| </section> | ||
| </main> | ||
| </div> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| /* @refresh reload */ | ||
| import { render } from 'solid-js/web' | ||
| import App from './App' | ||
| import '@unocss/reset/tailwind.css' | ||
| import 'virtual:uno.css' | ||
|
|
||
| const root = document.getElementById('app') | ||
| if (!root) | ||
| throw new Error('Missing #app root') | ||
|
|
||
| render(() => <App />, root) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /// <reference types="vite/client" /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { fileURLToPath } from 'node:url' | ||
| import { DevTools } from '@vitejs/devtools' | ||
| import UnoCSS from 'unocss/vite' | ||
| import { defineConfig } from 'vite' | ||
| import solid from 'vite-plugin-solid' | ||
| import { A11yCheckerPlugin } from '../src/node' | ||
|
|
||
| const unoConfig = fileURLToPath(new URL('../uno.config.ts', import.meta.url)) | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [ | ||
| DevTools({ | ||
| builtinDevTools: false, | ||
| }), | ||
| solid(), | ||
| A11yCheckerPlugin(), | ||
| UnoCSS({ | ||
| configFile: unoConfig, | ||
| }), | ||
| ], | ||
| }) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The markdown table uses
||at the start of each row, which won’t render as a table in VitePress/Markdown (it should be single|pipes). Please update the table syntax so the docs render correctly.