diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml
index b295678f4c5..080a39ab106 100644
--- a/.github/workflows/visual-regression.yml
+++ b/.github/workflows/visual-regression.yml
@@ -25,6 +25,8 @@ jobs:
- "packages/kilo-vscode/webview-ui/**"
- "packages/kilo-vscode/.storybook/**"
- "packages/kilo-vscode/tests/visual-regression*"
+ - "packages/kilo-vscode/tests/permission-dock-dropdown*"
+ - "packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/**"
- ".github/workflows/visual-regression.yml"
- name: Check if PR is from a fork
id: fork-check
@@ -302,10 +304,42 @@ jobs:
"
working-directory: packages/kilo-vscode
+ - name: Remove stale baselines for permission-dock-dropdown tests
+ run: |
+ node -e "
+ const fs = require('fs'), path = require('path');
+ const specPath = 'tests/permission-dock-dropdown.spec.ts';
+ const expected = new Set();
+ if (fs.existsSync(specPath)) {
+ const spec = fs.readFileSync(specPath, 'utf8');
+ const re = /toHaveScreenshot\(\[([^\]]+)\]\)/g;
+ let m;
+ while ((m = re.exec(spec)) !== null) {
+ const parts = m[1].split(',').map(s => s.trim().replace(/[\"']/g, ''));
+ expected.add(parts.join('/').replace(/\.png$/, '-chromium-linux.png'));
+ }
+ }
+ const dir = 'tests/permission-dock-dropdown.spec.ts-snapshots';
+ if (!fs.existsSync(dir)) process.exit(0);
+ for (const sub of fs.readdirSync(dir)) {
+ const sdir = path.join(dir, sub);
+ if (!fs.statSync(sdir).isDirectory()) continue;
+ for (const file of fs.readdirSync(sdir)) {
+ const rel = sub + '/' + file;
+ if (file.endsWith('.png') && !expected.has(rel)) {
+ console.log('Removing stale baseline:', rel);
+ fs.unlinkSync(path.join(sdir, file));
+ }
+ }
+ }
+ "
+ working-directory: packages/kilo-vscode
+
- name: Check for baseline changes (fork PRs)
if: needs.check-paths.outputs.is_fork == 'true'
run: |
git add packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/
+ [ -d packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots ] && git add packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/
if git diff --cached --quiet; then
echo "No visual regression detected."
else
@@ -320,6 +354,7 @@ jobs:
if: needs.check-paths.outputs.is_fork != 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update == 'true'
run: |
git add packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/
+ [ -d packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots ] && git add packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/
if git diff --cached --quiet; then
echo "Baselines are stable after auto-update."
else
@@ -339,6 +374,7 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/
+ [ -d packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots ] && git add packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/
if git diff --cached --quiet; then
echo "No new baselines — nothing to commit."
echo "changed=false" >> "$GITHUB_OUTPUT"
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts
new file mode 100644
index 00000000000..cdda6f3081c
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts
@@ -0,0 +1,294 @@
+/**
+ * Screenshot tests for the PermissionDock dropdown (expanded "Permission rules" section).
+ *
+ * These tests navigate to existing Storybook stories, interact with the dropdown
+ * (click to expand, toggle approve/deny on individual rules), and capture screenshots
+ * of each state.
+ *
+ * The existing visual-regression.spec.ts covers the collapsed (default) state.
+ * This file covers interactive states that require clicking.
+ */
+
+import { test, expect, type Page } from "@playwright/test"
+import { platform } from "node:os"
+
+const IS_DARWIN = platform() === "darwin"
+
+// Screenshot baselines are captured on Linux CI — skip on macOS.
+if (IS_DARWIN) {
+ console.warn("Visual regression tests must be run on CI, skipping on local macOS.")
+ test.skip()
+}
+
+const GLOBALS = "colorScheme:dark;theme:kilo-vscode;vscodeTheme:dark-modern"
+
+function storyUrl(storyId: string) {
+ return `/iframe.html?id=${storyId}&viewMode=story&globals=${GLOBALS}`
+}
+
+async function disableAnimations(page: Page) {
+ await page.addStyleTag({
+ content: `
+ *, *::before, *::after {
+ animation-duration: 0s !important;
+ animation-delay: 0s !important;
+ transition-duration: 0s !important;
+ transition-delay: 0s !important;
+ }
+ `,
+ })
+}
+
+async function openDropdown(page: Page) {
+ const header = page.locator('[data-slot="permission-rules-header"]')
+ await header.waitFor({ state: "visible" })
+ await header.click()
+ await page.locator('[data-slot="permission-rules-collapse"][data-open]').waitFor({ state: "visible" })
+}
+
+// Match the viewport used by visual-regression.spec.ts for composite stories.
+test.beforeEach(async ({ page }) => {
+ await page.setViewportSize({ width: 420, height: 720 })
+})
+
+// ---------------------------------------------------------------------------
+// Bash permission — dropdown expanded (all rules pending)
+// ---------------------------------------------------------------------------
+
+test.describe("Permission Dock Dropdown — bash", () => {
+ const STORY_ID = "composite-webview--bash-with-permission"
+
+ test("rules expanded — all pending", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "bash-expanded-pending.png"])
+ })
+
+ test("rules expanded — first rule approved", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ // Click the first approve toggle (dispatchEvent bypasses tooltip overlays)
+ const approveButtons = page.locator('[data-slot="permission-rule-toggle"][data-variant="approve"]')
+ await approveButtons.first().dispatchEvent("click")
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "bash-rule-approved.png"])
+ })
+
+ test("rules expanded — first rule denied", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ // Click the first deny toggle (dispatchEvent bypasses tooltip overlays)
+ const denyButtons = page.locator('[data-slot="permission-rule-toggle"][data-variant="deny"]')
+ await denyButtons.first().dispatchEvent("click")
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "bash-rule-denied.png"])
+ })
+
+ test("rules expanded — mixed (first approved, second denied)", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ // Approve first rule, deny second rule (dispatchEvent bypasses tooltip overlays)
+ const approveButtons = page.locator('[data-slot="permission-rule-toggle"][data-variant="approve"]')
+ const denyButtons = page.locator('[data-slot="permission-rule-toggle"][data-variant="deny"]')
+ await approveButtons.first().dispatchEvent("click")
+ await denyButtons.nth(1).dispatchEvent("click")
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "bash-rules-mixed.png"])
+ })
+})
+
+// ---------------------------------------------------------------------------
+// Glob permission — dropdown expanded
+// ---------------------------------------------------------------------------
+
+test.describe("Permission Dock Dropdown — glob", () => {
+ const STORY_ID = "composite-webview--glob-with-permission"
+
+ test("rules expanded — all pending", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "glob-expanded-pending.png"])
+ })
+
+ test("rules expanded — rule approved", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ const approveButtons = page.locator('[data-slot="permission-rule-toggle"][data-variant="approve"]')
+ await approveButtons.first().dispatchEvent("click")
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "glob-rule-approved.png"])
+ })
+})
+
+// ---------------------------------------------------------------------------
+// Write permission — dropdown expanded
+// ---------------------------------------------------------------------------
+
+test.describe("Permission Dock Dropdown — write", () => {
+ const STORY_ID = "composite-webview--permission-dock-write"
+
+ test("rules expanded — all pending", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "write-expanded-pending.png"])
+ })
+
+ test("rules expanded — mixed decisions", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ // Approve first rule, deny second (dispatchEvent bypasses tooltip overlays)
+ const approveButtons = page.locator('[data-slot="permission-rule-toggle"][data-variant="approve"]')
+ const denyButtons = page.locator('[data-slot="permission-rule-toggle"][data-variant="deny"]')
+ await approveButtons.first().dispatchEvent("click")
+ await denyButtons.nth(1).dispatchEvent("click")
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "write-rules-mixed.png"])
+ })
+})
+
+// ---------------------------------------------------------------------------
+// Edit permission — dropdown expanded
+// ---------------------------------------------------------------------------
+
+test.describe("Permission Dock Dropdown — edit", () => {
+ const STORY_ID = "composite-webview--permission-dock-edit"
+
+ test("rules expanded — all pending", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "edit-expanded-pending.png"])
+ })
+})
+
+// ---------------------------------------------------------------------------
+// Websearch permission — dropdown expanded
+// ---------------------------------------------------------------------------
+
+test.describe("Permission Dock Dropdown — websearch", () => {
+ const STORY_ID = "composite-webview--permission-dock-websearch"
+
+ test("rules expanded — all pending", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "websearch-expanded-pending.png"])
+ })
+})
+
+// ---------------------------------------------------------------------------
+// External directory permission — dropdown expanded
+// ---------------------------------------------------------------------------
+
+test.describe("Permission Dock Dropdown — external directory", () => {
+ const STORY_ID = "composite-webview--permission-dock-external-dir"
+
+ test("rules expanded — all pending", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "external-dir-expanded-pending.png"])
+ })
+})
+
+// ---------------------------------------------------------------------------
+// Bash with many rules (6 rules) — dropdown expanded
+// ---------------------------------------------------------------------------
+
+test.describe("Permission Dock Dropdown — many rules", () => {
+ const STORY_ID = "composite-webview--permission-dock-bash-many-rules"
+
+ test("rules expanded — all pending (overflow)", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "many-rules-expanded-pending.png"])
+ })
+
+ test("rules expanded — some approved, some denied", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ // Approve first 3 rules, deny the 4th.
+ // Use dispatchEvent to bypass any overlay/tooltip interception issues.
+ const rows = page.locator('[data-slot="permission-rule-row"]')
+ const approveInRow = (n: number) =>
+ rows.nth(n).locator('[data-slot="permission-rule-toggle"][data-variant="approve"]')
+ const denyInRow = (n: number) => rows.nth(n).locator('[data-slot="permission-rule-toggle"][data-variant="deny"]')
+
+ await approveInRow(0).dispatchEvent("click")
+ await expect(rows.nth(0)).toHaveAttribute("data-decision", "approved")
+ await approveInRow(1).dispatchEvent("click")
+ await expect(rows.nth(1)).toHaveAttribute("data-decision", "approved")
+ await approveInRow(2).dispatchEvent("click")
+ await expect(rows.nth(2)).toHaveAttribute("data-decision", "approved")
+ await denyInRow(3).dispatchEvent("click")
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "many-rules-mixed.png"])
+ })
+})
+
+// ---------------------------------------------------------------------------
+// Subagent permission — shows "(subagent)" in subtitle
+// ---------------------------------------------------------------------------
+
+test.describe("Permission Dock Dropdown — subagent", () => {
+ const STORY_ID = "composite-webview--permission-dock-subagent"
+
+ test("subagent label visible, rules expanded", async ({ page }) => {
+ await page.goto(storyUrl(STORY_ID), { waitUntil: "load" })
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+ await openDropdown(page)
+
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot(["permission-dock-dropdown", "subagent-expanded.png"])
+ })
+})
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-expanded-pending-chromium-linux.png
new file mode 100644
index 00000000000..f760f80b321
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-expanded-pending-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6ba76e2d5b993e999914fd0538650728adf78f7ad70978d8ee9982525f6ab5e8
+size 17626
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-approved-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-approved-chromium-linux.png
new file mode 100644
index 00000000000..25eae19771e
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-approved-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:29872fa36a6a6ba9e20765f92380b86f9a91beb9702b3cdb95914ea46a2cf40c
+size 17833
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-denied-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-denied-chromium-linux.png
new file mode 100644
index 00000000000..7840f5c9b49
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rule-denied-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ce915e449cdec411c00df613ffc1017f95dba69737e0474fc8c483ea9f284026
+size 17872
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rules-mixed-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rules-mixed-chromium-linux.png
new file mode 100644
index 00000000000..d0b71517797
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/bash-rules-mixed-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b39675360da34fc6cb9c1ff4e51afeff79417ca4baa88816967d2b0a4ece858f
+size 18043
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/edit-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/edit-expanded-pending-chromium-linux.png
new file mode 100644
index 00000000000..5deb79c5feb
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/edit-expanded-pending-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c3b7d96f123e880b1395d160d17a48f385ef59344326c44d7cd13b342d2ca49e
+size 24193
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/external-dir-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/external-dir-expanded-pending-chromium-linux.png
new file mode 100644
index 00000000000..fc41dfc1e64
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/external-dir-expanded-pending-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:739b8eb94e5cde39e4127a6ed3c499392174a380812e2e3497965643cac3dee9
+size 22265
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-expanded-pending-chromium-linux.png
new file mode 100644
index 00000000000..8e6f8a9a320
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-expanded-pending-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:62ef30b05bf2ec47a7ddd02a03f5f1cded5ced9cf9332dd3bd50d6809bcce96d
+size 18447
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-rule-approved-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-rule-approved-chromium-linux.png
new file mode 100644
index 00000000000..da8a959b81f
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/glob-rule-approved-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1f9864861136b578c4e46ece7dae5a139ada7e7be3d219462ef3a549b94239ec
+size 18648
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-expanded-pending-chromium-linux.png
new file mode 100644
index 00000000000..90aa5cfaa2e
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-expanded-pending-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:514803fa46f43ff58d8d403e630afc5c01a8841ac420d406755515bceb38beae
+size 23132
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-mixed-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-mixed-chromium-linux.png
new file mode 100644
index 00000000000..840e6dcdbc4
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/many-rules-mixed-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:578f6daa54a25fba38b049d623e2b12069b0cbe1c5e094031d8a0b0af0a56f52
+size 23966
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/subagent-expanded-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/subagent-expanded-chromium-linux.png
new file mode 100644
index 00000000000..949e7d47de5
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/subagent-expanded-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:91ba11c0a81ced5dfe824a4c9e8fdcbaebaaddc660ef2b245df56903e180e4b7
+size 18949
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/websearch-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/websearch-expanded-pending-chromium-linux.png
new file mode 100644
index 00000000000..e1016e91f6c
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/websearch-expanded-pending-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6f8bc26191620881314eff9ac921abba4e201c5af7ec8d222e405fdee9ddae9a
+size 17909
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-expanded-pending-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-expanded-pending-chromium-linux.png
new file mode 100644
index 00000000000..feff311b196
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-expanded-pending-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ce112e9762168271fd8b7b4316b740008269e9d248a668887adce3dde13478f0
+size 18362
diff --git a/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-rules-mixed-chromium-linux.png b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-rules-mixed-chromium-linux.png
new file mode 100644
index 00000000000..83a6fd71b75
--- /dev/null
+++ b/packages/kilo-vscode/tests/permission-dock-dropdown.spec.ts-snapshots/permission-dock-dropdown/write-rules-mixed-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d499d73467c37052d1277deefdd419e370a8239d7e117806d8442b2b9cc851a7
+size 18886
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/diff-panel-with-diffs-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/diff-panel-with-diffs-chromium-linux.png
index b285a468bc3..8555653044c 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/diff-panel-with-diffs-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/diff-panel-with-diffs-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:86f1c445d792a9c6353f0899ceb6a777d8e2d8a9490f4d520e79dd8081cc5314
-size 17458
+oid sha256:fc50e7ce14bc197134d5fdf38ac92087940906f7b627e2507c082a5be0527d31
+size 17378
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
index 1b0616657c7..0eb62954db7 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:63915e70810da741acb2ab50cc3d557d58ec359f8b7b5e90b85d1c8e8f54d476
-size 17823
+oid sha256:d23a2e49a796acc94f387f7d4cddf783de1c630a3cd3902f3cf558083839b043
+size 17817
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png
index 17022acc34c..20dc4b679f4 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4101f60556f1c55798df5aca5276449c42d12360419bb5596b7c3bd2548afa34
-size 9461
+oid sha256:de6be977bd3805fdbbbb1fdaf3498201c1097adea26ebe460c56b681d1828e39
+size 9457
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png
index 2af1145a533..6959a564fa5 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/components-shell/shell-execution-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c38b50cb5583e35e3828d42337e19c6d85ae6399c1ce432c58f7dc4fa2d63c85
-size 19155
+oid sha256:cf635f68a3a3ea84caa026f3be0481f21a2a7424acd8d2fdc138f8018f843af8
+size 18908
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-bash-many-rules-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-bash-many-rules-chromium-linux.png
new file mode 100644
index 00000000000..0fc73f39049
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-bash-many-rules-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1c43a0a2f6a351d53da542aba6b68113e8231c1ee9cf7276bf6a16263b65d811
+size 18775
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-edit-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-edit-chromium-linux.png
new file mode 100644
index 00000000000..7c9631bb33b
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-edit-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d23b2a1621e33e92e65c7ead23b42f05decd443b370ef7122b840e6f5cf043e1
+size 21177
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-external-dir-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-external-dir-chromium-linux.png
new file mode 100644
index 00000000000..4002998e5ab
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-external-dir-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:63b8651967a042ef6e79f721fc4bf99eae875296dbd991566d3c3b7e90f04669
+size 18542
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-subagent-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-subagent-chromium-linux.png
new file mode 100644
index 00000000000..cf8c0e73185
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-subagent-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:94ef29b3eca3b0e9e407daf9c17388dcf5735c134a74bc13500559bcf9707c9b
+size 16338
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-websearch-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-websearch-chromium-linux.png
new file mode 100644
index 00000000000..4667f970ab5
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-websearch-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9c2402fb61b05c355cedea5a26c39f4a5b4d4a1e04c0fb8ac1ac50edbdc746b3
+size 16426
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png
index f29c67bf06c..542c4451ee0 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bb5a77e875e168813dcd0d23ea4743b67e7f39b4fe154e19e0ef426e5679f030
-size 17436
+oid sha256:38712f08584902b1ccb267fed0271c6c1a9b5f549b47bd743ca344604df2095a
+size 17433
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png
index 67b032c28b2..2c82c3e045e 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1ccccd5ac104eaf39b4954e905c15e5c0f5fc7df5c71898569f490ee09608dc6
-size 19291
+oid sha256:84e3f1c43bd36828f6548f3a3a85827693b370f6f3fc319408829eb742146bb5
+size 19287
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/default-200-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/default-200-chromium-linux.png
index ae9df21ea3e..dcd264ff466 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/default-200-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/default-200-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:999e4d4b4452d5a3e5791768e98fbd12f0196be9bf4bc7ff0375fcc22352e80c
+oid sha256:e2eb5ca750d8a590df5a4c7a1be493290aa90535a98c5dc0f6fadfc802872567
size 3520
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-model-override-200-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-model-override-200-chromium-linux.png
index e84e4e7cf68..a91c3cf2456 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-model-override-200-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-model-override-200-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:107e9937d6640579e6b9823e609858be9060e7a6d2855bea1564b1f8585e3c60
-size 3880
+oid sha256:2da4bbf78414fd58ef84b03f421954741edae16df0a0fccc4ad4e99e752e70ff
+size 3882
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-model-override-420-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-model-override-420-chromium-linux.png
index 580f5fdf016..1acb9badaca 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-model-override-420-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-model-override-420-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5fc5cb33f9536414b23c63e0bb19259862f6a227603602802e8a41c15e898a47
-size 4110
+oid sha256:ca37ae191123410c4488e6c54a2f104c830c4adb98b2dc3f5a2ac344e4b64356
+size 4107
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-thinking-200-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-thinking-200-chromium-linux.png
index 8935f5c445c..1d05e973e9f 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-thinking-200-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-thinking-200-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:78c274e4ff22f272f83a48ac56d629a7523d21b4ae1574c18463e24966114650
-size 4161
+oid sha256:3a6043cb26f5fe15cf84106a8a63c0f04a96ca04b23b9e55c276cc5db8f8104a
+size 4159
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-thinking-420-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-thinking-420-chromium-linux.png
index 41f7c64f1cd..4a63d53efaf 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-thinking-420-chromium-linux.png
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/prompt-input/with-thinking-420-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5f1742a31626c747108fdf730dae8030106b771330584329c4292785fcb2381f
-size 4609
+oid sha256:22b2d117440cfcd773be53b8d20ac1ed5561f02c7b07f307efc54e722341e14b
+size 4606
diff --git a/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx
index 81475355146..cfecd549695 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx
@@ -601,3 +601,173 @@ export const TodoWriteCompleted: Story = {
)
},
}
+
+// ---------------------------------------------------------------------------
+// 12. Permission dock — edit tool with file patterns
+// ---------------------------------------------------------------------------
+
+const editPermission: PermissionRequest = {
+ id: "perm-edit-001",
+ sessionID: SESSION_ID,
+ toolName: "edit",
+ patterns: ["src/components/App.tsx", "src/utils/helpers.ts"],
+ args: { rules: ["src/components/App.tsx", "src/utils/helpers.ts"] },
+ tool: { messageID: ASST_MSG_ID, callID: "call-edit-001" },
+}
+
+export const PermissionDockEdit: Story = {
+ name: "Permission Dock — edit",
+ render: () => {
+ const perms = [editPermission]
+ const session = {
+ ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }),
+ messages: () => [{ id: "msg-001" }] as any[],
+ }
+ return (
+
+
+
+
+
+
+
+ )
+ },
+}
+
+// ---------------------------------------------------------------------------
+// 13. Permission dock — websearch tool
+// ---------------------------------------------------------------------------
+
+const websearchPermission: PermissionRequest = {
+ id: "perm-websearch-001",
+ sessionID: SESSION_ID,
+ toolName: "websearch",
+ patterns: ["*"],
+ args: { rules: ["*"] },
+ tool: { messageID: ASST_MSG_ID, callID: "call-websearch-001" },
+}
+
+export const PermissionDockWebsearch: Story = {
+ name: "Permission Dock — websearch",
+ render: () => {
+ const perms = [websearchPermission]
+ const session = {
+ ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }),
+ messages: () => [{ id: "msg-001" }] as any[],
+ }
+ return (
+
+
+
+
+
+
+
+ )
+ },
+}
+
+// ---------------------------------------------------------------------------
+// 14. Permission dock — external_directory tool
+// ---------------------------------------------------------------------------
+
+const externalDirPermission: PermissionRequest = {
+ id: "perm-extdir-001",
+ sessionID: SESSION_ID,
+ toolName: "external_directory",
+ patterns: ["/home/user/other-project/config.json"],
+ args: { rules: ["/home/user/other-project/config.json"] },
+ tool: { messageID: ASST_MSG_ID, callID: "call-extdir-001" },
+}
+
+export const PermissionDockExternalDir: Story = {
+ name: "Permission Dock — external directory",
+ render: () => {
+ const perms = [externalDirPermission]
+ const session = {
+ ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }),
+ messages: () => [{ id: "msg-001" }] as any[],
+ }
+ return (
+
+
+
+
+
+
+
+ )
+ },
+}
+
+// ---------------------------------------------------------------------------
+// 15. Permission dock — bash with many rules (5+ rules for overflow testing)
+// ---------------------------------------------------------------------------
+
+const bashManyRulesPermission: PermissionRequest = {
+ id: "perm-bash-many-001",
+ sessionID: SESSION_ID,
+ toolName: "bash",
+ patterns: ["npm install"],
+ args: {
+ command: "npm install",
+ rules: ["npm *", "npm install", "npm run *", "npm test", "npm run build", "npx *"],
+ },
+ tool: { messageID: ASST_MSG_ID, callID: "call-bash-many-001" },
+}
+
+export const PermissionDockBashManyRules: Story = {
+ name: "Permission Dock — bash many rules",
+ render: () => {
+ const perms = [bashManyRulesPermission]
+ const session = {
+ ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }),
+ messages: () => [{ id: "msg-001" }] as any[],
+ }
+ return (
+
+
+
+
+
+
+
+ )
+ },
+}
+
+// ---------------------------------------------------------------------------
+// 16. Permission dock — subagent (from child session)
+// ---------------------------------------------------------------------------
+
+const subagentPermission: PermissionRequest = {
+ id: "perm-subagent-001",
+ sessionID: "child-session-001",
+ toolName: "bash",
+ patterns: ["git status"],
+ args: { command: "git status", rules: ["git *", "git status"] },
+ tool: { messageID: ASST_MSG_ID, callID: "call-subagent-001" },
+}
+
+export const PermissionDockSubagent: Story = {
+ name: "Permission Dock — subagent",
+ render: () => {
+ const perms = [subagentPermission]
+ const session = {
+ ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }),
+ messages: () => [{ id: "msg-001" }] as any[],
+ // Override scopedPermissions to include child session permissions in the family
+ scopedPermissions: () => perms,
+ }
+ return (
+
+
+
+
+
+
+
+ )
+ },
+}