Skip to content

feat: VS Code extension for in-editor plan review#229

Merged
backnotprop merged 3 commits intomainfrom
feat/vscode-extension
Mar 6, 2026
Merged

feat: VS Code extension for in-editor plan review#229
backnotprop merged 3 commits intomainfrom
feat/vscode-extension

Conversation

@backnotprop
Copy link
Owner

Summary

Integrates the community VS Code extension from 7tg/plannotator-vscode (now archived) into the monorepo at apps/vscode-extension/. When Claude Code runs in VS Code's integrated terminal, Plannotator opens inside a VS Code tab instead of an external browser — no context switching.

  • Works for all three modes: plan review, code review, and annotate
  • Cookie persistence via reverse proxy (VS Code webview iframes block cookies)
  • Auto-close panel on approve/deny
  • Opt-out via plannotatorWebview.injectBrowser setting
  • Security audited: zero runtime deps, all servers localhost-only, no data exfiltration

Commit 1 is authored by @7tg (Barbaros Gören) to preserve contributor attribution.

Commit 2 adapts for the monorepo: updates publisher to backnotprop, replaces node -e dependency in router script with curl --data-urlencode, simplifies panel-manager tests, adds root build scripts.

Closes #91

Test plan

  • bun install succeeds with new workspace member
  • bun run build:vscode produces dist/extension.cjs (11.5kb)
  • bun test --cwd apps/vscode-extension — 21 tests pass
  • bun run package:vscode produces .vsix (16KB)
  • Install .vsix in VS Code, run Claude Code in integrated terminal, trigger plan review — opens in VS Code tab

🤖 Generated with Claude Code

7tg and others added 2 commits March 5, 2026 09:44
…ode)

Opens Plannotator plan reviews, code reviews, and annotations inside VS Code
tabs instead of an external browser. Intercepts PLANNOTATOR_BROWSER via env
var injection in integrated terminals, with cookie persistence and auto-close.

Original repository: https://github.com/7tg/plannotator-vscode
Closes #91

Co-Authored-By: Barbaros Gören <tayyipgoren@gmail.com>
- Update package.json: publisher → backnotprop, repo → plannotator, add private
- Replace node -e URL encoding with curl --data-urlencode in router script
- Simplify panel-manager tests to keep only behavioral tests
- Add dev:vscode, build:vscode, package:vscode scripts to root
- Add *.vsix to .gitignore
- Update bun.lock with new workspace member

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@7tg
Copy link
Contributor

7tg commented Mar 5, 2026

Thank you

@backnotprop
Copy link
Owner Author

Code review

Found 3 issues:

  1. Extension broken on macOS: open -a cannot execute a shell script path. The extension sets PLANNOTATOR_BROWSER to the absolute path of bin/open-in-vscode (a shell script). But packages/server/browser.ts on macOS runs open -a ${plannotatorBrowser} ${url}, which expects an application name or .app bundle — not a script path. This will silently fail, and plan reviews will never open in the VS Code webview on macOS.

const routerPath = path.join(binDir, "open-in-vscode");
context.environmentVariableCollection.replace(
"PLANNOTATOR_BROWSER",
routerPath,
);

The server-side code that will fail:

*/
export async function openBrowser(url: string): Promise<boolean> {
try {
const browser = process.env.PLANNOTATOR_BROWSER || process.env.BROWSER;
const platform = process.platform;
const wsl = await isWSL();
if (browser) {
const plannotatorBrowser = process.env.PLANNOTATOR_BROWSER;

  1. CLAUDE.md not updated with new app. The CLAUDE.md documents the project structure listing every apps/ directory and all root build/dev scripts. apps/vscode-extension/ and its three new scripts (dev:vscode, build:vscode, package:vscode) are not reflected in the Project Structure, Development, or Build sections.

plannotator/CLAUDE.md

Lines 5 to 59 in b70f709

## Project Structure
```
plannotator/
├── apps/
│ ├── hook/ # Claude Code plugin
│ │ ├── .claude-plugin/plugin.json
│ │ ├── commands/ # Slash commands (plannotator-review.md, plannotator-annotate.md)
│ │ ├── hooks/hooks.json # PermissionRequest hook config
│ │ ├── server/index.ts # Entry point (plan + review + annotate subcommands)
│ │ └── dist/ # Built single-file apps (index.html, review.html)
│ ├── opencode-plugin/ # OpenCode plugin
│ │ ├── commands/ # Slash commands (plannotator-review.md, plannotator-annotate.md)
│ │ ├── index.ts # Plugin entry with submit_plan tool + review/annotate event handlers
│ │ ├── plannotator.html # Built plan review app
│ │ └── review-editor.html # Built code review app
│ ├── marketing/ # Marketing site, docs, and blog (plannotator.ai)
│ │ └── astro.config.mjs # Astro 5 static site with content collections
│ ├── paste-service/ # Paste service for short URL sharing
│ │ ├── core/ # Platform-agnostic logic (handler, storage interface, cors)
│ │ ├── stores/ # Storage backends (fs, kv, s3)
│ │ └── targets/ # Deployment entries (bun.ts, cloudflare.ts)
│ └── review/ # Standalone review server (for development)
│ ├── index.html
│ ├── index.tsx
│ └── vite.config.ts
├── packages/
│ ├── server/ # Shared server implementation
│ │ ├── index.ts # startPlannotatorServer(), handleServerReady()
│ │ ├── review.ts # startReviewServer(), handleReviewServerReady()
│ │ ├── annotate.ts # startAnnotateServer(), handleAnnotateServerReady()
│ │ ├── storage.ts # Plan saving to disk (getPlanDir, savePlan, etc.)
│ │ ├── share-url.ts # Server-side share URL generation for remote sessions
│ │ ├── remote.ts # isRemoteSession(), getServerPort()
│ │ ├── browser.ts # openBrowser()
│ │ ├── draft.ts # Annotation draft persistence (~/.plannotator/drafts/)
│ │ ├── integrations.ts # Obsidian, Bear integrations
│ │ ├── ide.ts # VS Code diff integration (openEditorDiff)
│ │ └── project.ts # Project name detection for tags
│ ├── ui/ # Shared React components
│ │ ├── components/ # Viewer, Toolbar, Settings, etc.
│ │ │ ├── plan-diff/ # PlanDiffBadge, PlanDiffViewer, clean/raw diff views
│ │ │ └── sidebar/ # SidebarContainer, SidebarTabs, VersionBrowser
│ │ ├── utils/ # parser.ts, sharing.ts, storage.ts, planSave.ts, agentSwitch.ts, planDiffEngine.ts
│ │ ├── hooks/ # useSharing.ts, usePlanDiff.ts, useSidebar.ts, useLinkedDoc.ts, useAnnotationDraft.ts, useCodeAnnotationDraft.ts
│ │ └── types.ts
│ ├── editor/ # Plan review App.tsx
│ └── review-editor/ # Code review UI
│ ├── App.tsx # Main review app
│ ├── components/ # DiffViewer, FileTree, ReviewPanel
│ ├── demoData.ts # Demo diff for standalone mode
│ └── index.css # Review-specific styles
├── .claude-plugin/marketplace.json # For marketplace install
└── legacy/ # Old pre-monorepo code (reference only)
```

  1. Command palette title says "Simple Browser" but extension uses a custom WebviewPanel. The CHANGELOG.md documents this was intentionally replaced at v0.4.0: "Custom WebviewPanel with embedded iframe (replaces Simple Browser)." The package.json command title was not updated to match.

"commands": [
{
"command": "plannotator-webview.openUrl",
"title": "Plannotator: Open URL in Simple Browser"
}
],

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@backnotprop
Copy link
Owner Author

I'm addressing the issues.

- Fix macOS browser: detect script paths in PLANNOTATOR_BROWSER and
  invoke directly instead of via 'open -a' (which only accepts app
  names/.app bundles)
- Update CLAUDE.md: add vscode-extension to project structure,
  development, and build sections
- Fix command title: 'Simple Browser' -> 'Open URL in Editor'
- Add 1s debounce to panel opens: Claude Code fires ExitPlanMode hook
  twice, spawning two plannotator processes on different ports that
  both hit the IPC server simultaneously
- Add .vscode/launch.json + tasks.json for F5 debugging
@backnotprop
Copy link
Owner Author

Code review fixes + duplicate panel issue

Pushed commit 626f356 addressing the 3 code review issues plus a new bug found during testing.

Code review fixes:

  1. macOS browseropen -a can't execute shell script paths. Now detects script/binary paths (contains /, doesn't end .app) and invokes directly.
  2. CLAUDE.md — Added apps/vscode-extension/ to project structure, dev, and build sections.
  3. Command title — Changed "Simple Browser" to "Open URL in Editor".

Duplicate panel bug:
During testing, Claude Code fires the ExitPlanMode hook twice, spawning two independent plannotator processes on different ports. Both hit the extension's IPC server at the same millisecond:

[open] received url: http://localhost:51511
[open] received url: http://localhost:51510

This only happens via Claude Code — the test script (tests/manual/local/test-hook.sh) opens a single panel correctly. Added a 1-second debounce in openInPanel to skip duplicate opens. This is a Claude Code hook runner issue, not an extension bug, but the debounce makes the extension resilient to it.

Also added .vscode/launch.json + tasks.json so the extension can be debugged with F5 (Extension Development Host).

@backnotprop
Copy link
Owner Author

Code review

No issues found. Checked for bugs, CLAUDE.md compliance, git history context, previous PR comments, and code comment consistency. The previous review's 3 issues (macOS open -a with script paths, missing CLAUDE.md docs, stale command title) have all been addressed in the latest commits. The duplicate panel debounce fix is also sound.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@backnotprop backnotprop merged commit 1701424 into main Mar 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal for IDE-Based Control and Interactive Code Revision

2 participants