## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI sidebar shows plugin contributions: plugin nav slots,
plugin launchers, and the plugin panel at the bottom
> - These outlets read `GET /plugins/ui-contributions` through React
Query
> - When the server is unavailable, the refetch fails. Each outlet then
replaces its content with a red "Plugin extensions unavailable: Failed
to fetch" or "Plugin launchers unavailable" box
> - React Query still has the last good data, so the error box removes
useful content and adds noise in a persistent part of the UI
> - This pull request adds an option to hide the outlet error, and the
sidebar uses it
> - The benefit is a quiet sidebar during a server outage. The sidebar
keeps the last loaded plugin items. Other pages still show the error
inline
## Linked Issues or Issue Description
No public issue exists. I searched open and closed issues and PRs for
related work and found none.
**What happened?**
When the server is unavailable, the sidebar shows red error boxes in
place of plugin items. The text is "Plugin extensions unavailable:
Failed to fetch" or "Plugin launchers unavailable: Failed to fetch".
**Expected behavior**
The sidebar does not show errors while the server is unavailable. It
keeps the last loaded content.
**Steps to reproduce**
1. Start Paperclip with at least one plugin that contributes a `sidebar`
slot, a `sidebar` launcher, or a `sidebarPanel` slot.
2. Open the board UI and let the sidebar load.
3. Stop the server.
4. Wait for the next refetch of `/plugins/ui-contributions`, or focus
the window.
5. See the red error boxes in the sidebar.
**Paperclip version or commit**
`master` at 0ac194450a
**Deployment mode**
Not deployment-specific. This is UI behavior.
## What Changed
- `PluginSlotOutlet` (`ui/src/plugins/slots.tsx`) and
`PluginLauncherOutlet` (`ui/src/plugins/launchers.tsx`) accept a new
`errorBehavior: "inline" | "hidden"` prop. The default is `"inline"`, so
current behavior does not change.
- With `"hidden"`, the outlet does not render the error box. It
continues to render the last loaded contributions.
- `Sidebar.tsx` and `Sidebar.production.tsx` pass
`errorBehavior="hidden"` to the three sidebar outlets.
- New test `ui/src/plugins/outlet-errors.test.tsx`. It checks that the
inline default still shows the error. It also checks that hidden mode
keeps the cached `sidebar` slot, `sidebarPanel` slot, and `sidebar`
launcher rendered after a failed refetch. A mutation check confirmed the
test fails if hidden mode drops the slots or shows the launcher error.
## Verification
- `cd ui && pnpm vitest run src/plugins/
src/components/Sidebar.test.tsx`: 55/55 tests pass.
- `cd ui && pnpm typecheck`: clean.
- `pnpm check:token-gates`: clean.
- Browser check on a local `local_trusted` instance with the
kitchen-sink example plugin installed (it contributes a `sidebar` slot,
a `sidebar` launcher, and a `sidebarPanel` slot). I loaded the board,
stopped the server, waited past the 30s stale time, focused the window,
and let the retries finish. I took the "before" screenshot with the
`master` versions of the four changed UI files, and the "after"
screenshot with this branch.
- Not run locally: the full `pnpm -r typecheck`, `pnpm test:run`, and
`pnpm build`. CI runs them.
**Screenshots (sidebar only):**
| Server running | Server stopped, before (`master`) | Server stopped,
after (this PR) |
| --- | --- | --- |
| <img
src="https://raw.githubusercontent.com/paperclipai/paperclip/pr-assets/sidebar-plugin-outlets-offline/server-running.png"
width="240" alt="Sidebar with plugin items loaded"> | <img
src="https://raw.githubusercontent.com/paperclipai/paperclip/pr-assets/sidebar-plugin-outlets-offline/before-server-stopped.png"
width="240" alt="Sidebar with three red plugin error boxes"> | <img
src="https://raw.githubusercontent.com/paperclipai/paperclip/pr-assets/sidebar-plugin-outlets-offline/after-server-stopped.png"
width="240" alt="Sidebar keeps Kitchen Sink item and panel, no errors">
|
Main-content plugin outlets, like the dashboard widget, still show the
inline error while the server is down. That is intentional, because this
PR changes only the sidebar.
## Risks
- Low risk. The new prop is opt-in, and the default is unchanged.
- If the first load fails in the sidebar, the outlet shows nothing. It
does not show an error. Users can still see the error on other plugin
surfaces.
- This change does not change request timing or retries. Background
refetches still run while the server is unavailable. Only the sidebar
display changes.
## Model Used
- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, run as an
agent in Claude Code with tool use (file edit, shell, test execution).
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@paperclipai/ui
Published static assets for the Paperclip board UI.
What gets published
The npm package contains the production build under dist/. It does not ship the UI source tree or workspace-only dependencies.
Storybook
Storybook config, stories, and fixtures live under ui/storybook/.
pnpm --filter @paperclipai/ui storybook
pnpm --filter @paperclipai/ui build-storybook
Typical use
Install the package, then serve or copy the built files from node_modules/@paperclipai/ui/dist.
Editor dependency identity
Keep the root and workspace overrides for @codemirror/state,
@codemirror/view, and @lezer/common aligned. CodeMirror requires shared
extension identity, while Lezer parsers and syntax highlighters require shared
NodeProp IDs. Multiple Lezer copies can crash code-block highlighting with
tags is not iterable. src/lib/codemirror-single-instance.test.ts checks the
installed dependency graph and highlights sample code through the editor's real
language dependencies. GitHub Actions owns regeneration of pnpm-lock.yaml.