feat: add plugin sidebar contributions#16804
Conversation
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>
There was a problem hiding this comment.
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") andSidebarItemtype in@opencode-ai/plugin. - Adds
GET /plugin/sidebaron 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>
|
@copilot open a new pull request to apply changes based on the comments in this thread |
There was a problem hiding this comment.
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.
| 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 | ||
| } |
There was a problem hiding this comment.
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.
| .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), | ||
| } |
There was a problem hiding this comment.
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.
Issue for this PR
Related to #5971
Type of change
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 atGET /plugin/sidebar, the app renders them above Settings and Help, and the JS SDK exposes the new endpoint.Also includes a minimal local
semvertype shim so the repository's pre-pushbun typecheckpasses in this environment.How did you verify your code works?
bun tsc --noEmitinpackages/plugin,packages/opencode,packages/app, andpackages/uibun run ./packages/sdk/js/script/build.tsbun test test/plugin/sidebar.test.tsinpackages/opencodePLAYWRIGHT_BROWSERS_PATH=/home/ugur/.cache/ms-playwright bun --cwd packages/app test:e2e:local108 passed, 4 skippedorigin/dev:bun --cwd packages/app test:e2e:local -- e2e/app/navigation.spec.tsbun --cwd packages/app test:e2e:local -- e2e/app/session.spec.tsbun --cwd packages/app test:e2e:local -- e2e/app/home.spec.tsbun typecheckfrom repo root successfully after adding the localsemverdeclaration shimsCEO Settingsitem visibility and navigationScreenshots / recordings
UI screenshots were captured locally during verification for empty state, visible plugin item state, and invalid icon fallback.
Checklist