Skip to content

feat: add plugin sidebar contributions#16804

Open
vaur94 wants to merge 7 commits intoanomalyco:devfrom
vaur94:feat/plugin-sidebar-contributions
Open

feat: add plugin sidebar contributions#16804
vaur94 wants to merge 7 commits intoanomalyco:devfrom
vaur94:feat/plugin-sidebar-contributions

Conversation

@vaur94
Copy link
Contributor

@vaur94 vaur94 commented Mar 9, 2026

Issue for this PR

Related to #5971

Type of change

  • New feature
  • Bug fix
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds a small plugin sidebar contribution API and wires it through the web app.

Plugins can now contribute sidebar items via a new \"ui.sidebar\" hook, the server aggregates those items at GET /plugin/sidebar, the app renders them above Settings and Help, and the JS SDK exposes the new endpoint.

Also includes a minimal local semver type shim so the repository's pre-push bun typecheck passes in this environment.

How did you verify your code works?

  • Ran bun tsc --noEmit in packages/plugin, packages/opencode, packages/app, and packages/ui
  • Ran bun run ./packages/sdk/js/script/build.ts
  • Ran bun test test/plugin/sidebar.test.ts in packages/opencode
  • Ran full app e2e locally:
    • PLAYWRIGHT_BROWSERS_PATH=/home/ugur/.cache/ms-playwright bun --cwd packages/app test:e2e:local
    • result: 108 passed, 4 skipped
  • Re-ran targeted app e2e smoke tests after rebasing onto origin/dev:
    • bun --cwd packages/app test:e2e:local -- e2e/app/navigation.spec.ts
    • bun --cwd packages/app test:e2e:local -- e2e/app/session.spec.ts
    • bun --cwd packages/app test:e2e:local -- e2e/app/home.spec.ts
  • Ran bun typecheck from repo root successfully after adding the local semver declaration shims
  • Performed manual/browser verification for:
    • empty sidebar state
    • plugin-contributed CEO Settings item visibility and navigation
    • invalid icon fallback rendering

Screenshots / recordings

UI screenshots were captured locally during verification for empty state, visible plugin item state, and invalid icon fallback.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

vaur94 and others added 5 commits March 10, 2026 01:16
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Copilot AI review requested due to automatic review settings March 9, 2026 22:21
@vaur94 vaur94 requested a review from adamdotdevin as a code owner March 9, 2026 22:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a plugin-driven sidebar contribution surface area end-to-end (plugin hook → server aggregation endpoint → app rendering → JS SDK exposure), enabling plugins to surface quick-access sidebar actions.

Changes:

  • Introduces a new plugin hook ("ui.sidebar") and SidebarItem type in @opencode-ai/plugin.
  • Adds GET /plugin/sidebar on the server and exposes it via the generated JS SDK client (client.plugin.sidebar()).
  • Renders plugin-contributed sidebar buttons in the app sidebar rail, with icon-name fallback support in @opencode-ai/ui.

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/ui/src/components/icon.tsx Exports the icon registry and adds getIconName() for safe-ish icon fallback.
packages/sdk/js/src/v2/gen/types.gen.ts Adds generated API types for /plugin/sidebar.
packages/sdk/js/src/v2/gen/sdk.gen.ts Adds generated Plugin client and OpencodeClient.plugin accessor.
packages/script/src/semver.d.ts Adds a local semver typing shim for typecheck.
packages/plugin/src/index.ts Defines SidebarItem and adds the "ui.sidebar" hook to plugin hooks.
packages/plugin/src/example.ts Demonstrates contributing a sidebar item via "ui.sidebar".
packages/opencode/test/plugin/sidebar.test.ts Adds server-side tests for aggregation/sorting/dedup behavior.
packages/opencode/src/server/server.ts Mounts the new /plugin route group.
packages/opencode/src/server/routes/plugin.ts Implements GET /plugin/sidebar aggregation endpoint.
packages/opencode/src/semver.d.ts Adds a local semver typing shim for typecheck.
packages/app/src/pages/layout/sidebar-shell.tsx Renders plugin items as icon buttons in the sidebar rail.
packages/app/src/pages/layout.tsx Fetches plugin sidebar items and wires click behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@vaur94
Copy link
Contributor Author

vaur94 commented Mar 9, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 13 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +89 to +96
const safe = new Set(["http:", "https:", "mailto:"])

const external = (href: string) => {
if (!URL.canParse(href)) return
const url = new URL(href)
if (!safe.has(url.protocol)) return
return url.href
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URL.canParse is called unconditionally here. In some runtimes/browsers URL.canParse may be undefined (the codebase already guards for this in layout/deep-links.ts), which would throw at runtime. Consider checking typeof URL.canParse === "function" before calling it, or wrapping parsing in try/catch and returning undefined on failure.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +54
.then((output) => {
const map = new Map<string, SidebarItem>()
for (const item of output.items) map.set(item.id, item)
return {
items: [...map.values()].sort(sort),
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside the .then() block, the loop variable item (for (const item of output.items)) shadows the Zod schema constant item declared above. This is harmless but makes the route harder to read/maintain; consider renaming the loop variable (e.g., sidebarItem) or the schema constant to avoid shadowing.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants