Skip to content
| Marketplace
Sign in
Visual Studio Code>Linters>Quack - Quick QANew to Visual Studio Code? Get it now.
Quack - Quick QA

Quack - Quick QA

Ryan Boylett

|
2 installs
| (0) | Free
Run custom QA Bills against your work directly in VSCode.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Quack: Quick QA

Quack lets you define custom QA Bills: small JavaScript or TypeScript rules that run against the active document directly inside VS Code. Open any file, then click Check Bills in the Quack sidebar (or run Quack: Check Bills from the Command Palette) to evaluate every enabled bill.

See CHANGELOG.md for version history.


Quick start

  1. Open Settings (Cmd+, / Ctrl+,) and search for quack.bills.
  2. Add a bill to the JSON array (see examples below).
  3. Open a file; the Quack sidebar will list your bills.
  4. Click Check Bills.

The Quack sidebar

When a file is active, the sidebar shows:

  • Overall status: a coloured pill in the top bar reading Pass, Warning, or Fail based on the aggregate result of all checked bills.
  • Filter menu: click the funnel icon to show/hide passing bills, failing bills, uncategorized bills, or individual categories.
  • Category groups: bills that share a category value are grouped under a collapsible heading. Each heading shows coloured count badges for failures, warnings, and passes. Bills without a category appear in an Uncategorized group at the top.
  • Bill cards: each bill shows its title, optional description, result message, and action links. Unchecked bills show a Check link to run that bill individually. Once checked, a refresh icon appears to the left of the action links to re-run just that bill. The card border is coloured green (pass), amber (warn), or red (fail/error).

Commands

Command Description
Quack: Check Bills Runs all enabled bills against the active document.
Quack: Check a Bill Prompts you to pick a single enabled bill, then runs its check.
Quack: Fix All Applies fix for every enabled bill that has it, sequentially.
Quack: Fix a Bill Prompts you to pick a single fixable bill, then applies its fix.
Quack: Run Side Effect Prompts you to pick a side effect from the available bills, then runs it.
Quack: Open Browser Panel Opens a live browser preview of the active document in a VS Code panel.
Quack: Open Documentation Opens this README in the VS Code Markdown preview.

All commands are available in the Command Palette (Cmd+Shift+P / Ctrl+Shift+P). You can also bind them to keyboard shortcuts via Preferences: Open Keyboard Shortcuts.


AI agent tools

Quack registers three VS Code language model tools that any LM-backed agent -- including GitHub Copilot and Claude -- can invoke when you ask it to run your QA rules. No additional configuration is required; the tools are registered automatically when the extension activates.

Tool Description
quack_list_bills Returns all bills loaded for the active document: IDs, titles, descriptions, categories, and whether each bill has a check and/or fix.
quack_check_bills Runs one or more bills against the active document and returns results (status, message, selection count, fix availability). Pass specific billIds or omit to run all enabled bills.
quack_fix_bill Applies the fix for a specific bill by ID to the active document.

Example prompts

The agent decides when to invoke these tools based on context. Prompts like the following will typically trigger them:

  • "Run my Quack checks on this file"
  • "Check the alt text bill"
  • "What QA bills are available for this document?"
  • "Fix all the bills that are failing and have a fix"
  • "Check bill missing-subject-line and fix it if it fails"

How checks and fixes work

When an agent calls quack_check_bills, Quack runs the same check logic used by the sidebar -- including the prepare phase, ignore filter, and scope matching. quack_fix_bill applies the same WorkspaceEdit that the Fix button uses, so the change lands in the editor immediately and can be undone.


Configuring Bills (quack.bills)

Each bill is a JSON object in your settings.json:

"quack.bills": [
  {
    "id": "lang-attr",
    "title": "HTML lang attribute",
    "description": "The <html> element must declare a language.",
    "category": "Accessibility",
    "check": "...",
    "fix": "..."
  }
]

Bill properties

Property Type Required Description
id string Yes Stable unique identifier. Used to track enabled/disabled state.
title string Yes Display name shown in the sidebar.
description string Short description of what the bill checks. Shown below the title (and as a tooltip in compact mode).
category string Group label used to visually group bills together in the sidebar. Bills with the same category are rendered under a shared collapsible heading. Bills without a category appear in the Uncategorized group.
order number Sort position within the sidebar. Bills with an order value are sorted numerically and appear before bills without one. Bills without an order are sorted alphabetically by title.
priority number \| string Fix order priority for Fix All. Lower values are fixed first. Accepts any integer, "Infinity", or "-Infinity". Bills without a priority are fixed last.
author string Author of the bill in Full Name <email@address.com> format.
repository string URL to the Git repository where this bill is maintained.
version string Minimum Quack version required to run this bill (semver, e.g. "0.6.0"). If omitted, the bill is assumed compatible. Incompatible bills are greyed out and cannot be toggled or checked.
scope string \| string[] File extension(s) or glob pattern(s) this bill applies to. Extensions are plain strings such as "html", "html,htm", or ["html","htm"]. Globs are any entry containing / or *, matched against the workspace-relative file path (e.g. "src/**/*.html"). When both extensions and globs are supplied the filter is generous -- a match on either passes. When only globs are supplied the filter is specific -- the path must match at least one glob. If omitted, the bill applies to all file types.
prepare string \| string[] JavaScript or TypeScript code that runs before the check phase. All preparations run in parallel. Must return an object with at least one key — the merged results are available in check as a top-level context variable. See Prepare.
check string \| string[] Yes JavaScript or TypeScript to evaluate the document. Can be inline code, an array of lines, or a path to a .js/.ts file. Supports export default.
fix string \| string[] JavaScript or TypeScript to fix the document. Can be inline code, an array of lines, or a path to a .js/.ts file. Supports export default.
granular boolean Set to true when the fix script reads this.target to operate on only the focused occurrence. Enables the Fix Selected and Ignore Selected labels when navigating between occurrences with the <</>> arrows. Without this, both buttons show their normal labels and always operate on all occurrences.
browser string \| string[] JavaScript or TypeScript that runs in the browser context each time the preview page loads. Has access to window and all browser APIs. Returns the same format as check. Results are not shown in the sidebar -- browser bills run silently in the background. Can be inline code, an array of lines, or a path to a .js/.ts file. See Browser Preview.
configuration object A VS Code-style settings schema declaring the bill's configurable properties. When present, a Configure icon appears on the bill card. See Bill Configuration.
sideEffects array Named action buttons shown in the sidebar's Side Effects section. See Side Effects.

Writing check

check runs when the user clicks Check Bills (or Quack: Check Bills). All enabled bills run in parallel.

All bill code fields (check, fix, prepare, conditions, effect) support TypeScript and export default. You can write bills in .ts files or use TypeScript syntax in inline code — Quack transpiles it automatically via esbuild. You can also use export default to return a value instead of a bare return statement.

Execution context

Inside check and fix, this is a context object with the following properties:

Property Value
this.body The full document source as a raw string
this.bill The bill object (id, title, description, etc.)
this.config Resolved configuration for this bill — an object of key/value pairs merged from the bill's configuration defaults and the user's workspace settings
this.filename Absolute path to the active document
this.match(regex) Finds all regex matches in the document and returns { match, groups, line, column, length, start, end }[] (see below)
context The merged object from all bills' prepare results. Empty {} if no bills define prepare. See Prepare
progress(value) Report loading progress on the bill card — pass a number (0–100) for a determinate bar, true for indeterminate, or falsy to clear (see below)
vscode The full VS Code extension API object — use for notifications, clipboard, commands, workspace access, etc.
this.toString() Returns this.body — allows this to coerce to a string in template literals and concatenation
require(id) CommonJS require — fs, path, and crypto are always available; fs and path are sandboxed to the active document's directory. Other modules resolve only from the bill's own directory

Return value

check must return one of the following:

Return value Result
{ result: 'pass' } Bill passed
{ result: 'pass', message: '...' } Bill passed, with a message
{ result: 'warn', message: '...' } Warning: bill is highlighted amber, document is not failed
{ result: 'fail', message: '...' } Bill failed: document is marked FAIL
true Shorthand for pass
false Shorthand for fail
(nothing / undefined) Quack shows a VS Code warning alert (treat this as a bug in your bill)

Any uncaught exception is caught by Quack and surfaced as an error result (treated as a failure).

selections

Include a selections array in the returned object to indicate exactly where in the document the problem was found. When present, a Jump To link appears in the bill's sidebar card. Clicking it applies all selections at once, so multiple occurrences can be highlighted simultaneously.

Each entry in selections is an object. You can specify positions using character indexes (start/end) or line/column coordinates — start takes priority when present. Entries can also carry a message and a type to drive grouped navigation in the sidebar (see below).

// Using line/column/length
return { result: 'fail', message: 'Missing lang attribute.', selections: [{ line: 1, column: 1 }] };

// Using character indexes (from this.match results, which include start and end)
const hits = this.match(/<font[\s>]/i);
return { result: 'fail', message: `${hits.length} <font> tags.`, selections: hits };

// Multiple occurrences with line/column
return { result: 'fail', message: '3 <font> tags found.', selections: [
  { line: 4, column: 3, length: 6 },
  { line: 9, column: 1, length: 6 },
  { line: 22, column: 7, length: 6 },
] };

Selection behaviour — character index mode:

start end Cursor behaviour
y y Selects from character start to character end
y — Places cursor at character start (no selection)

Selection behaviour — line/column mode (used when start is not present):

line column length Cursor behaviour
y — — Selects the entire line
y — 0 Places cursor at the start of the line (no selection)
y y — or 0 Places cursor at the column (no selection)
y y > 0 Selects length characters from the column

Per-selection messages and grouped navigation:

Any selection entry can include a message string and an optional type ("warn" or "fail"). When at least one selection carries a message, the sidebar switches to grouped navigation mode: selections are grouped by their message string, and each group is displayed as a labelled block with its own prev/next navigation:

Missing alt attribute
« Select 3 »

Deprecated <font> tag
« Select 2 »

The message is coloured according to type ("fail" is red, "warn" is amber). When no selections carry a message, the sidebar shows the standard single "Select N" link.

const hits = this.match(/<img(?![^>]*\salt=)[^>]*/i);
const deprecated = this.match(/<font[\s>]/i);
return {
  result: 'fail',
  selections: [
    ...hits.map(h => ({ ...h, message: 'Missing alt attribute', type: 'fail' })),
    ...deprecated.map(d => ({ ...d, message: 'Deprecated <font> tag', type: 'warn' })),
  ],
};

canFix

Return canFix: false to suppress the Fix Issue button even when the bill has fix. This is useful when the fix is only safe under certain conditions that the check can detect:

const { parse } = require('node-html-parser');
const items = parse(this.body).querySelectorAll('td[bgcolor]');
if (items.length === 0) return { result: 'pass' };

// Only offer to auto-fix if all occurrences use the same colour
const colours = new Set(items.map(el => el.getAttribute('bgcolor')));
return {
  result: 'fail',
  message: `${items.length} bgcolor attribute(s) found.`,
  canFix: colours.size === 1,  // fix is unsafe when colours differ
};

When canFix is omitted or true, the Fix Issue button is shown normally (subject to the bill having fix and the result being fail, warn, or error).

this.match(regex)

this.match searches the document for all matches of a regular expression and returns their positions. The regex does not need to be global; this.match always finds all matches.

Returns an array of match objects:

Property Type Description
match string The full matched string
groups string[] Captured groups (same as m.slice(1))
line number Line number of the match
column number Column number of the match
length number Character length of the match
start number Start index of the match
end number End index of the match

Example: fail on all <font> tags and select every occurrence:

const hits = this.match(/<font[\s>]/i);

if (hits.length > 0) {
  return {
    result: 'fail',
    message: `<font> tag found (${hits.length} total).`,
    selections: hits,
  };
}
return { result: 'pass' };

progress(value)

Report loading progress on the bill's sidebar card. A 2px accent-coloured bar appears at the bottom of the card. Useful for bills that perform slow operations (network requests, large file scans, etc.).

Call Effect
progress(50) Determinate bar at 50% width
progress(33.7) Accepts floats for fine-grained progress
progress(true) Indeterminate animated bar
progress(100) Bar fills to 100%, holds briefly, then fades out
progress(0) / progress(false) / progress(null) Clears the bar immediately

Progress is automatically cleared when the bill's result is returned. If the bar is at 100% when the result arrives, it fades out gracefully before the result is displayed.

const items = parse(this.body).querySelectorAll('img');
for (let i = 0; i < items.length; i++) {
  progress((i / items.length) * 100);
  // ... check each image ...
}
progress(100);
return { result: 'pass' };

require(id)

Bills can load Node modules from their own directory using require. This is useful for DOM parsing, utility libraries, or shared helper files.

require is sandboxed in two ways:

  • fs — all file-path arguments are validated against the active document's directory. Any path that resolves outside it throws an access-denied error.
  • path — path.resolve() is re-rooted to the active document's directory, so relative paths resolve there rather than against process.cwd().
  • Other modules — only modules installed in the bill's own directory can be loaded. Any resolution that escapes that directory throws an error.
// Load node-html-parser from the bill's directory (npm install node-html-parser in that directory)
const { parse } = require('node-html-parser');
const lang = parse(this.body).querySelector('html')?.getAttribute('lang');
return { result: lang ? 'pass' : 'fail', message: 'The <html> element must have a lang attribute.' };

Inline example

"check": "const hits = this.match(/<html(?![^>]*\\slang=)[^>]*>/i);\nreturn { result: hits.length === 0 ? 'pass' : 'fail', message: 'The <html> element must have a lang attribute.' };"

As an alternative to escaped newlines, check and fix can be an array of strings: each item is a line of code, joined before execution:

"check": [
  "const hits = this.match(/<html(?![^>]*\\slang=)[^>]*>/i);",
  "return { result: hits.length === 0 ? 'pass' : 'fail', message: 'The <html> element must have a lang attribute.' };"
]

File example

Point check at a .js or .ts file in your workspace (relative path, no semicolons):

"check": "./qa/checks/lang-attr.js"

Contents of ./qa/checks/lang-attr.js:

const hits = this.match(/<html(?![^>]*\slang=)[^>]*>/i);
return {
  result: hits.length === 0 ? 'pass' : 'fail',
  message: 'The <html> element must have a lang attribute (e.g. lang="en").'
};

TypeScript example

"check": "./qa/checks/lang-attr.ts"

Contents of ./qa/checks/lang-attr.ts:

const hits: { line: number; column: number; length: number }[] = this.match(/<html(?![^>]*\slang=)[^>]*>/i);

export default {
  result: hits.length === 0 ? 'pass' : 'fail',
  message: 'The <html> element must have a lang attribute.',
  selections: hits,
};

export default works in both .js and .ts files and is equivalent to a bare return.


Writing fix

fix is optional. It runs when the user clicks Fix Issue on a failed or warned bill, or when the user runs Quack: Fix All. The result is written directly back to the open document.

Quack: Fix All applies the fix for every enabled bill that has fix (and where canFix is not false), running them sequentially so each fix sees the output of the previous one.

By default, Quack re-checks bills after a fix is applied: fixing a single bill re-checks all previously checked bills (since a fix may affect other bills' results), while Fix All re-checks every enabled bill. This keeps results up to date without a manual re-check. Disable this with quack.checkAfterFix: false.

Execution context

Same as check: this is the context object, require is available, with one additional property:

Property Value
this.target When the user clicks Fix Selected after navigating to a specific occurrence with the <</>> arrows, this is the SelectionTarget for that occurrence ({ start, end, line, column, length }). null when the user clicked Fix or Fix N (fix all). Use this to limit the fix to a single occurrence

If your fix script does not check this.target, it will fix all occurrences regardless of whether the user clicked Fix or Fix Selected — which is the correct default behaviour for most bills.

To signal that your fix supports per-occurrence targeting, set "granular": true in the bill schema. This causes the sidebar to show Fix Selected and Ignore Selected (instead of Fix and Ignore) when the user has navigated to a specific occurrence. Bills without granular: true always show Fix and Ignore even in navigation mode.

Return value

fix must return the modified document string.

Example

// Inline fix: insert lang="en" if the attribute is missing
return this.body.replace(/<html(?![^>]*\slang=)/i, '<html lang="en"');

Prepare

Bills can define a prepare field — code that runs before the check phase. All preparations run in parallel, and their return values are merged into a single context object available to every check script. This is useful for expensive shared computation (e.g. DOM parsing) that multiple bills need.

Rules

  • prepare must return a plain object with at least one key. An empty object or non-object return is a fatal error — Quack aborts the check cycle and no bills are checked.
  • If two or more bills return the same key from their prepare, a fatal error occurs (duplicate key conflict).
  • Preparations do not run for fixes or side effects — only for checks.
  • The merged object is available in check as a top-level context variable (not on this).

Execution context

prepare receives the same this context as check (this.body, this.bill, this.config, this.filename, this.match, require, vscode), but does not receive progress or context.

Example

A bill that parses the DOM once and shares it:

{
  "id": "dom-parser",
  "title": "DOM Parser",
  "scope": "html",
  "prepare": [
    "const { parse } = require('node-html-parser');",
    "return { dom: parse(this.body) };"
  ],
  "check": "return true;"
}

Other bills can then use context.dom in their checks without re-parsing:

{
  "id": "lang-attr",
  "title": "HTML lang attribute",
  "scope": "html",
  "check": [
    "const lang = context.dom?.querySelector('html')?.getAttribute('lang');",
    "return { result: lang ? 'pass' : 'fail', message: 'Missing lang attribute.' };"
  ]
}

Result states

State Border colour Effect on document
pass Green Bill passed.
warn Amber Concern flagged. Document is not failed.
fail Red Bill failed. Document is marked FAIL.
error Red Exception thrown in check. Treated as a failure.

If any bill fails, the overall document status is Fail. If there are only warnings and no failures, the status is Warning. Otherwise the status is Pass.


Bill Imports (quack.billImports)

Instead of (or in addition to) defining bills inline in settings.json, you can use quack.billImports to load .json bill files by path, glob pattern, or directory. This is useful for sharing bills across projects or keeping them under version control independently.

"quack.billImports": [
  "./qa/bills/lang-attr.json",
  "./qa/bills/*.json",
  "./qa/shared-bills",
  "/absolute/path/to/bills/**/*.json"
]

Each entry can be:

  • A path to a single file — loads exactly that bill.
  • A glob pattern — loads all matched .json files (e.g. ./qa/bills/*.json, ./qa/**/*.json).
  • A directory path — loads all .json files within the directory recursively.

Relative paths resolve from the workspace root. All matched bills are merged and loaded alongside any quack.bills entries. Quack watches the base directory of each pattern for changes: adding, editing, or deleting a JSON file refreshes the sidebar automatically.

Bill file format

Each .json file is a single bill. The structure is identical to an entry in quack.bills:

{
  "id": "lang-attr",
  "title": "HTML lang attribute",
  "description": "The <html> element must declare a language.",
  "check": "lang-attr.check.js",
  "fix": "lang-attr.fix.js"
}

The id field is optional. If omitted, the filename without the .json extension is used as the ID (e.g. lang-attr.json -> id lang-attr).

File resolution

When check, fix, or prepare is a relative .js or .ts path in an imported bill, it resolves relative to the directory containing that bill file. This means you can keep bill logic alongside the bill definition:

qa/bills/
+-- lang-attr.json
+-- lang-attr.check.ts
+-- lang-attr.fix.ts
+-- title-tag.json
+-- title-tag.check.js

File paths in bills defined via quack.bills (in settings.json) continue to resolve relative to the workspace root.


Enabling and disabling bills

Each bill has a toggle in the sidebar. Disabled bills are skipped when running Check Bills. The enabled/disabled state is stored per-workspace in .vscode/settings.json under quack.billsDisabled and persists across sessions. This setting is not shown in the Settings UI and is managed entirely by the sidebar toggles.


Bill Configuration

Bills can declare configurable properties using a configuration object that follows VS Code's settings schema format. When a bill has configuration, a gear icon appears in the bottom-right corner of its card in the sidebar. Clicking the icon opens .vscode/settings.json (creating it if needed) and pre-populates the bill's configuration section with default values so the user can see what is available and start editing immediately.

Declaring configuration

Add a configuration object to the bill. Each key is a setting name; the value is a schema descriptor:

{
  "id": "lang-attr",
  "title": "HTML lang attribute",
  "configuration": {
    "lang": {
      "type": "string",
      "default": "en",
      "description": "Expected value for the lang attribute on <html> (e.g. \"en\", \"fr\")."
    }
  },
  "check": [
    "const hits = this.match(/<html(?![^>]*\\slang=)[^>]*>/i);",
    "if (hits.length === 0) return { result: 'pass' };",
    "return { result: 'fail', message: `lang attribute missing — expected lang=\"${this.config.lang}\".` };"
  ],
  "fix": [
    "return this.body.replace(/<html(?![^>]*\\slang=)/i, `<html lang=\"${this.config.lang}\"`);"
  ]
}

Configuration schema properties

Each property in configuration can use the following fields:

Field Type Description
type string Data type: "string", "number", "boolean", "array", or "object"
default any Default value used when the user has not overridden the setting
required boolean When true, the user must supply a value in quack.billConfig before the bill will run. Bills with unsatisfied required properties are greyed out and excluded from checks and fixes
description string Short plain-text description shown in the settings file
markdownDescription string Markdown description (use instead of description for richer text)
enum any[] Allowed values
enumDescriptions string[] Human-readable labels for each enum value
minimum number Minimum value (numeric properties)
maximum number Maximum value (numeric properties)

Using this.config in check and fix

Configuration is available as this.config — a plain object of key/value pairs. Values are taken from the user's workspace settings if present, falling back to the schema defaults.

// this.config.lang resolves to "en" unless the user has overridden it
const hits = this.match(/<html(?![^>]*\slang=)[^>]*>/i);
if (hits.length === 0) return { result: 'pass' };
return { result: 'fail', message: `lang attribute missing — expected lang="${this.config.lang}".` };

Where values are stored

User-supplied values live in .vscode/settings.json under quack.billConfig:

{
  "quack.billConfig": {
    "lang-attr": {
      "lang": "fr"
    }
  }
}

This is workspace-scoped, so each project can have its own overrides independently of others.


Side Effects

Bills can define side effects — named action buttons that appear in a dedicated collapsible "Side Effects" section above the Bills list. A bill can provide side effects alongside checks and fixes, or only side effects. The section includes a search bar and a category filter (funnel icon) for filtering by category. Side effects are sorted by category, then by name.

Each side effect is an object in the bill's sideEffects array:

Property Type Required Description
name string Yes Display name for the button
icon string Codicon icon name (e.g. "file-code", "refresh")
category string Category for filtering and search. If omitted, inherits the parent bill's category
scope string \| string[] File extension(s) or glob pattern(s) this side effect applies to. Follows the same rules as the bill-level scope field. Use "browser" to make this a browser-context side effect (see below). If omitted, the side effect appears for all file types
conditions string \| string[] JavaScript or TypeScript condition code. Return true to enable the button, false to disable it. Evaluated after each check cycle. If omitted, the button is always enabled
effect string \| string[] JavaScript or TypeScript code to execute when the button is clicked. If a string is returned, the document content is replaced. Supports export default. Required for non-browser side effects
browser string \| string[] JavaScript or TypeScript code to run in the browser context when this side effect is triggered. Has access to window and all browser APIs. Required when scope is "browser". See Browser Preview

Execution context

Side effect code receives the same execution context as check/fix — the this context object, require, progress, and vscode.

Return value

effect can return:

Return value Effect
string Replaces the document content
{ body: string } Replaces the document content
{ selections: Array } Creates selections in the editor (same format as check selections)
{ body: string, selections: Array } Replaces the document, then creates selections in the updated content
(nothing) No document changes

When both body and selections are returned, the body is applied first so that selection positions refer to the new content.

Example

{
  "id": "html-tools",
  "title": "HTML Tools",
  "check": "return true;",
  "sideEffects": [
    {
      "name": "Minify HTML",
      "icon": "file-binary",
      "effect": [
        "return this.body.replace(/\\n\\s*/g, '');"
      ]
    },
    {
      "name": "Copy to Clipboard",
      "icon": "clippy",
      "conditions": "return this.body.length > 0;",
      "effect": [
        "await vscode.env.clipboard.writeText(this.body);",
        "vscode.window.showInformationMessage('Copied to clipboard!');"
      ]
    }
  ]
}

The Side Effects section can be hidden entirely with quack.showSideEffects: false. If no bills define side effects, the section does not appear.


Browser Preview

Click the Preview button in the Quack sidebar header (or run Quack: Open Browser Panel) to open a live preview of the active document in a VS Code panel. The document is served from a local HTTP server. The panel updates as you type (debounced 300 ms), serving the unsaved content directly from memory. When the file is saved, the memory buffer is cleared and the panel reloads from disk.

When the Browser Preview panel is focused, the sidebar will clear its bill list if the current file has no browser bills defined.

Browser bills

Add a browser field to any bill to run JavaScript or TypeScript inside the preview page on each load. Browser bill code runs in the live browser context and has access to window, document, and all other browser APIs.

Browser bills return the same format as check:

{
  "id": "layout-check",
  "title": "Layout check",
  "browser": [
    "const main = document.querySelector('main');",
    "return { result: main ? 'pass' : 'fail', message: 'Page must contain a <main> element.' };"
  ]
}

Results are not displayed in the sidebar -- browser bills run silently in the background on every page load.

Browser-context side effects

Side effects with scope: "browser" target the browser context instead of the editor. They use a browser property (not effect) for their code, which runs inside the preview window. Browser side effects appear in the sidebar's Side Effects section only while the Browser Preview panel is open.

{
  "id": "browser-tools",
  "title": "Browser Tools",
  "sideEffects": [
    {
      "name": "Log performance",
      "icon": "pulse",
      "scope": "browser",
      "browser": [
        "const entries = window.performance.getEntriesByType('resource');",
        "console.table(entries.map(e => ({ name: e.name, duration: e.duration })));"
      ]
    }
  ]
}

Context menu

Right-clicking any element in the Browser Preview panel opens a context menu. The available items depend on the element type:

Item Available on Action
Copy Text All elements Copies the element's innerText to the clipboard
Copy HTML All elements Copies the element's innerHTML to the clipboard
Copy Image URL Images (or children of images) Copies the image src to the clipboard
Copy URL Links (or children of links) Copies the link href to the clipboard
Copy Source Video/audio elements Copies the media src to the clipboard
Find in Code Elements with source data Selects the element's source range in the active editor
Inspect All elements Highlights the element with a 3-second blue outline, logs it to the browser console, and opens VS Code developer tools for the webview

Find in Code is only available when Quack can determine the element's position in the source file. It is supported for static HTML files served by Quack's built-in server. PHP and other dynamic modes do not currently support it.

CMD/CTRL+click

Hold Cmd (Mac) or Ctrl (Windows/Linux) and click any link in the preview to open it in the system's default browser instead of navigating the preview.

Browser dialog APIs

Browser bill code runs after the page has fully loaded, so window.load listeners will not fire. Use top-level code instead.

As alert, prompt, and confirm are not available in the VS Code webview sandbox, Quack replaces them with VS Code equivalents:

Call VS Code equivalent
alert(message) Info notification (fire-and-forget)
await prompt(message, default) Input box -- returns the entered string, or null if cancelled
await confirm(message) Modal message -- returns true (OK) or false (Cancel)

Because prompt and confirm are asynchronous, bill code that uses them must await their return values:

{
  "id": "confirm-example",
  "title": "Confirm example",
  "sideEffects": [
    {
      "name": "Rename heading",
      "scope": "browser",
      "browser": [
        "const h1 = document.querySelector('h1');",
        "const text = await prompt('New heading text', h1?.textContent ?? '');",
        "if (text && h1) h1.textContent = text;"
      ]
    }
  ]
}

Settings reference

Setting Type Default Description
quack.bills array [] Bills defined inline in settings.json.
quack.billImports array [] Glob patterns, file paths, or directory paths pointing to .json bill files. Bill code can reference .js or .ts files.
quack.workspaceBills boolean false When enabled, automatically loads .json bill files from the .quack directory at the workspace root (top-level only). Script paths within each bill resolve relative to the .quack directory.
quack.billsDisabled array [] IDs of bills that are currently disabled. Written to .vscode/settings.json (workspace scope). Managed by the sidebar toggles -- not shown in the Settings UI.
quack.autoCheck boolean false Re-run all enabled bills automatically after each edit (debounced 500 ms).
quack.checkAfterFix boolean true Re-check bills automatically after applying a fix. Fix on a single bill re-checks all previously checked bills; Fix All re-checks every enabled bill.
quack.showSideEffects boolean true Show the Side Effects section in the sidebar.
quack.compactMode boolean false Condense bill cards: hides descriptions and reduces padding. Bill descriptions are still visible as tooltips.
quack.fileExtensions array [] File extensions Quack activates on (e.g. "html", ".js"). Leave empty to activate on all file types.
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft