<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators create tasks in a dialog that includes the assignee and
project fields.
> - Mobile browsers reduce and offset the visual viewport when the
on-screen keyboard opens.
> - The dialog used layout viewport units, so its upper fields could
move off-screen while the user typed.
> - This pull request makes the dialog follow the live visual viewport
and keeps the focused editor visible.
> - The benefit is that operators can see the task context and the field
they edit on mobile devices.
## Linked Issues or Issue Description
**What happened?**
On mobile browsers, opening the keyboard in the new-task dialog could
move the assignee and project fields above the visible screen. The
active editor could also become difficult to see.
**Expected behavior**
The full dialog must stay inside the visible browser area. The active
editor and task controls must remain reachable while the on-screen
keyboard is open.
**Steps to reproduce**
1. Open Paperclip on a mobile browser.
2. Open the new-task dialog.
3. Focus the title or description editor to open the on-screen keyboard.
4. Observe that the upper fields can move outside the visible viewport.
**Paperclip version or commit**
Reproduced before commit `838cdbb325` on `master`.
**Deployment mode**
Local dev (`pnpm dev`) in a mobile browser viewport.
## What Changed
- Read `window.visualViewport` while the dialog is open.
- Apply token-based dialog geometry when the visual viewport is
constrained.
- Keep the focused editor visible after viewport resize and scroll
events.
- Add unit coverage for visual viewport updates and focus scrolling.
- Add Playwright coverage for mobile, tablet, desktop keyboard, and
unconstrained desktop layouts.
## Verification
- `pnpm exec vitest run ui/src/components/NewIssueDialog.test.tsx` — 27
tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — passed with all gates clean.
- `pnpm --filter @paperclipai/ui build-storybook` — passed.
- `pnpm exec playwright test
tests/storybook-visual/new-issue-dialog-viewport.spec.ts --config
tests/storybook-visual/playwright.config.ts` — 4 tests passed.
## Risks
- Low risk. The custom geometry only activates when
`visualViewport.height` is less than `window.innerHeight`.
- Browsers without the Visual Viewport API keep the existing dialog
primitive behavior.
- The browser test checks hit targets and visible bounds at mobile,
tablet, and desktop widths.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5. The session used reasoning, repository tools,
shell execution, and browser automation. The service did not expose the
context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The UI dev server proxies `/api` to the backend and injects
`x-forwarded-host`; a unit test asserts that injection with a sample
Host header
> - The sample Host header is a contributor's real machine and tailnet
hostname, committed to the public repository
> - Real personal hostnames do not belong in a public codebase, and this
one also contains the contributor's OS username, so
`scripts/check-forbidden-tokens.mjs` (which forbids the local username)
blocks `npm` publishing from that contributor's machine
> - This pull request replaces the fixture with a fictional
tailnet-style hostname
> - The benefit is no personal identifiers in the test fixtures and a
passing forbidden-token check for every contributor
## Linked Issues or Issue Description
No existing issue. Description follows the bug template:
**What happened?**
`ui/src/lib/vite-api-proxy.test.ts` uses a real contributor dev-machine
hostname as its `Host` header fixture. `node
scripts/check-forbidden-tokens.mjs` fails on that contributor's machine
because the hostname contains their OS username, blocking the publish
flow. Introduced in #10718.
**Expected behavior**
Test fixtures use fictional hostnames. The forbidden-token check passes
on every contributor machine.
**Steps to reproduce**
1. On a machine whose OS username appears in the fixture hostname, run
`node scripts/check-forbidden-tokens.mjs`.
2. The check reports the two lines in
`ui/src/lib/vite-api-proxy.test.ts` and blocks with exit code 1.
## What Changed
- `ui/src/lib/vite-api-proxy.test.ts`: the `Host` fixture is now
`dev-box.tail1234.ts.net:3101` (fictional). The test only asserts that
whatever host arrives is injected as `x-forwarded-host`, so the value is
arbitrary.
## Verification
- `pnpm vitest run src/lib/vite-api-proxy.test.ts` in `ui/` — 5 tests
pass.
- `node scripts/check-forbidden-tokens.mjs` — "No forbidden tokens
found" on the previously affected machine.
- Note: git history retains the old value; this removes it from the
current tree only.
## Risks
None. A test fixture string with no behavioral coupling.
## Model Used
- Claude (Anthropic), Claude Fable 5 (`claude-fable-5`), via Claude Code
CLI with tool use.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The web UI holds live websocket connections for run events,
coordinates cross-tab polling through a leader-election store, and
renders app chrome (sidebar, providers) around a routed outlet
> - When the backend is still cold-starting (managed hosting wake,
server restart, reverse proxy up before the app), the event websockets
refuse connections and the first SPA load mounts against a dead backend
> - In that state the mount cascade can exceed React's nested update
limit (minified error #185); the crash originates in shell hooks outside
the routed error boundary, so React unmounts the entire root to a blank
page, and the dead page keeps retrying the websocket on a flat 1.5s
timer until the user hard-refreshes
> - This pull request removes the wasted nested commits from the
shared-polling subscription path, adds exponential backoff to the
transcript websocket reconnect, and adds a last-resort app-shell error
boundary
> - The benefit is that a cold or briefly unreachable backend degrades
to a recoverable state instead of a blank page that hammers the server
## Linked Issues or Issue Description
No existing issue. Description follows the bug template:
**What happened?**
On the first load against a backend that was still starting, the app
showed its loading animation and then a blank page. The console showed
repeated `WebSocket connection to 'wss://…/api/companies/<id>/events/ws'
failed` lines and `Uncaught Error: Minified React error #185` with a
stack through the shared-polling coordinator's `subscribe`. The
websocket retries continued indefinitely on the dead page. A manual
refresh fixed it.
**Expected behavior**
A backend that is briefly unreachable degrades gracefully: websocket
reconnects back off, the UI keeps rendering from cache, and even a
worst-case crash shows a reload prompt instead of a blank page.
**Steps to reproduce**
1. Serve the UI while the backend API is still starting (websocket
upgrades and API calls refused).
2. Load any company page with several shared-polling consumers mounted
(dashboard with sidebar).
3. Observe repeated websocket failures; on affected loads the page goes
blank with React error #185.
## What Changed
- `ui/src/hooks/useSharedPolling.ts`: coordinator snapshot notifications
now keep the previous state object when leadership did not change, so
React bails out instead of scheduling a nested re-render. `subscribe`
invokes its listener synchronously from inside the mount effect with a
fresh object each time; before this change every mount and notify burned
nested-update budget even with no value change — the crash frame in the
field report was exactly this `subscribe → setState` call.
- `ui/src/components/transcript/useLiveRunTranscripts.ts`: the live
event websocket reconnect backs off exponentially (1.5s → 15s cap, reset
on successful open), mirroring `LiveUpdatesProvider`, instead of a flat
1.5s retry.
- `ui/src/components/AppErrorBoundary.tsx` (+ wiring in
`ui/src/main.tsx`): a dependency-free boundary above the router and
providers. `RouteErrorBoundary` only guards the routed `<Outlet />`; a
crash in the shell around it had no boundary, so React unmounted the
root to a blank page. The boundary renders a reload prompt with the
error message.
- Tests: `useSharedPollingSnapshot.test.tsx` (mount costs no extra
commit — fails against the previous code; a real leadership change
re-renders exactly once and ticks stay quiet), a backoff test in
`useLiveRunTranscripts.test.tsx` (delays grow 1.5s → 3s → 6s and reset
after a successful open), and `AppErrorBoundary.test.tsx` (render throw,
effect throw, healthy pass-through).
## Verification
- `pnpm vitest run` in `ui/` over the touched suites (shared polling,
cross-tab poll, transcripts, boundary): 34 tests pass.
- `pnpm typecheck` in `ui/` — clean.
- The snapshot regression test was verified to fail against the
pre-change hook (extra commit per mount).
- Not reproduced end-to-end: the exact 50-update cascade from the field
crash needs a live cold backend; the change removes the identified
per-mount/per-notify nested commits at the reported crash frame, bounds
the reconnect load, and guarantees the shell can no longer blank the
page.
## Risks
Low risk. The snapshot change only suppresses re-renders whose state is
value-identical; leadership changes propagate exactly as before. The
backoff only lengthens retry delays after consecutive failures and
resets on success. The new boundary renders children untouched unless an
error reaches it; behavior on healthy loads is unchanged. Self-hosted
deployments see the same code paths — the cold-backend window simply
rarely occurs there.
## Model Used
- Claude (Anthropic), Claude Fable 5 (`claude-fable-5`), via Claude Code
CLI with extended thinking and tool use (code search, edit, test
execution; diagnosis included mapping the production minified stack to
source).
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The web UI is a PWA-capable SPA; `ui/index.html` links
`/site.webmanifest` so browsers can read app metadata
> - Browsers fetch `<link rel="manifest">` in "omit credentials" mode
unless the link opts in with `crossorigin="use-credentials"`
> - Self-hosted this is harmless, but when Paperclip runs behind an
authenticating reverse proxy (a managed hosting front door), the
cookie-less manifest request is rejected with 401 on every page load and
logs a console error pair on each navigation
> - This pull request adds `crossorigin="use-credentials"` to the
manifest link so the request carries the same session cookies as every
other same-origin asset request
> - The benefit is a clean console and a servable manifest in proxied
deployments, with self-hosted behavior unchanged
## Linked Issues or Issue Description
No existing issue. Description follows the bug template:
**What happened?**
On every page load behind an authenticating reverse proxy, the browser
logs `Failed to load resource: the server responded with a status of
401` for `/site.webmanifest`, plus `Manifest fetch from … failed, code
401`. The proxy rejects the request because the browser sends the
manifest fetch without cookies.
**Expected behavior**
The manifest request carries the same session credentials as every other
same-origin asset request, so the proxy can authenticate and serve it.
No console errors.
**Steps to reproduce**
1. Serve Paperclip behind a reverse proxy that requires a session cookie
for all app routes.
2. Sign in and load any page.
3. Open the browser console: the manifest fetch fails with 401 while all
other assets load.
## What Changed
- `ui/index.html`: the manifest link now carries
`crossorigin="use-credentials"`.
- `ui/src/lib/pwa-install-mode.test.ts`: a regression test asserts the
attribute stays on the link.
## Verification
- `pnpm vitest run src/lib/pwa-install-mode.test.ts` in `ui/` — 2 tests
pass.
- Manual check of the rendered link tag in `ui/index.html`.
## Risks
Low risk. The manifest is same-origin, so `use-credentials` only
switches the fetch from "omit" to the include behavior all other
same-origin requests already have. Self-hosted deployments see no
change. Cross-origin manifest hosting is not used in this project.
## Model Used
- Claude (Anthropic), Claude Fable 5 (`claude-fable-5`), via Claude Code
CLI with extended thinking and tool use (code search, edit, 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Environments give each agent run an execution target, and sandbox
providers (Daytona, E2B, Novita, exe.dev) run as plugin workers
> - A managed deployment provisions one platform-managed sandbox row
with no credential in config; the provider is documented to fall back to
its process env var (for example `DAYTONA_API_KEY`)
> - Plugin workers spawn with a scrubbed environment, so that fallback
never sees the host env var — probe and lease acquisition fail with
"require an API key in config or DAYTONA_API_KEY" even when the
deployment sets the var
> - Separately, the managed-sandbox-only mode hides local rows from
every list, but the instance Default picker renders a hardcoded
synthetic "Local" option that no filter touches
> - This pull request forwards each bundled provider's documented
credential env var to its own plugin worker, and gates the synthetic
Local option on the flag
> - The benefit is that the documented host-env credential fallback
works for plugin-backed providers, and managed-sandbox-only instances no
longer offer Local anywhere
## Linked Issues or Issue Description
**Subsystem affected**
Plugin worker environment construction
(`server/src/services/plugin-loader.ts`) and the environments UI
(instance Default picker, agent form inherited-environment label).
**Problem or motivation**
Two follow-ups to the managed-sandbox-only mode (#11200), both found on
a live managed deployment:
1. The deployment sets `DAYTONA_API_KEY` as a server env var and the
managed sandbox row omits `config.apiKey` by contract. "Test Connection"
fails with `Sandbox environment probe failed for provider "daytona".
Daytona sandbox environments require an API key in config or
DAYTONA_API_KEY.` A real agent run fails the same way at lease
acquisition. The cause: sandbox providers run as plugin workers, and
`buildPluginWorkerEnv` passes only model-provider keys and in-cluster
Kubernetes vars. The provider's own documented credential env var never
reaches the worker, so the in-plugin `process.env` fallback reads
nothing. The self-hosted path has the same gap: the Daytona plugin
README documents `DAYTONA_API_KEY` as a host-level fallback, and it does
not work today.
2. With `enableManagedSandboxOnly` on, the instance Default environment
picker still shows "Local". The server filters local *rows* out of the
list, and the client filter mirrors that for cached lists, but this
option is a hardcoded `<option value="">Local</option>` — not a list row
— so no filter removes it. Selecting it writes a null default, which run
selection then rejects fail-closed.
**Proposed solution**
Forward each bundled sandbox provider's documented credential env var
into its plugin worker, keyed by the manifest's declared
`environmentDrivers[].driverKey` so a worker only receives its own
provider's credential (daytona → `DAYTONA_API_KEY`, e2b → `E2B_API_KEY`,
exe-dev → `EXE_API_KEY`, novita → `NOVITA_API_KEY`). Keep the existing
gate: only plugins that declare `environment.drivers.register` receive
any passthrough. In the UI, render the synthetic Local option only when
managed-sandbox-only is off; under the flag show a disabled "Select
environment" placeholder only while no default is stamped yet, and stop
the agent form's inherited label from reading "Local".
**Alternatives considered**
Adding `DAYTONA_API_KEY` to the existing `ADAPTER_ENV_PASSTHROUGH` list
was rejected: that list goes to every environment-driver plugin, so each
provider would receive every other provider's credential. A manifest
schema field for declared credential env vars was rejected as heavier
than needed: the bundled providers are known, and the mapping lives next
to the two existing passthrough lists.
## What Changed
- `server/src/services/plugin-loader.ts`: new
`SANDBOX_PROVIDER_CREDENTIAL_ENV_PASSTHROUGH` map (driverKey →
documented credential env vars). `buildPluginWorkerEnv` reads the
manifest's `environmentDrivers` and forwards only the matching vars,
after the existing `environment.drivers.register` gate. Blank values
stay excluded.
- `server/src/__tests__/plugin-database.test.ts`: the daytona worker
receives `DAYTONA_API_KEY` and not another provider's key; a plugin
whose drivers have no mapping (kubernetes) receives no credential var.
- `ui/src/pages/CompanyEnvironments.tsx`: the Default picker's synthetic
Local option renders only when managed-sandbox-only is off. Under the
flag, a disabled "Select environment" placeholder renders only while the
default is unset.
- `ui/src/pages/CompanyEnvironments.test.tsx`: the Local option is
present by default and absent under the flag; saved non-local
environments stay selectable.
- `ui/src/components/AgentConfigForm.tsx`: the inherited-environment
label falls back to "Managed sandbox" instead of "Local" under the flag.
## Verification
- `server`: `npx vitest run src/__tests__/plugin-database.test.ts -t
buildPluginWorkerEnv` — 5 passed (3 existing, 2 new).
- `ui`: `npx vitest run src/pages/CompanyEnvironments.test.tsx` — 22
passed (2 new); `npx vitest run
src/components/AgentConfigForm.render.test.tsx` — 10 passed.
- `tsc --noEmit` clean in `server` and `ui`.
- Live managed deployment: confirmed the tenant service env carries
`DAYTONA_API_KEY` while the probe fails with the exact message above,
which pins the root cause to the worker env, not delivery.
## Risks
- The worker env grows by exactly one var per matching bundled provider,
only when the deployment sets it and only for plugins that declare a
matching environment driver. Plugins without a mapping see no change.
- Self-hosted behavioral shift is the fix itself: a host-level
`DAYTONA_API_KEY` (or E2B/EXE/NOVITA equivalent) now reaches the
provider as its README documents. Deployments that set the var but
expected it to stay inert had no working configuration to preserve — the
provider errored on every keyless probe and run.
- UI change is inert unless `enableManagedSandboxOnly` is on (default
false everywhere).
## Model Used
Claude Fable 5 (`claude-fable-5`) via Claude Code — extended thinking,
tool use, parallel read-only subagents for the two root-cause traces.
## 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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents at
work
> - Paperclip stores an `issuePrefix` on each company (e.g. "OPS") used
for issue identifiers (`OPS-1`) and company-prefixed routes
(`/OPS/dashboard`)
> - The create-dialog badges in NewIssueDialog, NewProjectDialog, and
NewGoalDialog were derived from the company display name using
`company.name.slice(0, 3).toUpperCase()` — so "Acme Labs" showed "ACM"
> - This is misleading because the badge visually represents the issue
prefix, but actually shows an unrelated 3-letter slice of the display
name
> - When a company has `issuePrefix = "OPS"` but `name = "Acme Labs"`,
the badge showed "ACM" while issues use "OPS-1"
> - This pull request replaces `name.slice(0, 3).toUpperCase()` with
`company.issuePrefix` in all three dialog badge components
> - The benefit is that the badge now matches the actual prefix used for
issues and routes, eliminating confusion
## Linked Issues or Issue Description
Fixes: #8501
## What Changed
- `ui/src/components/NewIssueDialog.tsx` (line ~1339): Replaced
`company.name.slice(0, 3).toUpperCase()` with `company.issuePrefix` in
the selected-company header badge
- `ui/src/components/NewIssueDialog.tsx`: Replaced
`company.name.slice(0, 3).toUpperCase()` with `company.issuePrefix` in
the company picker list badge
- `ui/src/components/NewProjectDialog.tsx`: Replaced
`selectedCompany.name.slice(0, 3).toUpperCase()` with
`selectedCompany.issuePrefix` in the selected-company header badge
- `ui/src/components/NewGoalDialog.tsx`: Replaced
`selectedCompany.name.slice(0, 3).toUpperCase()` with
`selectedCompany.issuePrefix` in the selected-company header badge
## Verification
1. Create or configure a company whose `issuePrefix` differs from the
first 3 letters of its display name (e.g. name = "Acme Labs",
issuePrefix = "OPS")
2. Open the New Task dialog — the selected-company header badge should
show "OPS", not "ACM"
3. Open the company picker dropdown inside the New Task dialog — each
company list badge should show the actual `issuePrefix`
4. Open the New Project dialog — the selected-company header badge
should show "OPS"
5. Open the New Goal dialog — the selected-company header badge should
show "OPS"
6. Verify that companies whose prefix matches the first 3 letters (e.g.
name="Ops Team", prefix="OPS") still display correctly
**Before/After:**
- Before: Company "Acme Labs" with `issuePrefix = "OPS"` showed badge
"ACM"
- After: Same company shows badge "OPS"
(Screenshots require running the UI locally against a test instance with
the relevant company configuration.)
## Risks
Low risk — this is a purely visual change to 3 React component badge
labels. No API changes, no schema changes, no behavioral changes to
issue creation or routing. The `issuePrefix` field is already loaded on
the company objects used by these components.
## Model Used
- **Provider:** OpenCode
- **Model:** MiMo v2.5 Free
- **Reasoning:** N/A (standard code generation)
## 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 searched GitHub for duplicate or related PRs and found none
targeting the same badge code
- [x] I have linked the existing issue with Fixes: #8501
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change (`fix/issue-8501`)
- [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
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The previous pull request added server-side chunked resumable import
transfers; without clients, large imports still ride the single fragile
upload
> - The Import page and the CLI need to slice large packages, upload
parts with retry and progress, resume after interruptions, and preview
before applying
> - Preview is the missing server piece: the browser flow is
preview-then-import, so a completed spool must be previewable without
re-uploading
> - This pull request adds the transfer preview endpoint, switches the
Import page to the chunked path for zips over 48 MB, and teaches the CLI
the same for oversized local imports
> - The benefit is that large imports get progress, per-part retry, and
resume in both clients, while small imports keep the exact single-shot
path they have today
## Linked Issues or Issue Description
**What happened?**
With only the server transfer routes in place, users still upload large
company packages as one request from the Import page and the CLI: no
progress indication, no retry below the whole file, and no resume after
a dropped connection or refresh. The preview-then-import flow also
cannot run against an uploaded transfer, forcing a second full upload.
**Expected behavior**
A large package uploads once as verified parts with visible progress;
preview and import both run against the uploaded spool; an interrupted
upload resumes with only the missing parts re-sent; packages at or below
48 MB behave exactly as before.
**Steps to reproduce**
1. Select a 500 MB zip on the Import page over an unreliable connection.
2. Watch the single upload fail near the end and restart from zero,
twice — once for preview, once for import.
3. Same story headless via the CLI.
## What Changed
- Server: `POST /import/transfers/:id/preview` runs the existing preview
logic against the completed spool (shared assembly + whole-file
verification helper with apply); preview neither completes the run nor
deletes the spool, so the subsequent apply reuses it. Missing parts
respond with the missing list.
- UI: zips over 48 MB take the chunked path in both preview and import —
the file is sliced into 32 MB parts hashed with WebCrypto (single
ArrayBuffer, no second copy), the transfer is created or resumed (the
create response's missing-parts list drives what uploads), parts upload
sequentially with three attempts each and visible progress, then
transfer preview/apply replace the multipart calls. The existing preview
pane, collision handling, adapter overrides, and async job polling are
unchanged; ≤ 48 MB keeps the single-shot path.
- CLI: oversized local `.zip` or folder imports zip/slice/hash with node
crypto, upload with resume and per-part retry and progress lines, and
use transfer preview/apply. Small packages keep the inline path
byte-identical.
- Failure honesty: adapters/API errors fail open to existing behavior; a
part failing all attempts surfaces a durable error panel with resume
intact.
## Verification
- Server: preview-then-apply on one spool (run stays open, spool intact,
then apply completes), preview with missing parts rejected — added to
the transfer route suite (embedded Postgres).
- UI suite: large file takes the chunked path (manifest shape, part
uploads, progress, apply on a resumed transfer, single-shot endpoints
never called), small file stays single-shot, part failure after three
attempts surfaces the error panel without running preview, resume
re-uploads only the missing part.
- CLI: manifest slicing/hashing, threshold behavior for zip and folder
sources, folder-zip round-trip through the real zip reader, upload
resume/retry/exhaustion/already-completed, full-command chunked and
small-zip inline flows.
- Server, ui, cli typechecks clean. Exact counts in the PR checks.
## Risks
- The 48 MB threshold only routes between two verified paths; behavior
below it is untouched.
- Chunked CLI imports use the board-scoped transfer routes, so oversized
CLI imports need board credentials (agent tokens keep the agent-safe
small-file path). No privilege change — board actors already had the
generic routes — but the two size regimes differ semantically; called
out for review.
- A CLI dry-run over the threshold uploads parts before previewing; the
spool persists (24 h sweep) and a later apply resumes without re-upload
— inherent to preview-against-spool.
Stacked on #11223 — merge that first; this PR then shows only the
preview endpoint and client changes.
## Model Used
- Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended
thinking and tool use (multi-agent implementation with independent
verification).
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The issue detail page is a core operator surface where perceived
latency directly affects task navigation
> - Performance work needs repeatable evidence so later optimizations
can be compared against the same scenarios
> - The page did not expose stable user-timing marks for its header or
first useful content
> - There was also no isolated seeded browser rig that measured warm
navigation, cold deep links, waterfalls, or server time
> - This pull request adds the instrumentation and a one-command
Playwright baseline harness
> - The benefit is that issue-page performance changes can be validated
with reproducible median measurements instead of anecdotes
## Linked Issues or Issue Description
**Subsystem affected**
Cross-cutting: `ui/`, `server/`, and browser performance tooling.
**Problem or motivation**
The issue detail page performs a large client bootstrap and request
fan-out, but the repository lacks stable user-timing boundaries and a
repeatable benchmark. That makes performance changes difficult to
compare and allows regressions to be judged from anecdotes instead of
consistent evidence.
**Proposed solution**
Add stable header/content paint measures, development/QA-only lifecycle
vital reporting, aggregate server timing for the issue endpoint, and a
seeded Playwright command that runs warm/cold scenarios under throttled
and unthrottled profiles with N≥5 median reporting.
**Alternatives considered**
Ad hoc DevTools recordings were rejected because they are not repeatable
or reviewable. Production telemetry was rejected because this baseline
should not change production data collection. A unit-only harness was
rejected because it cannot capture browser bootstrap, rendering, and
network waterfall costs.
**Roadmap alignment**
The roadmap calls for agent performance to be measurable over time. This
change applies that evidence-first principle to a core operator page and
does not duplicate a listed roadmap deliverable.
**Additional context**
The generated report includes warm and cold medians, TTFB/FCP/LCP where
applicable, request and byte totals before first useful content,
JavaScript bytes, and issue endpoint server timing.
## What Changed
- Added `issue-detail:navigate→header-paint` and
`issue-detail:navigate→content-paint` user-timing measures to the issue
detail page.
- Added development/QA-only TTFB, LCP, and INP console reporting without
production telemetry delivery.
- Added `Server-Timing` for `GET /api/issues/:id`.
- Added `pnpm exec playwright test --config
tests/perf/issue-detail/playwright.config.ts`, which seeds an isolated
instance and runs N≥5 warm/cold samples under unthrottled and Fast 4G/4x
CPU profiles.
- Added Markdown, raw JSON, and Chrome-trace outputs with median
baseline tables and waterfall data.
## Verification
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm check:token-gates`
- `npx playwright test --config
tests/perf/issue-detail/playwright.config.ts --list`
- `pnpm exec playwright test --config
tests/perf/issue-detail/playwright.config.ts` — passed 20 samples in 9.4
minutes (5 runs × 2 scenarios × 2 profiles) for the baseline;
post-review integrity reruns also exercised the corrected paths, while
this shared runner intermittently killed Chromium processes, so the rig
now performs one bounded browser-crash retry per sample.
- Baseline medians: warm unthrottled 278/447 ms header/content; cold
unthrottled 646/646 ms; warm throttled 1240/2060 ms; cold throttled
3932/3933 ms.
## Risks
- Low product risk: the new browser measurements are development/QA
tooling and the UI timing work does not change visible layout.
- `Server-Timing` exposes only aggregate handler duration, not query
contents or private identifiers.
- Native INP reporting uses supported browser event timing entries and
silently no-ops where unsupported.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.4, tool-assisted coding and browser execution with
reasoning enabled; context-window size is not exposed in this
environment.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Dev Agent <dev@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The task detail page uses a chat-style thread with a right sidebar;
the sidebar has a Plan tab and an Artifacts tab (#11101 made this UI the
default)
> - The Plan tab only showed the one issue document named `plan`, and
the Artifacts tab only listed formal work products; other agent-authored
documents (for example a `synthesis` doc) and agent-attached files were
invisible in the sidebar
> - Users could see an agent mention a document in the thread but had no
way to find that document in the sidebar, which breaks trust in the task
view as the record of the work
> - This pull request surfaces every non-system issue document in the
Plan tab, composes the Artifacts tab from work products, documents, and
agent-created attachments, and gives thread images a full-screen
lightbox with download
> - The benefit is that anything an agent produces on a task is now
reachable from the sidebar, while user uploads stay with their comments
in the thread
## Linked Issues or Issue Description
Refs #11101 (chat-style task UI default — this PR extends its sidebar).
**Subsystem affected**
Task detail UI (chat-style thread sidebar): Plan tab, Artifacts tab, and
thread attachment rendering in `ui/src`.
**Current behavior**
The Plan tab renders only the issue document literally named `plan`. The
Artifacts tab renders only formal work products. Agent-authored
documents with any other name, and files agents attach to comments, do
not appear anywhere in the sidebar. Thread images open as bare links.
**Proposed behavior**
The Plan tab lists every non-system issue document, with the `plan`
document first and the others rendered inline below it. The Artifacts
tab composes three sources — work products, issue documents, and
agent-created comment attachments — deduplicated against
attachment-backed work products via `metadata.attachmentId`, and shows
whenever any source is non-empty. Work-product rows without a resolvable
attachment or document fall back to links found in their metadata so
they stay clickable. Images in the thread open a shared full-screen
lightbox with a download action. Files uploaded by users stay
thread-only and are not mixed into the Artifacts tab.
**Reason and benefit**
Agents routinely produce documents that are not named `plan` and attach
files to their comments. Users reading the thread must be able to find
every one of those outputs from the sidebar. Redundant surfacing is
acceptable; an unfindable document is not.
**Breaking changes**
None. This is additive rendering; no schema or API changes.
## What Changed
- `IssuePropertiesPlansTab.tsx`: renders all non-system issue documents,
`plan` primary, others inline below via `MarkdownBody`
- `IssuePropertiesArtifactsTab.tsx`: composes work products + documents
+ agent-created attachments with dedupe; rows without an
attachment/document target fall back to `metadata` links
- `IssueProperties.tsx`: Artifacts tab visibility now derives from the
composed source set
- New `ui/src/lib/issue-artifacts.ts`: pure composition/dedupe logic,
unit-tested
- New `ui/src/components/task-chat/task-chat-attachments.ts`: splits
agent vs user comment attachments, unit-tested
- `TaskChatBubble.tsx`: thread images open the shared full-screen
lightbox with download
- `useIssueDocuments.ts`: hook now exposes the full issue-document list
## Verification
- `pnpm typecheck` — passes across the workspace
- `pnpm check:token-gates` — 3/3 CLEAN
- `cd ui && pnpm vitest run src/lib/issue-artifacts.test.ts
src/components/task-chat/task-chat-attachments.test.ts
src/pages/IssueDetail.test.tsx` — 74 tests pass
- Manual: open a task whose agent created a document not named `plan`
(for example `synthesis`); confirm it appears in the Plan tab below the
plan and in the Artifacts tab; confirm an image the agent attached
appears under Artifacts; confirm a user-uploaded image stays only in the
thread and opens full screen with a download button
Snapshot baselines are intentionally not updated for this visual change,
per the `doc/design/DECISION-SHEET.md` entry "Per-change snapshot
verification demoted to dormant (Jul 13 2026)".
## Risks
- Low risk: rendering-only change scoped to the task sidebar and thread
bubbles; composition logic is pure and unit-tested
- Dedupe relies on `metadata.attachmentId` linkage; a work product with
malformed metadata would render as a duplicate row (cosmetic only)
## Model Used
- Claude (Anthropic), model id `claude-fable-5`, extended thinking
enabled, agentic tool use via Claude Agent SDK (Claude Code harness)
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Environments give each agent run an execution target: the local
host, SSH, or a sandbox provider
> - A managed deployment can provision one platform-managed sandbox
environment through the `PAPERCLIP_MANAGED_CONFIG` `environments`
section
> - That row is fully locked today. A tenant cannot add environment
variables for their agents. There is also no way to hide local execution
— run selection falls back to the local row
> - A platform that manages the sandbox for its tenants needs both: the
tenant adds env vars (and nothing else), and local execution is neither
visible nor reachable
> - This pull request opens exactly one tenant edit (env vars) on the
managed sandbox row, and adds an `enableManagedSandboxOnly` mode that
hides local and makes run selection fail closed
> - The benefit is a complete managed-sandbox experience with no change
for self-hosted instances
## Linked Issues or Issue Description
**Subsystem affected**
Environments (managed sandbox provisioning, environment routes, run
environment selection) and the environments UI.
**Problem or motivation**
Platform-provisioned sandbox environments
(`metadata.managedByPaperclip`) reject every write on cloud-managed
instances. Agents often need environment variables inside their sandbox.
The tenant has no way to set them on the managed row. Separately, an
operator cannot remove local execution: the environment list always
shows the local row, and run selection falls back to it when no default
is set.
**Proposed solution**
Allow an envVars-only PATCH on the managed sandbox row, and echo those
env vars back for editing. Add a managed-tier feature
(`enableManagedSandboxOnly`) that hides the local environment from all
read surfaces and redirects local-landing run selection to the managed
sandbox environment, failing closed when it is unavailable.
**Alternatives considered**
UI-only hiding of the local row. This was rejected: it does not stop a
run from resolving to local, so it is presentation without enforcement.
Full unlock of the managed row was also rejected: name, driver, and
config stay platform-owned so boot reconciliation cannot fight tenant
edits.
## What Changed
- `server/src/routes/environments.ts`: the platform-provisioned write
floor admits an envVars-only PATCH on the generalized managed sandbox
row (sandbox driver, `managedByPaperclip`, not legacy kubernetes-marker
rows). Name, driver, config, status, metadata, and DELETE stay rejected.
The read floor stops blanking env vars on that row; credential-shaped
config keys stay redacted for every actor. Legacy kubernetes-marker rows
keep the full floor.
- Same file: under `enableManagedSandboxOnly`, the environments list and
the by-id read omit the local row for every actor, including instance
admins.
- `server/src/services/execution-workspace-policy.ts`:
`resolveExecutionWorkspaceEnvironmentId` gains the managed-sandbox-only
inputs. A selection that lands on the local environment is redirected to
the managed sandbox environment. With no active managed row it throws
`ManagedSandboxUnavailableError` — never local. Non-local selections
(ssh, user-created sandboxes) are untouched.
- `server/src/services/heartbeat.ts`: the run path reads the flag, looks
up the managed row (`findManagedSandboxEnvironment`, new read-only
finder in `environments.ts`), and passes both to the resolver. Mirrors
the forced-kubernetes precedent, which keeps precedence when both
regimes are on.
- `server/src/services/managed-environments.ts`: after a successful
reconcile, the instance default environment moves to the managed sandbox
row when the current default is unset, local, or dangling. A
tenant-chosen custom environment is never overridden.
- `packages/shared`: new `enableManagedSandboxOnly` key (schema default
false, catalog tier `managed`, cloudDefault false, selfHostedDefault
false) and the matching interface field.
- UI: managed rows show a "Managed by Paperclip" lock badge; editing one
opens a dedicated env-vars-only editor that sends the one PATCH shape
the server admits (the old full form failed with a 403 on save). New
`ui/src/lib/managed-sandbox-environment.ts` mirrors the local filter for
cached lists (applied in the project picker; the agent picker already
excluded local). The experimental settings page gains the toggle at its
alphabetical card position. `environmentsApi.update` now declares the
`envVars` field it already sent.
- Tests: environment route floor coverage (envVars-only accepted, mixed
bodies rejected, legacy rows still blanked and locked, local hidden and
404 under the flag, self-hosted unchanged), an embedded-postgres service
test pinning that boot reconciliation never touches tenant env vars,
resolver redirect/fail-closed cases, managed-environments
default-stamping cases, and UI lib/settings tests.
## Verification
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/environment-routes.test.ts
src/__tests__/environment-service.test.ts
src/__tests__/execution-workspace-policy.test.ts
src/services/managed-environments.test.ts` — all pass.
- `pnpm --filter @paperclipai/shared exec vitest run` — 425 pass
(catalog/schema default parity is pinned by an existing test).
- `pnpm --filter @paperclipai/ui exec tsc --noEmit` and the affected UI
suites (CompanyEnvironments, InstanceExperimentalSettings incl.
card-order test, new lib test) — all pass.
- Full workspace `pnpm test`: 3,414 passed. 17 files report failures on
this machine; the identical 17 fail on a clean `origin/master` worktree
in the same environment (git-worktree/skills/embedded-postgres
environment dependencies and plugin-SDK zero-test collections). One
additional file (`issue-monitor-scheduler.test.ts`) failed one
timing-sensitive test in one of two full-suite runs and passes 7/7 in
isolation on this branch — a flake in a domain this diff does not touch.
The branch introduces no new failures.
- Self-hosted zero-delta: every new behavior is gated on the
cloud-managed instance check or the new flag, which defaults to false in
schema and catalog; pinned by the "does not floor platform-marked rows
on self-hosted instances" and flag-off tests.
## Risks
- Behavior is opt-in twice over: the write-floor exception applies only
to rows the managed-config provisioner stamps, and the hiding/forcing
applies only when `enableManagedSandboxOnly` is on (default false
everywhere). Self-hosted instances see no change.
- The env-vars echo is scoped to the generalized managed sandbox row;
legacy kubernetes-marker rows keep the blanket floor because
pre-generalization builds may have written platform values there.
- Fail-closed run selection means a managed instance with the flag on
and an archived managed row (provider plugin down) refuses runs with a
precise error instead of running locally. That is the intended posture.
## Model Used
Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use
via Claude Code CLI.
## 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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Decisions desk shows work that needs an operator response.
> - It also exposed decision-training actions and a separate training
library.
> - Paperclip does not plan to use these training surfaces now.
> - Keeping inactive controls makes the Decisions workflow harder to
scan.
> - This pull request removes the training UI and keeps the backend
snapshot contract unchanged.
> - The benefit is a smaller and clearer Decisions workflow without a
data migration.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The Decisions desk currently exposes training controls, training state,
and a separate training library route.
**Subsystem affected**
`ui/` — React and Vite board UI.
**Current behavior**
Operators can open a training library from the Decisions toolbar. They
can also mark a decision for training from rows and inspect the result
in a drawer.
**Proposed behavior**
Remove the training controls, badges, drawer, library pages, and routes
from the Decisions UI. Keep the server APIs and stored training examples
unchanged.
**Reason and benefit**
The product does not plan to use decision training now. Removing the
unused surfaces reduces Decisions UI noise and avoids presenting a
workflow that operators should not use.
**Breaking changes**
The `/decisions/training` UI routes are no longer registered. Existing
server endpoints and stored decision-training data remain compatible.
## What Changed
- Removed decision-training controls and state from Decisions toolbars,
rows, queue pages, and shelves.
- Removed the training drawer, library, inspector, API client, helpers,
query keys, and routes.
- Added route and row regressions that assert training UI does not
return.
- Updated the Decisions Storybook description to match the available
controls.
## Verification
- `pnpm exec vitest run ui/src/App.test.tsx
ui/src/components/AttentionQueueRow.test.tsx` — 32 tests passed.
- `pnpm check:token-gates` — all gates clean.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — server and UI partitions passed. The CLI partition
had one environment-only failure because this agent runtime injects
static AWS credentials. The exact CLI file passed all 8 tests when those
credential variables were unset.
- Searched `ui/src` and `ui/storybook` for the removed training routes,
drawer, library, badges, and actions. Only negative regression
assertions remain.
## Risks
- Low implementation risk. This change deletes UI-only entry points and
does not change the database or server APIs.
- Saved training-page URLs no longer render a board route. This is the
intended behavior.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, model `gpt-5.6-sol`, with `xhigh` reasoning. The runtime
did not expose the context-window size. The agent used repository tools,
shell execution, and automated tests.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip helps operators supervise AI-agent work.
> - The Mine inbox keeps tasks that need an operator's attention in one
place.
> - Operators can archive a task from its detail page after they finish
triage.
> - That action is easy to select accidentally and did not offer
immediate recovery.
> - This pull request adds Undo to the archive success toast and keeps
inbox caches consistent.
> - The benefit is fast recovery without searching for or reopening the
task.
## Linked Issues or Issue Description
**What happened?**
Archiving a task from the Mine inbox removed it and showed a success
toast with no recovery action.
**Expected behavior**
The success toast should offer Undo. Selecting Undo should restore the
task through the existing unarchive API while preserving a consistent
inbox view.
**Steps to reproduce**
1. Open a task from the Mine inbox.
2. Select the archive action.
3. Observe that the task leaves the inbox and the success toast has no
Undo action.
**Paperclip version or commit**
Reproduced on `master` before this change.
**Deployment mode**
Local dev, built from source.
Related prior work: #9931 and #10668.
## What Changed
- Add an Undo action to the successful inbox archive toast.
- Optimistically restore the task in captured inbox query caches before
the unarchive request completes.
- Cancel in-flight inbox fetches and clear the local archive guard so
stale responses cannot hide the restored task.
- Reapply the archive guard and remove the cached task if the unarchive
request fails.
- Add regression tests for successful Undo, the in-flight cache race,
and failed Undo rollback behavior.
## Verification
- `pnpm exec vitest run ui/src/pages/IssueDetail.test.tsx
ui/src/lib/inboxArchiveCache.test.ts` — 51 tests passed on the final
head.
- `pnpm check:token-gates` — passed.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — the general server and UI groups passed. One CLI
doctor assertion detected injected host AWS credentials and passed all 8
tests with those unrelated variables unset. A task-watchdog scheduler
test also passed all 18 tests in isolation after one full-suite timing
failure.
## Risks
Low risk. The change uses the existing unarchive endpoint and inbox
cache helpers. Undo failure returns the task to its archived state,
shows an error toast, and invalidates the inbox queries for server
reconciliation.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, model ID GPT-5. The service manages the context window.
Reasoning, tool use, and code execution were enabled.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->
## Thinking Path
> - Paperclip helps operators manage AI-agent companies.
> - Operators create tasks and comments through shared rich-text
editors.
> - These editors show slash-command and mention matches in a floating
menu.
> - Modal dialogs treat that body-level menu as outside content and
cancel its wheel and touch movement.
> - This pull request keeps scroll events inside the floating menu and
preserves native scrolling.
> - The benefit is that operators can reach every match with a mouse
wheel, a trackpad, or a touch screen.
## Linked Issues or Issue Description
No public GitHub issue exists for this bug.
**What happened?**
Slash-command and mention menus could contain more matches than their
visible height. When an editor was inside a modal dialog, the modal
scroll lock canceled wheel and touch movement on the body-level menu
portal. Operators could not scroll to later matches.
**Expected behavior**
The autocomplete menu must scroll with a mouse wheel, a two-finger
trackpad gesture, and a vertical touch gesture. Keyboard selection and
normal editor behavior must stay unchanged.
**Steps to reproduce**
1. Open a task or comment editor inside a modal dialog.
2. Enter a slash command or mention query that has more matches than the
menu can show.
3. Try to scroll the menu with a wheel, trackpad, or touch gesture.
**Paperclip version or commit**
`7ea2068ef8` on `master`.
**Deployment mode**
Local development UI built from source.
## What Changed
- Keep wheel and touch movement inside the shared autocomplete menu
portal.
- Add vertical overscroll containment while preserving native momentum
scrolling.
- Add a regression test that mounts the real dialog and verifies that
wheel and touch movement stay uncanceled.
## Verification
- `pnpm --dir ui exec vitest run src/components/MarkdownEditor.test.tsx`
- `pnpm check:token-gates`
- `pnpm -r typecheck`
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm test:run`
- `pnpm build`
The two AWS variables are omitted from the full test command because
this agent runtime injects static AWS credentials. One unrelated CLI
doctor test correctly warns when those credentials are present. The CI
environment does not inject them.
## Risks
- Low risk. Event propagation stops only on the open autocomplete menu
portal.
- Ancestor listeners no longer receive wheel or touch movement from that
menu. Native menu scrolling and option-level touch handling still
receive the events.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex with model ID `gpt-5`. The deployment suffix and
context-window size are not exposed to the agent. The model used agentic
reasoning, repository tools, GitHub tools, and local code 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task comments can contain references to files in project and
execution workspaces.
> - Paperclip detected path-shaped inline code and showed it as an
actionable file chip.
> - The UI did not first confirm that the current board session could
open the file.
> - Missing, denied, ambiguous, remote, and unsupported files therefore
looked actionable and failed after a click.
> - This pull request adds an issue-scoped availability check and
promotes only confirmed files to chips.
> - The benefit is that the task thread shows a file action only when
that action can succeed.
## Linked Issues or Issue Description
**What happened?**
Task comments promoted path-shaped inline code to file chips before
Paperclip checked the file. A chip could point to a missing, denied,
ambiguous, remote, or non-previewable file. The action then failed after
the user selected it.
**Expected behavior**
Paperclip must show a file chip only after the server confirms that the
current board session can open the exact file reference. All other
path-shaped text must stay ordinary inline code.
**Steps to reproduce**
1. Add a task comment that contains inline code with a missing or
inaccessible workspace path.
2. Open the task thread as a board user.
3. Observe that the path looks like an actionable file chip.
4. Select the chip and observe that the file cannot open.
**Paperclip version or commit**
`19be4cf927` and earlier.
**Deployment mode**
Local dev and self-hosted server.
**Access context**
Board user.
## What Changed
- Added shared request, response, and validation contracts for batched
workspace-file availability checks.
- Added an issue-scoped server endpoint that resolves file references
with company, issue, workspace, and preview-access checks.
- Added bounded batch concurrency and tests for missing, denied,
ambiguous, remote, unsupported, and available files.
- Added an issue-scoped UI availability registry that deduplicates,
batches, caches, and invalidates file checks.
- Changed task-comment markdown rendering so only confirmed files get
chip styling and file-viewer behavior.
- Bound each chip to the exact workspace target that passed the
availability check.
## Verification
- `pnpm exec vitest run
packages/shared/src/workspace-file-resource.test.ts
server/src/__tests__/file-resources.test.ts
ui/src/components/MarkdownBody.test.tsx
ui/src/components/WorkspaceFileMarkdownBody.availability.test.tsx
ui/src/lib/remark-workspace-file-refs.test.ts
ui/src/lib/workspace-file-availability.test.ts` — 93 passed, 35 skipped.
- `pnpm check:token-gates` — clean.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — all server and UI groups passed. One unchanged CLI
test saw the run-injected static AWS credentials and expected only its
local `AWS_PROFILE`. The same test passed, 8 of 8, after removing only
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` from its process
environment.
## Risks
- File chips now appear after an asynchronous availability check, so
path-shaped text can briefly render as inline code.
- Availability results use the existing 30-second file-resource cache
window. File-resource invalidation forces a new check.
- The endpoint limits each request to 100 references and the client
chunks larger sets.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex with GPT-5. The service did not expose a more specific
model ID or context-window size. The agent used high-reasoning mode,
repository tools, command execution, and 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
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip helps operators manage agent skills across a company.
> - The installed skills view groups project-backed skills into folders.
> - The view showed two folder creation controls and only offered a
global project scan.
> - Operators need one clear folder action and a refresh action for the
selected project.
> - This pull request keeps folder creation in the folder rail and adds
a scoped project refresh.
> - The benefit is a calmer skills view and faster, more precise project
skill updates.
## Linked Issues or Issue Description
No public GitHub issue exists for this focused UI bug.
**What happened?**
The installed skills view repeated the folder creation action in the
toolbar. A selected project folder also had no way to refresh only its
own project skills.
**Expected behavior**
The folder rail must own folder creation. A selected project-backed
folder must offer a refresh action that scans only that project and
refreshes the skill and folder queries.
**Steps to reproduce**
1. Open the installed skills view for a company with project-backed
skill folders.
2. Select a project folder.
3. Observe the duplicate folder action and the absence of a
project-scoped refresh action.
**Paperclip version or commit**
Reproduced before this two-commit fix on `master`.
**Deployment mode**
Local development with `pnpm dev`.
## What Changed
- Removed the duplicate toolbar folder creation button when the folder
rail exists.
- Preserved the toolbar folder action when no folder rail exists.
- Added a refresh action beside the breadcrumb for a selected
project-backed folder.
- Passed the selected project ID to the project scan API.
- Refreshed both the installed skill list and skill folder data after
scans.
- Added component tests for compact folder creation, the empty-folder
fallback, and scoped project refresh.
## Verification
- `pnpm exec vitest run ui/src/pages/CompanySkills.test.tsx` — 20 tests
passed.
- `pnpm check:token-gates` — passed with all three gates clean.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — the server and UI stages passed 7,475 tests. The CLI
stage then found one environment-sensitive AWS doctor assertion because
this agent runtime injects static AWS credentials.
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest
run cli/src/__tests__/secrets.test.ts --project paperclipai` — all 8
tests passed.
- GitHub CI — all latest-head checks passed.
## Risks
- Low risk. The scoped refresh depends on the existing `project:<id>`
folder system key.
- The global scan path is unchanged.
- There are no schema, migration, API contract, dependency, workflow, or
documentation changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 family. The runtime did not expose a more specific
model ID or context-window size. The agent used high-reasoning mode,
repository tools, GitHub tools, and code 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip gives operators a summary for each workspace.
> - An execution workspace detail page used the parent project-workspace
summary slot.
> - Two execution workspaces under one project workspace could therefore
show the same summary.
> - This pull request gives each execution workspace its own summary
scope.
> - It also limits the summary snapshot and generated issue to that
execution workspace.
> - The benefit is that a new or parallel execution workspace cannot
inherit unrelated status.
## Linked Issues or Issue Description
**What happened?**
An execution workspace detail page read and refreshed the summary slot
for its parent project workspace. Parallel execution workspaces could
show the same status and include issues from each other.
**Expected behavior**
Each execution workspace must have one isolated summary slot. Its
generated snapshot must include only issues assigned to that execution
workspace.
**Steps to reproduce**
1. Create two execution workspaces under one project workspace.
2. Add different issues to each execution workspace.
3. Generate the summary in the first execution workspace.
4. Open the second execution workspace.
5. Observe that the old implementation could reuse the first summary.
**Paperclip version or commit**
The problem exists on `master` before this pull request.
**Deployment mode**
The issue affects both local trusted and authenticated deployments.
## What Changed
- Added `execution_workspace` to the shared summary-slot scope contract.
- Validated execution-workspace ownership and stored generated summary
issues on the correct execution workspace.
- Limited execution-workspace snapshots to issues with the matching
execution workspace ID.
- Updated the execution workspace page to use its own summary slot.
- Updated Summarizer instructions, routine options, catalog metadata,
documentation, and regression tests.
## Verification
- `NODE_ENV=test pnpm exec vitest run
packages/shared/src/summary-slot.test.ts
server/src/__tests__/summary-slots.test.ts
ui/src/pages/ExecutionWorkspaceDetail.test.tsx` — 30 focused tests
passed; the embedded-Postgres server tests were run outside the
process-restricted sandbox.
- `pnpm check:token-gates` — passed.
- `pnpm --filter @paperclipai/skills-catalog validate` — passed with 17
catalog skills.
- [Latest-head GitHub
Actions](https://github.com/paperclipai/paperclip/actions/runs/31491475405)
— all 22 jobs passed on `beea14cbaf`, including typecheck, build,
server/workspace tests, serialized suites, e2e, canary, and aggregate
verification. One unrelated adapter cleanup test initially hit an
`ENOTEMPTY` temp-directory race; its single permitted rerun passed.
- Greptile — 5/5 confidence on `beea14cbaf`, 12 files reviewed, zero
comments added, and zero unresolved threads.
## Risks
- Low risk. The new scope is additive.
- Existing project and project-workspace summary slots keep their
current keys and behavior.
- A summary generated for an execution workspace now excludes sibling
workspace issues by design.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex with GPT-5. The deployment does not expose a more
specific model ID or context-window value. It used agentic reasoning,
repository tools, code execution, and GitHub tooling.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The inbox helps operators scan parent tasks and their sub-tasks
> - Operators can fold a parent task to hide its sub-tasks
> - The inbox previously forgot that fold state after a page refresh
> - This pull request stores the fold state for each company and
restores it when the inbox loads
> - The benefit is that the inbox keeps the operator's chosen task
layout across page refreshes
## Linked Issues or Issue Description
**What happened?**
The inbox reset every folded parent task after a page refresh. This made
all nested sub-tasks visible again.
**Expected behavior**
The inbox must keep each folded or unfolded parent state after a page
refresh. The state must remain separate for each company.
**Steps to reproduce**
1. Open the inbox with parent and child tasks.
2. Fold one parent task.
3. Refresh the page.
4. Observe that the child task is visible again without this fix.
**Paperclip version or commit**
Current `master` before this pull request.
**Deployment mode**
Local dev and built-from-source deployments.
## What Changed
- Added company-scoped local storage helpers for collapsed inbox parent
IDs.
- Restored the stored parent fold state when the inbox mounts or the
selected company changes.
- Saved both direct toggle changes and explicit collapse changes.
- Added helper tests and an inbox remount regression test for both
folded and unfolded states.
## Verification
- `pnpm exec vitest run ui/src/lib/inbox.test.ts
ui/src/pages/Inbox.test.tsx` — 77 tests passed.
- `pnpm check:token-gates` — all gates passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/ui build` — passed.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — 3,521 tests passed and four skipped. One unrelated
server test on the current base fails because it reads
`heartbeat.scheduling_suppressed` instead of `issue_commented`; the same
test fails alone and this pull request changes only inbox UI files.
## Risks
- Low risk. The state is local to the browser and scoped by company ID.
- Old parent IDs can remain in local storage after tasks are deleted,
but they do not affect visible tasks.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, exact model ID `gpt-5.6-sol`, with reasoning, tool use,
and code execution. The runtime does not expose its context-window size.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Company import lets an operator bring a package of agents into an
instance, and each agent declares which adapter runs it (Claude Code,
Codex, and so on)
> - The export and the server importer preserve each agent's adapter
faithfully, but the Import page seeds an adapter override for every
agent with the destination CEO's adapter before the user touches
anything
> - Every imported agent therefore arrives as the CEO's adapter (usually
Claude Code) even when the source package holds a mix, and the picker
shows the coerced value as if it were the source's, so nothing looks
wrong
> - This pull request makes the manifest adapter the default, sends
overrides only for agents the user actually changed, and replaces the
silent coercion with an explicit per-agent fallback warning when the
destination truly lacks the source adapter
> - The benefit is that a mixed Claude/Codex team imports as a mixed
Claude/Codex team, and any real adapter gap is visible instead of silent
## Linked Issues or Issue Description
**What happened?**
A user imported a company package whose agents were a mix of Claude Code
and Codex on the source instance. After the import, every agent was
configured as Claude Code. The import preview showed no sign that
anything had been changed. Cause: the Import page initializes its
adapter-override map by assigning every agent the destination CEO's
adapter type and sends that override for every agent, overriding the
manifest's per-agent adapter server-side. For imports into a new
company, the "CEO adapter" is read from whichever unrelated company is
currently selected.
**Expected behavior**
Imported agents keep the adapter declared in the package. An override is
sent only when the operator explicitly picks a different adapter, or
when the source adapter is not installed on the destination — and in
that case the page must say so per agent, not silently substitute.
**Steps to reproduce**
1. On a source instance, create a company with one Claude Code agent and
one Codex agent, and export it.
2. Import the package on another instance whose CEO uses Claude Code,
changing nothing in the import dialog.
3. Both agents arrive configured as Claude Code; the Codex identity is
gone.
## What Changed
- The preview no longer seeds adapter overrides; the override map starts
empty, and the picker displays each agent's manifest adapter
(`ui/src/pages/CompanyImport.tsx`).
- `buildFinalAdapterOverrides` sends an entry only when the effective
adapter differs from the manifest or the agent's adapter config was
edited — untouched agents flow through with no override.
- The page fetches the destination's installed adapters (existing
`adaptersApi.list()` client). When a manifest adapter is missing or
disabled on the destination, only that agent defaults to the CEO's
adapter, with a visible amber warning naming both adapters. If the
adapters request fails, the page fails open: manifest adapters are kept
and no coercion happens.
- Tests: untouched mixed-adapter import sends no overrides; a
user-changed agent sends exactly one; a missing destination adapter
produces the fallback plus rendered warning for that agent only; an
adapters-endpoint failure produces no coercion.
## Verification
- `npx vitest run ui/src/pages/CompanyImport.test.tsx` — 19 passed (15
pre-existing + 4 new).
- `pnpm --filter ./ui typecheck` (`tsc -b`) — clean.
## Risks
- Behavior change: users who previously relied on the silent conversion
(importing packages that reference adapters they don't have) now get an
explicit per-agent fallback with a warning — same outcome, visible. The
server's hard rejection of unknown adapter types remains the backstop
for API callers.
- UI-only change; no server or schema impact.
## Model Used
- Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended
thinking and tool use (multi-agent implementation with independent
verification).
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Company import/export lets an operator move a full company package
between instances, with the Import page uploading the package as one
compressed `.zip`
> - The server caps that upload at 128 MB, and real company packages
with attachments now exceed it — imports fail at the preview step
> - The failure message tells the user to use the CLI folder import, but
that path posts inline JSON capped at 64 MB, so the advice is a dead end
for exactly these packages
> - This pull request raises the zip upload cap to a 1 GB default, makes
it operator-configurable through an environment variable, scales the
decompression-bomb guards from the cap in effect, and replaces the
misleading hint
> - The benefit is that large real-world company packages import
successfully, and operators with unusual needs can tune the cap without
a code change
## Linked Issues or Issue Description
**What happened?**
A company import fails at the preview step with `Preview failed: Import
package exceeds 134217728 bytes`. The package is a valid Paperclip
export. Its compressed size is larger than the 128 MB server cap (one
reported package is 257 MB). The error panel suggests the CLI folder
import, but that path sends the package as one inline JSON body capped
at 64 MB, so it also fails.
**Expected behavior**
A valid company package of realistic size imports successfully through
the Import page. If a package is too large, the error must state the
limit clearly and suggest a step that can work.
**Steps to reproduce**
1. Export a company with enough attachments to make the compressed
package larger than 128 MB.
2. Open the Import page and upload the `.zip`.
3. Click "Preview import".
4. The preview fails with `Import package exceeds 134217728 bytes`.
**Deployment mode**
Reported from a managed deployment; the limit applies to all deployment
modes.
## What Changed
- Raise `PORTABLE_ZIP_UPLOAD_LIMIT_BYTES` from 128 MB to a 1 GB default
(`server/src/http/body-limits.ts`).
- Add the `PAPERCLIP_IMPORT_ZIP_MAX_BYTES` environment override. Invalid
or non-positive values fall back to the default.
- Scale the zip decompression-bomb guard from the configured cap at the
import route: the aggregate inflated ceiling is 4x the cap. The
per-entry ceiling stays at 512 MB because V8's string length limit
applies to an entry regardless (`server/src/routes/companies.ts`,
`packages/shared/src/portability-zip.ts`).
- Report the 422 limit error in MB instead of raw bytes.
- Replace the "use the CLI folder import for very large packages" hint
on preview failure with advice that works: re-export the package without
large attachments (`ui/src/pages/CompanyImport.tsx`).
- Update the stale comment in `ui/src/lib/import-preflight.ts` that made
the same CLI claim.
- Add tests for the new default, the env override, and the
invalid-override fallback.
## Verification
- `pnpm vitest run server/src/__tests__/body-limits.test.ts
packages/shared/src/portability-zip.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/company-portability.test.ts
server/src/__tests__/company-portability-import-batching.test.ts` — all
pass.
- `pnpm vitest run ui/src/pages/CompanyImport.test.tsx` — passes,
including the updated failure-panel copy assertion.
- `pnpm typecheck` — clean across the workspace.
- Manual: upload a `.zip` larger than the configured cap; the preview
fails with `Import package exceeds the 1024 MB upload limit` and the new
hint. A package between 128 MB and 1 GB now previews and imports.
## Risks
- Peak per-import memory rises with the cap: the upload is buffered in
memory and unzipped in one pass. A 1 GB compressed package can use
several GB transiently. Imports are instance-admin actions, so the
exposure is a deliberate operator action, not anonymous traffic.
Operators on small hosts can lower the cap with
`PAPERCLIP_IMPORT_ZIP_MAX_BYTES`.
- The aggregate bomb guard moves from a fixed 512 MB to 4x the
configured cap. It still bounds expansion far below what a decompression
bomb needs.
- No migration and no API shape change. The 422 message text changes; no
code matches on the old text.
## Model Used
- Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended
thinking and tool use (file edits, local test runs, live-instance
inspection).
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The agent detail page has a Costs section. That section renders a
data table of per-run spend.
> - The table has a header row, but its `<th>` elements carry no `scope`
attribute.
> - A screen reader uses `scope="col"` to bind each data cell to its
column header. Without it, the reader announces a number without telling
the user which column it belongs to.
> - A table of costs is exactly the case where that hurts. Every cell is
a bare figure.
> - This pull request adds `scope="col"` to the five header cells in
that table.
> - The benefit is that assistive technology announces the cost table
correctly. The change is markup only, so sighted users see no
difference.
## Linked Issues or Issue Description
No public GitHub issue covers this. The problem is described in-PR,
following the enhancement template.
**What existing behavior does this improve?**
The Costs table rendered by `CostsSection` in
`ui/src/pages/AgentDetail.tsx`.
**Subsystem affected**
ui/ — React + Vite board UI
**Current behavior**
The table renders five header cells: Date, Run, Input, Output, and Cost.
None of them set `scope`. A screen reader must guess the header-to-cell
relationship, so a user hears a value with no column name attached to
it.
**Proposed behavior**
Each header cell sets `scope="col"`. A screen reader then announces the
column name together with each cell, so a cost figure is read as part of
the Cost column.
**Reason and benefit**
`scope` is the standard way to associate header cells with data cells in
an HTML table. The attribute has no visual effect, so the fix carries no
design cost and makes the table usable with a screen reader.
**Breaking changes**
None. `scope` is a presentational-neutral HTML attribute. No component
API, no styling, and no test changes.
**Related pull requests**
- #2215 proposed the same attribute for the Routines table. It is
closed, because that table no longer exists on master.
- #1524 and #1522 applied `scope="col"` to other tables. Both are
closed.
## What Changed
- Added `scope="col"` to the five `<th>` elements in the `CostsSection`
table in `ui/src/pages/AgentDetail.tsx`.
- Rebased the branch onto current master.
- Dropped the original `ui/src/pages/Routines.tsx` hunks. Master rebuilt
the Routines page around folder-grouped rows, so the table those hunks
targeted no longer exists.
- Dropped the original `HintIcon` opacity change. It altered a visible
colour, which is out of scope for a markup-only accessibility fix.
## Verification
- Run `pnpm --filter @paperclipai/ui typecheck` and `pnpm --filter
@paperclipai/ui build`. This is a markup-only change, so a clean
type-check and build is the relevant automated signal.
- Open an agent detail page and go to the Costs section. Inspect the
header row. Each `<th>` now carries `scope="col"`.
- Navigate the same table with a screen reader, cell by cell. Each cell
is announced with its column name.
- Compare the rendered page before and after. It is unchanged, because
`scope` has no styling effect.
## Risks
Low risk. The change adds one standard HTML attribute to five header
cells in a single table. It introduces no code path, changes no
component API, and has no visual effect. The worst case is that the
attribute is redundant for a reader that already infers the column,
which is harmless.
## Model Used
Anthropic Claude Opus 5, exact model ID `claude-opus-5`. It ran with
extended thinking and repository read/write tools, inside a
maintainer-operated triage agent. The model rebased the branch, dropped
the two out-of-scope hunks, and wrote this description. The original
change was authored by @bluzername, and the model used for that work is
not recorded here.
## 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 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` references)
- [x] I have considered and documented any risks above
- [x] My branch name describes the change and contains no internal
ticket id
- [ ] I have run tests locally and they pass — not run. This is a
markup-only change and the package has no test covering this table.
- [ ] I have added or updated tests where applicable — no test added,
which is why this PR is titled `refactor:`.
- [ ] I have updated relevant documentation to reflect my changes — no
documentation describes this markup.
- [ ] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work — not checked by the maintainer who rebased this.
- [ ] All Paperclip CI gates are green — CI re-runs on this push.
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
Greptile re-reviews on this push.
---------
Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip posts system comments when automatic run recovery cannot
continue
> - These comments currently mix the main event with recovery
identifiers and routing details
> - The task chat shell also renders these comments as large raw text
blocks
> - Operators need a short explanation first and inspectable evidence on
demand
> - This pull request emits structured recovery notices and renders them
as compact humanized rows
> - The benefit is a quieter task thread that keeps the full recovery
evidence available
## Linked Issues or Issue Description
Related prior extraction source: #11070. This pull request replaces only
its structured recovery notice slice with a focused branch based on
current master.
**What existing behavior does this improve?**
Paperclip recovery escalations and the experimental task chat
system-comment renderer.
**Current behavior**
Recovery escalation comments put action identifiers, owner details, run
details, and failure codes into the visible markdown body. The task chat
shell renders the complete system comment as a large text block.
**Proposed behavior**
The server emits a short system notice with typed metadata sections. The
task chat shell classifies known recovery families and renders one
compact row. An operator can expand the row to inspect the full body and
metadata.
**Reason and benefit**
The main thread stays readable during repeated recovery activity. Typed
links and evidence remain available without exposing raw failure text in
the default view.
**Breaking changes**
The visible recovery comment body is shorter. Recovery action
deduplication now reads the structured metadata and still recognizes
legacy body markers. No API schema or database migration changes.
## What Changed
- Emit stranded recovery escalations with `system_notice` presentation
and typed recovery, owner, run, and failure-code metadata.
- Share bounded metadata row builders across recovery notice producers
and preserve legacy deduplication compatibility.
- Humanize known recovery notice families and render compact expandable
task-chat rows.
- Route system-authored comments ahead of derived agent authorship so
recovery notices do not appear as agent bubbles.
- Add focused server and UI regression coverage.
## Verification
- `pnpm check:token-gates` — 3/3 clean.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/shared exec vitest run
src/validators/issue.test.ts` — 32 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/services/recovery/stranded-notice.test.ts
src/__tests__/issue-recovery-actions.test.ts` — 57 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/services/recovery/successful-run-handoff.test.ts
src/services/recovery/stranded-notice.test.ts` — 39 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-process-recovery.test.ts -t 'escalates an
exhausted failed successful-run handoff without using generic
continuation recovery first|escalates an exhausted successful handoff
run that still leaves no disposition'` — 2 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-process-recovery.test.ts -t 'blocks assigned
todo work after the one automatic dispatch recovery was already used'` —
passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/system-notice-humanizer.test.ts
src/components/task-chat/TaskChatSystemNotice.test.tsx
src/components/task-chat/task-chat-adapter.test.ts` — 15 tests passed.
- Storybook visual baselines were not updated because this chat-shell
path has no affected snapshot baseline. Focused rendering tests and
token gates cover this change.
## Risks
- Consumers that parse recovery action identifiers from comment markdown
must move to structured metadata. Server deduplication remains backward
compatible with legacy comments.
- The humanizer uses stable recovery-family phrases. Unknown notices use
a generic truncated first-sentence fallback.
- The UI changes only the experimental task chat presentation. The
stored comment body and expanded metadata remain available.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex with GPT-5, reasoning mode, repository tools, shell
execution, and GitHub integration. The runtime did not expose a
context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - People send task instructions through the board task chat.
> - A page refresh or task switch can discard an unfinished message in
the redesigned composer.
> - The existing task chat already supplies a task-specific draft key.
> - The redesigned composer must use that key without changing
attachment or send behavior.
> - This pull request restores, saves, and clears text drafts in the
redesigned composer.
> - The benefit is that users can return to unfinished task messages
without losing their text.
## Linked Issues or Issue Description
Related prior work: #11070. This pull request extracts only the final
composer draft behavior from that larger draft.
**Subsystem affected**
ui/ — React + Vite board UI.
**Problem or motivation**
The redesigned task chat composer does not use the draft key that the
task thread already provides. A refresh, navigation, or unmount can lose
an unfinished message.
**Proposed solution**
Persist text drafts by task key in local storage. Restore a draft when
the composer mounts. Save changes after a short delay and flush pending
text during unload or unmount. Clear the draft only after a successful
send.
**Alternatives considered**
The composer could save on every keystroke. A short delay avoids
unnecessary synchronous storage writes. The feature could also stay in
the larger predecessor PR, but a focused PR is easier to review and
verify.
**Roadmap alignment**
This is a focused usability improvement for the task conversation
surface. It does not add or duplicate a roadmap capability.
## What Changed
- Added safe draft storage helpers for load, save, and clear operations.
- Connected the task-specific draft key to the redesigned task chat
composer.
- Preserved drafts across debounce windows, unmounts, page unloads,
failed sends, and React Strict Mode probes.
- Cleared drafts after successful sends without changing current
attachment safeguards.
- Added focused composer and thread integration tests.
## Verification
- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat/TaskChatComposer.test.tsx
src/components/TaskChatThread.test.tsx`
## Risks
- Local storage can be unavailable or full. The helpers catch storage
errors and keep the composer usable.
- Only text is persisted. Attachments, work mode, and assignee
selections remain session state.
- Draft keys remain task-scoped, so text does not cross task boundaries.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex with model `gpt-5`. The context-window size is not exposed
in this environment. The model used agentic reasoning, tool use, code
execution, and 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Humans oversee those agents in teams, so each team needs its own
spend controls
> - The BudgetPolicyCard component shows how much of a budget is
consumed
> - The utilization bar in that card is a styled div with no ARIA role,
value, or label
> - A screen reader user therefore cannot hear how much budget is used
> - This pull request adds role="progressbar" and the matching ARIA
value attributes
> - The benefit is that assistive technology announces budget
utilization the same way sighted users see it
## Linked Issues or Issue Description
No existing issue. Related pull requests: #1869 and #1878 add the same
progressbar
semantics to ProviderQuotaCard and QuotaBar. They touch different files.
The problem follows the bug report template:
**What happened?**
The budget utilization bar in `BudgetPolicyCard` renders as a plain
`div`. It has no
`role`, no `aria-valuenow`, and no accessible name. A screen reader
announces nothing
for it. The user can read the "Remaining" amount, but not the
utilization percentage.
**Expected behavior**
The bar is announced as a progress bar. It reports the current
utilization percentage,
with its minimum and maximum.
**Steps to reproduce**
1. Open a project or agent page that shows the budget card.
2. Start VoiceOver (Cmd+F5 on macOS).
3. Move the cursor to the budget utilization bar.
4. VoiceOver announces nothing.
**Paperclip version or commit**
master, `ui/src/components/BudgetPolicyCard.tsx`.
**Deployment mode**
Local dev (pnpm dev).
## What Changed
- Added `role="progressbar"` to the inner bar element.
- Added `aria-valuenow` with the rounded utilization percentage.
- Added `aria-valuemin={0}` and `aria-valuemax={100}`.
- Added `aria-label` in the form `Budget utilization: 73% used`.
`aria-valuenow` and `aria-label` use the same `progress` value. That
value is already
capped at 100 by `Math.min(100, summary.utilizationPercent)`, so the
reported value
stays inside the min/max range when a scope is over budget.
An earlier revision also changed the budget amount `Input` to
`type="number"`. That
change is removed. It changed input behavior and was not an
accessibility fix.
See "Risks".
## Verification
1. Open a page that shows the budget card.
2. Start VoiceOver (Cmd+F5 on macOS) and move to the utilization bar.
3. VoiceOver announces "Budget utilization: X% used, progress
indicator".
4. Inspect the element. `aria-valuenow` equals the displayed percentage,
and it stays
at 100 when utilization is above 100%.
The change adds attributes only. There is no visual change.
## Risks
Low risk. The change adds ARIA attributes to one element in one
component. It changes
no logic, no styling, and no layout.
The `type="number"` change is removed on purpose. With `type="number"`,
a browser
reports an empty string for text it cannot parse. `parseDollarInput("")`
then returns
`0` instead of `null`, so the existing "Enter a valid non-negative
dollar amount."
error never appears and unparseable input silently becomes a $0.00
budget. The
`inputMode="decimal"` input keeps that validation path working.
## Model Used
Original implementation by @bluzername. The author did not state a
model.
Scope reduction, branch update, and this description: Claude Opus 5 (1M
context,
extended thinking, tool use), prepared under maintainer review.
## 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 searched GitHub for duplicate or related PRs and linked
them above
- [x] I have described the issue in-PR following the relevant issue
template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change
- [x] I have considered and documented any risks above
- [ ] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [ ] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable
- [ ] I have updated relevant documentation to reflect my changes
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
This reverts commit 11e56654f8.
#10786 ported the onboarding flow from a standalone prototype and
repointed `/onboarding` at the new `CloudOnboardingFlow`, deleting the
existing `OnboardingWizard.tsx` in the process. The ported flow is not
ready to be the shipping onboarding experience: it landed as a single
large port rather than an incremental migration, it pulled `motion`,
`three` and `@types/three` onto the UI dependency list for prototype
visuals, and it deleted the wizard that four in-flight pull requests
(#9900, #9501, #8982 and one more) were building on — those went
CONFLICTING the moment the file disappeared.
Rather than keep the half-migrated state on master while that is sorted
out, back the port out whole and re-land it incrementally. This restores
`OnboardingWizard.tsx` and the previous versions of the four e2e specs,
drops the `onboarding-preview.html` Vite entry, the DesignGuide
onboarding section and the `data-viz-misc` storybook story, and removes
the three prototype dependencies from `ui/package.json`.
This is an exact mechanical inverse of the squash commit — 41 files,
+2089/-3647, no hand edits. Reverting this commit restores all 41 files
byte for byte, so the port is recoverable in full when it is ready.
`pnpm-lock.yaml` is deliberately not touched. #10786 never updated it;
bot commit 4683f26c9 (#11036) added the `motion`/`three` entries
afterwards, so the lockfile is now ahead of the manifest. CI owns
lockfile updates (`.github/workflows/pr.yml`) and the policy job
regenerates it from the changed manifest.
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Apps subsystem connects company tools through governed provider
connections.
> - A company can need more than one account for the same provider.
> - The current database constraint and Apps flow assume one named
connection per company.
> - New quarantined actions also need an explicit review decision before
activation.
> - This pull request supports multiple provider connections and
complete action review decisions.
> - The benefit is safer access control and a clear multi-account Apps
workflow.
## Linked Issues or Issue Description
Refs: #11040
**Subsystem affected**
Cross-cutting. This change affects the Apps UI, the tool access API, the
shared request contract, and the database schema.
**Problem or motivation**
The connection name constraint prevents a company from keeping more than
one connection for a provider. The Apps UI also reuses an existing OAuth
connection when a user asks to connect another account. Action review
can enable selected entries without recording a decision for every
quarantined action.
**Proposed solution**
Remove the company and connection name uniqueness constraint. Let users
open, count, edit, and create multiple provider connections. Require the
finish request to cover every quarantined action exactly once before the
server activates reviewed entries.
**Alternatives considered**
The UI could generate unique internal names and keep the database
constraint. This would preserve a one-connection assumption in the data
model and would make display names part of identity. The server could
also infer review decisions from enabled actions. This would not
distinguish a reviewed disabled action from an action that the user did
not review.
**Roadmap alignment**
This change extends the completed MCP Tool Gateway and Apps milestone.
It also supports the Connected Apps roadmap item. It follows the
navigation and connection management work in #11040.
## What Changed
- Remove the company-scoped connection name uniqueness index with an
ordered and idempotent migration.
- Add a reviewed action list to the finish-app contract and reject
incomplete or duplicate review decisions.
- Activate reviewed entries and keep unreviewed quarantined entries
blocked.
- Enable a completed connection and preserve the company and connection
scope in all updates.
- Show provider connection counts and open the provider setup page from
Browse.
- Let users edit existing connections or connect another account without
reusing an active OAuth connection.
- Update focused server and UI coverage for multiple connections and
action review.
## Verification
- Ran the focused Apps UI suite. All 116 tests passed in 11 files.
- Ran the focused server and CLI suite. All 276 tests passed in 3 files.
- Ran `pnpm --filter @paperclipai/db check:migrations`. The migration
safety check passed.
- Ran `pnpm -r typecheck`. All projects passed.
- Ran `pnpm build`. All projects built successfully.
- Ran `pnpm test:run`. It passed 3,735 tests and skipped 4 tests. One
worktree-safety assertion failed because the execution workspace reloads
its worktree marker. The same test passed with an isolated non-worktree
marker.
- Ran `pnpm check:token-gates`. It reports 12 existing violations in the
unchanged `PaperclipOrbit3D.tsx` file from the target branch.
- Started the six affected Playwright specifications. Chromium could not
start because the host does not provide `libatk-1.0.so.0`. The GitHub
e2e jobs will verify these specifications.
- GitHub Actions passed every final-head CI gate, including all three
e2e shards and the aggregate `e2e` and `verify` jobs.
- Greptile reviewed final commit `9af9200426` at 5/5 with zero review
threads.
## Risks
- Removing the name uniqueness index permits duplicate display names.
Stable connection IDs and UIDs remain unique within a company.
- The finish-app endpoint accepts the new review field as optional for
backward compatibility. When clients send it, the server requires a
complete decision for all quarantined actions.
- Multiple OAuth connections depend on the explicit new-connection route
flag. Focused tests cover active and draft connection reuse.
- The migration is ordered after migration 0210. Its `DROP INDEX IF
EXISTS` statement is safe to repeat.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex with the `gpt-5.6-sol` model assisted this change. The
agent used repository tools, code execution, test execution, and agentic
reasoning. The Codex runtime manages the context window.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Apps UI manages app discovery and app connections.
> - The managed worktree runtime starts agent work in repository
worktrees.
> - The Apps routes do not match the main discovery flow, and the
connections view lacks a delete action.
> - Legacy managed worktrees can also start before their pending seed
operation runs.
> - This pull request makes app discovery the main Apps route and makes
connection management explicit.
> - It also seeds legacy managed worktrees before runtime startup and
makes the CLI read the repository-local config.
> - The benefit is a clearer Apps workflow and a safer managed-worktree
startup path.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
This improves the Apps navigation, app connection management, managed
git-worktree startup, and CLI worktree selection.
**Subsystem affected**
Cross-cutting. The change affects `ui/`, `server/`, `cli/`, and
development documentation.
**Current behavior**
The `/apps` route opens the connections list while discovery uses a
nested route. The connections list has no delete action. Some legacy
managed worktrees can start runtime work before their pending seed
operation runs. The CLI can also read an ambient Paperclip config
instead of the repository-local config.
**Proposed behavior**
The `/apps` route opens Browse, and `/apps/connections` opens the
connection list. Users can delete a connection after confirmation.
Runtime startup seeds legacy managed worktrees when required. The CLI
resolves the current worktree from the repository-local
`.paperclip/config.json` file.
**Reason and benefit**
Users can discover apps from the canonical Apps route and can manage
existing connections from a dedicated route. Legacy worktrees receive
their required repository content before agent runtime starts. CLI
worktree selection stays scoped to the current repository.
**Breaking changes**
The `/apps` and `/apps/browse` route behavior changes. Old Browse links
redirect to `/apps`. The change does not modify an API schema or
database schema.
## What Changed
- Make Browse the canonical `/apps` page and move the connection list to
`/apps/connections`.
- Align Apps navigation, redirects, attention links, empty states, and
connection actions with the new routes.
- Add connection deletion with confirmation and clear failure feedback.
- Seed legacy managed git worktrees before runtime startup when their
seed status is pending.
- Read the CLI worktree selection from the repository-local Paperclip
config.
- Update focused UI, server, CLI, and development documentation
coverage.
## Verification
- Ran 202 focused UI, server, and CLI tests. All tests passed.
- Ran `pnpm -r typecheck`. All projects passed.
- Ran `pnpm build`. All projects built successfully.
- Ran `pnpm test:run`. The server and UI stages passed 7,168 tests. The
CLI stage found one environment-sensitive secrets test because this
workspace injects static AWS credentials. The isolated CLI file passed
all 8 tests after those injected variables were unset.
- Ran `pnpm check:token-gates`. It reports 12 existing color-token
violations in the unchanged `PaperclipOrbit3D.tsx` file from the target
branch. This pull request does not modify that file.
- Ran focused regression coverage for repository-root CLI config
resolution and connection deletion state. All tests and affected package
typechecks passed.
- Collected all 27 tests in the six changed Playwright specifications
successfully.
- GitHub Actions passed every latest-head CI gate, including all three
e2e shards and the aggregate `e2e` and `verify` jobs.
- Greptile reviewed the final commit at 5/5 with zero unresolved
threads.
## Risks
- Existing bookmarks for `/apps/browse` redirect to `/apps`.
- Connection deletion changes visible connection state and requires user
confirmation.
- The legacy seed path runs only for managed git worktrees with pending
seed state. Tests cover the startup condition.
- The rebase preserves the target branch's direct OAuth policy for the
Notion connection flow.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex with the GPT-5 model family assisted this change. The agent
used reasoning, repository tools, code execution, and test execution.
The runtime does not expose the exact model snapshot or context-window
size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task list and the chat views show a Live badge and a Working
shimmer for an issue that has an active run.
> - A finished task kept the Live badge and the Working shimmer after
the run ended and the sandbox stopped.
> - The user interface reads run liveness from the
`heartbeat_runs.status` row. The run finalizer writes the terminal
status in a step that is separate from the agent `status=done` update.
When the sandbox or the run process stops between the two steps,
`heartbeat_runs.status` stays `running` forever.
> - A run row that stays `running` makes a finished task look
perpetually Live, and the user interface has no guard for an issue that
already reached a terminal status.
> - This pull request closes the invariant "environment lease released
implies the run is terminal" on the server, and adds a user interface
guard that suppresses live state for a terminal issue.
> - The benefit is that a finished task stops showing Live and Working,
both at the source (the run row) and at the surface (the badge and the
shimmer).
## Linked Issues or Issue Description
**Bug description**
- A completed task kept the Live badge and the Working shimmer after its
run ended and the sandbox was torn down.
**Steps to reproduce**
- Run an agent task to completion. Let the sandbox tear down while the
run finalizer is between the `status=done` update and the terminal
run-status write.
- Open the task list or the chat view for the finished task.
**Expected behavior**
- A finished task shows no Live badge and no Working shimmer.
**Actual behavior (before this change)**
- The finished task showed the Live badge and the Working shimmer
because its `heartbeat_runs.status` row stayed `running`.
This pull request supersedes the two separate pull requests #10954
(frontend) and #10955 (backend). It carries all of their changes for the
same race.
## What Changed
Server:
- Run teardown terminalizes a still-running or still-queued run before
it releases the environment lease. It writes `succeeded` when the issue
already reached `done`, `cancelled` when the issue is `cancelled`, and
`interrupted` otherwise. It never overwrites a status that another path
already made terminal.
- The recovery stale-lock sweep terminalizes an orphaned running run to
`interrupted` after it confirms the process and the sandbox are both
gone. It requires recorded process metadata, so it never terminalizes a
live run, a queued run, or a scheduled retry.
- Each terminal transition writes a run event.
- The stale-lock sweep continues and clears the lock when the audit
write fails. It logs the failure loudly.
- New server tests cover both invariants.
User interface:
- A shared guard suppresses the Live badge and the Working shimmer when
the issue status is terminal.
- The guard keeps non-terminal `queued` and `running` issues live.
- The guard prefers the newest issue live-status snapshot.
- New user interface tests cover the guard and the snapshot preference.
## Verification
Server:
- `pnpm --filter @paperclipai/plugin-sdk ensure-build-deps && tsc
--noEmit` in `server/` — 0 errors.
- `pnpm --filter server test
heartbeat-run-lease-release-terminalization.test.ts
recovery-stale-issue-lock-sweep.test.ts` — 12 tests pass.
User interface:
- `pnpm --filter @paperclipai/ui typecheck` — 0 errors.
- `pnpm exec vitest run ui/src/lib/liveIssueIds.test.ts
ui/src/lib/issue-chat-messages.test.ts` — 40 tests pass.
## Risks
- Low risk. The server change only forces a still-live run row to a
terminal status when the lease releases or when the recovery sweep
confirms the process is dead. It never overwrites an existing terminal
status, and it guards the recovery path with process metadata to avoid
terminalizing a live run.
- The user interface change is additive. The guard only suppresses live
state for a terminal issue and keeps queued and running issues live.
- No database migration. No change to any external endpoint.
## Model Used
- Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, tool use, and
code 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - First-run onboarding is the subsystem that turns a brand-new install
into a working company: it creates the company, its goal, a lead agent,
and that agent's first task
> - The existing `OnboardingWizard` carried all of that wiring
correctly, but its UI had drifted from the current design direction, and
a separate design prototype (`paperclip-onboard`) existed as a
standalone visual mock with no backend
> - Porting the prototype's *logic* would have thrown away working,
well-tested backend orchestration; leaving the two apart meant the
design never shipped
> - Separately, cloud and local (self-hosted) installs need meaningfully
different first runs — local has no sign-in and must let the user pick a
locally-installed CLI adapter — so a single linear wizard could not
serve both
> - This pull request rebuilds the presentational layer from the
prototype on top of the existing backend orchestration, and splits it
into two thin flow containers over a shared core
> - The benefit is that the shipped onboarding matches the intended
design, cloud and local can diverge without duplicating logic, and each
can later ship to a different app version while sharing one set of step
components
## Linked Issues or Issue Description
No existing issue — describing inline (feature request).
**What problem does this solve?**
Onboarding is the first thing a new user sees, and the shipped wizard
had drifted from the current design. In parallel, cloud and local
installs need different first-run paths: local has no hosted sign-in,
and its agent runs on a CLI adapter installed on the user's machine,
which the cloud path never has to ask about. There was no way to express
that difference without either forking the whole wizard or bolting
conditionals onto a single linear flow.
**Proposed solution**
Extract the onboarding step views and shell into a shared core, then
compose two thin flow containers (cloud and local) over it. Keep all
backend orchestration in the existing `useOnboardingFlow` hook so no
working logic is rewritten.
**Alternatives considered**
- *Single flow with a `variant` prop* — most DRY, but the two flows are
intended to ship on different app versions, and a shared file would have
to be split later anyway.
- *Two fully independent copies* — simplest per-flow, but every shared
refinement (spacing, motion, copy) would have to be made twice and would
drift.
## What Changed
- **Shared core** under `ui/src/components/onboarding/`:
`OnboardingScaffold` owns the full-screen shell and the single
`AnimatePresence` step crossfade, so both flows transition identically;
step views (Start / Company / Agent / Task), `FooterNav`, `AgentPreview`
and the motion constants are extracted for reuse.
- **`CloudOnboardingFlow`** — `start → company → agent → task`; mounted
in the real app via `OnboardingWizardVariant`. Behaviour matches the
retired wizard, including `previewMock` and the existing-company ("add
an agent") entry point.
- **`LocalOnboardingFlow`** — skips sign-in and adds an optional email
ask (with a privacy assurance), a local model/adapter step that hires
with `requireEnvProbe: true`, and a "star us on GitHub" interstitial
before completing. **Harness-only for now** — the real app still mounts
the cloud flow.
- **Deleted `OnboardingWizard.tsx`** (1,786 lines); updated its
Storybook stories and the `OnboardingWizardVariant` test to the new
components.
- **Orbiting 3D paperclip backdrop** behind the auth and welcome screens
(`three`), code-split so it only downloads on those screens; honours
`prefers-reduced-motion` and disposes its GL context on unmount.
- **`motion`** added for step transitions and the agent-capsule
choreography.
- Visual values routed through design tokens per `DESIGN.md`; `Stepper`
generalized to take a step total (backward compatible); `/design-guide`
page and the component index updated.
- **Standalone preview harness** (`ui/onboarding-preview.html`) with
`?flow=` and `?step=` for backend-free review, wired as a second Vite
rollup input.
- **Adapter env probe bound to the adapter it ran against.**
`hireLeadAgent` reused `adapterEnvResult` for any adapter, so when a
hire failed and the user picked a *different* local adapter and retried,
the previous adapter's verdict satisfied the `requireEnvProbe` guard
while the hire posted the new adapter's config — hiring it unprobed. The
cache is now keyed on the adapter type plus the exact config posted to
the test endpoint, the config is built once and shared by probe and
hire, a failed probe clears the cache, and `clearAdapterEnvResult()`
(called on adapter change) stops the step displaying a stale verdict.
Cloud is unaffected — it hires with `requireEnvProbe: false`. Reported
by Greptile.
- **E2E specs re-pointed at the new flow.** Four specs still drove the
deleted wizard (`onboarding`, `conference-room-typing-intro`,
`planning-mode-visual-verification`, `nux-phase4-screenshots`) and
failed with `element(s) not found` on `"Name your company"` /
`input[placeholder="Acme Corp"]`. Rather than repeat the new drive
sequence four times, `tests/e2e/onboarding-flow.ts` adds one driver per
step (`startCloudOnboarding`, `completeCompanyStep`,
`completeAgentStep`, `completeTaskStep`, `completeCloudOnboarding`) and
the specs import it, so the next flow change touches a single file. Two
now-dead `**/test-environment` route stubs went with it — the cloud flow
hires with `requireEnvProbe: false`, so that probe never fires.
## Verification
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `npx vitest run` over the onboarding suites
(`OnboardingWizardVariant`, `AgentCapsule`, `onboarding-launch`,
`onboarding-goal`, `onboarding-route`, `onboarding-adapter-config`) — 33
tests pass.
- `pnpm --filter @paperclipai/ui build` — succeeds; the three.js chunk
splits out separately (522 kB raw / 133 kB gzip) rather than entering
the main bundle.
- Both flows driven end-to-end in the preview harness in `previewMock`
(no database writes), plus the cloud flow rendered in the real
authenticated app at `/onboarding` to confirm the mount swap.
- The four re-pointed e2e specs pass locally against the new flow.
- New `ui/src/hooks/useOnboardingFlow.test.tsx` — 4 cases pinning the
adapter-probe cache (switch-adapter retry, cold path, explicit clear,
and the cloud flow's `requireEnvProbe: false`). Verified non-vacuous:
the switch-adapter case fails against the pre-fix code.
- Rebased onto current `master`; `pnpm-lock.yaml` is deliberately
**not** committed — `.github/workflows/pr.yml` regenerates it when a
manifest changes and shares it with downstream jobs as the `pr-lockfile`
artifact.
## Risks
- **Deleting `OnboardingWizard.tsx` is the one change that alters
existing app behaviour.** The cloud flow is intended to be
behaviour-equivalent, and its entry points are covered by the updated
`OnboardingWizardVariant` test, but this is the area to review most
closely.
- **Conflict risk with open PRs that touch the old wizard**: #9900,
#9501, #8982 and #6636 all modify
`ui/src/components/OnboardingWizard.tsx`, which this PR removes.
Whichever lands second will need its change re-applied to the new step
components. Flagging so ordering can be decided deliberately.
- **New dependencies**: `motion` and `three` (+ `@types/three`). `three`
is large, so it is lazily imported and code-split — it does not affect
the main bundle. Both are MIT.
- The **local flow is not reachable in the app** yet (harness/canary
only), so it carries no runtime risk today; wiring it up is a follow-up.
- The auth screens remain **presentational only** — they are not wired
to real auth, unchanged from before this PR.
- **Pre-existing, not introduced here:** `OnboardingWizardVariant`
renders outside `<Routes>` in `App.tsx`, so its `useParams()` never
resolves `:companyPrefix` and `/{prefix}/onboarding` opens the welcome
screen instead of jumping to the agent step. `master` has the identical
structure, so this PR faithfully ports existing behaviour; the working
"add an agent" entry is the launcher card behind the overlay, which is
what the screenshot spec drives. Worth a separate fix.
## Model Used
Claude Opus 5 (`claude-opus-5`) via Claude Code, with extended thinking
and tool use (repo search/edit, local test + build execution, and
browser-driven visual verification of the rendered flows). Portions of
the session also ran on `claude-opus-4-8` and `claude-fable-5`.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The agent detail page has a Dashboard tab. The Dashboard tab shows a
"Live Run" section for the agent's current heartbeat.
> - The "Live Run" section has two clickable pieces: the section heading
and the running row. Both pieces linked to the same run detail page.
> - Two controls that go to the same place waste a navigation affordance
and hide the task the agent runs.
> - This pull request splits the two destinations. The heading goes to
the run. The running row goes to the task.
> - The benefit is that a user reaches the run internals from the label
and the work item from the row, in one click each.
## Linked Issues or Issue Description
No public GitHub issue exists for this change. The description follows
the enhancement issue template.
**What existing behavior does this improve?**
The "Live Run" section on the agent detail page, Dashboard tab (the
`LatestRunCard` component in `ui/src/pages/AgentDetail.tsx`).
**Current behavior**
The "Live Run" heading and the running row both link to the run detail
page (`/agents/:agentId/runs/:runId`). The row shows the run code and an
invocation-source chip. There is a separate "View details →" link that
also goes to the run detail page. A user cannot reach the task the run
works on from this section.
**Proposed behavior**
The heading becomes a link to the run detail page and appends the short
run code, shown as `Live Run · <run code>`. The redundant "View details
→" link is removed. The running row links to the task detail page when
the run's context snapshot resolves to a known issue, and the row then
shows the task status glyph, the task slug, and the task title. A pure
timer heartbeat with no resolvable task keeps the previous behavior: run
code plus source chip, linking to the run detail page.
**Reason and benefit**
The heading and the row now go to distinct, intuitive destinations. A
user reaches the run internals from the label and the work item from the
row, each in a single click. The fallback keeps heartbeats with no task
readable and avoids a blank or broken row.
## What Changed
- Made the "Live Run" / "Latest Run" heading a `Link` to the run detail
page and appended the short run code (`run.id.slice(0, 8)`) in a mono
span, formatted `Live Run · <run code>`. Kept the pulsing live dot.
- Removed the redundant "View details →" link.
- Changed the running row `Link` target to the task detail page
(`/issues/:identifier`) when a task resolves, falling back to the run
detail page otherwise.
- Resolved the task from the run context snapshot
(`contextSnapshot.issueId`, falling back to `contextSnapshot.taskId`)
against a `Map` of the agent's assigned issues threaded in from
`AgentOverview`.
- When a task resolves, replaced the run code and source chip in the row
with the task status glyph (`StatusGlyph`), the task slug, and the task
title. Kept the running spinner, the run status badge, and the
timestamp.
## Verification
- `pnpm check:token-gates` → 3/3 gates clean.
- `pnpm --filter ui typecheck` → passes.
- `pnpm --filter ui exec vitest run
src/pages/AgentDetail.progress.test.ts
src/pages/AgentDetail.instructions.test.tsx` → 10/10 pass.
- Manual (needs a reviewer with a browser): open an agent detail page →
Dashboard tab.
- For a live issue-execution run: the heading reads `Live Run · <run
code>` and opens the run detail page; the row shows the task status
icon, slug, and title and opens the task detail page.
- For a pure timer heartbeat with no task: the row falls back to run
code + source chip and opens the run detail page. No blank row.
- Confirm both states in light and dark mode.
## Risks
Low risk. The change is presentational and scoped to one component. The
task lookup is defensive: it reads the context snapshot with a fallback
key and only renders the task row when the issue is present in the
already-loaded assigned-issue set, so an unknown or missing issue
degrades to the previous run-detail behavior rather than breaking.
## Model Used
- Provider: Anthropic (Claude).
- Model: claude-opus-4-8 (Opus 4.8).
- Context window: 200K.
- Reasoning mode: extended thinking, tool use.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents can select company skills and synchronize them to adapter
runtimes
> - The skill sync API replaced the complete selection without an
explicit destructive choice
> - Company package import also replaced conflicting skills by default
> - These defaults could remove operator edits during setup and import
reruns
> - This pull request adds explicit assignment merge modes and safe
package conflict handling
> - The benefit is that reruns preserve operator work unless the caller
explicitly requests replacement
## Linked Issues or Issue Description
**What existing behavior does this improve?**
This change improves agent skill synchronization and company package
import.
**Subsystem affected**
This is a cross-cutting change across the shared contracts, server, CLI,
and UI.
**Current behavior**
Agent skill synchronization replaces the full desired skill set from a
modeless request. Package import replaces a conflicting skill when the
caller does not select a conflict mode.
**Proposed behavior**
Agent skill synchronization requires `add`, `remove`, or `replace`.
Package import skips conflicts by default. Each imported skill reports
whether it was created, renamed, replaced, or skipped.
**Reason and benefit**
Setup and import reruns must preserve operator edits by default.
Explicit destructive modes make data loss less likely and make each
outcome inspectable.
**Breaking changes**
Callers of the agent skill sync API must now send `mode`. Callers that
need the former behavior must send `replace`. Package import now uses
`skip` when `onConflict` is absent.
## What Changed
- Added required `add`, `remove`, and `replace` modes to the shared
agent skill sync contract.
- Added actionable `422` validation for missing or invalid modes.
- Updated first-party UI and CLI callers with explicit modes.
- Changed package skill conflict handling to use `skip` by default.
- Kept plugin-owned and built-in stock skill imports on explicit
`replace`.
- Added created, renamed, replaced, and skipped results to company
imports.
- Added regression coverage for merge modes and package conflict
outcomes.
## Verification
- `pnpm check:token-gates`
- `pnpm -r typecheck`
- `pnpm build`
- `pnpm test:run:serialized` (128 suites passed)
- `pnpm --filter @paperclipai/skills-catalog test` (20 tests passed)
- Focused agent skill route, company skill service, portability, CLI,
and UI tests passed.
- GitHub CI passed build, typecheck, canary, all general and serialized
test shards, all browser shards, policy, security, and final
verification on commit `2cfbb3e4c5`.
- Greptile reviewed the latest commit at 5/5 with zero unresolved
threads.
## Risks
- This change intentionally rejects modeless agent skill sync requests.
- The safe package default can leave an existing skill unchanged where
the old default overwrote it.
- All first-party callers now select a mode. Regression tests cover each
outcome.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI `gpt-5.6-sol` through Codex. The runtime used agentic
reasoning, tool use, code execution, and repository editing. The runtime
did not expose the context window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Apps give agents governed access to external tools.
> - The Apps gallery lists Notion, but the server required manually
configured OAuth credentials.
> - Notion's hosted MCP server supports OAuth discovery and dynamic
client registration.
> - Notion also requires HTTPS or a loopback HTTP redirect URI.
> - This pull request adds a direct Notion MCP OAuth path with PKCE and
reusable dynamic clients.
> - It also adds the current Apps UI states for connect and
reauthorization.
> - The benefit is a secure Notion connection with no manual client
credential setup.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The Apps gallery, Apps connect route, OAuth token lifecycle, and managed
MCP gateway.
**Subsystem affected**
`server/`, `packages/shared/`, `scripts/`, and `ui/`.
**Current behavior**
The Notion gallery cards are disabled. The server uses the classic
Notion OAuth endpoints and requires operator-supplied client
credentials. It does not register an OAuth client from provider
metadata. Concurrent refreshes can also replay a rotating refresh token.
**Proposed behavior**
Enable the Notion Apps flow. Discover OAuth metadata from
`https://mcp.notion.com/mcp`. Register and reuse a public RFC 7591
client with PKCE. Require HTTPS or loopback HTTP callbacks. Serialize
refreshes, store each rotated refresh token before the new access token
can be used, and show a reconnect state for `invalid_grant`.
**Reason and benefit**
Operators can connect the built-in Notion MCP app without creating or
copying OAuth credentials. Paperclip keeps dynamic clients and rotating
tokens in the company secret store.
**Breaking changes**
None. Explicit environment client credentials still take priority.
Existing Slack and Linear OAuth endpoint hints remain unchanged. Other
OAuth apps remain disabled unless they are allowlisted.
**Additional context**
PR #10910 is a related, broader Connections v3 wizard replacement. This
PR is the focused current Apps flow. The MCP Tool Gateway and Connected
Apps items in `ROADMAP.md` cover this planned capability.
## What Changed
- Classify all 20 reviewed Notion MCP tools with provider-scoped read
and write defaults.
- Require approval for selected Notion mutations, including move,
duplicate, and convert actions that generic verb matching missed.
- Preserve company-scoped connection and catalog resolution for Notion
profiles and policies.
- Add RFC 7591 dynamic client registration with
`token_endpoint_auth_method=none` and mandatory PKCE.
- Store the dynamic client ID on the connection and store any returned
client secret in the company secret store.
- Reuse the registered client for later connects and keep explicit
environment credentials as the first choice.
- Discover protected-resource and authorization-server metadata from the
Notion MCP endpoint.
- Add `redirectConstraints: "https-or-loopback-http"` to the generated
Notion app definition and shared contract.
- Reject non-loopback plain HTTP callbacks before network access with a
TLS setup error.
- Serialize client registration and token refresh operations within the
server process.
- Store a rotated refresh token before publishing the refreshed access
token.
- Treat `invalid_grant` as terminal and move the connection to a clear
reauthorization state.
- Add focused coverage for registration reuse, callback constraints,
refresh rotation, and terminal grants.
- Enable the Notion Apps route and add connect, redirect, success,
error, and reconnect UI states.
- Keep non-allowlisted OAuth apps blocked and cover the UI policy with
regression tests.
## Verification
- The focused Notion policy integration test passed with embedded
PostgreSQL.
- The focused 20-tool classification test passed.
- The server typecheck passed on the governance head.
- `pnpm -r typecheck` passed on the rebased head.
- `pnpm --filter @paperclipai/server typecheck` passed after the
security follow-up.
- `pnpm exec vitest run server/src/__tests__/tool-access-service.test.ts
-t \u0027DCR|refresh tokens|invalid_grant|abandoned lease\u0027` passed
10 focused security tests.
- `pnpm build` passed on the rebased head.
- `pnpm exec vitest run server/src/__tests__/tool-access-service.test.ts
-t 'OAuth|oauth'` passed 14 tests.
- `pnpm exec vitest run packages/shared/src/app-definitions.test.ts`
passed 5 tests.
- The complete server group passed 3,686 tests with 4 skipped.
- The complete UI group passed 3,656 tests.
- The full local runner found one environment-only CLI failure because
this agent runtime injects static AWS credentials into a test that
expects `AWS_PROFILE` only. `env -u AWS_ACCESS_KEY_ID -u
AWS_SECRET_ACCESS_KEY pnpm exec vitest run
cli/src/__tests__/secrets.test.ts` passed all 8 tests.
- The prior UI verification passed 55 focused tests, `pnpm
check:token-gates`, the Storybook build, and review of six 1440 x 1000
screenshots.
- OAuth request sequence: protected-resource metadata `GET
https://mcp.notion.com/.well-known/oauth-protected-resource/mcp`;
authorization metadata `GET
https://mcp.notion.com/.well-known/oauth-authorization-server`; dynamic
registration `POST https://mcp.notion.com/register`; authorization `GET
https://mcp.notion.com/authorize`; token exchange and refresh `POST
https://mcp.notion.com/token`; MCP traffic `POST
https://mcp.notion.com/mcp`.
- The live metadata and registration probe confirmed that Notion accepts
HTTPS and loopback HTTP redirects. It rejects a plain HTTP private
hostname.
- A later QA task owns the full browser consent and managed gateway
tool-list dry run against a configured HTTPS deployment.
## Risks
- Notion can add tools. Unrecognized names use the generic classifier,
and new or changed risky tools stay quarantined after connection
activation.
- A deployment that uses a private non-loopback hostname must configure
HTTPS before it can connect Notion.
- Dynamic registration creates a provider-side client. Paperclip reuses
it because registration does not provide a standard delete operation.
- Refresh coordination uses a database CAS lease across service
instances. An unclean crash leaves an uncertain lease and requires
reconnect instead of risking refresh-token replay.
- The current Apps surface overlaps with PR #10910. Merge order can
require a small conflict resolution if that PR lands first.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex on a GPT-5 runtime. The exact deployment ID and context
window are not exposed. The runtime used reasoning, repository tools,
code execution, and network tools.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task view shows a threaded conversation between the user and the
agent, with agent replies rendered as bubbles that carry a "✓ Worked · N
tools" summary line.
> - The conference-room chat already offers per-message copy and
thumbs-up / thumbs-down feedback, but the redesigned task thread dropped
these controls from the agent bubble footer.
> - Users lose a quick way to copy an agent reply or send feedback on
it, and the redesign silently ignored the feedback-vote props it was
already given.
> - This pull request prepends a copy · thumbs-up · thumbs-down cluster
to the bubble summary line and wires the existing feedback-vote props
through.
> - The benefit is a consistent feedback surface across both chat views,
with no new API.
## Linked Issues or Issue Description
No public GitHub issue exists for this change; the underlying issue is
described inline below following the feature template
(`.github/ISSUE_TEMPLATE/feature_request.yml`).
#### Problem or motivation
The redesigned task thread renders each agent reply with a "✓ Worked · N
tools · <timestamp>" summary line, but it dropped the copy and thumbs-up
/ thumbs-down controls that the conference-room chat still shows. Users
can no longer copy an agent reply or vote feedback from the task thread.
The redesign component already received `feedbackVotes` and `onVote`
props but ignored them.
#### Proposed solution
Prepend a copy · 👍 · 👎 cluster to the summary line, leading the
always-visible timestamp, reusing the shared `IssueChatFeedbackButtons`
so both chat views speak the same feedback language. Anchor the cluster
to the turn's summary row (a sibling of the expandable tool-history
fold) so it stays on the summary line whether the tool history is
collapsed or expanded.
#### Alternatives considered
Placing the cluster inside the expandable fold — rejected because
expanding the tool history then re-centered the actions to the middle of
the tall fold.
#### Roadmap alignment
UI polish to the task thread; no core-roadmap overlap.
## What Changed
- Add `TaskChatBubbleActions`: a copy · thumbs-up · thumbs-down cluster
built on the shared `IssueChatFeedbackButtons`.
- Render the cluster on the agent bubble's "✓ Worked · …" summary line,
leading the timestamp; runless agent replies get the same cluster with
the timestamp trailing. Human and system bubbles are unchanged.
- Add a `leading` slot to `TaskChatTurn` so the actions sit on the
summary row, a sibling of the tool-history fold, and stay anchored when
the fold expands.
- Wire the redesign to the `feedbackVotes` / `onVote` props it already
received.
- Add a demo binding in the `TaskChatLab` dev harness.
## Verification
- `pnpm check:token-gates` — 3/3 gates CLEAN.
- `pnpm typecheck` — clean across all packages.
- `cd ui && pnpm vitest run
src/components/task-chat/TaskChatBubble.test.tsx
src/components/task-chat/TaskChatTurn.test.tsx` — 31/31 pass.
- Manual: open a task thread, confirm the copy / 👍 / 👎 cluster shows on
the agent bubble summary line before the timestamp, copy works, votes
toggle, and the cluster stays on the summary line when the tool history
is expanded.
Visual change: snapshot baselines are intentionally not updated, per the
`doc/design/DECISION-SHEET.md` entry "Per-change snapshot verification
demoted to dormant (Jul 13 2026)".
## Risks
Low risk. UI-only change scoped to the redesigned task-chat bubble
footer. It reuses an existing shared feedback component and existing
vote props; no API, schema, or server change. Human and system bubbles
are untouched.
## Model Used
Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, tool use.
## 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
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Tasks and issues carry a `priority` field that renders across many
product surfaces: the detail header, the Triage properties panel, Kanban
and thread cards, the New Task composer, list Sort/Group/Filter menus,
search filters, and the dashboard chart.
> - Product feedback found the priority level adds visual noise and
decision cost without clear value in day-to-day task flow.
> - We want to remove priority from the interface, but keep the data
model, API, validation, and search DSL fully intact so the choice is
reversible with no migration.
> - This pull request hides every priority indicator and control behind
one compile-time flag, `SHOW_TASK_PRIORITY_UI`, set to `false`.
> - The benefit is a calmer, simpler UI now, with a single-boolean
revive path and zero data loss.
## Linked Issues or Issue Description
<!-- No public GitHub issue exists. Describing in-PR per the feature
template. -->
**Subsystem affected**
The web UI (`ui/src`): issue detail, properties panel, Kanban/thread
cards, New Task dialog, issues list Sort/Group/Filter menus, search
filter bar/sheet, dashboard charts, and the design-guide showcase.
**Problem or motivation**
The task/issue priority level appears across many surfaces and adds
visual clutter and decision overhead without pulling its weight in
normal task flow. We want it gone from the interface without discarding
the underlying data or breaking anything that depends on it.
**Proposed solution**
Add a single compile-time UI flag, `SHOW_TASK_PRIORITY_UI` (default
`false`), and gate every priority indicator and control behind it. Leave
the data model, API params, Zod validation (including the `"medium"`
default), and the search filter DSL untouched. Reviving priority is a
one-line flip of the flag back to `true`.
**Alternatives considered**
Deleting the priority code and schema outright. Rejected: it is
irreversible, needs a data migration, and throws away a field the API
and search still support. A gated flag keeps the change reversible and
low risk.
**Roadmap alignment**
UI simplification. This is a presentation-only change; it does not alter
core agent or data behavior.
## What Changed
- Added `ui/src/lib/ui-flags.ts` exporting `SHOW_TASK_PRIORITY_UI:
boolean = false` (typed `boolean` so gated branches are not flagged as
dead code).
- Gated the priority row in the Triage properties panel and the editable
priority control in the issue detail header (plus its skeleton seed).
- Gated the per-card priority icon in `KanbanBoard` and in
`IssueThreadInteractionCard`.
- Hid the priority chip and the mobile "more" menu priority section in
the New Task dialog. The submit path still sends the `"medium"` default.
- Removed the Priority options from the issues list Sort and Group-by
menus; the comparator and grouping logic stay dormant.
- Hid the Priority sections in the issue filters popover and in the
search filter bar and sheet. The `priority:` search DSL and filter state
stay functional at the data layer.
- Suppressed active-filter priority pills for consistency.
- Gated the "Tasks by Priority" dashboard chart and the design-guide
priority showcase subsection.
- Left activity-feed "changed priority" history text intact as a
historical record.
- Updated call-site tests to assert priority UI is absent while the flag
is off, added focused hidden-surface tests, and added a test that proves
creating a task still persists `priority: "medium"`.
## Verification
- `pnpm check:token-gates` — all 3 gates clean.
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `pnpm --filter @paperclipai/ui exec vitest run` on the touched
surfaces (IssueProperties, IssueFiltersPopover, IssuesList,
NewIssueDialog, IssueDetail, PriorityIcon and its interaction test) —
all green under `TZ=UTC`.
- Manual: with the flag off, priority does not appear in the detail
header, Triage panel, New Task composer, Sort/Group/Filter menus, or the
dashboard chart. Creating a task still persists `priority: "medium"`,
and the `priority:` search token still filters at the data layer.
## Risks
Low risk. The change is presentation-only and additive: no data model,
API, validation, or search-DSL changes. The priority code paths remain
compiled and tested; flipping `SHOW_TASK_PRIORITY_UI` to `true` restores
the full UI. Visual snapshot baselines are intentionally not updated per
the `doc/design/DECISION-SHEET.md` entry "Per-change snapshot
verification demoted to dormant (Jul 13 2026)".
## Model Used
Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, with tool use / code execution in an agentic coding
harness.
## 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 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
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip can run as a self-hosted app or as a Cloud-managed tenant.
> - These modes need different sign-out sequences because Cloud owns
three sessions.
> - Several visible controls implemented sign-out separately and could
choose different paths.
> - This pull request adds one Cloud-aware sign-out action and moves
every visible control to it.
> - The benefit is one safe sign-out path in Cloud and unchanged local
sign-out in self-hosted deployments.
## Linked Issues or Issue Description
Refs #2073. That older PR adds a separate company-settings sign-out
surface. This change centralizes the existing account, company, and
instance-settings surfaces and preserves the self-hosted behavior
described there.
**What happened?**
Visible sign-out controls used separate implementations. A Cloud-managed
control could call the app-local sign-out endpoint and open the local
auth page. That path did not enter the Cloud-owned logout sequence for
the tenant, Cloud, and identity sessions.
**Expected behavior**
Every visible sign-out control must use one action. Cloud-managed
instances must navigate the top-level window to the same-origin
`/cloud/logout` route without a local sign-out call first. Authenticated
self-hosted instances must keep the local sign-out API and cache
invalidation behavior.
**Steps to reproduce**
1. Open a Cloud-managed tenant.
2. Use the account menu, company menu, or instance-settings sign-out
control.
3. Observe that independently implemented controls can enter different
sign-out paths.
**Paperclip version or commit**
The problem reproduces at `656ecfa585b31938e2685ffab3db22e794474803`.
**Deployment mode**
Cloud-managed tenant built from source. The regression tests also cover
authenticated self-hosted mode.
## What Changed
- Added `useSignOut` as the shared Cloud-aware sign-out action.
- Navigated Cloud-managed sessions to `/cloud/logout` exactly once
without calling local auth first.
- Preserved local API sign-out and cache invalidation for authenticated
self-hosted sessions.
- Migrated the account menu, company menu, and instance general settings
to the shared action.
- Added focused tests for mode selection, menu closure, pending state,
failure state, and settings behavior.
## Verification
- `pnpm exec vitest run ui/src/hooks/useSignOut.test.tsx
ui/src/components/SidebarAccountMenu.test.tsx
ui/src/components/SidebarCompanyMenu.test.tsx
ui/src/pages/InstanceGeneralSettings.test.tsx` — 26 tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — passed.
- `git diff --check origin/master..HEAD` — passed.
## Risks
- Low risk. The change centralizes existing behavior and adds no schema,
API, telemetry, or style-token changes.
- Cloud mode depends on the existing health decision. Tests pin both
mode branches.
- The change does not alter Fetch Metadata, CSRF, cookie, prefetch, or
return-URL protections owned by the Cloud logout route.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5. The runtime does not expose a more specific model
ID or context-window size. The agent used high-reasoning mode, shell
tools, and API tools.
## 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
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The task chat thread renders issue comments, system notices, and run
transcripts
> - The server routes comment payloads through the run-secret redaction
walker before it sends them
> - The walker rebuilds each object with `Object.entries`, and this
collapses `Date` instances to `{}`
> - The chat renderer then calls `.toISOString()` on an invalid date and
throws, and the thread falls back to the error banner
> - This pull request keeps `Date` instances intact in redacted
responses and makes the renderer safe against bad timestamps
> - The benefit is that task threads with system notices render
correctly again
## Linked Issues or Issue Description
**What happened**
Task threads that contain a system notice showed the banner "Chat
renderer hit an internal state error." in place of the conversation.
This occurred on many tasks.
**Expected behavior**
The thread renders all comments and system notices with correct
timestamps.
**Steps to reproduce**
1. Open a task that has at least one system notice comment (for example
a "Workspace ready" notice).
2. `GET /api/issues/{id}/comments` returns `createdAt: {}` for every
comment because the secret-redaction walker collapses `Date` objects.
3. The system-notice row calls `new Date({}).toISOString()`. This throws
`RangeError: Invalid time value` and trips the thread error boundary.
**Version / deployment**
Regression from #9934 (`e43f187ca`). It applies to all deployments that
include that commit.
## What Changed
- `server/src/services/run-secret-redaction.ts`:
`redactRegisteredSecretValues` now returns `Date` instances as-is. Dates
hold no redactable text, and the `Object.entries` rebuild turned them
into `{}`.
- `ui/src/components/IssueChatThread.tsx`: the system-notice row formats
its timestamp with a new `toValidIsoString` helper. A value that does
not parse as a date now degrades to "no timestamp" instead of a render
crash.
- Regression tests at three layers:
- Walker unit tests: `Date` values survive with and without registered
secret values.
- Route test: `GET /issues/:id/comments` serializes `createdAt` /
`updatedAt` as ISO strings.
- Render test: a system notice with a malformed `createdAt` renders
without the error boundary.
## Verification
- `npx vitest run --root server
src/__tests__/run-secret-redaction.test.ts` — 5 passed.
- `npx vitest run --root server
src/__tests__/issue-comment-redaction.test.ts` — 4 passed (embedded
Postgres route test).
- `cd ui && npx vitest run src/components/IssueChatThread.test.tsx
src/components/IssueChatThreadSystemNotice.test.tsx
src/lib/issue-chat-messages.test.ts` — 121 passed.
- Each new test was run against the unfixed code and failed there, which
confirms it guards the regression.
- A local sweep rendered 47 real issue threads through
`IssueChatThread`: 7 tripped the boundary before the fix, 0 after.
## Risks
- Low risk. The server change only preserves `Date` objects that the
walker destroyed before. String redaction behavior does not change, and
the registry-key stripping does not change.
- The UI change only affects the timestamp of system-notice rows and
omits it when the value is invalid.
## Model Used
- Claude Fable 5 (`claude-fable-5`), Anthropic. Agentic coding session
with tool use (file edits, shell, Vitest). No extended-context or
special reasoning mode.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task detail view shows the conversation as chat bubbles. The
requester's own messages sit in a solid accent-colored bubble.
> - The bubble container sets `text-white`, but the message body renders
through `MarkdownBody`. Tailwind prose tokens (`--tw-prose-body`) win
over the inherited container color.
> - `prose-invert` only lightens the prose text in dark mode. In light
mode the prose body stayed its default dark color, so the text read as
near-black on the blue bubble and was hard to read.
> - This pull request maps the human bubble's prose tokens to the
inherited text color in both themes.
> - The benefit is that the requester's chat text is readable
white-on-blue in light mode, and dark mode stays exactly as it was.
## Linked Issues or Issue Description
<!-- No public GitHub issue exists; described in-PR per the bug report
template. -->
**What happened?**
In light mode, the text inside the user's own chat bubbles in the task
detail view rendered as dark (near-black) on the solid blue accent
background. This made the requester's messages hard to read.
**Expected behavior**
The text inside the user's accent-colored chat bubbles should be white
in light mode, matching the bubble's `text-white` intent. Dark mode
already rendered correctly and should not change.
**Steps to reproduce**
1. Open a chat-style task detail view in light mode.
2. Post a message as the requester (human) so it renders in the solid
blue accent bubble.
3. Observe the body text renders dark on blue instead of white.
**Paperclip version or commit**
Reproduces on `master` (branched from `814cb3367`).
**Agent adapter(s) involved**
Not adapter-specific (core UI bug).
## What Changed
- Add the existing `paperclip-markdown-on-accent` class to the
human-branch `MarkdownBody` in `TaskChatBubble.tsx`. This class (already
used by `IssueChatThread` for the same accent bubble) maps prose
body/heading tokens to `currentColor`, so the text follows the bubble's
`text-white` in both themes.
- Apply the same class to the human-branch `MarkdownBody` in
`TaskChatDescriptionBubble.tsx` (the description-as-first-bubble
surface) for consistency.
- Add unit tests covering that the human accent bubble carries the
on-accent class and the agent/neutral bubbles do not.
## Verification
- `pnpm check:token-gates` → 3/3 CLEAN.
- `pnpm --filter ./ui vitest run
src/components/task-chat/TaskChatBubble.test.tsx` → 9/9 passing.
- Manual: in light mode, the requester's chat bubble text renders white
on blue; agent/neutral bubbles unchanged; dark mode unchanged.
This is a visual change. Snapshot baselines are intentionally not
updated, per `doc/design/DECISION-SHEET.md` → "Per-change snapshot
verification demoted to dormant (Jul 13 2026)".
## Risks
Low risk. The change is scoped to the human-branch `MarkdownBody`
className on two chat-bubble components and only remaps prose color
tokens to the inherited text color. Agent and neutral bubbles are
untouched, and dark mode behavior is unchanged.
## Model Used
Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, ~200K context
window, extended thinking mode, with tool use / code 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Issues use `in_review` to request a final decision from an
authorized writer
> - The server rejected an assignee agent that tried to close its own
review, even when the issue had no independent-review rule
> - This rejection stopped the default agent workflow and did not
represent the configured execution-stage rules
> - Paperclip needs an open default and explicit issue-level constraints
for teams that require an independent or human verdict
> - This pull request removes the unconditional rejection and adds
`anyone`, `not_creator`, and `human_only` review policies
> - The benefit is a working default path with opt-in, authenticated
verdict controls
## Linked Issues or Issue Description
Refs #10635, #4429, and #10671.
The related public work covers execution-stage independence,
self-approval fallback behavior, and durable review paths. This change
is distinct. It controls who can resolve an issue review verdict. It
keeps configured execution stages active.
## What Changed
- Added a nullable `review_policy` issue column. Null has the same
meaning as `anyone`. The migration does not backfill existing issues.
- Added shared create, update, response, and compact issue contracts for
`anyone`, `not_creator`, and `human_only`.
- Removed the unconditional agent self-approval rejection for
`in_review` issues.
- Added one reusable verdict-actor check for terminal status changes and
pending interaction accept or reject actions.
- Used the authenticated principal type for `human_only`. Agent keys and
run tokens remain agent principals.
- Used the latest transition into `in_review` to identify the requester
for `not_creator`.
- Added actionable 403 responses that name the policy, the allowed
actor, and the next step.
- Kept the configured execution-stage transition and signoff behavior.
- Added focused contract, helper, status-route, interaction-route, and
execution-stage regression tests.
- Updated the implementation specification for the new issue field.
## Verification
- `pnpm exec vitest run packages/shared/src/validators/issue.test.ts
server/src/__tests__/issue-review-policy.test.ts
server/src/__tests__/issue-stalled-review-decision-routes.test.ts
--reporter=dot` passed: 42 tests.
- `pnpm --filter @paperclipai/shared typecheck` passed.
- `pnpm --filter @paperclipai/db typecheck` passed, including migration
numbering and safety checks.
- `pnpm --filter @paperclipai/server typecheck` passed.
- `pnpm run typecheck:build-gaps` passed across server, CLI, plugin
SDK/examples, plugin wiki, and UI.
- `git diff --check origin/master...HEAD` passed.
- SecurityEngineer review approved the authenticated-principal checks
and accepted policy-relaxation tradeoff with no required changes.
- Greptile reviewed the latest head at 5/5 with zero inline comments or
follow-ups.
- The latest-head GitHub rollup passed build, typecheck,
server/workspace tests, serialized suites, canary, e2e, and external
security checks.
## Risks
- The migration adds one nullable text column. It has no default and no
backfill.
- `not_creator` reads the latest recorded transition into `in_review`.
It denies the verdict when it cannot identify the requester.
- Agents can change or relax `reviewPolicy` when they have issue write
access. This is intentional for this issue-level control.
- Null and `anyone` do not add a database query to the verdict path.
- Configured execution-stage checks still run after the issue-level
policy check.
> This work aligns with the completed "Agent Reviews and Approvals" and
"Enforced Outcomes" roadmap items. It does not add a new roadmap
capability.
## Model Used
- OpenAI Codex, GPT-5. The exact deployment ID and context-window size
are not exposed to the agent. The run used reasoning, repository tools,
code execution, and GitHub CLI access.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents ask humans for decisions through interaction blocks: plan
confirmations, structured questions, suggested tasks, and checkbox
prompts
> - Each agent writes these decision prompts in its own style, so
operators can get long or unclear text at the exact moment they must
decide
> - There is no instance-level control that makes agents use a
controlled language for these decision points only
> - This pull request adds an experimental setting that tells agents to
write all user-interaction content in ASD-STE100 Simplified Technical
English, with the context the user needs and the effect of each choice
> - The benefit is faster, clearer human decisions, with agent thinking
and normal responses unchanged
## Linked Issues or Issue Description
Refs #10410 (optional `/simplified-english` skill in the skills catalog;
this PR adds the instance-level toggle for interactions).
**Problem or motivation**
Agent-posted user interactions (plan confirmations, structured
questions, suggested-task proposals, checkbox prompts) are written in
each agent's default style. Operators who want fast, unambiguous
decisions have no way to ask agents to use a controlled language for
exactly those decision points.
**Proposed solution**
Add an experimental instance setting,
`enableSimplifiedEnglishInteractions` ("Simplified English
Interactions"). When it is on, the server sets
`simplifiedEnglishInteractions: true` in the heartbeat wake payload. The
shared wake-prompt renderer, used by every adapter, then emits a
directive: write all user-interaction content in ASD-STE100 Simplified
Technical English, state what information the user needs to decide, and
state what happens for each choice. The directive applies to interaction
content only. Thinking, comments, documents, and other responses keep
their usual style.
**Alternatives considered**
Per-agent instructions work today, but someone must maintain them on
every agent. An instance-level toggle applies uniformly and turns off in
one place. Server-side rewriting of interaction payloads was rejected:
post-hoc translation is lossy and cannot add the decision context that
only the agent has.
**Roadmap alignment**
Extends the experimental settings surface with another opt-in
agent-behavior refinement, consistent with existing prompt-side flags.
## What Changed
- Added `enableSimplifiedEnglishInteractions` to the experimental
instance-settings zod schema, mirror type, and feature catalog (default
off, tier preference) in `packages/shared`.
- Server `instance-settings.ts` normalizes the flag on both read
branches; `heartbeat.ts` reads it once and passes
`simplifiedEnglishInteractions` into `buildPaperclipWakePayload`.
- Shared adapter renderer
(`packages/adapter-utils/src/server-utils.ts`): added the field to
`PaperclipWakePayload`, normalization, and an `- interaction language
(experimental): ...` directive emitted in both fresh and resume prompt
lanes, so one injection point covers all adapters.
- UI: new experimental settings card "Simplified English Interactions"
in `ui/src/pages/InstanceExperimentalSettings.tsx`, in alphabetical card
order; fixtures updated.
- Tests: renderer coverage for flag on/off in both lanes, plus
schema/catalog/UI fixture updates.
## Verification
- From the repo root: `node_modules/.bin/vitest run
packages/adapter-utils` (88/88), `packages/shared` validators (25/25),
server instance-settings + heartbeat suites (47/47 and 70/70 consumer
tests), `ui` settings tests (32/32).
- Typecheck is clean in all four touched packages.
- Manual check: turn the flag on in Settings → Experimental, wake an
agent, and confirm the wake prompt contains the interaction-language
directive; turn it off and confirm the directive is absent.
## Risks
- Low risk: the flag defaults to off, and the only behavior change is
one extra directive line in the wake prompt when an operator turns it
on.
- The directive is advisory to the agent; models can still deviate from
STE. No data or API shape changes; no migration.
## Model Used
- Claude Fable 5 (Anthropic, model ID `claude-fable-5`), extended
thinking, agentic tool use via Claude Agent SDK.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the control plane people use to manage AI-agent
companies.
> - Agents can encounter credentials during work.
> - Directly creating live secrets or bindings would bypass human
governance.
> - Proposal records must remain inert and separate from live secret
resolution until an authorized human approves them.
> - Approval must reuse the existing secret-create and protected
agent-config write paths.
> - This pull request adds the propose, review, approve, and reject
lifecycle.
> - The benefit is that agents can safely hand credentials into
Paperclip without exposing plaintext or gaining authority to activate
them.
## Linked Issues or Issue Description
Follow-on to #9921, which established run-bound agent secret access.
**Problem / motivation:**
Agents can receive credentials during work. There is no governed way for
them to propose a credential or binding without exposing plaintext in
work artifacts or immediately creating live access.
**Proposed solution:**
Store agent-authored proposals outside live secret tables. Encrypt each
proposed value and register exact-value redaction when Paperclip
receives it. Require an authorized human to approve or reject each
proposal. Approval executes through the normal write paths as the human
approver. Binding proposals can target only the proposer or its downward
reporting chain under the restrictive V1 policy.
**Alternatives considered:**
We rejected live secrets with a `proposed` status. That design would put
untrusted rows in resolver, list, and sync paths. It would also allow
uniqueness squatting. We rejected direct agent binding writes because a
binding is an agent-config write and must keep the existing human
permission gate.
**Roadmap alignment:**
This change extends the run-bound agent secret-access foundation in
#9921 with a governed proposal workflow.
## Security Verdict
Q0 SecEng verdict: **PASS-with-required-changes**. The review accepted
the separate proposal-table design and required the implementation to:
- fail closed unless both encryption and exact-value run redaction
registration succeed;
- scrub ciphertext idempotently on reject, withdraw, and expiry, with
audit-visible state;
- treat agent justification as hostile input and foreground action,
target, provenance, and approver permissions;
- snapshot and re-check the target agent plus reports-to chain at
approval to prevent org-chart laundering;
- make cascade approval atomic and fail closed if either secret creation
or binding authorization fails;
- deny low-trust, `skill_test`, `task_bridge`, and non-run-bound sources
consistently; and
- execute approval through the normal human secret/config write paths,
including protected-change gates.
Those requirements are implemented and covered by focused service,
route, and UI tests. Residual V1 risk remains the accepted 14-day
encrypted retention window. Proposal-time redaction also cannot clean a
value that leaked before the propose call.
## What Changed
- Added `company_secret_proposals`, migration `0207`, shared proposal
contracts, and a state-machine service for create, approve, reject,
withdraw, cascade, expiry, and ciphertext scrubbing.
- Added run-bound agent proposal routes and board review routes. The
routes derive provenance from authentication and enforce source
restrictions, company isolation, chain-of-command checks,
approval-as-approver, wake-on-resolution, and dual audit trails.
- Added durable per-run exact-value redaction registration so proposal
values remain redacted on later read surfaces.
- Added the Secrets **Proposals** tab and agent configuration **Proposed
access** rows. The UI shows fingerprint and length only. It also frames
agent justification as untrusted input, runs permission preflight,
supports approve and reject actions, and confirms cascades.
- Updated OpenAPI, agent skill guidance, API reference documentation,
and focused server and UI regression coverage.
- Rebased the branch onto current `master` and renumbered the proposal
migration after `0206`.
## QA Acceptance Results
Q5 QA verdict: **PASS — 9/9 acceptance criteria met**, with one Minor
non-blocking follow-up.
- **AC1:** proposed values never echo, never appear in live
lists/resolvers, and expose only fingerprint + length to board
reviewers.
- **AC2:** restrictive `self_and_reports` matrix passes: self/downward
allowed; upward/lateral denied.
- **AC3:** secret approval uses the normal create path, honors rename
overrides, records proposer/approver provenance, and scrubs ciphertext.
- **AC4:** approved bindings materialize and resolve through the target
agent's runtime list/fetch routes.
- **AC5:** pending-secret bindings require cascade; cascade succeeds
atomically and permission failures leave nothing applied.
- **AC6:** reject, withdraw, dependent rejection, and expiry paths scrub
ciphertext and preserve reasons/audit state.
- **AC7:** token/source and approver denial matrix passes through live
checks plus focused route tests.
- **AC8:** proposal lifecycle events and reused
`secret.created`/config-write events form the required dual audit trail;
origin-issue notification and wake are queued.
- **AC9:** both review surfaces render and execute correctly; UI
approval materializes the binding.
QA also confirmed zero plaintext occurrences for all exercised proposal
values in server logs. The single finding is that the company-level
`bindingTargetPolicy` toggle is not wired yet. V1 is hardcoded to the
restrictive `self_and_reports` policy. The matrix is correct and the
follow-up is tracked separately, so QA classified it as non-blocking.
## Verification
- Focused server proposal and redaction suite: 83 tests pass.
- Focused proposal review UI suite: 54 tests pass.
- Embedded-Postgres migration reapply test: 1 test passes with the
documented 30-second timeout.
- `pnpm --filter @paperclipai/db typecheck` passes, including migration
numbering and safety checks.
- `pnpm --filter @paperclipai/shared typecheck` passes.
- `pnpm --filter @paperclipai/ui typecheck` passes.
- `pnpm check:token-gates` passes with all gates clean.
- Q5 exercised the complete propose, review, approve, bind, and
runtime-resolve flow over real HTTP, JWT, and database paths. It
verified 9/9 acceptance criteria.
## Risks
- Proposal ciphertext is retained encrypted for up to 14 days while
pending. Terminal-state and expiry scrub paths reduce but do not remove
server-compromise risk during that window.
- The V1 target policy is restrictive but not yet company-configurable.
A separate follow-up owns that change.
- A new migration can require another renumber if another migration
lands before maintainers merge this pull request.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex coding agent. The exact runtime model ID and
context-window size are not exposed. The agent used reasoning,
repository editing, terminal execution, Paperclip API, and GitHub CLI
capabilities. Q3 UI work also records Claude Opus 4.8 assistance in its
commit trailers.
## 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 or instance-local Paperclip issues
or links
- [x] My branch name describes the change 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Chat-style tasks show an agent's plan in a dedicated "Plan" pane,
and a plan confirmation lets the user accept or request changes to that
plan
> - When an agent asked for confirmation but never actually published
the plan document (it only wrote the plan in a comment or a question),
the Plan pane rendered empty, and the confirmation call-to-action that
used to sit pinned at the bottom of the pane had disappeared
> - A user asked to confirm a plan they cannot see, with no visible CTA,
is stuck — the feature silently fails
> - This pull request closes the gap on both sides: it prevents plan
confirmations that don't point at a real, latest plan revision, it
teaches agents to publish the plan document before confirming, and it
restores the sticky confirmation action bar and an explanatory empty
state so the pane never goes silently blank
> - The benefit is that when a plan is expected, it reliably shows up in
the right pane with reachable accept/revise actions
## Linked Issues or Issue Description
<!-- No public GitHub issue exists; describing in-PR per the bug
template. -->
**Bug report**
- **What happened:** A task in planning mode could present a plan
confirmation while the Plan pane stayed empty (no plan document
rendered), and the plan-card confirmation CTAs that were previously
pinned to the bottom of the Plan pane no longer appeared.
- **Expected behavior:** When a plan is expected, the plan document
appears in the Plan pane; when a plan is genuinely missing, the pane
explains why rather than showing nothing; and the accept/request-changes
CTAs stay visible and reachable while the plan scrolls.
- **Steps to reproduce:** Put a task in planning mode with the
chat-style task view enabled, have an agent create a plan confirmation
without first publishing the `plan` document, and open the Plan tab —
the pane is blank and the confirmation actions are missing.
- **Deployment mode:** Local dev and self-hosted; UI + server.
Related PR (not a duplicate): #9609 "Pin pending confirmations by
composer" pins confirmations in a different surface (the composer); this
PR restores the Plans-pane action bar and the server/agent guarantees
behind it.
## What Changed
- **Server:** Reject a `request_confirmation` whose target is a plan
document unless a plan document exists and the target points at its
*latest* revision, so a confirmation can never reference a plan the pane
cannot render (`readPlanTarget` is now exported for reuse).
- **Agent instructions:** The CEO and default agent instruction bundles
now spell out a plan-publish contract — publish the `plan` document,
re-`GET` it and capture `latestRevisionId`, then create the confirmation
targeting that revision; never present a plan only in a thread comment
or via `ask_user_questions`.
- **UI — sticky CTAs:** Restore the plan confirmation action bar pinned
to the bottom of the Plans tab so accept/revise stay reachable while the
plan scrolls.
- **UI — diagnostics:** Keep the Plan tab visible whenever an issue is
in planning mode (even before a plan document exists) and show an empty
state explaining why the pane is empty instead of rendering nothing.
- **UI — annotations:** Add a `panelPlacement="inline"` mode so the
plan-document annotation panel renders in document flow instead of as a
floating side panel when hosted in the narrow task properties pane.
## Verification
- `pnpm check:token-gates` → 3/3 CLEAN
- `pnpm typecheck` → clean (all packages)
- UI: `pnpm --filter @paperclipai/ui exec vitest run
src/components/issue-properties/IssuePlanConfirmationActionBar.test.tsx
src/components/IssueProperties.test.tsx
src/components/IssueDocumentAnnotations.test.tsx` → 69 passed
- Server: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/issue-thread-interaction-routes.test.ts
src/__tests__/agent-skills-routes.test.ts` → 60 passed
- Manual: with a planning-mode task, the Plan tab stays visible, shows
the plan document (or a diagnostic empty state), and the confirmation
CTAs stay pinned at the bottom.
Visual note: snapshot baselines are intentionally not updated — per
`doc/design/DECISION-SHEET.md` "Per-change snapshot verification demoted
to dormant (Jul 13 2026)". The `storybook-visual` label is intentionally
not added.
## Risks
Low-to-moderate. The server change adds a validation gate on
plan-document confirmations: an interaction that targets a stale or
nonexistent plan revision is now rejected with a 422 instead of being
created. This is the intended guarantee, but any caller that relied on
creating such confirmations will now need to publish the plan document
first (which the updated agent instructions cover). UI changes are
additive to the Plans tab and gated by the existing chat-style-task
experimental flag.
## Model Used
Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, with tool use (file editing, 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task detail page shows a breadcrumb header with the task status
glyph and the task title.
> - The breadcrumb did not show the task identifier, so a reader could
not name the task without opening extra context.
> - Agents and people refer to tasks by identifier, so the identifier
belongs next to the title.
> - This pull request renders the task identifier in the breadcrumb
header, between the status glyph and the title.
> - The benefit is faster reference: a reader sees the task key and the
title together at the top of the page.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The task detail breadcrumb header. It shows the status glyph and the
task title, but not the task identifier.
**Subsystem affected**
Web UI — the breadcrumb bar on the task detail page
(`ui/src/components/BreadcrumbBar.tsx`,
`ui/src/context/BreadcrumbContext.tsx`, `ui/src/pages/IssueDetail.tsx`).
**Current behavior**
The breadcrumb header renders the status glyph and then the task title.
The task identifier does not appear in the header.
**Proposed behavior**
The breadcrumb header renders the task identifier between the status
glyph and the title. The identifier uses gray monospace styling from
design tokens (`font-mono text-muted-foreground`).
**Reason and benefit**
A reader can name and reference the task from the header without opening
more context. The identifier and the title appear together.
**Breaking changes**
None. The identifier field is optional. Crumbs without an identifier
render as before.
## What Changed
- Add an optional `identifier` field to the `Breadcrumb` type and
include it in the `breadcrumbsEqual` comparison so an identifier change
triggers a fresh render.
- Add a `CrumbIdentifier` helper in `BreadcrumbBar` that renders the
identifier in gray monospace (`font-mono text-muted-foreground`), placed
after the leading status glyph in each crumb variant.
- Wire the issue identifier onto the task crumb in `IssueDetail`.
- Add unit tests that cover the identifier field in `breadcrumbsEqual`
(fresh render on change, no-op on identical value).
## Verification
- `pnpm check:token-gates` → 3/3 gates CLEAN (color literals, arbitrary
bracket values, raw font-size).
- `pnpm --filter @paperclipai/ui exec vitest run
src/context/BreadcrumbContext.test.tsx` → 4/4 tests pass.
- `pnpm typecheck` → the four changed files typecheck clean.
- Manual: open a task detail page. The breadcrumb header shows the
status glyph, then the task identifier in gray monospace, then the
title.
Visual change. Snapshot baselines are intentionally not updated, per
`doc/design/DECISION-SHEET.md` → "Per-change snapshot verification
demoted to dormant (Jul 13 2026)".
## Risks
Low risk. The change is additive and the identifier field is optional.
It touches only the breadcrumb header rendering and the equality check.
No data model or API change.
## Model Used
Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, tool use enabled.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip supports self-hosted and Cloud-managed authenticated
deployments
> - A Cloud-managed tenant uses the Cloud harness to own the full
browser session
> - The account menu treated every authenticated deployment as Cloud and
called the local sign-out API first
> - This pull request uses the existing Cloud health metadata as the
mode gate
> - Cloud-managed sign-out now starts the harness logout round trip with
a top-level navigation
> - Self-hosted authenticated sign-out keeps the existing local API flow
> - The benefit is a complete Cloud logout without changing self-hosted
behavior
## Linked Issues or Issue Description
Related prior work: Refs #10802.
**What happened?**
The account menu called the app-local sign-out endpoint before it moved
an authenticated browser to the Cloud logout route. It also used
authenticated deployment mode as the Cloud test. This test included
self-hosted authenticated instances.
**Expected behavior**
A Cloud-managed tenant must navigate the top-level browser directly to
`/cloud/logout`. A self-hosted authenticated instance must keep the
app-local sign-out flow.
**Steps to reproduce**
1. Open a Cloud-managed tenant.
2. Open the account menu.
3. Select **Sign out**.
4. Observe that the browser returns through the tenant auth route
instead of completing the Cloud logout round trip.
**Paperclip version or commit**
Reproduced on `master` after `76f442040c`.
**Deployment mode**
Paperclip Cloud-managed authenticated deployment.
## What Changed
- Read the existing Cloud instance metadata in the account menu.
- Navigate directly to `/cloud/logout` for Cloud-managed instances
without calling the local sign-out API.
- Keep the local sign-out API and cache refresh for self-hosted
authenticated instances.
- Add regression coverage for both sides of the mode gate.
## Verification
- `pnpm exec vitest run ui/src/components/SidebarAccountMenu.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
## Risks
- Low risk. The change is limited to the account-menu action.
- The Cloud branch depends on the existing `health.cloud` metadata that
already gates other Cloud UI behavior.
- The self-hosted regression test verifies that authenticated mode alone
does not select the Cloud route.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex based on GPT-5. The runtime provided agentic reasoning,
repository tools, shell execution, and test execution. The exact
internal model ID and context window are not exposed to the agent.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The agent Test action builds adapter config from the form.
> - The build-config parser kept plain and secret_ref bindings.
> - It dropped user_secret_ref bindings on the create path.
> - This PR shares one parser that keeps every binding shape.
> - The test path now sends the same env binding set that a real run
sees.
> - The benefit is one fix across every adapter build-config path.
## Linked Issues or Issue Description
**What happened?**
The agent Test action dropped a user-scoped env binding in create mode.
The same agent config worked in a real run. Related public PRs: #10115,
#9321, #9921, #8825.
**Expected behavior**
The Test action should keep user-scoped env bindings and resolve them
like a real run.
**Steps to reproduce**
1. Set a user-scoped env binding on an agent config form.
2. Run Test in create mode.
3. The probe runs without the variable.
**Paperclip version or commit**
c09d2509e3
**Deployment mode**
Local dev (pnpm dev)
**Agent adapter(s) involved**
Not adapter-specific (core bug)
**Database mode**
Embedded PGlite (default — DATABASE_URL unset)
**Additional context**
This change is not Claude-specific.
## What Changed
- Added a shared env binding parser in `@paperclipai/adapter-utils`.
- Replaced the eight adapter build-config copies with the shared helper.
- Kept `plain`, `secret_ref`, and `user_secret_ref` bindings intact in
create mode and edit mode.
- Preserved the runtime merge behavior from the earlier env merge
change.
## Verification
- Author-recorded test run:
`packages/adapter-utils/src/env-bindings.test.ts`
- Author-recorded test run:
`packages/adapters/claude-local/src/ui/build-config.test.ts`
- Author-recorded test run: six adapter build-config test files
- Author-recorded typecheck: `tsc --noEmit` for adapter-utils and the
eight adapter packages
- GitHub checks: all required PR checks pass on PR #10926.
- Greptile review: 5/5 with no open comments.
## Risks
- The change touches adapter config assembly.
- A wrong binding shape would change test-time probe input.
- Tests cover the binding types and the create-mode path.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5, code execution and repo inspection.
## 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 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 or
confirmed no documentation update is needed
- [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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Instance Settings area exposes an Experimental page that lists
opt-in feature toggles as cards.
> - New experimental features were appended to the list over time, so
the cards sat in insertion order with no predictable arrangement.
> - An unordered list is hard to scan when you are looking for one
specific feature.
> - Each card also carried a small "Experimental" secondary badge, which
is redundant on a page that is itself titled Experimental.
> - This pull request sorts every card alphabetically by its title and
removes that redundant badge.
> - The benefit is a list that is faster to scan and headings that are
less cluttered.
## Linked Issues or Issue Description
No public GitHub issue exists for this change, so the enhancement is
described inline following `.github/ISSUE_TEMPLATE/enhancement.yml`:
**What existing behavior does this improve?**
The Instance Settings → Experimental page, which lists opt-in feature
toggles as a stack of cards.
**Subsystem affected**
UI — the Instance Experimental settings page
(`ui/src/pages/InstanceExperimentalSettings.tsx`).
**Current behavior**
Cards render in insertion order (the order features happened to be
added), so finding a specific feature means scanning the whole list.
Several headings also carry a redundant "Experimental" secondary badge.
**Proposed behavior**
Cards render top-to-bottom in A→Z order by title, and no card shows an
"Experimental" secondary badge. Toggle logic, footnotes, conditional
visibility, and the "Managed by Paperclip Cloud" badge are unchanged.
**Reason and benefit**
Alphabetical order makes the list predictable and quick to scan for a
specific feature. The "Experimental" badge repeats information already
conveyed by the page title, so removing it declutters the headings.
**Breaking changes**
None. This touches card render order and the removal of a decorative
badge only — no state, persistence, toggle, or visibility logic changes.
## What Changed
- Sorted every card on the Instance Experimental settings page
alphabetically by its heading title.
- Removed the redundant "Experimental" secondary badge from the card
headings (previously on Apps, Cases, and Chat-Style Tasks).
- Added tests asserting the cards render in case-insensitive
alphabetical order and that no card renders an "Experimental" secondary
badge.
- No behavior change: toggle handlers, footnotes, managed-key handling,
and conditional cards (Conference Room Chat, worktree-scoped run) are
untouched and now sort into their alphabetical slots.
## Verification
- `pnpm check:token-gates` → all 3 gates CLEAN.
- `npx vitest run ui/src/pages/InstanceExperimentalSettings.test.tsx` →
32/32 tests pass (the suite renders the real component and now covers
ordering + badge removal).
- `pnpm --filter @paperclipai/ui typecheck` (`tsc -b`) → clean.
- Manual: open Instance Settings → Experimental. The cards read A→Z and
no card shows an "Experimental" chip.
## Risks
Low risk. The change is limited to one page component: card render order
and the removal of a decorative badge, plus new tests. No state,
persistence, toggle, or visibility logic is modified.
## Model Used
Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, tool use enabled.
## 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
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip separates workspace provisioning lifecycle from whether
the work was actually delivered.
> - Git ancestry alone cannot recognize squash merges or deliveries into
a branch other than the workspace base.
> - A merged pull request linked from a terminal issue is stronger
delivery evidence for those cases.
> - The read contract should expose that evidence without changing
persisted workspace schema.
> - Cleanup must remain conservative: terminal descendants, delivered
work, and no active run checkout are all required.
> - Reusing the existing cleanup primitives keeps service shutdown,
lease cleanup, activity logging, and archival behavior consistent.
> - Focused regression coverage locks in both the honest read signal and
the fail-closed reaper guards.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
Execution workspace close-readiness payloads and terminal workspace
cleanup.
**Current behavior**
Delivered squash-merged or cross-branch workspaces can remain `active`
and report a permanent “not merged” warning because git ancestry does
not contain their original commits.
**Proposed behavior**
Read payloads distinguish PR-confirmed delivery, ancestry delivery,
unmerged work, and unknown state. Fully terminal delivered workspace
trees are archived only when no active run holds the checkout.
**Reason and benefit**
Operators and automation receive an honest delivery signal, while
shipped worktrees stop looking active forever and genuinely unmerged
work retains its warning.
**Breaking changes**
The workspace payload gains a derived field. Existing fields and
persistence remain unchanged; no database migration is required.
**What happened?**
A delivered workspace can remain `active` and warn that it is not merged
forever after its issue ships through a squash or cross-branch pull
request.
**Expected behavior**
Pull-request delivery should be represented honestly, and a fully
terminal delivered workspace should become cleanup-eligible when no run
holds its checkout.
**Steps to reproduce**
1. Create an issue workspace with commits ahead of its configured base.
2. Deliver those commits with a squash merge or into a different target
branch.
3. Mark the source issue and descendants done, then read workspace close
readiness.
Before this change, the workspace remains active with a “not merged”
warning indefinitely.
## What Changed
- Added the derived `deliveryState` workspace contract: `merged_via_pr`,
`merged_by_ancestry`, `unmerged`, or `unknown`.
- Extracted a shared GitHub pull-request merge classifier and reused it
for merge confirmations and workspace delivery checks.
- Suppressed false ancestry warnings when a terminal issue has
ground-truth merged-PR evidence.
- Added an idempotent terminality reaper with descendant-terminal,
active-run, and delivered-work guards.
- Restricted PR delivery evidence to the source issue, then required
live merged state plus matching GitHub repository, head branch, and
current workspace HEAD; persisted status, stale PRs, lexical mentions,
inbound references, and descendant PRs cannot authorize cleanup.
- Preserved workspaces with modified or untracked files even when their
committed HEAD was delivered.
- Bounded both long-lived pull-request state caches to 1,000 entries
with oldest-entry eviction.
- Routed eligible workspaces through existing runtime shutdown, lease
cleanup, activity logging, and archival machinery with exclusive Git
index, HEAD, and branch-ref locks plus non-forced removal.
- Added regression coverage for delivery derivation, warning behavior,
reaper guards, scheduler wiring, and squash/cross-branch delivery.
## Verification
- `pnpm -r typecheck`
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/execution-workspaces-service.test.ts
src/__tests__/merged-pr-confirmation-sweep.test.ts
src/__tests__/server-startup-feedback-export.test.ts --reporter=verbose`
— 63 passed
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/execution-workspaces-service.test.ts --reporter=verbose`
after review hardening — 43 passed
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/execution-workspaces-service.test.ts
src/__tests__/merged-pr-confirmation-sweep.test.ts
src/__tests__/external-objects-service.test.ts --reporter=dot` on the
final local head — 73 passed
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-workspace-busy.test.ts --reporter=verbose` — 15
passed
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm test:run` —
server 3,662 passed (4 skipped), UI 3,599 passed, CLI 327 passed, shared
415 passed, and skills catalog 20 passed; the aggregate DB stage ran
both source and built copies of one unrelated embedded-Postgres
migration test and both reached its 5-second timeout
- `pnpm --filter @paperclipai/db exec vitest run
src/status-card-migrations.test.ts --reporter=verbose` — isolated
aggregate-timeout verification passed in 3.99 seconds
- `NODE_ENV=production pnpm build`
- `pnpm check:token-gates`
## Risks
The reaper intentionally fails closed when issue terminality,
pull-request state, git ancestry, or checkout ownership cannot be
proven. GitHub lookups can delay classification and cleanup but cannot
cause an unproven workspace to be archived. Automated terminal archival
holds exclusive Git index, HEAD, and branch-ref locks across validation
and removal, skips configured destructive hooks, and uses non-forced
removal so dirty writes fail closed. Reopening a source issue does not
restore an archived workspace; it emits an audit event so a human or
agent can re-provision explicitly.
## Model Used
OpenAI Codex, GPT-5. The runtime did not expose a more specific model ID
or context-window size. Reasoning, tool use, repository editing, test
execution, and GitHub CLI access were enabled.
## 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 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - When an agent run ends without recording a disposition, Paperclip
raises a "missing disposition" handoff so the work does not silently
stall
> - The server already tracks whether such an issue has a live
continuation (a running or queued run, or a queued wake) in
`successfulRunHandoff.hasLiveContinuation`
> - But no UI surface read that flag, so an issue that an agent was
actively working on still showed the "This task still needs a next step"
banner, a loud thread warning, and "Needs next step" badges
> - This pull request makes every missing-disposition complaint respect
liveness: warn only when no live agent is on the issue and it is really
stuck
> - The benefit is that users see the warning only when action is
needed, and the noise disappears while an agent is already handling the
issue
## Linked Issues or Issue Description
No public GitHub issue exists for this bug. Description follows the
bug-report template:
**What happened?**
An issue that a live agent run was actively working on showed the
"missing disposition" warning banner, a loud thread notice, and "Needs
next step" badges at the same time. The API payload for that issue
showed `successfulRunHandoff.required: true` together with
`hasLiveContinuation: true` and a `liveRunId`, but the UI ignored the
liveness fields.
**Expected behavior**
The missing-disposition warning appears only when the issue has no live
run or queued wake. A live agent records a disposition when its run
ends. Paperclip complains only if the run ends and no disposition
exists.
**Steps to reproduce**
1. Let a run finish on an in-progress issue without a disposition.
Paperclip raises the handoff and queues a corrective wake.
2. Open the issue page while the corrective run (or any new run) is
live.
3. See the banner, the badges, and the loud thread notice — all visible
while the agent works.
**Paperclip version or commit**
Current `master` (reproduced at commit 6ffe9df842).
**Deployment mode**
Self-hosted development instance.
## What Changed
- `isSuccessfulRunHandoffRequired` (ui lib) returns `false` while a live
continuation exists. This quiets the Kanban card badge and the
issues-list badge. Exception: when the only continuation is a
not-yet-promoted scheduled retry, the notice stays visible so the
**Retry now** control stays reachable.
- `IssueBlockedNotice` also checks the real-time live-run set
(`liveIssueIds`). A run that starts after the issue payload was fetched
hides the banner at once.
- `IssueChatThread` derives an effective handoff state from the live
runs it already tracks. The loud "Missing issue disposition" thread
notice folds into the quiet collapsed row while a continuation is live,
and unfolds if the run ends without a disposition.
- Server: `hydrateSuccessfulRunHandoffLiveness` now hydrates escalated
handoffs too. The blocked-inbox `missing_disposition` attention is
suppressed for escalated handoffs with a live run or wake. This matches
the existing required-state suppression.
## Verification
- `cd ui && npx vitest run src/components/IssueBlockedNotice.test.tsx
src/components/IssueChatThreadSystemNotice.test.tsx
src/components/IssueChatThread.test.tsx` — 106 tests pass, including 6
new tests for the live/stale/scheduled-retry matrix
- `cd ui && npx vitest run src/components/IssuesList.test.tsx
src/components/KanbanBoard.test.tsx src/lib` — pass
- `cd server && npx vitest run
src/__tests__/issue-blocker-attention.test.ts
src/__tests__/issue-list-assignee-filter-routes.test.ts
src/services/recovery/successful-run-handoff.test.ts
src/__tests__/attention-service.test.ts` — pass, including new
escalated-liveness cases
- `pnpm typecheck` clean in `ui` and `server`; `node
scripts/check-token-gates.mjs` clean
- Manual check: a live issue's API payload showed `required: true` with
`hasLiveContinuation: true` and a `liveRunId` while the banner was still
on screen; with this change that state renders no complaint
## Risks
- Behavioral shift only; no schema or migration changes. All complaints
reappear as soon as the continuation stops without a disposition, so
nothing can get lost permanently.
- A queued wake counts as a live continuation. If a wake sits queued for
a long time, the warning stays hidden for that time. The blocked-inbox
path already behaved this way; the UI now matches it.
- The scheduled-retry carve-out keeps the current Retry-now workflow
intact.
## Model Used
- Claude Fable 5 (`claude-fable-5`), Anthropic — agentic coding session
with extended thinking and 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)
- [ ] 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: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - On Paperclip Cloud a tenant instance holds exactly one company, and
the cloud control plane pushes the stack's uploaded workspace icon into
that company's branding.
> - The cloud-mode organization switcher trigger always rendered the
deterministic monogram, so an uploaded organization logo never appeared
in the app chrome.
> - This pull request renders the trigger through the tenant company's
logo and brand color, with the monogram as the fallback.
> - The benefit is that the logo a customer uploads for their
organization actually shows up inside their Paperclip app.
## Linked Issues or Issue Description
No existing public issue found (searched open/closed PRs and issues for
"organization logo", "switcher logo", "company logo cloud" — closest
related PR is #10850, which introduced the cloud-mode switcher).
Describing in-PR:
**Subsystem affected**
Board UI: the sidebar organization switcher in Paperclip Cloud mode
(`SidebarCompanyMenu`).
**Problem or motivation**
A Cloud customer uploads an organization logo when creating their
workspace; the control plane syncs it into the tenant company's branding
(`company.logoUrl`). But the cloud branch of the switcher trigger
rendered `StackIcon` — monogram-only by design for stack rows — for the
trigger too, ignoring `selectedCompany.logoUrl`. Result: the uploaded
logo never appears in the app chrome; users see a letter tile instead.
**Proposed solution**
Add a `CurrentStackIcon` for the trigger that passes the selected
company's `logoUrl`/`brandColor` into `CompanyPatternIcon`, seeded by
the stack display name. Falls back to the exact previous monogram when
no logo is set. Stack rows are unchanged: the portfolio payload
deliberately carries no hot-linkable icon URL for other stacks.
**Alternatives considered**
Fetching per-stack icons for the rows was rejected: the cloud portfolio
payload carries no icon URLs (embedding signed, expiring control-plane
URLs would be wrong), and the defect is the current organization's
chrome, which the already-synced company logo covers.
## What Changed
- `ui/src/components/SidebarCompanyMenu.tsx`: cloud-mode trigger renders
the tenant company logo (fallback: monogram); stack rows untouched;
self-hosted path untouched.
- `ui/src/components/SidebarCompanyMenu.test.tsx`: new regression test
that the trigger carries the company logo while stack rows keep
monograms; the `CompanyPatternIcon` mock now exposes `logoUrl`.
## Verification
- `pnpm --dir ui exec vitest run
src/components/SidebarCompanyMenu.test.tsx`: 12/12 pass (11 existing + 1
new).
- `pnpm --dir ui exec tsc --noEmit`: clean.
## Risks
- Cloud-only rendering branch; self-hosted trigger rendering is
untouched.
- If the branding sync has not run yet, the trigger shows the same
monogram as before — no regression, and it upgrades in place once
`logoUrl` arrives.
## Model Used
- Claude Fable 5 (`claude-fable-5`), Anthropic. The run used extended
reasoning, repository tools, shell execution, and GitHub integration.
## 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
- [ ] All Paperclip CI gates are green
- [ ] 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: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators often open self-hosted Paperclip over plain HTTP on a LAN
or private network.
> - Browser Clipboard API writes are not reliable in that insecure
context.
> - Paperclip already has one shared helper with a legacy copy fallback,
but many current copy actions bypass it.
> - This pull request routes every core UI copy action and the
first-party workspace-diff plugin through the shared helper.
> - The benefit is consistent copy behavior on HTTPS, localhost, and
plain-HTTP private deployments.
## Linked Issues or Issue Description
Refs #3529.
This change supersedes the stale prior attempt in #3531. Current master
has more copy surfaces and a first-party plugin UI bridge that the prior
branch does not cover.
## What Changed
- Replaced direct Clipboard API writes and duplicate fallback
implementations across the current core UI with `copyTextToClipboard`.
- Added an HTTP-safe clipboard function to the plugin UI SDK and wired
the host bridge to the same implementation.
- Migrated the first-party workspace-diff plugin to the plugin SDK
clipboard function.
- Added unit coverage for native rejection fallback and plugin host
delegation.
- Added a source-level regression test that rejects new direct clipboard
writes outside the shared implementation.
- Documented the plugin UI clipboard function.
## Verification
- `NODE_ENV=test pnpm exec vitest run ...` for 14 affected suites: 164
tests passed.
- `pnpm exec vitest run tests/ui-clipboard.test.ts` in
`packages/plugins/sdk`: 1 test passed.
- `NODE_ENV=test pnpm -r typecheck`: passed for 31 workspace projects.
- `NODE_ENV=test pnpm test:run`: passed.
- `NODE_ENV=production pnpm build`: passed.
- `pnpm check:token-gates`: passed with all gates clean.
## Risks
Low risk. Secure contexts still use the modern Clipboard API. Plain HTTP
and rejected modern writes use the existing `execCommand("copy")`
fallback. That API is deprecated, but it is the compatibility path
required for insecure contexts. The change has no schema, API, or visual
design effect.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, `gpt-5.6-sol`. The runtime did not expose a context-window
size. Reasoning, tool use, repository editing, test execution, and
GitHub CLI access were enabled.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators use issue pages to read task state and control task work
> - The issue header showed separate summaries for open decisions and
review paths
> - These summaries repeated state that belongs in the Decisions view
> - The extra sections added noise before the issue description and
thread
> - This pull request removes both header summaries and keeps decision
actions in the Decisions view
> - The benefit is a simpler issue header with one place for decision
work
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The issue detail header shows separate pending-decision and review-path
sections.
**Subsystem affected**
`ui/` — React and Vite board UI.
**Current behavior**
An issue header can show a decision strip and a larger review panel
before the issue content.
**Proposed behavior**
The issue header does not show either decision section. Operators
continue to manage decisions and stalled reviews in the Decisions view.
**Reason and benefit**
This removes duplicate decision state from the issue header and reduces
visual noise.
**Breaking changes**
The issue page no longer provides these summaries or shortcuts. Decision
data, review state, and the Decisions view do not change.
## What Changed
- Removed the pending-decision strip and review-path panel from the
issue detail header.
- Deleted the two unused header components and the panel-specific test.
- Kept stalled-review actions and their Storybook examples in the
Decisions queue.
- Added an issue-detail regression test that covers both removed
sections.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/IssueDetail.test.tsx` (46 tests passed)
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
- `pnpm build-storybook`
- `git diff --check`
## Risks
- Low risk. This change removes two issue-header surfaces. It does not
change decision APIs or data.
- Users must open the Decisions view to find pending decisions and
stalled-review actions.
> This change does not duplicate planned core work in `ROADMAP.md`.
GitHub searches found no related open issue or pull request.
## Model Used
- OpenAI Codex, GPT-5. The exact deployment ID and context window are
not exposed. Tool use and code execution were enabled.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The decisions desk shows pending decisions that need an operator
response
> - A strict decision cannot apply its effects after its target task
changes
> - A decision still remained pending when every target task finished
after proposal
> - The card also linked only the origin task, even when the decision
acted on another task
> - This pull request expires those moot decisions and links their
target tasks
> - The benefit is an accurate queue and a clear path to the work that
each decision affects
## Linked Issues or Issue Description
Related PR: #10801 removes the issue-page decision strip, which makes
clear queue provenance more important.
**What happened?**
A strict decision stayed pending until its time-to-live limit after
every target task reached `done`. The decision card linked only the
origin task. The origin task is where the agent proposed the decision,
and it can differ from the task that the decision affects. An operator
could therefore open a finished task with no visible decision and no
explanation of the real target.
**Expected behavior**
Paperclip must expire a strict decision when all of its targets finish
after the decision is proposed. The card must show and link every target
task that differs from the origin task.
**Steps to reproduce**
1. Create a strict decision that targets an active task from a different
origin task.
2. Move the target task to `done` without resolving the decision.
3. Run the decision expiry sweep.
4. Observe that the old code keeps the decision open until its
time-to-live limit.
5. Observe that the old card links only the origin task.
**Paperclip version or commit**
The bug reproduces on upstream `master` before this pull request.
**Deployment mode**
Local dev and self-hosted server modes are affected because the behavior
is in the shared decision service and board UI.
## What Changed
- Expire an open strict decision with reason `target_completed` when
every strict target reached `done` after proposal.
- Keep decisions that intentionally target an already-finished task.
- Keep lenient-only decisions open.
- Keep continuation delivery consistent with other expiry reasons.
- Add target-task links to the decision card provenance line.
- Use one shared target-ID helper across signing, execution, expiry,
card provenance, and resolver preloading.
- Add service and UI regression tests for primary, secondary, and
target-completed cases.
## Verification
- `pnpm exec vitest run ui/src/components/DecisionCard.test.tsx
server/src/__tests__/decisions-service.test.ts` — 51 tests passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — all gates clean.
- `git diff --check origin/master...HEAD` — passed.
## Risks
- Low migration risk. This change does not alter the database schema.
- The expiry sweep performs the existing strict-target query and adds a
snapshot comparison before expiry.
- A decision remains open if any strict target is active or if a target
was already `done` at proposal time.
> The roadmap lists work queues as planned. This pull request fixes the
existing decisions desk. It does not add a new queue subsystem.
## Model Used
- Implementation: Anthropic Claude through Claude Code. The runtime did
not expose the exact model snapshot or context-window size. The model
used reasoning, repository tools, code execution, and test execution.
- PR preparation: OpenAI Codex with GPT-5. The runtime did not expose a
dated model snapshot or context-window size. The model used reasoning,
repository tools, code execution, and 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The board has two different pages for change history: a basic
Activity list and a rich Audit feed
> - The two pages show the same kind of information, so an operator must
guess which page to open
> - The basic list also caps at 200 rows and has no filters, so it hides
older changes
> - This pull request merges both pages into one Activity page that is
built on the rich audit feed
> - The page adds a scope toggle for all actors or agent actions only,
and it hides privileged controls from members who do not have the audit
permission
> - The benefit is one obvious place to answer "who changed what", for
every member, with filters and full history
## Linked Issues or Issue Description
Related pull requests in this stack (open both before this one):
- Refs #10830 — adds the company prefix to the board audit route.
- Refs #10831 — adds the two-tier all-actors scope to the audit
endpoint. This pull request calls that scope.
This branch is stacked on those two pull requests. The diff therefore
shows their commits until they merge. After they merge, this pull
request contains only the last two commits: the page merge and the
actor-label fix.
**Problem or motivation**
The board has two overlapping history pages. `/:company/activity`
renders a plain list that is capped at 200 rows and has no filters. The
audit page renders a filtered, paginated feed of agent actions, but it
is a separate sidebar item and it was reachable only by members with the
audit permission. A member who wants to know who changed an issue must
know which of the two pages answers the question.
**Proposed solution**
Keep one sidebar item, "Activity", and build it on the rich feed. Add a
scope toggle: "All activity" reads every actor kind, and "Agent actions"
keeps the earlier audit behavior. Put the scope in the `mode` query
parameter so a person can link to it. Show the responsible-user filter
and the CSV export only to callers that the server answers at the
privileged tier. Redirect the earlier audit paths to the merged page
with the agent scope preset, so old links continue to work. Delete the
plain list page.
**Alternatives considered**
Keeping both pages and adding filters to the plain list. That duplicates
the feed logic and keeps the "which page?" problem. Deleting the audit
page instead was also rejected, because the audit feed has the
pagination, filters, and export that the plain list does not.
**Roadmap alignment**
The roadmap marks the activity log and action attribution as shipped.
This change improves that shipped capability. It does not add a new
subsystem.
## What Changed
- Added a scope toggle to `AuditFeed`. "All activity" requests
`actorScope=all`, and "Agent actions" keeps the earlier agent-only
request. Cursor pagination works in both scopes.
- Stored the scope in the `mode` query parameter, so a person can
bookmark or share a scope.
- Made the page chrome permission-aware. The toggle, the
responsible-user filter, and the CSV export appear only when the server
answers at the privileged tier. A basic member sees the shared feed and
no upsell wall.
- Replaced the sidebar "Audit" item. The sidebar now has one "Activity"
item.
- Redirected `/:company/audit` and the unprefixed `/audit` to
`/:company/activity?mode=agents`.
- Deleted the earlier `ui/src/pages/Activity.tsx` list page and the
`CompanyAudit` page wrapper. Added `CompanyActivity` as the single route
target.
- Fixed the actor label for stripped rows. The basic tier removes the
agent id but keeps the actor kind, so every agent row rendered as
"System". Rows now fall back to the actor kind: "Agent", "User",
"Plugin", or "System".
- Widened the responsible-user filter control, which truncated its own
label.
- Resolved agent names on the basic tier. The basic tier removes the
privileged `agentId` but keeps the acting principal `actorId`, and the
company agent directory this page already reads is
authorization-filtered. The feed therefore resolves an agent actor from
`agentId` first and from an agent-typed `actorId` second. Hiding the
name only in the UI gave no confidentiality benefit, because any reader
could join the retained id against the readable directory. Agents that
the directory filters out still fall back to the generic kind label. No
server payload or permission was widened.
- Fixed a stuck state in the access-downgrade recovery. A downgrade
between cursor requests leaves full-tier and basic-tier pages in one
cache, which starts a single recovery refetch. If that refetch did not
clear the mix, the cached pages kept the condition true, the "Refreshing
audit access…" banner rendered permanently, and it hid the error state
together with its "Try again" button. The banner is now tied to an
outstanding attempt. The refetch effect also depended on the whole query
object, which changes identity every render, so it repeated the request
on each render; the attempt is now tracked in state and runs once per
downgrade.
- Kept the agent detail "Audit" tab unchanged. That tab passes a locked
agent id, which keeps the earlier privileged scope and hides the toggle.
The `GET /companies/:id/activity` endpoint stays. The dashboard still
reads it. This pull request does not change that endpoint.
## Verification
- `pnpm exec vitest run ui/src/pages/audit/AuditFeed.test.tsx
ui/src/App.activity-routing.test.tsx ui/src/lib/company-routes.test.ts
ui/src/components/Sidebar.test.tsx
server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-action-audit-routes.test.ts` — all tests
pass.
- New `ui/src/App.activity-routing.test.tsx` drives the real route
table. It asserts that the company activity path resolves, and that both
the company audit path and the unprefixed audit path reach the activity
path with the agent scope preset.
- New `AuditFeed` tests cover the scope toggle, the basic tier without
privileged chrome, the locked-agent case, the actor-kind fallback label,
basic-tier name resolution, and both downgrade-recovery paths (the
refetch errors, and the refetch returns a still-mixed pair).
- Mutation-checked the three new guards: disabling each one fails the
test that covers it, so none of them pass vacuously.
- `pnpm -r typecheck` is clean. Both design token gates are clean.
- Rendered every state in a browser at 1440x900 and at 390x844: both
scopes, the basic member view, the loading state, the error state, the
filtered-empty state, and the true-empty state. A designer reviewed the
renders and approved them.
## Risks
- The default company page now reads the all-actors scope, which returns
more rows than the earlier agent-only query. Cursor pagination and the
existing page limit bound each request.
- The page is now visible to every company member. The server decides
what each member sees. The UI only hides controls that the caller cannot
use. Refs #10831 for the server rules and tests.
- The basic tier now shows agent names that the previous revision
withheld. The name was already recoverable from the retained `actorId`
through the readable agent directory, so this closes an inconsistency
rather than widening access. A security reviewer chose this outcome over
stripping `actorId`.
- Old audit links now redirect. The redirect keeps the agent scope, so a
person who bookmarked the audit page sees the same rows.
- Low migration risk. There is no database change.
> The roadmap marks activity log and action attribution as shipped. This
change improves that existing capability.
## Model Used
Claude Opus 5 (`claude-opus-5`, 1M context) with extended thinking and
tool use, run through Claude Code.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators need one activity feed for human, agent, plugin, and
system changes
> - The existing audit endpoint returns only rows that have agent
attribution
> - The full audit view also requires a dedicated permission
> - This pull request adds an explicit all-actors scope with basic and
privileged access tiers
> - The benefit is that company members can inspect the shared activity
history while sensitive attribution and export controls stay protected
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The company audit activity endpoint and the board audit route.
**Subsystem affected**
Server REST API and board UI routing/API contracts.
**Current behavior**
The agent-action audit endpoint excludes activity without an agent ID.
It also rejects company members who do not have the full audit
permission.
**Proposed behavior**
Callers can opt into `actorScope=all`. A company member receives all
actor kinds with sensitive attribution fields removed. A permitted board
user receives complete rows and can use attribution filters. The default
scope and CSV permission remain unchanged.
**Reason and benefit**
The board needs one chronological activity source for user, agent,
plugin, and system actions. A two-tier response keeps the feed useful
without widening access to detailed attribution or export capabilities.
**Breaking changes**
None. The endpoint keeps the existing agent-only scope and permission
behavior by default.
## What Changed
- Added `actorScope=all` to the unified audit query and included
activity from every actor type.
- Added a company-readable basic tier that removes run,
responsible-user, agent, and details attribution.
- Kept attribution filters and CSV export behind
`audit:view_agent_actions`.
- Added route and integration coverage for basic readers, permitted
readers, pagination, filter denial, and all actor kinds.
- Added the missing unprefixed `/audit` redirect and company route
classification.
## Verification
- `pnpm exec vitest run server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-action-audit-routes.test.ts
ui/src/lib/company-routes.test.ts --reporter=verbose` (35 tests passed)
- `pnpm -r typecheck`
- `pnpm test:run`
- `pnpm build`
## Risks
- The all-actors query can return more rows than the legacy agent-only
query. Cursor pagination and existing limits bound each request.
- The basic tier intentionally exposes action and actor-kind context. It
removes detailed run, agent, responsible-user, and details attribution.
- The legacy endpoint behavior remains the default, which reduces
compatibility risk.
> The roadmap marks activity log and action attribution as shipped. This
change improves that existing capability and does not introduce a
separate workflow system.
## Model Used
- OpenAI Codex, `gpt-5.6-sol`, 114K context, agentic reasoning with tool
use and code 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI keeps company work under a company-prefixed route.
> - The Audit sidebar link used a bare `/audit` path.
> - The route helper treated `audit` as a company prefix because the
board-route list did not include it.
> - The router also had no redirect for a bare `/audit` deep link.
> - This pull request registers Audit in both places and adds regression
coverage.
> - The benefit is that the Audit sidebar link and old bare deep links
open the active company's audit feed.
## Linked Issues or Issue Description
Related PR: #9744
**What happened?**
The Audit sidebar link opened `/audit`. The router interpreted `AUDIT`
as a company prefix and showed the invalid-company page.
**Expected behavior**
The Audit sidebar link must open `/<company-prefix>/audit`. A bare
`/audit` deep link must redirect to the active company.
**Steps to reproduce**
1. Open a company board.
2. Select Audit in the sidebar.
3. Observe that the app opens `/audit` and shows an invalid-company
error.
**Paperclip version or commit**
Reproduced on master after #9744.
**Deployment mode**
Board UI in local or self-hosted deployments.
## What Changed
- Added `audit` to the board-route root list.
- Added the unprefixed `/audit` redirect route.
- Added regression tests for Audit prefixing, prefix extraction, and
relative-path conversion.
## Verification
- `pnpm exec vitest run ui/src/lib/company-routes.test.ts`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
- Manual check: select Audit in the sidebar and confirm the URL is
`/<company-prefix>/audit` and the audit feed renders.
## Risks
- Low risk. This change only reserves one existing board route and adds
one redirect.
- A company cannot use `AUDIT` as an issue prefix after this change.
That prefix already conflicts with the existing Audit board page.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex with GPT-5. The runtime does not expose a more specific
model ID or context-window size. The agent used reasoning, repository
tools, code execution, and 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents write to tasks they do not own. They comment, they change
fields, and the control plane now permits this by default for
standard-trust agents on any task they can read
> - This makes a task thread ambiguous. A reader sees a comment from an
agent that is not the assignee, but no surface says whose authority that
write rode
> - The same gap applies to field edits. The activity stream named the
verb, but it did not show the before value, the after value, or the
reason the write was permitted
> - The remaining refusals are also opaque. An agent that hits a wall
receives a 403 with no boundary name, no actor who can act, and no
sanctioned path. One real incident spent a full detour to find the
workaround
> - This pull request adds the three surfaces that make open cross-task
writes legible: an attribution chip, a field-level audit receipt, and an
actionable denial contract shared by the API and the UI
> - The benefit is that a reader can answer "who did this, on whose
authority, and was it allowed?" on the task itself, and a blocked writer
is told what to do next
## Linked Issues or Issue Description
No public issue exists for this work, so the enhancement is described
here.
**What existing behavior does this improve?**
Cross-task agent writes are permitted, but they are not explained. A
task thread can hold comments from agents that are not the assignee, and
the activity stream can hold field changes made by those agents. Neither
surface names the responsible user behind the write. When a write is
refused, the error text does not name the boundary or the way forward.
**Subsystem affected**
Issue detail UI (comment thread and activity stream), the issue write
authorization responses in the server, and the shared copy contract that
both consume.
**Current behavior**
- An agent comment on a task the agent does not own looks the same as an
assignee comment.
- An `issue.updated` activity row states the verb only. It does not show
the field-level before and after values, the responsible user, or the
authorization reason.
- A refused write returns a short message such as an ownership error.
The message does not state which rule fired, who is able to perform the
action, or which alternative path is sanctioned.
**Proposed behavior**
- An agent comment on a task the agent does not own carries a chip that
reads "for {user}". The chip names the responsible user. Its tooltip
states that the author is not the assignee and cannot exceed that user's
permissions.
- Each `issue.updated` row shows a receipt: the changed fields with
before and after values, the responsible user, and the authorization
reason. This applies to board edits as well as agent edits.
- Each refusal states three things: the boundary that fired, who is able
to act, and the sanctioned path. The API error body and the in-app
notice use the same words, because both read one shared contract.
Related pull requests, found by searching this repository:
- Refs #10837 — merged. It added the default-open cross-task write rule,
the comment attribution data, and the per-run containment cap that this
pull request makes visible.
- Refs #10114 — open. It proposes a narrower authorization change in the
same area.
- Refs #7998 — open. It proposes append-only cross-assignee comments as
an alternative to opening writes.
## What Changed
- Adds `packages/shared/src/issue-write-denial.ts`. This is one copy
contract for eight ways an issue write can be refused: not visible,
responsible-user ceiling, responsible user unavailable, excluded actor
class, assignee run lock, per-run cross-task cap, missing run context,
and rejected attribution. Each entry names the boundary, who can act,
and the sanctioned path.
- Maps server authorization decisions onto that contract in
`server/src/routes/issues.ts` and
`server/src/services/cross-issue-influence-limit.ts`. The flattened
`error` string carries all three obligations, and `details.code` lets
the UI render the same words. The two cap codes keep the names they
already ship under.
- Adds `CommentAttributionChip`. It renders "for {user}" beside the
author name on agent comments where the author is not the assignee. It
renders nothing when no responsible user is recorded, so older rows stay
clean. It is wired into both `IssueChatThread` and the flagged
`TaskChatThread` redesign.
- Adds `IssueFieldChangeReceipt`. It renders the change receipt under
`issue.updated` rows in the activity stream. Ids resolve to agent and
user names where the directory is loaded. Server-truncated text is
labelled as a preview, so the receipt never implies that it shows a
whole value.
- Adds `IssueWriteDenialNotice`. It renders the shared copy in the app,
keyed off the denial events the server logs on a task.
- Adds a public `/ux-lab/cross-issue-collaboration` page. It renders all
three surfaces and their edge cases for review without a seeded thread.
This follows the existing `ux-lab` pages.
## Verification
Automated, all green:
```
pnpm --filter @paperclipai/shared exec vitest run src/issue-write-denial.test.ts # 17 tests
pnpm --filter @paperclipai/ui exec vitest run src/components/IssueWriteDenialNotice.test.tsx \
src/components/IssueFieldChangeReceipt.test.tsx src/components/CommentAttributionChip.test.tsx \
src/lib/issue-change-receipt.test.ts src/lib/comment-attribution.test.ts # 46 tests
pnpm --filter @paperclipai/server exec vitest run src/__tests__/cross-issue-influence-limit.test.ts \
src/__tests__/issue-comment-attribution-audit-routes.test.ts \
src/__tests__/issue-agent-mutation-ownership-routes.test.ts \
src/__tests__/low-trust-red-team-routes.test.ts # 98 tests
```
`tsc --noEmit` passes for the shared, ui, and server packages.
Manual, in a browser:
1. Start the UI only: `pnpm --filter @paperclipai/ui exec vite`.
2. Open `/ux-lab/cross-issue-collaboration`. No session is needed,
because `ux-lab` routes are public.
3. All three surfaces were captured at 1440x900 in light mode and dark
mode, and at 390x844. The page reported no errors.
4. The chip tooltip was opened by a hover and by a keyboard focus.
Rendering the page found defects that the tests had missed. Three copy
and contrast defects were fixed, and two of them are now pinned by a
test. A design review then found three layout defects, which are also
fixed: the denial notice orphaned its label when a value wrapped, the
receipt icon wrapped onto its own line at narrow widths, and the chip
tooltip was reachable by hover only.
## Risks
Low risk, and additive.
- Every new surface renders nothing when its data is absent. Comments
without a recorded responsible user show no chip, and activity events
without a receipt show no receipt, so existing rows do not change.
- No migration is included. The data these surfaces read already ships.
- The wire values of the two per-run cap denial codes are unchanged.
Only the human-readable text changes, plus six codes that had no
`details.code` before.
- The denial copy is read by agents as well as people. If wording must
change later, one shared module is the only place to change it.
- Roadmap check: this extends the completed "Activity log & action
attribution" area rather than duplicating planned core work.
## Model Used
Claude Opus 5 (Anthropic), model id `claude-opus-5[1m]`, 1M context
window, extended thinking, with tool use and code execution. It ran as
an agent in Claude Code and drove a real browser to capture the review
screenshots.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators use the same board application in self-hosted and
Paperclip Cloud deployments.
> - A Cloud tenant contains one company, so an in-app company switch
does not change the active Cloud stack.
> - Cloud operators need the sidebar and company surfaces to use the
signed-in user's stack portfolio.
> - The server must derive Cloud identity and links from trusted
instance context instead of client input.
> - This pull request adds canonical Cloud context, a trusted stack
portfolio proxy, and Cloud-aware navigation.
> - The benefit is consistent stack switching on Cloud while self-hosted
company behavior stays unchanged.
## Linked Issues or Issue Description
**Subsystem affected**
Cross-cutting: server REST routes and the React board UI.
**Problem or motivation**
A Cloud-managed instance contains one company. The existing company
switcher could only switch records inside that tenant. It could not move
the operator to another Cloud stack. The existing header also gave long
organization names too little width.
**Proposed solution**
Expose a canonical public Cloud context in health data. Add a trusted
server proxy for the current user's stack portfolio. Use that data in
the board UI to switch stacks with top-level navigation. Keep the
existing company behavior on self-hosted instances. Move search into the
navigation and keep long organization names inside the sidebar panel.
**Alternatives considered**
An in-app `/stacks` route was rejected because Cloud tenant hosts
reserve that path and stack selection must wake or authenticate another
tenant. Client-supplied user identity was rejected because the server
can derive the trusted Cloud actor.
**Roadmap alignment**
This change advances the Cloud deployments milestone. It keeps the
product local-first and Cloud-ready without changing the self-hosted
mental model.
## What Changed
- Added canonical Cloud instance context and public health metadata.
- Added a Cloud-only stack portfolio proxy with trusted actor forwarding
and per-user caching.
- Prevented normal company creation on Cloud-managed instances.
- Switched the sidebar and Companies page from company actions to stack
actions on Cloud.
- Added full-page stack navigation and Cloud create-stack links.
- Moved search into the sidebar navigation so the organization name
keeps more width.
- Added truncation and hover recovery for long organization and stack
names.
- Added server and UI regression coverage for Cloud and self-hosted
behavior.
- Updated the implementation specification for the Cloud contracts.
## Verification
- `node scripts/check-token-gates.mjs` passed. All three token gates are
clean.
- `pnpm --dir server exec vitest run src/__tests__/health.test.ts
src/__tests__/cloud-instance.test.ts src/__tests__/cloud-routes.test.ts
src/__tests__/company-cloud-floor.test.ts
src/__tests__/company-portability-routes.test.ts` passed: 5 files and 66
tests.
- `pnpm --dir ui exec vitest run
src/components/SidebarCompanyMenu.test.tsx` passed: 1 file and 11 tests.
- Pre-PR QA report `7da87ca7` passed all 8 acceptance criteria with real
HTTP route factories and real Chromium screenshots in Cloud and
self-hosted modes.
- Security reviews passed for the canonical Cloud context and stack
portfolio proxy.
## Risks
- Cloud stack switching depends on the configured Cloud application and
tenant portfolio URLs.
- The new health `cloud` block is public by design, but it contains only
canonical public instance metadata.
- The stack proxy fails closed on self-hosted instances and derives the
user identity from the trusted actor.
- Self-hosted navigation and company creation retain their existing
paths and behavior.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, model `gpt-5`. The run used reasoning, repository tools,
shell execution, and GitHub integration. The deployment did not expose
its context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
- TypeScript editor integration surfaces the warning `Option 'baseUrl'
is deprecated and will stop functioning in TypeScript 7.0` on
`ui/tsconfig.json`.
- TS 5+ resolves `paths` relative to the `tsconfig.json` file when
`baseUrl` is absent.
- The existing `paths` entries already use `./` prefixes (`./src/*`,
`./node_modules/lexical/index.d.ts`), so removing `baseUrl: "."` is a
no-op at runtime.
- Clearing the warning now avoids the cliff when TypeScript 7 ships.
## What Changed
- Removed `"baseUrl": "."` from `ui/tsconfig.json`.
## Verification
- `pnpm --filter @paperclipai/ui typecheck` passes unchanged.
- `@/...` and `lexical` imports continue to resolve identically (same
prefixes work with or without `baseUrl` because they start with `./`).
## Risks
- None expected. `baseUrl` was only used for path-mapping resolution,
and every entry in `paths` is already relative.
## Checklist
- [x] Ran `pnpm typecheck` locally — passes
- [x] No runtime behavior change
- [x] Single-file, single-line cleanup
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents move issues to `in_review` and rely on a "review path" (an
interaction, an approval, a monitor, or a named reviewer) to tell them
who decides next.
> - That review path can silently disappear. A user comment supersedes
the pending interaction, a monitor is exhausted, or a run ends without
restoring a path. The issue then sits in `in_review` with nobody
reviewing it and no visible action.
> - Such issues become invisible zombies. Nobody knows a decision is
owed, so the work stalls forever.
> - This pull request makes the review path a maintained invariant,
exposes a `reviewAttention` surface, and gives every stalled review
three inline actions in the UI.
> - The benefit is that an `in_review` issue always shows who reviews
it, or shows an amber "nobody is reviewing this" notice with one-click
Approve, Request changes, and Send back to work.
## Linked Issues or Issue Description
This pull request describes the problem inline. The tracking issue is
internal.
**Subsystem affected**
The review and attention loop that agents and humans share: the
`in_review` status, the `reviewAttention` surface, the /decisions
attention feed, and the issue-page review panel.
**Problem or motivation**
Agent-owned issues in `in_review` can lose their last review path. A
user comment supersedes the pending interaction. A monitor is exhausted.
A run ends without restoring a path. The issue then sits in `in_review`
with no reviewer and no visible action. It becomes an invisible zombie
and the work never progresses.
**Proposed solution**
Maintain the review path as a server invariant. Expose a
`reviewAttention` field that says what is under review, who decides, and
since when. Render a persistent review panel on the issue page and
inline actions on the /decisions feed. Keep human PATCHes into
`in_review` ungated, but record the requesting user so the panel never
renders empty.
**Alternatives considered**
A pure background auto-recovery sweep. This stays opt-in and is not
enough on its own, because it is invisible to the human. A bare status
banner. This is rejected, because it gives no action to resolve the
stall.
**Roadmap alignment**
This improves the core review and attention loop that both agents and
humans use every day.
## What Changed
- **Server — maintained review-path invariant:** when an issue enters or
sits in `in_review`, the server derives and persists a review path
(interaction, approval, monitor, or the requesting user) and recovers a
stale path with one bounded wake instead of leaving the issue pathless.
- **Server — `reviewAttention` surface:** a new field describes what is
under review (bound target with links), who decides, since when, and
whether the review is stalled. Stalled agent-assigned reviews are now
included in the attention feed.
- **Server — inline stalled-review decisions:** secured routes let a
permitted responder Approve (→ `done`), Request changes (→ `todo` + wake
carrying the note), or Send back to work (→ `todo` + wake) directly from
the attention feed.
- **Server — resume-intent wake:** an `in_review -> todo` transition now
wakes the assigned agent so a resumed review is not dropped.
- **Server — user-entry symmetry:** user PATCHes into `in_review` stay
ungated (no 422 for humans) and record the requesting user, who becomes
the named responder when no other path exists.
- **UI — review panel:** a persistent `IssueReviewPanel` renders above
the thread whenever status is `in_review`. The covered state shows the
bound target, responder, and outcomes and hoists the pending
interaction/approval card. The stalled state shows the amber notice plus
the three actions.
- **UI — decisions card actions:** the same three actions render inline
on the /decisions `AttentionQueueRow`.
- **UI — responsive fix:** the stalled action row stacks to full-width
buttons at phone width and returns to a horizontal row at `sm` and up.
New 390px stories capture the phone layout.
## Verification
- `cd ui && npx vitest run src/components/IssueReviewPanel.test.tsx
src/components/AttentionQueueRow.test.tsx src/lib/attention.test.ts
src/api/issues.test.ts` — 91 tests pass.
- Server suites added and updated: `issue-review-attention`,
`issue-stalled-review-decision-routes`, `review-path-recovery`,
`recovery-observability`, and related route/liveness tests (run by CI).
- A designer reviewed the UI at 390px and desktop in light and dark
themes on both the issue-page panel and the /decisions card. The stalled
action row stacks cleanly at phone width with no overlap and keeps the
horizontal row on desktop.
## Risks
- **Migration:** adds migration `0200` (next after master `0199`, no
renumber). It extends the agent-wakeup-requests schema and is additive.
- **Behavioral shift:** `in_review -> todo` now dispatches a wake. This
is intended (resume intent) and covered by tests.
- **Authz:** the inline decision routes are permission-gated. Only a
permitted responder sees and can trigger the actions.
- Overall risk is moderate and contained to the review and attention
loop.
## Model Used
- Claude, Opus 4.8 (`claude-opus-4-8`), extended thinking, tool use and
code 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the control plane that lets humans govern companies of
AI agents.
> - Issue-thread interactions are the structured handoff point for
confirmations, questions, suggested tasks, and other governed decisions.
> - Those interactions previously assumed that only board users could
resolve them, preventing one agent from explicitly addressing another
agent for a response.
> - Agent resolution needs company-level governance, auditable resolver
identity, safe terminal-state handling, and attention routing so
authorization is enforced server-side rather than inferred from UI
behavior.
> - This pull request adds governed agent resolution, withdrawal and
terminal expiry semantics, explicit agent addressees, lifecycle
reconciliation, and attention-feed filtering.
> - The benefit is that agents can participate in structured decisions
without weakening board control, company isolation, wake behavior, or
audit invariants.
## Linked Issues or Issue Description
### Subsystem affected
Issue-thread interactions across database, shared contracts, server
authorization/services, adapter callbacks, agent skill guidance, API
docs, and UI governance surfaces.
### Problem or motivation
Structured interactions were board-only, had no explicit agent
addressee, and lacked durable withdrawal/terminal-expiry semantics. That
made peer-agent decisions impossible to authorize and audit safely.
### Proposed solution
Persist requested/effective resolver policy and addressee identity,
enforce company governance and eligible agent resolution, reconcile
addressee lifecycle changes, expose withdrawal and terminal expiry, and
route attention to the intended active agent with board fallback.
### Alternatives considered
Implicitly authorizing the issue assignee or mentioned agents was
rejected as ambiguous and difficult to audit. Using comments alone was
rejected because it loses structured outcomes and continuation behavior.
### Roadmap alignment
Supports the ROADMAP direction for lightweight leadership-agent
communication that still resolves into governed decisions and work
objects.
### Additional context
Public GitHub issue/PR search found no duplicate implementation; open PR
search for interaction resolver governance and agent addressees only
returned this PR.
## What Changed
- Add company-scoped interaction resolver governance contracts and
persistence.
- Add requested/effective resolver policy, resolver identity,
withdrawal, and terminal-expiry behavior.
- Add explicit `addresseeAgentId` validation, authorization,
persistence, lifecycle reconciliation, API documentation, and skill
guidance.
- Route pending addressed interactions to the intended invokable agent
and fall back to board attention when that agent becomes ineligible or
is deleted.
- Preserve sandbox callback identity fields required by governed
resolution paths.
- Add migrations `0193` and `0194` plus route, service, attention,
adapter, CLI, and UI coverage.
- Add governance state and company settings UI, including responsive
mobile behavior and distinct withdrawn/expired audit presentation.
## Verification
- `pnpm check:token-gates` — passed.
- `pnpm -r typecheck` — passed, including migration numbering and safety
checks.
- `pnpm test:run` — feature/server and UI workspace suites passed; one
unrelated CLI AWS doctor test observed injected static AWS credentials
and warned instead of passing.
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest
run cli/src/__tests__/secrets.test.ts --project paperclipai` — 8 tests
passed, confirming the failure was environment-sensitive.
- `pnpm build` — passed.
- Latest rebased head `e24cece6be9f1877bdbac7691bcb44fd583c0161`
completed all GitHub CI jobs successfully.
## Risks
- Migrations add interaction and company-governance fields; numbering is
conflict-free on current `master`, additive statements are idempotent,
and migration safety checks pass.
- Agent authorization behavior expands beyond board-only resolution, but
defaults remain board-only and coverage exercises company boundaries,
resolver eligibility, lifecycle invalidation, wake behavior, withdrawal,
expiry, and attention fallback.
- Attention routing depends on current agent invokability;
reconciliation and read-time filtering prevent stale addressees from
retaining visibility or resolution authority.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex using `gpt-5.6-sol` with reasoning, terminal tool use,
code execution, Git/GitHub integration, and Paperclip control-plane
tools. Context-window metadata was not reported by the runtime.
## 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 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators create tasks from the board UI.
> - The create form must show clear progress while it submits.
> - The form showed the same pending state twice.
> - One copy also used the old term "issue" instead of the UI term
"task."
> - This pull request keeps the pending state in the submit button and
removes the duplicate status.
> - The benefit is a clearer form with consistent task language.
## Linked Issues or Issue Description
No matching public GitHub issue exists for this focused UI bug.
**What happened?**
The create-task form showed `Creating issue...` beside a submit button
that already showed `Creating...`. A low-trust notice in the same form
also used the old UI term `issue`.
**Expected behavior**
The submit button shows the pending state once. Visible UI copy uses
`task` for the work object.
**Steps to reproduce**
1. Open the create-task dialog.
2. Enter a task title.
3. Select **Create Task**.
4. Observe the duplicate loading status beside the pending button.
**Paperclip version or commit**
Reproduced from upstream `master` at
`2c90cf0f2c60d3851880eca3c643c01313af9ffd`.
**Deployment mode**
Local development build.
## What Changed
- Removed the duplicate loading status beside the create-task submit
button.
- Kept inline create errors in the dialog footer.
- Changed the low-trust notice from `issue` to `task`.
- Added a regression test for the single pending-state presentation and
`aria-busy` state.
## Verification
- `cd ui && pnpm exec vitest run src/components/NewIssueDialog.test.tsx`
— 24 tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — passed.
## Risks
- Low risk. The change only removes duplicate pending copy and updates
one UI term.
- The regression test keeps the submit button pending indefinitely to
verify its accessible loading state.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, model ID `gpt-5`. The runtime did not expose the
context-window size. The session used reasoning, repository tools, and
code 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The decisions desk and queue help operators find work that needs a
human decision.
> - The current views use different grouping, sorting, and labels.
> - Repeated confirmation requests can also leave stale pending actions
in the queue.
> - Blocked-work attention can point at an intermediate issue instead of
the terminal blocker.
> - This pull request aligns the server contract and both user
interfaces.
> - The benefit is a smaller, clearer queue that ranks the decisions
with the largest impact.
## Linked Issues or Issue Description
Related PR: #10774
**What existing behavior does this improve?**
The decisions desk and queue currently use different triage rules. They
can show stale repeated confirmations and can rank blocked work by an
intermediate issue.
**Subsystem affected**
This change affects attention aggregation, issue thread interactions,
shared attention contracts, and the decisions user interface.
**Current behavior**
The desk uses a can-wait group that has no clear arrival meaning. The
queue has fewer controls than the desk. Repeated pending confirmations
remain actionable. Blocked-work rows do not always identify the terminal
actionable blocker.
**Proposed behavior**
Group desk items by arrival date, and reserve Decide now for explicit
due dates. Use one toolbar and shelf model on both pages. Supersede
older repeated pending confirmations. Aggregate blocked work under the
terminal actionable blocker and rank it by impact.
**Reason and benefit**
Operators get one consistent triage model. The badge reflects new and
overdue work. High-impact blockers move to the top. Duplicate
confirmation work no longer consumes attention.
**Breaking changes**
The attention summary field `decideNowCount` changes to
`deskBadgeCount`. Consumers must use the new field. Older repeated
confirmation interactions can now finish with the
`superseded_by_newer_request` outcome.
## What Changed
- Supersede older pending confirmation requests for the same issue and
record the mutation in activity history.
- Resolve blocked-work attention to actionable terminal blockers,
suppress live blocker trees, and rank rows by blocked-work impact.
- Group the decisions desk into New today and Earlier, and count new
plus overdue work in the desk badge.
- Share the decision toolbar and shelf components across the desk and
queue.
- Add queue grouping, sorting, filtering, aging, visible training
controls, and clearer recommendation copy.
- Add server, shared-contract, and user-interface tests for the new
behavior.
## Verification
- `pnpm check:token-gates`
- `pnpm -r typecheck`
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm test:run` (all
server, UI, CLI, shared, and catalog tests passed; one fixed five-second
DB timeout flaked under full-suite load)
- `NODE_ENV=test pnpm --filter @paperclipai/db exec vitest run
src/status-card-migrations.test.ts` (passed in isolation)
- `pnpm build`
## Risks
- The attention summary field rename requires synchronized consumers.
- Terminal-blocker traversal uses cycle and depth guards. A malformed
dependency graph can stop at the last safe node.
- The new arrival grouping changes which items contribute to the
decisions badge.
- Superseding repeated confirmations changes the terminal state of older
pending interactions.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex with GPT-5. The runtime did not expose a dated model
snapshot or context-window size. The model used reasoning, repository
tools, code execution, and 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators talk to their agents on the issue detail page. An
experimental "Chat-Style Tasks" view (#10606) makes that page read as a
conversation instead of a ticket form.
> - The first release of that view shipped with a plain-text composer,
no visible narration while an agent works, and a desktop-only layout.
> - Users write formatted replies, paste screenshots, and follow long
agent runs from their phones. The experimental view should support all
of that before it can graduate.
> - This pull request is the next iteration of the same experiment: a
rich-text composer with attachments, live-turn narration on the status
line, cleaner settled-turn history, and a mobile layout.
> - The benefit is a chat view that feels alive while the agent works
and stays readable after it finishes, on desktop and mobile, still fully
behind the existing opt-in flag.
## Linked Issues or Issue Description
Refs #49 (chat with agents is a much-wanted feature).
Refs #10606 (the merged first release of the experimental chat-style
task view; this PR iterates on it).
Related PRs found in the dedup search:
- #8228 — open PR that polishes the classic issue chat composer. It
targets the flag-off legacy path; this PR only changes the flag-on
experimental view.
- #10466 — merged blockquote-recovery fix in the shared MarkdownEditor.
This PR now reuses that editor inside the chat composer.
**What existing behavior does this improve?**
The experimental "Chat-Style Tasks" view on the issue detail page
(Settings → Experimental, `enableTaskChatRedesign`, default off).
**Subsystem affected**
UI (issue detail page, chat-style task view).
**Current behavior**
With the experiment enabled, the composer is a plain textarea with no
formatting, no attachment preview, and no mention support. While an
agent runs, the status line shows only a static label, and the agent's
narration text is hidden. Finished runs render one settled row per turn,
so a run with many short turns produces a long list of near-duplicate
"Worked" rows, and turns without a comment append at the bottom out of
order. On mobile, the desktop bounded-height thread makes the page
scroll poorly.
**Proposed behavior**
The composer uses the shared MarkdownEditor: markdown formatting,
mentions, image paste with thumbnail previews, and non-image attachment
chips. Sending posts on Cmd/Ctrl+Enter. While an agent runs, the status
line rotates playful status words and surfaces the agent's own narration
as short interstitial updates: each update holds for a minimum dwell, is
replaced only when superseded, and slides through a one-line viewport
with tokenized motion. Back-to-back settled turns coalesce into one
"Worked" row with summed durations and re-derived tool counts, and
comment-less settled turns insert chronologically at their run's start
time. On mobile, the thread renders in the document flow with window
auto-follow and a sticky safe-area composer; the desktop layout is
unchanged.
**Reason and benefit**
The chat view is only convincing if it feels like a conversation with a
working agent. Rich text and screenshots are table stakes for chat
input. Live narration gives moment-to-moment feedback without opening
transcripts. Coalesced history keeps long-running tasks readable. Mobile
support lets operators follow runs away from their desks.
**Breaking changes**
None. Every change is gated behind the existing `enableTaskChatRedesign`
flag, which is off by default. The flag-off page is unchanged.
## What Changed
- `TaskChatComposer` swaps its textarea for the shared `MarkdownEditor`:
markdown formatting, mentions, image paste with object-URL thumbnail
previews (revoked on clear and unmount), and posting on Cmd/Ctrl+Enter.
- Non-image attachments render as chips on a new shared
`ui/attachment.tsx` primitive (adds the `@base-ui/react` dependency it
builds on).
- New `status-whimsy.ts`: deterministic rotation of playful status words
on the live status line.
- Live interstitial narration: the transcript adapter tags agent
self-talk, and the live status line shows it as ephemeral one-line
updates with a ~4s minimum dwell, hold-until-superseded replacement, and
a slide transition driven by new `--motion-line-scroll` tokens
(cataloged in `motion-tokens.ts`, which a test keeps 1:1 with
`index.css`). Hover affordance applies only to the status line, with no
leading icon.
- Settled-turn history: `coalesceSettledTurns` merges back-to-back
settled agent turns into one "Worked" row (summed per-run durations,
tool counts re-derived from the merged turn); `assembleThreadItems`
inserts comment-less settled turns chronologically at run start instead
of appending them at the bottom; settled turns render tool rows only
(the separate thinking block component is removed).
- The "Worked" summary attaches to the reply timestamp row, and thread
timestamps are always visible.
- Mobile layout: the thread renders with `scroll={false}` in the page
scroll, a new `useWindowAutoFollow` hook keeps the window pinned to new
content, the composer is sticky with safe-area padding, and the editor
uses 16px text so iOS does not zoom on focus. The desktop bounded chain
is untouched.
- New `TaskChatDescriptionBubble` renders the issue description as the
first chat bubble, and `McpIcon` gives MCP tools a distinct icon.
- `IssueDetail.test.tsx` stubs `TaskChatThread`: the composer's
`@mdxeditor` dependency cannot load under jsdom's CSSOM, and the suite
exercises the flag-off path.
## Verification
- `pnpm check:token-gates` — 3/3 CLEAN.
- `node scripts/check-task-chat-motion.mjs` — OK (30 files scanned,
seams present).
- `cd ui && npx tsc -b` — clean.
- `cd ui && pnpm vitest run` — 3,482 of 3,483 tests pass locally. The
one failure is the `IssueProperties.test.tsx` monitor-row
time-formatting test, which is timezone-sensitive: it fails identically
on unmodified `origin/master` in a non-UTC timezone and passes with
`TZ=UTC`. It is not related to this change.
- Manual: enable "Chat-Style Tasks" in Settings → Experimental and open
an issue with an assigned agent. Comment to start a run: the status line
rotates status words and shows the agent's narration as short held
updates. After the run, consecutive turns fold into one "Worked" row
under the reply timestamp. Paste an image into the composer to see a
thumbnail chip; attach a non-image file to see a file chip; send with
Cmd+Enter. Open the same issue in a narrow viewport to see the
document-flow layout with the sticky composer.
- Visual snapshot baselines are intentionally not updated: per
`doc/design/DECISION-SHEET.md`, "Per-change snapshot verification
demoted to dormant (Jul 13 2026)".
## Risks
- The composer now loads the shared MarkdownEditor inside the chat view.
The editor is already used across the app (issue descriptions,
comments), so its behavior is well exercised; composer-specific handling
(paste, attachments, submit keys) is covered by new tests.
- The transcript adapter changes how live narration and settled turns
are derived from run logs. Malformed or legacy logs degrade to generic
rows rather than crashing, and the adapter suites cover the merge and
ordering rules.
- Object URLs for paste previews are revoked on send-clear and unmount
to avoid leaks; jsdom environments without `URL.createObjectURL` are
guarded.
- All changes are behind the default-off `enableTaskChatRedesign` flag.
Overall risk with the flag off is low.
## Model Used
- Claude (Anthropic), model id `claude-fable-5` (Claude Fable 5),
extended thinking enabled, agentic tool use (file editing, shell, test
execution) via Claude Code / Claude Agent SDK.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The project workspace policy editor sets how agent runs share a
project's execution workspace.
> - The server now has a `sharedWorkspaceConcurrency` policy (Refs
#10759), but the UI had no control for it.
> - Users could not choose the concurrency mode without editing the API
directly.
> - This pull request adds a 3-option select (Auto / Serialize / Allow)
to the policy editor.
> - The benefit is that users set shared-workspace concurrency in the
UI, with clear helper text for each mode.
## Linked Issues or Issue Description
Refs #10759 (server contract this UI drives).
**Feature request**
- **Is your feature request related to a problem? Please describe.**
The `sharedWorkspaceConcurrency` policy field shipped on the server, but
the project workspace policy editor had no control to set it. Users
could not pick a concurrency mode from the UI.
- **Describe the solution you would like.**
Add a 3-option select (Auto / Serialize / Allow) to the
execution-workspace policy editor, with helper text that explains each
mode. An unset value must show as Auto.
- **Describe alternatives you have considered.**
A set of radio buttons was considered. A select matches the compact
style of the other controls in the same editor (environment, base ref).
## What Changed
- Added a "Shared workspace concurrency" select to the project
execution-workspace policy editor
(`ui/src/components/ProjectProperties.tsx`).
- The select offers three options with helper text:
- **Auto** (default): "Concurrent runs on local/SSH runners; runs take
turns in cloud sandboxes."
- **Serialize**: "Runs always take turns in the shared project
workspace."
- **Allow**: "Runs never wait for the workspace; concurrent edits are
possible."
- An unset or absent value shows as **Auto**. The UI writes a value only
after the user picks one, so the policy round-trips as Auto until then.
- Added a `SharedWorkspaceConcurrency` type import and a new
`execution_workspace_shared_concurrency` save-state key.
- Added a stateful Storybook story so the controlled select can be
exercised.
### Screenshots
**Before** (light / dark) — the editor had no concurrency control:


**After** (light / dark) — the select shows Auto by default:


**Helper text updates per option** (Serialize / Allow):


## Verification
- `pnpm --filter @paperclipai/ui typecheck` passes.
- `pnpm --filter @paperclipai/shared build` passes.
- Rendered the editor in Storybook (light and dark). The select shows
Auto when the policy is unset. Selecting Serialize or Allow updates the
helper text and the stored value.
## Risks
- Low risk. UI-only change. The control is additive and only appears
when isolated task checkouts are enabled. An unset value keeps the
current Auto behavior, so existing projects are unaffected.
## Model Used
- Claude Opus 4.8 (claude-opus-4-8), extended thinking, tool use / code
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
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Inbox helps operators scan task state on desktop and mobile
screens
> - Read and unread rows must keep the same title and status alignment
> - The mobile unread control was a flex item, so unread rows gained an
extra leading column
> - Moving the control out of flex flow fixes the indent, but the tap
target must stay inside clipped row containers
> - This pull request reserves one mobile gutter for both states and
overlays the unread control inside that gutter
> - The benefit is stable mobile alignment without clipping the control
or blocking the parent-row chevron
## Linked Issues or Issue Description
Related merged work: Refs #9383, Refs #9685, and Refs #9767.
**What happened?**
On mobile screens, an unread Inbox row placed the mark-as-read control
in the flex layout. The extra flex item moved the status and title to
the right. A first overlay position could also place part of the control
outside a row that clips overflow.
**Expected behavior**
Read and unread rows must use the same title and status positions. The
unread control must stay tappable inside the row. A parent-row chevron
must remain independently usable.
**Steps to reproduce**
1. Open the Inbox on a mobile viewport.
2. Compare read and unread rows with the same nesting depth.
3. Include an unread parent row with a collapse chevron.
4. Observe that the unread row content starts farther right than the
read row content.
**Paperclip version or commit**
The issue reproduced on the `master` parent of this pull request.
**Deployment mode**
Local dev (`pnpm dev`).
**Installation method**
Built from source.
## What Changed
- Reserve a mobile leading gutter whenever an Inbox row participates in
unread state.
- Position the mark-as-read control absolutely inside that gutter so it
does not add a flex column.
- Keep read and unread rows on the same mobile padding path.
- Keep the control inside overflow-clipping row containers and separate
from the parent-row chevron.
- Update the `IssueRow` regression test to verify absolute placement,
the internal gutter, and the absence of `order-first` layout.
## Verification
- `pnpm exec vitest run ui/src/components/IssueRow.test.tsx` — 16 tests
passed.
- `pnpm check:token-gates` — all token gates passed across 722 files.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — the general server shard passed 3,448 tests and the
UI shard passed 3,362 tests. The CLI shard had one host-environment
failure because inherited temporary AWS access-key variables changed an
AWS doctor assertion from `pass` to `warn`.
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY -u
AWS_SESSION_TOKEN pnpm exec vitest run
cli/src/__tests__/secrets.test.ts` — the affected file passed 8 tests in
an isolated environment.
- Mobile QA at 402 × 874 confirmed equal read/unread title positions, no
shift after marking a row as read, and an independently usable parent
chevron.
## Risks
- Low risk. The change affects only `IssueRow` mobile presentation and
its focused regression test.
- The main risk is breakpoint-specific placement. The component test
covers the responsive classes, and mobile QA covers the rendered
interaction.
- No API, schema, dependency, telemetry, or documentation contract
changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex based on GPT-5, with reasoning, repository tools, command
execution, and test execution. The runtime did not expose the exact
deployment ID or context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI is a React SPA served by the paperclip server; the
standard local dev flow is `pnpm dev`, which runs vite in dev mode with
HMR and an unbundled module graph
> - The unbundled dev bundle is hundreds of MB of JS across many
requests, which is fine on a local machine but unusable from a phone or
tablet on slow/lossy links (airplane wifi, mobile data, distant tailnet
peers)
> - Contributors who want to iterate on the board from a mobile device
today have no supported way to preview a small production-shaped bundle
without stopping the dev server and running a one-off `vite preview`
with manual proxy plumbing
> - This pull request adds `pnpm dev:mobile` — build the UI and serve
`ui/dist` via `vite preview` on port 3101, with `/api` proxied to the
running dev server on 3100 — plus `pnpm dev:both` to run both flavors
together
> - The benefit is a supported second flavor of the dev server for
phones/tablets that runs alongside the normal one, without touching the
primary `pnpm dev` flow
## Linked Issues or Issue Description
**Subsystem affected**
ui/ — React + Vite board UI
**Problem or motivation**
The vite dev server serves an unbundled module graph, which is fine on
localhost but unusable from a phone or tablet on a slow link.
Contributors testing responsive behavior on mobile devices have no
supported way to serve a small production-shaped SPA against the running
dev API. Running `vite preview` directly does not work either — the
server's board mutation guard checks that the browser's Origin matches
the request Host, and a preview on a second port would fail every
mutation.
**Proposed solution**
Add two root scripts:
- `pnpm dev:mobile` — build `ui/dist` and serve it via `vite preview` on
port 3101, with `/api` proxied to the API server on 3100.
- `pnpm dev:both` — run `pnpm dev` and `pnpm dev:mobile` together in a
single terminal with prefixed output and shared signal handling.
The vite preview config binds `0.0.0.0`, sets `allowedHosts: true` so it
accepts arbitrary hostnames (LAN, tailnet, ngrok, etc.), and the shared
`/api` proxy forwards the client's original Host header as
`x-forwarded-host`. The paperclip server's mutation guard already
prefers `x-forwarded-host` over `host` when computing trusted origins,
so the browser's Origin becomes trusted automatically.
**Alternatives considered**
- Bespoke node proxy script — works but duplicates what vite preview
already does.
- Loosen the mutation guard to accept arbitrary origins — reduces
security for the primary server for the sake of a dev-only workflow.
- Second server config that binds a second port from the paperclip
server itself — much larger change and mixes runtime concerns with a
dev-tooling convenience.
## What Changed
- New `pnpm dev:mobile` script — build UI then run `vite preview` on
port 3101.
- New `pnpm dev:both` script — run `pnpm dev` and `pnpm dev:mobile`
together via `scripts/dev-both.mjs`, which prefixes each child's output,
propagates SIGINT/SIGTERM, and exits when either child exits.
- `ui/vite.config.ts` — add a `preview` block (port 3101, host
`0.0.0.0`, `allowedHosts: true`, shared `/api` proxy).
- New `ui/src/lib/vite-api-proxy.ts` — extracts the `/api` proxy factory
shared by dev and preview, and forwards the client Host as
`x-forwarded-host` (plus `x-forwarded-proto`).
- New unit test `ui/src/lib/vite-api-proxy.test.ts` covering the
header-injection behavior and the pass-through when no Host is present.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/vite-api-proxy.test.ts` — 3 tests pass.
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `pnpm --filter @paperclipai/ui build` — clean.
- Manual: ran `vite preview` against an echo listener and confirmed the
request arrives with `x-forwarded-host` set to the client Host header
and `x-forwarded-proto: http`. Then ran `pnpm dev:mobile` against the
live dev server and verified board mutations (mark issue read, resolve
recovery action, run routine) succeed from a second-port browser session
that previously 403'd.
## Risks
Low risk. Changes are limited to dev tooling — no runtime code paths, no
server changes, no schema/migrations. The `apiProxy` refactor is a no-op
behaviorally for the existing dev server (same target, same `ws: true`);
the only new behavior is the two `x-forwarded-*` headers, and the server
side already prefers those headers when trusting origins. `dev:mobile`
and `dev:both` are additive; existing `pnpm dev` is untouched.
## Model Used
Claude Opus 4.7 (1M context), extended thinking, tool use (bash, file
edits).
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue properties panel can show external objects such as GitHub
pull requests.
> - Those objects are resolved by external-object providers and then
displayed as compact status labels.
> - A GitHub pull request could remain in the fallback `unknown` state
and appear as `Not yet resolved`.
> - That label is confusing when the object is known but has not been
refreshed yet.
> - This pull request refreshes due external objects from the heartbeat
scheduler and improves the unknown-status copy.
> - The benefit is a properties panel that moves from pending refresh to
the real pull request state without a manual refresh.
## Linked Issues or Issue Description
No public GitHub issue exists for this bug. I searched for related
public issues and pull requests using the terms `Not yet refreshed`,
`external objects refresh`, and `external PR status`, and did not find a
duplicate implementation.
**What happened?**
The issue properties panel could show a GitHub pull request as `Not yet
resolved` even when the referenced pull request was valid. The object
stayed stale unless a manual refresh path ran.
**Expected behavior**
A known external object should show pending-refresh copy while it waits
for provider data. When the scheduler refreshes it, the properties panel
should show the provider status such as open, merged, or closed.
**Steps to reproduce**
1. Create or view an issue that references a GitHub pull request.
2. Open the issue properties panel.
3. Observe the external object row before a manual refresh has run.
**Paperclip version or commit**
Current `master` before this pull request.
**Deployment mode**
Local dev and self-hosted server.
**Installation method**
Built from source.
**Agent adapter(s) involved**
Not adapter-specific.
**Database mode**
Not database-related.
**Access context**
Board view.
**Privacy checklist**
I reviewed this description and did not include logs, credentials,
private URLs, internal issue IDs, or PII.
## What Changed
- Added a heartbeat scheduler tick that refreshes due external objects
for active companies.
- Kept manual external-object refresh behavior on the same service path.
- Changed display copy so known provider objects use liveness labels
such as `Not yet refreshed`, while fresh unknown provider statuses show
`Status unavailable`.
- Added server and UI tests for scheduled refresh and label behavior.
## Verification
- `corepack pnpm install --frozen-lockfile`
- `pnpm check:token-gates`
- `pnpm exec vitest run
server/src/__tests__/external-objects-service.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts
ui/src/components/ExternalObjectPill.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/lib/external-objects.test.ts`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/server build`
- `pnpm --filter @paperclipai/ui build`
- `pnpm run typecheck:build-gaps`
- GitHub PR checks passed on head `0e7fcd30`
- Greptile reported 5/5 on head `0e7fcd30` with no unresolved review
threads
Notes:
- I ran recursive typecheck and build first. Both hit container resource
limits with exit 137 during concurrent package work, so I reran the
affected server and UI targets separately.
- An unrelated workspace-runtime auto-port test fails in this container
with a PID ownership mismatch. It is outside the files changed here.
## Risks
Low to medium risk.
The scheduler does more periodic external-object work, so the main risk
is extra provider refresh load. The implementation bounds the work to
active companies, due non-terminal objects, and 50 objects per company
per tick. The path also stays behind the external-objects experimental
setting.
## Model Used
OpenAI GPT-5 Codex in the Codex execution environment, with shell and
GitHub CLI tool use. The runtime did not expose a more specific internal
model ID or context window.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Inbox helps operators scan issues that need attention.
> - Inbox search can add supplemental sections for archived matches and
other matches.
> - The supplemental search section builder still sent empty sections
into the grouped render path.
> - That made the Archived and Other results dividers appear even when
those sections had no rows.
> - This pull request drops empty supplemental sections before
rendering.
> - The benefit is a cleaner near-empty inbox search view.
## Linked Issues or Issue Description
No public GitHub issue exists for this report. Public GitHub search
found no duplicate or related open issues or pull requests for this
inbox search behavior.
**What happened?**
Inbox search could show Archived and Other results divider headers even
when those supplemental sections had no rows.
**Expected behavior**
Empty supplemental search sections should not render divider headers.
**Steps to reproduce**
1. Open the Inbox.
2. Search in a near-empty inbox with no archived matches and no
outside-inbox matches.
3. Observe that empty supplemental divider headers can appear.
**Paperclip version or commit**
`master` before this change.
**Deployment mode**
Built from source.
## What Changed
- Dropped empty supplemental inbox search sections before they reach the
grouped inbox render path.
- Added a unit regression test for empty Archived and Other results
sections.
- Refreshed the branch against current `master` to clear the merge
conflict.
## Verification
- `git diff --check origin/master...HEAD` passed.
- Public diff is limited to `ui/src/lib/inbox.ts` and
`ui/src/lib/inbox.test.ts`.
- Local focused Vitest could not run in this execution checkout because
dependencies are not installed and `corepack pnpm exec vitest ...`
reports `Command "vitest" not found`.
- Pull request CI is green for typecheck, build, server tests, e2e,
security checks, policy checks, canary dry run, and aggregate verify.
- Greptile Review passed on commit `dc2e224` with confidence score 5/5
and no comments.
## Risks
Low risk. The Inbox change only filters empty supplemental search
sections. Normal inbox sections and non-empty archived or other search
results keep their current behavior.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5, reasoning-enabled with terminal tool use and code
execution. The runtime context-window size is not exposed.
## 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
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Inbox and Tasks screens render issues as a vertical list of
`IssueRow` components
> - A recent refactor split `IssueRow` into a root `div` plus a full-row
overlay `Link`, and the divider and hover styles moved onto that overlay
> - As a result every row shows a bottom border and hover greys the text
instead of tinting the background
> - This pull request moves the divider and hover/selected wash back
onto the root row band and keeps only positioning on the overlay
> - The benefit is the list reads cleanly again: no stray dividers, and
hover tints the background behind the text
## Linked Issues or Issue Description
No public GitHub issue exists. Describing the bug in-PR (bug report):
**What happened**
- In the Inbox and Tasks list views, every row shows a 1px bottom
border, including the last row.
- Hovering a row dims/greys the row text instead of showing a background
tint behind the content.
**Expected behavior**
- List rows in Inbox and Tasks show no separator lines by default.
- Hover shows a subtle background tint behind the row content; the text
stays fully legible.
- The blocked inbox view keeps its intentional separators.
**Steps to reproduce**
1. Open the Inbox or Tasks list view.
2. Note the horizontal border under every row, including the last.
3. Hover a row and note the text greys out rather than the background
tinting.
**Root cause**
- PR #10526 restructured `IssueRow` from a single root `Link` into a
root `div` plus a full-row `absolute inset-0` overlay `Link` (to keep
header controls clickable). The divider and hover/selected/checklist
background classes moved onto the overlay `Link`. `last:border-b-0` no
longer matched (the Link is the first child of a multi-child div), and
the hover wash painted on top of the content instead of behind it.
**Paperclip version/commit**
- Base commit: `8b83d69e3` (branched from current `master`).
**Deployment mode**
- UI (web) list views: Inbox and Tasks.
## What Changed
- `ui/src/components/IssueRow.tsx`: moved the divider classes and the
hover/selected/checklist background wash from the overlay `Link` to the
root row `div`, so the tint paints behind the content and
`last:border-b-0` matches the real last row. The overlay `Link` now
keeps only `absolute inset-0` positioning and the focus ring. Renamed
the `hideDivider` prop to an opt-in `showDivider` (default `false`).
Kept `[&_button]:relative [&_button]:z-10` on the root so the Archive
button stays clickable above the overlay, and kept the `isArchiving`
collapse animation on the root row.
- `ui/src/components/IssuesList.tsx`: dropped the old `hideDivider`
usage (dividers are now opt-in).
- `ui/src/pages/Inbox.tsx`: dropped the old `hideDivider` usage.
- `ui/src/components/BlockedInboxView.tsx`: added `showDivider` so this
view keeps its separators.
## Verification
- `cd ui && npx tsc -b` — typecheck passes with the change.
- Manual (recommended for reviewer): in the Inbox and Tasks list views,
confirm no per-row bottom border and that the last row has none. Because
dark-mode `--border` is 10% white and near-invisible in screenshots,
assert the computed `border-bottom-width` on a row element rather than
eyeballing pixels.
- Hover a row: text stays legible; a background tint appears behind the
content.
- Inbox: the Archive button appears on hover and is clickable (the
overlay does not swallow the click).
- Blocked inbox view: separators still render.
## Risks
- Low risk. The change relocates existing Tailwind classes between two
elements of the same row and renames one internal prop; no data or API
surface changes. All `IssueRow` call sites were updated in this PR
(verified: no remaining `hideDivider` references).
## Model Used
- Claude, Opus 4.8 (`claude-opus-4-8`), extended thinking with tool use.
## 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)
- [ ] 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
- [ ] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [ ] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents execute in environments — local, SSH, or sandboxes — resolved
at run time as agent environment → instance default → local
> - The Configuration page has a Test button that probes the adapter
(working directory, command, a model call) in the environment it will
run in
> - But the Test sent only the agent's own environment id, with no
instance-default fallback, so agents relying on the instance default
were probed on the Paperclip host instead
> - A sandbox image carrying an extra CLI then fails the Test with
"command not found" even though every real run would resolve to the
sandbox and succeed — the Test lies about a working setup
> - This pull request mirrors the run-time resolution in the Test call
via a small shared helper with tests
> - The benefit is that the Test button reports the truth about where
the agent actually runs
## Linked Issues or Issue Description
No existing public issue — inline description following the bug report
template:
**What happened?**
With the instance default environment set to a sandbox (whose image
includes the adapter CLI) and an agent that leaves its environment unset
("use instance default"), the Configuration page's Test fails with
`command not found` for that CLI.
**Expected behavior**
The Test probes the environment a real run would use — here the
instance-default sandbox, where the CLI exists — and passes.
**Steps to reproduce**
1. Set the instance default environment to a sandbox whose image carries
an adapter CLI not installed on the Paperclip host (e.g. `grok`).
2. Create a `grok_local` agent without selecting an environment.
3. Press Test on the agent's Configuration page → `command not found`,
while a real heartbeat run resolves to the sandbox and works.
**Paperclip version or commit**
Reproduced on `sha-53bcf38-cloud`-era master; root-caused in
`ui/src/components/AgentConfigForm.tsx` (`environmentId =
currentDefaultEnvironmentId || null`) versus the server's
`resolveExecutionWorkspaceEnvironmentId` (agent → instance default →
local).
## What Changed
- New `ui/src/lib/adapter-test-environment.ts`:
`resolveAdapterTestEnvironmentId` — agent environment first, else
instance default, else null (host probe) — documented as the mirror of
the server's run-time resolution.
- `AgentConfigForm` uses it in the Test mutation. The raw agent
environment id is now sent even when it points at the local environment:
the server already resolves the driver and probes the host for local, so
explicit-local behavior is unchanged, and the test-environment route's
remote paths (SSH/sandbox lease + custom-image template) engage exactly
as they do for the fallback environment.
- Tests pin the fallback (agent wins; instance default when agent unset;
null when neither).
Deliberately untouched: the onboarding wizard's adapter test still sends
no environment — during onboarding an instance default frequently
doesn't exist yet, and changing that flow deserves its own look.
## Verification
- `vitest run` on the new helper suite plus both `AgentConfigForm`
suites — 19 tests pass; `tsc` clean in `ui/`.
- Root cause verified against a live deployment: an agent with
`default_environment_id = NULL`, instance default = sandbox environment;
the Test posted `environmentId: null` and probed the host (no `Probing
inside environment: …` check in the result), which lacks the CLI that
the sandbox image carries.
## Risks
- Low. The change only widens which environment the Test probes,
matching run-time reality. Sandbox-backed tests boot a throwaway sandbox
(existing route behavior — lease, custom-image template,
archive-on-release), so Tests for instance-default-sandbox agents now
take sandbox-boot time instead of failing fast and wrongly.
## Model Used
Claude Fable 5 (`claude-fable-5`, extended thinking, via Claude Code
with tool use and code execution); diagnosis included live inspection of
a deployed instance's agent/environment configuration.
## 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
(helper doc-comment carries the rationale; no user-facing doc covers the
Test button)
- [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
## Thinking Path
> - Paperclip is the control plane teams use to manage AI agents and
their reusable capabilities.
> - Skills Manager lets operators discover and import skills from
project workspaces.
> - Automatic discovery only surfaces skills in conventional locations,
so valid skills stored elsewhere in a project are invisible.
> - Operators need a safe way to navigate project folders without
exposing paths outside the selected workspace.
> - This pull request adds company-scoped workspace folder browsing and
selection to the project skill import flow.
> - The benefit is that operators can find and import valid skill
folders regardless of repository layout while preserving workspace
boundaries.
## Linked Issues or Issue Description
- **Subsystem affected:** Cross-cutting (`server/`, `ui/`, and
`packages/shared`).
- **Problem or motivation:** Project skill imports rely on conventional
directory discovery, which prevents operators from selecting valid
`SKILL.md` folders stored in atypical locations.
- **Proposed solution:** Add a company-scoped browse endpoint and a
folder browser in the import dialog. The server resolves real paths,
rejects traversal outside the workspace, skips symlinks and high-noise
directories, identifies skill directories/files, and caps listings at
250 entries.
- **Alternatives considered:** Expanding the automatic scan to every
directory would be slower and noisier, while accepting arbitrary
filesystem paths would weaken project/workspace scoping.
- **Roadmap alignment:** This extends the completed “Skills Manager,
Skill Studio & Skills Store” capability in `ROADMAP.md` without
duplicating planned core work.
- **Additional context:** GitHub search found no duplicate or closely
related public issues or pull requests.
## What Changed
- Added shared browse request/result contracts and validation for
project workspace navigation.
- Added a company-scoped API route and service that safely lists local
workspace folders and detects `SKILL.md` entries.
- Added project workspace/folder navigation to the import dialog,
including parent navigation, workspace switching, truncation feedback,
and direct skill selection.
- Added service and route regression tests for browsing, skill
detection, company isolation, and traversal rejection.
- Added shared response schemas and OpenAPI documentation for the browse
endpoint.
- Hardened explicit skill selections with realpath containment so
symlinked directories cannot escape the project workspace.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/company-skills.test.ts` — 64 tests passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts` — 3
tests passed.
- Focused post-review reruns: `company-skills-service.test.ts` — 45
tests passed; shared/server typechecks passed.
- GitHub latest-head checks — all green after one transient e2e rerun;
no pending or failing checks.
- `pnpm check:token-gates` — all gates clean on the rebased head.
## Risks
- Low-to-moderate risk: this adds a filesystem browsing surface.
Realpath containment checks prevent workspace escape, symlinks are
excluded, remote-managed workspaces are rejected, and directory listings
are capped.
- The browser intentionally hides `.git` and `node_modules`; skills
inside those directories cannot be selected through this flow.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, exact model ID `gpt-5.5`, reasoning-enabled with
terminal/tool use and code execution; runtime context-window size is not
exposed.
## 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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
— the execution harness requires preserving the assigned branch name.
- [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 — no
user-facing docs changes are needed beyond this PR description because
the flow is self-explanatory UI behavior.
- [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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Decisions desk shows work that needs a human decision
> - The queue foundation can group and rank decision work
> - Operators also need a focused daily view and a safe way to handle
old work
> - This pull request adds the desk controls, the aging shelf, and
reversible retention
> - It also binds bulk archive decisions to the exact reviewed item set
> - The benefit is a smaller daily queue without lost or orphaned work
## Linked Issues or Issue Description
**Subsystem affected**
Cross-cutting: server, UI, database, and shared contracts.
**Problem or motivation**
Decision work can grow into one large company list. Operators need quick
queue and date controls. Old items also need a safe retention path that
does not delete work.
**Proposed solution**
Add queue and date controls to the Decisions desk. Compute the aging
shelf on the server. Archive idle items after 90 days unless an operator
keeps them. Keep archived items searchable and revivable. Notify origin
agents in one batch per sweep. Bind bulk archive proposals to a signed,
exact item manifest.
**Alternatives considered**
Client-only aging can drift across browsers and source kinds. Deleting
old rows removes audit and recovery paths. An unsigned dynamic bulk
query can archive items that the reviewer did not inspect.
**Roadmap alignment**
This work supports the Work Queues and decision-memory directions in
`ROADMAP.md`. It extends the Decisions and attention-feed foundation
from #10651. Related earlier work includes #9380, #10010, and #10474.
## What Changed
- Added the queue rail, date chips, decide split, triage strip, queue
page, and aging shelf UI.
- Added server-owned shelf state with per-queue retention overrides.
- Added reversible retention state, archive history, and an idempotent
notification outbox.
- Added the 90-day archive sweeper, Keep exemption, archived feed query,
and revive actions.
- Added one origin-agent notification per agent and sweep.
- Added signed bulk archive proposals with exact-set and version checks.
- Persisted queue-exclusion reasons atomically and kept cross-domain
source resolution per-item until it has an exact-set transaction
contract.
- Added API contracts, OpenAPI entries, migration coverage, focused
tests, and Storybook screens.
## Verification
- `pnpm -r typecheck`
- `pnpm test:run` (server: 329 files and 3,453 tests passed; UI: 408
files and 3,362 tests passed)
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest
run cli/src/__tests__/secrets.test.ts`
- `pnpm build`
- `pnpm check:token-gates`
- Focused retention, attention, decisions, migration replay, startup,
and UI API tests.
- Complete queue snapshot regression with 51 items across the normal
50-item page boundary.
The unmodified CLI test reports one warning assertion in this runtime
because the harness injects static AWS credential variables. The
isolated test passes when those two variables are removed.
## Risks
- The migration adds retention and notification outbox tables. It uses
idempotent table, index, and foreign-key creation.
- Retention runs on the heartbeat scheduler interval. Compare-and-set
version checks prevent stale archive writes.
- Bulk archive acceptance fails closed when authority, activity,
version, or the reviewed set changes.
- Archive is reversible and does not delete source records.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex with `gpt-5.6-sol`. The model used tool calls, code
execution, database migration generation, and test execution. The
context-window size is not exposed in this runtime.
## 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
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Isolated workspaces give each task a safe and reproducible checkout.
> - The existing setup cloned the development database before an agent
needed to run the app.
> - This made worktree creation slower and heavier for tasks that never
start a service.
> - Runtime services already use one server start path for heartbeat,
operator, and startup recovery flows.
> - This pull request moves heavy setup to that start path and keeps
worktree creation lean.
> - The benefit is faster isolated workspace creation with the same
reliable runtime setup when a service starts.
## Linked Issues or Issue Description
Related pull request: #10652 covers the initial deferred
database-seeding slice. This pull request supersedes it with end-to-end
runtime provisioning and safe cleanup.
**What existing behavior does this improve?**
This improves isolated worktree creation, runtime service startup, and
isolated instance cleanup.
**Subsystem affected**
Cross-cutting: CLI worktree setup, server runtime orchestration, shared
workspace contracts, and development scripts.
**Current behavior**
Paperclip seeds an isolated development database during worktree
creation. It can also leave an isolated instance directory after
workspace teardown. This work happens even when no runtime service
starts.
**Proposed behavior**
Paperclip creates the worktree with a lean eager setup. It runs an
idempotent runtime provision command before the first managed service
spawn. Concurrent starts share one provision attempt. Teardown removes
the isolated instance safely.
**Reason and benefit**
Many agent tasks only edit and test code. They do not need a running
Paperclip instance. Deferring the database seed reduces workspace
startup cost while preserving automatic setup for tasks that start the
app.
**Breaking changes**
None. The new runtime provision command is optional. Existing workspace
behavior is unchanged when it is absent.
## What Changed
- Split Paperclip worktree setup into a lean eager script and an
idempotent runtime provision script.
- Added `runtimeProvisionCommand` to project, issue, realized workspace,
and persisted workspace contracts.
- Added a per-workspace provision mutex before local service spawn for
heartbeat, operator, and startup recovery flows.
- Added a persisted `provisioning` service state and the
`workspace_runtime_provision` operation phase.
- Kept provision time outside the service readiness timeout and made
failed attempts visible and retryable.
- Reclaimed isolated instance data during safe workspace teardown.
- Serialized deferred database seeding across processes and bound
teardown to the instance root captured in persisted workspace metadata.
- Added tests for config flow, concurrency, retry, no-op behavior,
readiness timing, scripts, CLI commands, and cleanup.
- Documented the eager and runtime provisioning contracts.
## Verification
- `pnpm -r typecheck`
- `pnpm build`
- `pnpm test:run` (server: 3,201 passed; UI: 3,345 passed; the CLI phase
exposed one environment-sensitive AWS doctor assertion because the agent
runtime injects static AWS credentials)
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest
run cli/src/__tests__/secrets.test.ts -t 'passes AWS doctor checks when
non-secret provider config is present'`
- Focused runtime tests cover serialized provisioning, retry after
stderr failure, absent-command no-op behavior, operation logging,
persisted state order, and readiness timeout exclusion.
- Focused CLI and cleanup tests cover concurrent seed serialization,
stale-lock fail-closed behavior, persisted instance ownership, and
rewritten sibling pointers.
## Risks
- A faulty runtime provision script blocks service startup. Paperclip
records stderr, marks the service failed, and retries on the next start.
- Concurrent service requests share an in-process provision attempt,
while the seed command uses an atomic filesystem lock across processes.
A stale lock fails closed and requires an operator to verify no seed is
running before removing it.
- Isolated instance cleanup is destructive. The cleanup service
validates ownership and path containment before removal.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, `gpt-5.6-sol`, with agentic reasoning, tool use, and
code execution. The service does not expose the context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Inbox lists work items by recent activity.
> - An archive changes activity data and can change the computed order.
> - The list can then move rows under the pointer during a fast archive
sequence.
> - The Inbox must keep its shown order while the operator is engaged.
> - This pull request adopts fresh order only at an idle, visibility, or
view-change boundary.
> - The benefit is a stable Inbox that still receives fresh data and new
items.
## Linked Issues or Issue Description
**What happened**
The Inbox re-sorts while an operator archives items quickly. An archive
can lower a parent group's activity time. Unrelated rows then move, and
the row under the pointer can change.
**Expected behavior**
Keep the shown order stable while the operator works in the Inbox.
Insert new items at their computed positions. Adopt a fresh order after
an idle period, after a long hidden-tab interval, or when the view
changes.
**Steps to reproduce**
1. Open the Inbox on the Mine tab with several items, including nested
items.
2. Archive several items quickly with the pointer or keyboard.
3. Observe rows move before the archive sequence is complete.
**Deployment mode**
Any deployment. This change affects only the web UI.
**Additional context**
This PR supersedes the closed subset PR #10621. It keeps the full change
in one review.
## What Changed
- Added an order-pin utility for sections, root rows, non-issue items,
and nested children.
- Kept archived rows in place for the existing five-second undo
interval.
- Added an attention-boundary hook for idle, visibility, and view-change
commits.
- Connected pointer, wheel, hover, keyboard, and archive interactions to
the idle boundary.
- Made idle commits repeat while the Inbox stays idle.
- Made view changes adopt the fresh order in the same render.
- Added unit and integration coverage for order pins and attention
boundaries.
## Verification
- `pnpm exec vitest run ui/src/hooks/useInboxSortAttention.test.tsx
ui/src/lib/inboxOrderPin.test.ts ui/src/pages/Inbox.test.tsx` — 33 tests
passed.
- `pnpm check:token-gates` — passed.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — 3,429 tests passed, 2 skipped, and 1 unrelated
server test failed. The isolated failure expects `issue_commented` but
current master records `heartbeat.scheduling_suppressed` in
`plugin-orchestration-apis.test.ts`.
## Risks
- Low risk. The change is client-only and does not change the sort
algorithm.
- A bad pin can show an old order until the next boundary. The tests
cover repeated idle commits and immediate view changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Anthropic Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, tool
use, and code execution for the implementation.
- OpenAI Codex (`gpt-5`), reasoning, tool use, and code execution for PR
preparation and verification. The serving snapshot, context-window size,
and hidden reasoning configuration were not exposed.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->
## Thinking Path
> - Paperclip is the open source control plane for companies of AI
agents.
> - Operators use the attention feed to find decisions that need action.
> - The feed has eleven source kinds, but it has no durable queue or
triage state.
> - The feed also returns every item and lacks decision deadlines,
snooze state, and decision-focused ordering.
> - This pull request adds secure queue sidecars and enriches the
attention feed with triage data, filters, cursor pagination, and
decide-now ranking.
> - The benefit is a bounded feed that can show the most urgent
decisions first without weakening source visibility rules.
## Linked Issues or Issue Description
This pull request replaces the closed
[#10634](https://github.com/paperclipai/paperclip/pull/10634). It
combines that queue foundation with the dependent attention-feed change
as one review unit.
**Subsystem affected**
Database schema, shared contracts, server authorization and REST APIs,
and the UI attention client library.
**Problem or motivation**
The attention feed can contain hundreds of mixed decision items.
Operators cannot group them into durable queues, set a decision
deadline, snooze an item, or request a bounded page ordered by urgency.
The current client must download the full feed on each refresh.
**Proposed solution**
Store queue membership and triage state by stable attention identity.
Re-authorize each source during queue reads and writes. Enrich attention
items with queue, deadline, snooze, expiry, rule, and origin data. Add
activity and queue filters, opaque cursor pagination, decide-focused
ordering, and a decide-now count.
**Alternatives considered**
Adding queue fields to every source would duplicate schema and
authorization logic across eleven source kinds. Client-only filtering
and sorting would still transfer the full feed and would make pagination
unstable.
**Roadmap alignment**
This change improves the core decision-attention surface and operator
oversight. It does not implement the separate general-purpose work queue
milestone in `ROADMAP.md`.
## What Changed
- Added company-scoped queue, membership, triage, and append-only event
tables with actor and run provenance.
- Added queue CRUD, item membership, starter-rule discovery, and
decide-by and snooze endpoints.
- Kept source authorization on each queue mutation, read, and count.
- Added attention fields for expiry, rule, origin agent, queues,
decide-by attribution, and snooze state.
- Added activity date filters, queue filters, opaque cursor pagination,
and configurable page limits.
- Added decide-now ordering by deadline, expiry, severity, and activity.
- Added `decideNowCount` and excluded actively snoozed items from the
default feed.
- Updated the shared and UI client contracts.
- Added focused server, route, OpenAPI, and UI client tests.
## Verification
- `pnpm exec vitest run server/src/__tests__/attention-service.test.ts
server/src/__tests__/decision-queues-routes.test.ts
server/src/__tests__/openapi-routes.test.ts ui/src/api/attention.test.ts
ui/src/lib/attention.test.ts` (72 tests passed)
- `pnpm --filter @paperclipai/db check:migrations`
- `pnpm -r --filter @paperclipai/db --filter @paperclipai/shared
--filter @paperclipai/server --filter @paperclipai/ui typecheck`
- `git diff --check origin/master...HEAD`
## Risks
- The migration adds four company-scoped tables and provenance foreign
keys. Migration numbering and safety checks pass.
- Attention reads can lazily create starter queues and memberships.
Inserts are idempotent, audited, and transactional.
- Cursor validity depends on the filtered feed. The API returns a clear
validation error when the cursor item no longer exists in that feed.
- Queue reads re-check source visibility. This favors correct
authorization over fewer queries.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, model `gpt-5`. The runtime used agentic reasoning,
repository tools, code execution, and test execution. The runtime did
not expose the context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents escalate work up the org chart (`reports_to`), and operators
pause agents — notably, instance imports pause every agent by default
> - A paused manager does not invalidate the chain (subordinates stay
invokable), so nothing surfaces when an operator unpauses workers but
leaves their manager paused
> - Escalations then dead-letter silently: agent-created issues assigned
to the paused manager sit in a queue nothing will ever run
> - This pull request computes paused ancestors in the existing
org-chain health model and surfaces a non-blocking warning on the agent
read models and detail page
> - The benefit is that the operator learns their escalation paths are
dead before work vanishes into them
## Linked Issues or Issue Description
Fixes#10647 (companion to #10648, which refuses agent-initiated
assignment to paused agents at write time — this PR makes the standing
hazard visible)
## What Changed
- `AgentOrgChainHealth` gains two additive, optional fields:
`pausedAncestors` (paused agents in the `reports_to` chain) and
`escalationWarning` (human-readable, only set when the agent itself can
work — a paused/terminated agent's escalation path is moot). Chain
validity, invokability, and assignability are byte-identical.
- No server route changes needed: the fields flow through every existing
agent read model (list, detail, org chart) since they ride the same
`getAgentWorkEligibility` computation.
- Agent detail page shows an amber "Escalation path is paused" banner
(same visual language as the invalid-chain banner, but non-blocking)
with the warning text naming the paused manager and the two remedies.
## Verification
- `pnpm vitest run packages/shared/src/agent-eligibility.test.ts` — 5
new cases: paused direct manager warns; paused grandparent through a
healthy manager warns; the agent itself paused → no warning (but
ancestors still reported); fully active chain → no warning, empty list;
terminated ancestor keeps the invalid-chain classification without
double-counting as paused.
- Full `@paperclipai/shared` suite (392 tests) and
`agent-eligibility-routes` (54) unchanged.
- `tsc --noEmit` in shared, server, and ui.
## Risks
- Low. Purely additive fields plus one UI banner; no behavior gates on
the new data.
## Model Used
Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended
thinking, agentic tool use. No other models involved.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators spend most of their time on the issue detail page. They
talk to the assigned agent there through comments.
> - The current page reads as a ticket form. The thread sits below
properties, the composer sits mid-page, and live agent activity renders
as dense transcript logs.
> - Talking to an agent is a conversation. A chat-first layout matches
that mental model better than a ticket form.
> - A layout change this large must not disrupt current users. It needs
a safe opt-in path and full parity with the existing thread features.
> - This pull request adds a chat-style task view behind a new
"Chat-Style Tasks" experiment toggle. The flag is off by default and the
existing page is unchanged when it is off.
> - The benefit is a focused, readable conversation with the agent: live
tool activity folds into compact summaries, the composer stays at the
bottom, and properties, plan, and artifacts move into header tabs.
## Linked Issues or Issue Description
Refs #49 (chat with agents is a much-wanted feature).
Related PRs found in the dedup search:
- #4489 — an earlier, closed attempt to promote the conversation to the
primary surface on issue detail. This PR is a fresh, flag-gated take on
the same goal.
- #8837 — an open PR that proposes a two-column task layout. It
restructures the same page but keeps the ticket paradigm; this PR is
orthogonal because it is opt-in and chat-first.
**Subsystem affected**
UI (issue detail page).
**Problem or motivation**
The issue detail page presents agent conversations as a ticket:
properties first, thread below, composer in the middle of the page, and
raw transcript noise during live runs. Users who mainly converse with
their agents must scroll past chrome to follow the conversation, and
live activity is hard to read.
**Proposed solution**
An opt-in chat-style view of the issue detail page, gated by a new
"Chat-Style Tasks" experiment toggle in Settings → Experimental. With
the flag on, the thread fills the center pane, the composer docks to the
bottom of the viewport, Properties / Plan / Artifacts become header
tabs, live turns show a status pill with the current tool action and
elapsed time, and settled turns collapse to a "Worked · N tools" summary
that expands into per-tool rows. With the flag off, nothing changes.
**Alternatives considered**
Restyling the existing layout in place (rejected: too disruptive without
an opt-out), and a separate chat page beside the issue page (rejected:
splits the task's single source of truth). A per-request lab page
(`/task-chat-lab`, dev-only) was kept for design iteration instead.
**Roadmap alignment**
ROADMAP.md "CEO Chat" wants lighter conversations that still resolve to
real work objects. This PR keeps the core task-and-comments model — it
only changes presentation, opt-in — so it does not duplicate that
planned work.
## What Changed
- New `enableTaskChatRedesign` instance setting, exposed as a
"Chat-Style Tasks" experiment card in Settings → Experimental (shared
feature catalog, validators, server instance-settings service, and UI
settings page).
- New `ui/src/components/task-chat/` component family: chat thread with
turn grouping, agent reply bubbles, live status pill, collapsible turn
summaries with per-tool rows, plan tab with a sticky CTA action bar,
inline interaction cards, per-request mode chips, and a bottom-docked
composer.
- A shared tool taxonomy (`tool-taxonomy.ts`) maps tool names to verbs
and icons; the status pill, tool rows, and the classic transcript view
all use it.
- A transcript adapter converts stored run logs into chat turns; it
dedupes tool-call updates by `toolUseId` so tool counts match the
expanded rows, and it keeps a tool row's first real name when later
generic updates arrive.
- Composer: posts on Cmd/Ctrl+Enter, supports image paste with
object-URL thumbnail previews (revoked on clear/unmount), and uploads
through the issue attachments route.
- `IssueDetail.tsx`: with the flag on, pane tabs move to the header bar,
the header is not sticky, and the chat fills the center; with the flag
off, the previous layout renders unchanged.
- Motion tokens for the new animations live in `ui/src/index.css` with a
`motion-tokens.ts` catalog and a test that keeps the two in sync (the
catalog now also covers the shared enter/exit/swap tokens that the
decision/quicklook block declares).
- A dev-only `/task-chat-lab` page with fixtures and a tweak panel for
motion tuning.
## Verification
- `pnpm typecheck` — clean across the workspace.
- `pnpm check:token-gates` — 3/3 CLEAN.
- `cd ui && pnpm vitest run` — 3,344 of 3,345 tests pass locally. The
one failure is `IssueProperties.test.tsx` monitor-row time formatting,
which is timezone-sensitive: it also fails on unmodified `origin/master`
in a non-UTC timezone and passes with `TZ=UTC`. It is not related to
this change.
- `cd server && pnpm vitest run
src/__tests__/instance-settings-service.test.ts` — 21/21 pass (covers
the new setting).
- Manual: start the dev server, open Settings → Experimental, enable
"Chat-Style Tasks", and open any issue. The thread fills the page, the
composer docks to the bottom, and Properties / Plan / Artifacts appear
as header tabs. Assign an agent and comment to watch a live run: the
status pill shows the current tool action with elapsed time, and the
finished turn folds into a "Worked · N tools" summary. Disable the
toggle and confirm the classic page is unchanged.
- Visual snapshot baselines are intentionally not updated: per
`doc/design/DECISION-SHEET.md`, "Per-change snapshot verification
demoted to dormant (Jul 13 2026)".
## Risks
- The flag-off path goes through the same `IssueDetail.tsx` file, so a
regression there would affect current users. Mitigation: the classic
markup renders through the same components as before behind explicit
flag conditionals, and the full UI suite passes.
- The transcript adapter interprets stored run-log formats, including
legacy entries without `toolUseId`. Malformed logs degrade to generic
tool rows rather than crashing.
- The new view changes no server behavior other than one additive
instance setting; it is additive and default-off. Overall risk with the
flag off is low.
## Model Used
- Claude (Anthropic), model id `claude-fable-5` (Claude Fable 5),
extended thinking enabled, agentic tool use (file editing, shell, test
execution) via Claude Code / Claude Agent SDK.
## 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
- [ ] All Paperclip CI gates are green
- [ ] 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: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents can currently perform many mutations directly, while humans
often need a durable review point before cross-issue or destructive
actions occur
> - Existing approvals and issue-thread interactions do not provide a
standalone, reusable object for presenting options, collecting typed
inputs, detecting stale targets, and auditing effect execution
> - The control plane therefore needs a first-class propose mode that
separates an agent's recommendation from the governed mutation it may
cause
> - This pull request adds Decisions v1 across the database, shared
contracts, server execution and telemetry, agent skill guidance, and
operator UI
> - The benefit is that agents can propose multi-option actions safely
while operators get explicit provenance, fail-closed execution,
per-effect results, and a focused attention workflow
## Linked Issues or Issue Description
### Subsystem affected
Cross-cutting: `packages/db`, `packages/shared`, `server`, and `ui`.
### Problem or motivation
Agents need a governed way to propose consequential work without
immediately mutating issues, especially when one choice can affect
several issue trees. Existing approvals and issue-thread interactions do
not provide a standalone object with typed options, target snapshots,
effect-level authorization, expiration, execution outcomes, and reusable
attention-feed presentation.
### Proposed solution
Add first-class Decisions that store options and typed inputs, surface
open proposals in the operator attention feed, validate target freshness
and the origin-agent/operator authorization intersection at decision
time, execute a bounded set of auditable effects, and retain terminal
outcomes. Decisions v1 supports comments, status and assignee changes,
follow-up issue creation, blocker resolution, and issue-tree
cancellation, plus bundle grouping, expiration/dismissal, rule-key
telemetry, and agent-facing API guidance.
### Alternatives considered
- Extend approvals with arbitrary effects: rejected because approvals
represent governed yes/no actions and would become an unsafe generic
mutation envelope.
- Model every proposal as an issue-thread interaction: rejected because
decisions can span several targets and need independent lifecycle,
telemetry, idempotency, and effect results.
- Let agents perform the mutation and ask for retrospective review:
rejected because it removes the pre-execution governance boundary this
feature is meant to provide.
### Roadmap alignment
Aligns with `ROADMAP.md` sections **Agent Reviews and Approvals**,
**Enforced Outcomes**, **MCP Tool Gateway & Apps (governed tool
access)**, and **Activity History** by making explicit decisions,
authorization gates, auditable execution, and terminal outcomes
first-class control-plane objects.
### Additional context
This does not replace existing approvals or issue-thread interactions,
and it does not add an unrestricted generic mutation effect.
## What Changed
- Added company-scoped decision, option, target, and effect-execution
schema plus migration and shared TypeScript/Zod contracts.
- Added decision routes and services for propose, list/get, decide,
dismiss, cancel, target freshness checks, authorization intersection,
idempotency, activity logging, and execution auditing.
- Added rule-key decision telemetry and attention-feed metadata so open
decisions are visible and measurable.
- Added agent skill documentation for proposing and resolving decisions
through the Paperclip API.
- Added the Decisions UI: API client, query keys, inline attention
resolver, bundle grouping, target-issue strip, terminal history,
destructive confirmation, and per-effect result rendering.
- Added server service coverage, DecisionCard state tests, and Storybook
stories for the supported visual states.
## Verification
- `pnpm -r typecheck` — passed.
- `pnpm test:run` — 2,876 passed, 1 skipped, with one unrelated
cross-suite cleanup-order failure in
`heartbeat-responsible-user-invariant.test.ts`; the failing file passes
in isolation (`6/6`).
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-responsible-user-invariant.test.ts` — passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/DecisionCard.test.tsx` — passed (`9/9`).
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/authz-existence-oracle-guard.test.ts
src/__tests__/openapi-routes.test.ts` — passed (`5/5`).
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/decisions-service.test.ts` — passed (`16/16`).
- `pnpm --filter paperclipai exec vitest run
src/__tests__/company-import-export-e2e.test.ts` — passed (`1/1`).
- `pnpm --filter @paperclipai/server typecheck` and `pnpm --filter
paperclipai typecheck` — passed.
- `pnpm build` — passed.
- Rebased-head focused suite — passed (`6` files, `88` tests): shared
decision contracts, Decisions service, OpenAPI routes, startup feedback
export, DecisionCard states, and attention helpers. The follow-up
stale-secondary-target regression passes in the DecisionCard suite
(`10/10`).
- Rebased-head scoped typechecks — passed for `@paperclipai/shared`,
`@paperclipai/db`, `@paperclipai/server`, and `@paperclipai/ui`.
- Rebased-head migration numbering and safety checks — passed after
renumbering the additive migration to `0193` and making it replay-safe
for environments that applied the earlier feature-branch number.
- `pnpm check:token-gates` — passed with all gates clean.
- GitHub PR workflow and Greptile review for
`1f9f7645882d05dfdd9c99377c03a1f53f20e8be` — running after the
stale-secondary-target fix and PR metadata refresh on July 27, 2026.
- `pnpm --filter @paperclipai/ui build-storybook` exposes an existing
Storybook version mismatch (`storybook` 10.4.6 vs
`@storybook/addon-docs` 10.5.0); Decisions stories were validated with
the docs addon temporarily disabled and the tracked config remains
unchanged.
## Risks
- **Migration:** Adds replay-safe migration `0193`; migration numbering
and safety checks pass. The new tables and indexes are additive.
- **Authorization:** Effect execution intersects the proposing agent's
permissions with the responsible user context and fails closed; mistakes
could reject a valid proposal rather than silently over-authorize it.
- **Concurrency:** Target snapshots and idempotency keys protect against
stale or duplicate execution, but reviewers should focus on mixed-effect
partial outcomes and retry behavior.
- **UI:** Decisions are integrated into the existing attention feed
rather than a separate navigation surface, reducing routing risk but
increasing the importance of attention-item metadata compatibility.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex CLI using `gpt-5.6-sol` for final PR preparation, review
fixes, and verification; repository tools and code execution were
enabled, and context-window size is not exposed in this runtime.
- Anthropic Claude Opus 4.8 with 1M context assisted with the Decisions
UI implementation, as recorded in the relevant commits.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators need an audit record of agent actions across tasks,
comments, documents, approvals, and runs
> - The permission-gated audit read API provides that record, but
operators cannot inspect it in the product
> - A readable UI must preserve company boundaries, server-side
permission decisions, and redaction
> - Audit exports must also be safe to open in spreadsheet software and
must record the export itself
> - This pull request adds company and per-agent audit views plus a
guarded CSV export
> - The benefit is a searchable, filterable, and reviewable agent action
history with direct links back to work
## Linked Issues or Issue Description
**Feature.** This change adds the frontend and CSV export for the agent
action audit log.
Refs #9731 and #9735.
- Problem: agent actions are recorded, but operators have no readable
product surface to inspect or export them.
- Solution: add a company audit page and a per-agent Audit tab that use
the permission-gated audit API.
- Alternative: build a separate plugin-only surface. This was rejected
because the existing permission model already supports a unified,
server-authoritative view.
This pull request targets the audit epic branch, which contains the
merged #9735 audit API.
## What Changed
- Added a company Audit page and sidebar entry.
- Added a per-agent Audit tab with a fixed agent filter.
- Added filters for agent, responsible user, action domain, entity type,
and date range.
- Added task and run links, responsible-user context, cursor pagination,
and readable action text.
- Added a permission-denied Enterprise card for callers without
`audit:view_agent_actions`.
- Added a CSV export that is permission-gated, capped, self-audited,
CSV-escaped, and protected against spreadsheet formula injection.
- Preserved the merged audit API cursor validation, redaction, and
sub-millisecond pagination behavior.
## Verification
- `pnpm exec vitest run ui/src/pages/audit/AuditFeed.test.tsx` — 6
passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/agent-action-audit-routes.test.ts` — 8 passed with
embedded PostgreSQL.
- `pnpm -r typecheck` — passed across all workspaces.
- `pnpm build` — passed across all workspaces.
- `pnpm test:run` — all completed shards passed except one
environment-sensitive CLI assertion caused by injected static AWS
credential variables; the exact test passes 8/8 with those variables
unset.
- Manual Chromium QA exercised the populated feed, active filters,
permission-denied card, per-agent tab, and CSV export.
## Screenshots and Manual QA
- [All audit states exercised in
Chromium](https://github.com/paperclipai/paperclip/pull/9744#issuecomment-4998997001)
- [Detailed browser report and per-agent tab root
cause](https://github.com/paperclipai/paperclip/pull/9744#issuecomment-4998771061)
The per-agent redirect defect found during QA is fixed in this branch.
## Risks
Low to moderate risk. The UI and export route are additive and use the
existing company-scoped permission gate. The main risks are large
exports and spreadsheet interpretation. The export is capped at 10,000
rows, records truncation accurately, and prefixes formula-like cells as
text. There are no schema changes or migrations.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Anthropic Claude Opus 4.8, 1M context, extended thinking, tool use,
and code execution produced the original implementation.
- OpenAI Codex, GPT-5 (deployment ID and context window not exposed),
reasoning, tool use, code execution, browser-test orchestration, and
GitHub review tooling repaired and verified the pull request.
## 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)
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The UI test suite protects the board route table
> - The Cases routing regression test needs only the route table and
sentinel pages
> - The test initialized the full cloud access query flow for each route
> - That unrelated setup made the two assertions spend several seconds
polling
> - This pull request isolates the routing dependency and removes the
long timeout
> - The benefit is faster and more focused route regression coverage
## Linked Issues or Issue Description
**What happened?**
The Cases routing regression test initialized cloud health, session, and
board access queries. Its two route assertions spent about 6.69 seconds
in test execution.
**Expected behavior**
The route regression test must bypass unrelated cloud access checks and
resolve the two route assertions synchronously.
**Steps to reproduce**
1. Run `pnpm --dir ui exec vitest run src/App.cases-routing.test.tsx` on
the base commit.
2. Inspect the Vitest test duration.
3. Observe that the test waits through unrelated query transitions.
**Paperclip version or commit**
`7301fae942c3d5826974335cb40d6f1e0d95d1e0`
**Deployment mode**
Built from source. The defect affects the UI unit test suite.
Related pull request: #9198 introduced the Cases route regression
coverage.
## What Changed
- Mock `CloudAccessGate` at the routing boundary.
- Import the app after hoisted CSS setup and module mocks.
- Remove the query client and three unrelated API mocks.
- Replace long polling with a bounded three-turn route wait.
- Remove the custom 20-second test timeouts.
## Verification
- `pnpm --dir ui exec vitest run src/App.cases-routing.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
The focused run passed both tests. Test execution changed from about
6.69 seconds on the base commit to 40 milliseconds on this branch.
## Risks
Low risk. The production route table is unchanged. The test still
renders the real `App` route table and the same sentinel pages.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex with model ID `gpt-5`. The context-window size is not
exposed to this run. The run used reasoning, repository tools, code
execution, and GitHub tools.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents update tasks through the issue API.
> - The update response did not state which values changed.
> - Blocker updates also did not echo the scalar blocker IDs.
> - Agents therefore used an extra GET request to confirm a successful
write.
> - This pull request adds an authoritative change receipt and an
optional small response.
> - The benefit is fewer API calls with a clear and compatible write
contract.
## Linked Issues or Issue Description
No public GitHub issue exists for this change.
### Subsystem affected
Cross-cutting: `server/`, `packages/shared`, and the UI issue cache.
### Problem or motivation
A successful issue PATCH returned the updated issue, but it did not
identify the effective changes. Blocker writes returned relation
summaries without the scalar IDs. Agents could not distinguish a
confirmed clear operation from missing data. The response must confirm
committed field and blocker changes while existing UI clients continue
to receive the full issue by default.
### Proposed solution
Add a `changes` receipt. Add a conditional `blockedByIssueIds` echo.
Support `Prefer: return=minimal`. Keep the full response as the default.
### Alternatives considered
Make the small response the default for agent tokens. This would create
different response contracts by actor type, so this pull request does
not use that design.
### Roadmap alignment
This is a focused control-plane reliability improvement. It does not
duplicate an open roadmap milestone.
## What Changed
- Compute committed issue row and relation changes in the issue service.
- Omit no-op fields and truncate changed long text values to 200
characters.
- Echo blocker ID arrays for blocker set and clear requests.
- Add the opt-in `Prefer: return=minimal` response and
`Preference-Applied` header.
- Keep receipt metadata out of React Query issue caches.
- Add route and embedded Postgres tests for the new contract.
## Verification
- `pnpm exec vitest run
server/src/__tests__/issue-activity-events-routes.test.ts`
- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts -t
"returns authoritative update receipts for row fields and blocker
relations"`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
- `git diff --check`
## Risks
- Low compatibility risk. The default response only adds receipt fields.
- Minimal mode is opt-in. Existing clients do not receive a smaller
body.
- The receipt excludes `updatedAt` because the response already returns
it as the freshness anchor.
- Prose API and agent workflow guidance will follow after the server
contract is available.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex based on GPT-5. The exact deployment ID, context window
size, and reasoning mode are not exposed to the agent. The agent used
repository tools, code execution, and 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The web UI uses one shared markdown editor for comments, issue
descriptions, and documents.
> - Users type `>` at the start of a line to insert a blockquote.
> - The live editor shortcut does not always run in every browser and
input method.
> - The markdown exporter then changes the leading `>` to `\>` and saves
literal text.
> - The saved text does not render as a blockquote.
> - This pull request restores the blockquote marker when markdown
enters or leaves the editor.
> - The benefit is reliable blockquote insertion on every surface that
uses the shared editor.
## Linked Issues or Issue Description
No public GitHub issue exists.
Related prior attempt: #10465.
**What happened?**
The shared markdown editor sometimes saved a blockquote as literal text.
This happened when the live shortcut did not run. The exporter saved `\>
text`, which rendered as literal `> text`.
**Expected behavior**
A line that starts with `>` must render as a blockquote in comments,
issue descriptions, and documents.
**Steps to reproduce**
1. Open a task comment composer, description editor, or document editor.
2. Add `> ` to an existing line, or use an input method that does not
run the live shortcut.
3. Save the content.
4. Observe that the saved line renders as literal text instead of a
blockquote.
**Paperclip version or commit**
`master` at `78f8c6c3d4`.
**Deployment mode**
Self-hosted server.
## What Changed
- Add `unescapeBlockquoteMarkers()` to restore block-level `\>` markers.
- Keep indented code, list content, nested content, and fenced code
unchanged.
- Apply the helper when markdown enters and leaves `MarkdownEditor`.
- Add focused tests for line position, indentation, container prefixes,
and CommonMark fence rules.
## Verification
- `pnpm exec vitest run ui/src/lib/blockquote-markdown.test.ts` passes
with 22 tests.
- `pnpm exec vitest run ui/src/components/MarkdownEditor.test.tsx`
passes with 37 tests.
- `pnpm --filter @paperclipai/ui typecheck` passes.
- `pnpm check:token-gates` passes.
- `git diff --check origin/master...HEAD` passes.
- A browser harness used the real `MarkdownEditor` and `IssueChatThread`
composer. It confirmed that `> text` renders as a blockquote and exports
as `> text`.
- The [Cutter
preview](https://github.com/paperclipai/paperclip/pull/10466#issuecomment-5140558263)
supplies a task-page screenshot and an editor interaction video.
## Risks
- Low risk. The helper returns the input unchanged when it contains no
`\>`.
- A paragraph that deliberately starts with literal `\>` now becomes a
blockquote. The editor has no literal-marker control, so this matches
the available input behavior.
- There are no database, API, or migration changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Anthropic Claude Opus 4.8 (`claude-opus-4-8`, 1M context), extended
thinking, with tool use and code execution.
- OpenAI Codex with GPT-5 (`gpt-5`; runtime build and context-window
metadata were not exposed), with reasoning, tool use, code execution,
and GitHub review tools.
## 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 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 (none
needed)
- [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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
The Decisions queue ran five parallel colour/icon vocabularies chosen by
source kind, plus a separate severity badge, so two rows needing the same
response could look unrelated and none of it matched the task list.
Every row now resolves to one of two kinds, each borrowing the task status
it corresponds to: blocking renders as `blocked`, review as `in_review`,
both through StatusGlyph and the existing --status-task-icon-* tokens.
Source kinds keep their own wording; only colour and icon merge.
Card anatomy follows the design mock: no left accent rail, rounded cards
16px apart, a "/"-separated meta breadcrumb, a named See more / See less
control, and no separately tinted drawer when expanded. Verb order is
fixed across both states. Severity moves from chrome to a toolbar filter.
Four defects fixed along the way:
- blocked rows reported themselves as their own blocker (server-side)
- the task key was missing wherever the row's subject IS the task
- the task quicklook stuck open, because closing handed focus back to a
trigger that opens on focus
- the card ring appeared on click, and only on cards with a toggle
Also: the standard task preview is aligned to its trigger's text and
scales out of it, the task eyebrow renders its project as a tile, and the
first motion tokens land alongside the disclosure and crossfade.
Supersedes #9574 and #9575.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Environment configs (sandbox providers, SSH) can bind stored company
secrets through `format: "secret-ref"` fields, picked in the environment
editor's secret picker
> - Environments are instance-scoped and shared by every company on an
instance, but the picker lists only the current company's secrets, so a
ref pointing at another company's secret renders as "Missing secret (…)"
in destructive styling
> - That state is indistinguishable from a genuinely deleted secret, so
operators "fix" a healthy binding by creating a duplicate secret in
their own company — the exact sequence that used to corrupt bindings
before #10576
> - This pull request adds an instance-gated metadata endpoint for an
environment's secret refs and teaches the picker to name a cross-company
secret and its owner honestly
> - The benefit is that operators can tell a healthy cross-company
binding from a broken one, and stop creating duplicate secrets
## Linked Issues or Issue Description
**Is your feature request related to a problem? Please describe.**
In the environment editor, a secret-ref field that points at a secret
owned by a different company shows "Missing secret (22095402…)" in red,
with "The previously selected secret is no longer available. Pick
another or remove the binding." The binding is actually healthy — the
current company's picker just cannot list the other company's secrets.
Operators react by creating a duplicate secret and re-pointing the
field.
**Describe the solution you'd like**
The editor should know the referenced secret's name, status, and owning
company (metadata only, never the value) and present a cross-company ref
neutrally, a deleted secret as deleted, and only an unknown id as
missing.
Related: #10576 (fixes the binding corruption this UI state used to
trigger).
## What Changed
- New `GET /environments/:id/secret-refs` returns `{ refs: [{
configPath, secretId, name, status, companyId, companyName }] }` for the
environment's config-derived secret refs. Values are never returned. The
route sits behind `assertCanAccessInstanceEnvironments`, the same gate
as environment editing.
- New `secretService.describeSecretRefs` loads that metadata across
companies; unknown ids are omitted.
- `SecretBindingPicker` reads an optional `SecretRefHintsContext` (keyed
by secret id). With a hint, a ref the company list cannot show renders
as `NAME — Owning Company` with neutral styling and the note "Owned by
the … company. The binding keeps working; selecting a secret from this
list re-points it here." A hint with `status: "deleted"` reports the
secret as deleted. Without hints, behavior is byte-identical to before —
agent editors and other picker users are unaffected.
- `CompanyEnvironments` fetches descriptors for the environment being
edited and provides them through the context.
## Verification
- `cd server && pnpm vitest run src/__tests__/environment-routes.test.ts
src/__tests__/secrets-service.test.ts` — new endpoint happy path, agent
403 (descriptors never computed), and embedded-Postgres coverage proving
cross-company names resolve and unknown ids drop out.
- `cd ui && pnpm vitest run src/components/SecretBindingPicker.test.tsx
src/components/JsonSchemaForm.test.tsx
src/pages/CompanyEnvironments.test.tsx` — hinted cross-company
rendering, hinted deleted secret, and unchanged no-hint fallback.
- `pnpm run typecheck` in `server` and `ui`.
- Manual: edit an environment whose secret-ref field references another
company's secret; the field names the secret and its owning company
instead of "Missing secret".
## Risks
- The endpoint exposes secret names and company names across companies
to instance-level environment editors. Those actors already manage
instance-shared environments (and instance admins are implicit members
of every company), so this reveals no secret material and no new reach;
the service method documents that callers must sit behind an
instance-level gate.
- UI change is additive and context-gated; pickers without a provider
render exactly as before.
## Model Used
Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — extended
thinking, agentic tool use (file edits, vitest/tsc runs). No other
models involved.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI shows the workspace attached to an issue in
`ui/src/components/IssueWorkspaceCard.tsx`
> - That card renders values such as the branch name and the workspace
path through a small `CopyableInline` component, each with an icon-only
copy button
> - The button has a `title` attribute only. Screen readers do not
announce `title` reliably. A screen reader user hears no useful name for
the button, because the button contains an icon and no text
> - The button also starts a 1.5 second `setTimeout` to reset its
"copied" state. Nothing clears that timer. If the card unmounts first,
the callback sets state on an unmounted component
> - This pull request adds a dynamic `aria-label` to the button and
clears the timer in a `useEffect` cleanup
> - The benefit is a copy control that assistive technology can
announce, and no stray timer after the card unmounts
## Linked Issues or Issue Description
No existing GitHub issue covers this. The problem is described below
with the fields from
[`bug_report.yml`](.github/ISSUE_TEMPLATE/bug_report.yml).
**What happened?**
Open an issue that has a workspace attached. Tab to the copy button next
to the branch or the workspace path in the workspace card. The screen
reader announces an unlabeled button, because the button holds only a
lucide `Copy` icon and a `title` attribute. Separately, copy a value and
navigate away within 1.5 seconds. The pending `setTimeout` then calls
`setCopied(false)` on an unmounted component.
**Expected behavior**
The copy button has an accessible name that says what it copies, and the
name changes to confirm the copy. The reset timer is cleared when the
component unmounts.
**Steps to reproduce**
1. Run the app locally with `pnpm dev`.
2. Open an issue that has a workspace attached, so `IssueWorkspaceCard`
renders.
3. Turn on a screen reader (VoiceOver, NVDA).
4. Tab to the copy button next to the workspace path or the branch name.
The button has no useful accessible name.
5. Click the copy button, then navigate away from the issue in under 1.5
seconds. The reset timer is still pending.
**Paperclip version or commit**
Reproducible on `master` at this pull request's base commit.
**Deployment mode**
Local dev (pnpm dev).
Related pull request, not a duplicate: #3531 makes copy-to-clipboard
buttons work in non-secure contexts. That pull request changes the
clipboard write path. This one changes the button label and the timer
cleanup, so the two do not overlap.
## What Changed
- Added an `aria-label` to the `CopyableInline` copy button in
`ui/src/components/IssueWorkspaceCard.tsx`. The label reads `Copy
<label>` (for example "Copy branch"), falls back to `Copy value` when
the component gets no `label` prop, and changes to `Copied to clipboard`
after a copy.
- Added a `useEffect` cleanup that calls
`clearTimeout(timerRef.current)` on unmount, so the 1.5 second reset
timer cannot fire after the component unmounts.
## Verification
- CI is green on this pull request.
- Static check: `pnpm -r typecheck`.
- Test suite: `pnpm test`.
- Manual, screen reader: open an issue with a workspace, tab to the copy
button next to the path or the branch, and confirm the announcement is
"Copy path" or "Copy branch". Activate the button and confirm the
announcement changes to "Copied to clipboard".
- Manual, timer: click the copy button and navigate away from the issue
immediately. Confirm the console shows no unmounted-component state
update.
## Risks
Low risk. The change adds one ARIA attribute and one unmount cleanup in
a single presentational component. No behavior changes for mouse users,
no API or schema change. `clearTimeout(undefined)` is a no-op, so the
cleanup is safe when the user never copied.
## Model Used
- Anthropic Claude Opus, model ID `claude-opus-4-6`, 200K context
window, extended thinking enabled, with tool use for file edits.
- Recorded by a maintainer while bringing this description up to the
current template. The original description predates the Model Used
requirement, so the author did not state a model. Author: please correct
this line if the model was different.
## 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
- [ ] 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
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Notes on the checklist: no test or documentation change applies to a
two-line ARIA and cleanup fix in one component. The Greptile box stays
unchecked until the current review round closes.
## Problem
In the ExecutionWorkspaceCloseDialog, the "Last checked" timestamp was
using \`new Date()\` which show the current render time, not when the
readiness check API call actually completed.
\`\`\`tsx
Last checked {formatDateTime(new Date())} // always NOW
\`\`\`
This mean every time React re-render the component (which happen
frequently), the timestamp update to the current moment. User see "Last
checked 2:45:30 PM" and think the check just ran, but actually it might
have ran 30 seconds ago. The timestamp is lying.
## What I changed
Changed from \`new Date()\` to \`new
Date(readinessQuery.dataUpdatedAt)\` which is the actual timestamp from
React Query tracking when the API response was last received.
\`\`\`tsx
Last checked {formatDateTime(new Date(readinessQuery.dataUpdatedAt))} //
actual check time
\`\`\`
Now the timestamp accurately show when the close readiness check was
performed. It stay stable between re-renders until the query actually
refetch.
## How to test
1. Open an execution workspace > click Close button to open the dialog
2. The "Last checked" timestamp should show when the API call completed
3. Wait a few seconds - timestamp should NOT update (it's the query
time, not render time)
4. Click "Recheck" or trigger refetch - timestamp should update to new
fetch time
1 file, 1 line changed.
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators use the task page header to read and change task state
> - The status and priority icons already had picker logic, but the
compact triggers were not semantic controls
> - This made pointer and keyboard interaction unreliable in the task
header
> - This pull request makes both compact icon triggers real buttons and
keeps the existing picker behavior
> - The benefit is that operators can change status and priority
directly from the header with pointer or keyboard input
## Linked Issues or Issue Description
### Subsystem affected
`ui/` — React + Vite board UI.
### Problem or motivation
The compact status and priority icons can receive change handlers, but
their popover triggers are plain icon elements. They do not provide a
reliable click target, keyboard focus, or control label. Operators need
to change both values directly from the task header.
### Proposed solution
Use semantic button triggers in the shared status and priority
components. Keep the existing API update wiring and picker options. Keep
task-row navigation links separate from editable row controls. An
operator can select either icon, open its picker, and choose a new task
status or priority.
### Alternatives considered
A task-page-only wrapper would duplicate control behavior. Moving the
controls would also change the page layout. The shared components
already own the picker behavior, so a shared trigger fix is smaller and
more consistent.
### Roadmap alignment
This is a focused board UI usability and accessibility fix. It does not
duplicate a planned roadmap feature.
### Additional context
The task page already passes change handlers to these shared components.
This change makes that existing path interactive and accessible.
## What Changed
- Added semantic button triggers for compact and labeled status
controls.
- Added semantic button triggers for compact and labeled priority
controls.
- Added accessible current-state labels and keyboard focus styles.
- Separated issue-row navigation links from row controls to avoid nested
interactive elements.
- Added real popover interaction coverage and row semantics regressions.
- Added task-page regression tests for update requests.
## Verification
- Focused task-header and row suites passed with 131 tests.
- The final row, inbox, and picker regression suites passed with 83
tests.
- `pnpm --filter @paperclipai/ui exec tsc -b --force` passed.
- `pnpm run typecheck:build-gaps` reproduced the CI typecheck before the
fix. The forced UI build passed after the fix.
- `pnpm check:token-gates` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `pnpm test:run` completed the server and UI partitions. One unrelated
CLI AWS doctor assertion saw injected static AWS credentials and
returned `warn` instead of `pass`. The exact test passed after those two
environment variables were removed.
- `git diff --check origin/master...HEAD` passed.
- All latest-head GitHub checks passed, including both e2e shards.
- Greptile passed at the required threshold with zero open review
threads.
## Risks
- The shared issue-row DOM now uses a full-area navigation link beside
native action buttons.
- Existing visual layout, pointer navigation, keyboard navigation, and
action behavior remain covered by row, inbox, and list tests.
- Read-only status and priority icon uses are unchanged.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex with the `gpt-5` model. The runtime did not expose its
context window size. The agent used reasoning, repository tools, code
execution, and GitHub CLI integration.
## 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 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The recovery subsystem restores work after an agent run stops or
loses state
> - Recovery notices currently use the same visual weight as normal work
comments
> - Recovery agents can also post long narratives that obscure the
useful hand-off
> - The server must identify recovery output because agents cannot set
presentation controls
> - This pull request adds compact recovery notices, structured action
references, and brief recovery prompts
> - The benefit is a quieter issue thread that still keeps recovery
state inspectable
## Linked Issues or Issue Description
**Subsystem affected**
Cross-cutting: `server/`, `packages/shared`, and
`packages/adapter-utils`.
**Problem or motivation**
Recovery notices and recovery-run comments can dominate an issue thread.
Operators must scan routine recovery narration before they find the work
hand-off.
**Proposed solution**
Give routine recovery output a compact system-notice presentation.
Derive the presentation on the server so agents cannot hide arbitrary
comments. Keep the successful missing-state summary fully visible
because that comment is the recovery deliverable.
**Alternatives considered**
The UI could detect recovery text. That approach is fragile and does not
provide structured action references. Agents could also set presentation
directly, but that would weaken the current board-only security
boundary.
**Roadmap alignment**
This change refines the completed “Self-healing runs & automatic
recovery” and “Enforced Outcomes” roadmap areas. It does not add a
competing roadmap capability.
**Additional context**
The scope covers shared comment validation, server recovery notices,
agent-comment derivation, and recovery prompt text. No database
migration is needed because presentation data already uses JSON.
## What Changed
- Add the `compact` issue-comment presentation density to shared
constants, types, and validation.
- Give recovery escalation, waiting, and in-place notices compact titles
and structured recovery-action metadata.
- Use recovery-action metadata for notice deduplication, with the legacy
text marker as a compatibility fallback.
- Derive compact presentation for comments from recovery-scoped runs
while preserving the board-only presentation boundary.
- Keep successful missing-state recovery summaries fully visible.
- Ask recovery participants to record outcomes in `resolutionNote` and
keep source-issue comments brief.
- Add shared, route, service, and prompt tests for the new behavior and
exceptions.
## Verification
- `pnpm -r typecheck`
- Focused Vitest coverage: 320 tests passed across shared validators,
adapter prompts, issue comments, recovery actions, and heartbeat
recovery.
- Full server phase: 292 files passed, 3,094 tests passed, and 2 tests
skipped.
- Full UI phase: 386 files passed and 3,182 tests passed.
- `pnpm build`
- Known master baseline: `cli/src/__tests__/secrets.test.ts` expects
`pass`, but the current implementation returns `warn` when strict secret
mode is disabled for Postgres. This branch does not change CLI secrets
code.
## Risks
- Low migration risk. The presentation column is JSON and needs no
database migration.
- Recovery-run detection depends on the persisted run context snapshot.
- Structured metadata becomes the primary deduplication key. The
existing body marker remains as a fallback for older comments.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex with `gpt-5.6-sol`. The runtime did not expose the
context-window size. The model used agentic reasoning, repository tools,
code execution, and 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The timeline page visualizes company activity across a selected date
window
> - The UI requested only the first paginated issue batch even when the
selected zoom covered seven or thirty days
> - A busy company could therefore render an incomplete timeline while
the controls implied the full window was loaded
> - The timeline query needs to exhaust the API pagination for the
selected date range and combine each page without duplicating shared
timeline records
> - This pull request adds a paginated window loader, merges the
returned timeline data, and covers the multi-page behavior with a
regression test
> - The benefit is that the visible timeline matches the selected zoom
window instead of silently omitting later issues
## Linked Issues or Issue Description
### Pre-submission checklist
- [x] I searched existing open and closed issues and pull requests; no
matching report or implementation was found.
- [x] I reproduced the behavior against the pre-change `master`
implementation.
- [x] I confirmed the error originates in Paperclip's core timeline UI,
not an adapter, provider, or local configuration.
### What happened?
Selecting the default seven-day timeline range loaded only the first API
page (up to 500 issues). Companies with more activity therefore
displayed incomplete data even though the controls showed the full
selected window.
### Expected behavior
The timeline should load all issue pages that fall within the selected
date window.
### Steps to reproduce
1. Open the company timeline for a date range containing more than 500
issues.
2. Keep the default seven-day range or select another multi-day preset.
3. Observe that only the first page of issue-backed timeline data is
shown.
### Paperclip version or commit
Pre-change `master`.
### Deployment mode
Local dev source build. The behavior is not adapter-specific and is
independent of database mode and access context.
### Privacy checklist
- [x] No logs, configuration, personally identifiable information, or
user data are included.
## What Changed
- Added pagination parameters to the timeline API client contract.
- Added a timeline window loader that requests every issue page and
deduplicates actors, spans, events, and edges while preserving
pagination metadata.
- Switched the timeline query to use the complete-window loader.
- Added a regression test proving a 501-issue window loads both API
pages and combines their records.
- Preserved delegation events and edges when parent and child issues
fall on different API pages, with a server regression test.
## Verification
- `pnpm exec vitest run
server/src/__tests__/work-timeline-service.test.ts
ui/src/pages/Timeline.test.tsx` — 16 tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm check:token-gates` — all gates clean.
- `git diff --check origin/master...HEAD` — passed.
- Remote CI: build, typecheck, both e2e shards, canary, policy,
security, every general/serialized test shard, and the aggregate
`verify` gate passed on head `24784b28e9`.
## Risks
- Low risk: the change is isolated to timeline data loading and has no
schema or API endpoint changes.
- Large date windows now make sequential requests for all issue pages,
increasing request count for very active companies; the 500-item page
size bounds each response.
- Merged records rely on stable identifiers or composite event/edge
keys; the regression test covers cross-page combination and
deduplication behavior.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex using GPT-5.4 with reasoning, repository tool use, shell
execution, and test execution. The runtime does not expose the exact
context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Company Import/Export (#10507, hardened in #10523 and #10531) now
imports a large company end to end via an async job
> - A real 1,418-issue import succeeded, but three rough edges showed up
in that success
> - Imported issues flooded the inbox, a completed import surfaced a
false "failed" message after its in-memory result expired, and the new
company didn't appear in the switcher until a manual refresh
> - This pull request keeps imported issues out of the inbox, treats an
expired-but-completed import as success, and refreshes the company list
on completion
> - The benefit is that a successful import looks and feels successful,
and doesn't bury the user's inbox in historical tasks
## Linked Issues or Issue Description
- Refs #10507 / #10523 / #10531 (Import/Export and its hardening). No
open issue; three post-import bugs described above.
## What Changed
- **Imported issues no longer flood the inbox.** The inbox "mine" tab is
a query: an issue is "touched" if the user authored a comment on it, and
import re-attributes bundled user comments to the importing user — so
every imported issue appeared. Import now seeds a per-user
`issue_inbox_archives` row for each imported issue (via a batched
`issues.archiveImportedInbox`), the exact table the inbox visibility
query excludes. Gated on an actor user id, so agent/system imports and
normal issue creation are untouched; genuine new activity still
resurfaces the issue.
- **A completed import no longer shows a false failure.** The in-memory
job's terminal retention was 5 minutes, so a poll after that 404'd and
the UI showed "failed." Retention is extended to 60 minutes — the real
mitigation for a user who steps away during a long import.
`watchImportJob` additionally treats a *server-confirmed* success whose
full result is no longer retained (a `succeeded` status carrying only
the compact summary — a cloud tenant job, or a board job whose full
in-memory result aged out) as a soft success ("import completed — open
the company"), navigating by the summary's company id. A 404 while the
job is still being watched is *not* treated as success: a running job is
never dropped by the retention sweep, so its disappearance means a
restart mid-import that may not have finished, and it surfaces the
honest "may have restarted while the import ran" error. A first-poll 404
(the id never existed) is likewise a real error.
- **The imported company appears without a refresh.** `onSuccess` now
invalidates the companies/switcher query unconditionally (covering both
the full-result and expired-but-completed paths) and navigates by the
job's company id.
## Verification
- shared/server/ui typechecks clean; 15 UI tests in the touched spec
green, plus the embedded-Postgres import batching and portability-routes
suites.
- New tests: embedded-Postgres test that imported touched issues are
archived for the actor and excluded from the inbox query while a
normally-created issue still appears; job resolvable at the old window+1
and only 404s past 60 min; UI soft success on a server-confirmed
`succeeded` job without a retained full result (no error, list
invalidated, navigates by company id), a running-then-gone job → honest
error (restart mid-import), and a first-poll 404 → error.
## Risks
- Low and import-scoped: the inbox archive only affects imported issues
for the importing user; normal issue creation and non-user
(agent/system) imports are unchanged. Retention extension is a constant;
the async job store remains in-memory by design. A restart mid-import
still 404s and is surfaced honestly as a possible failure (never masked
as success); only a server-confirmed success whose full result has
expired is reported as a soft success.
## Model Used
- Implementation: Claude Fable 5 (`claude-fable-5`, Anthropic). Review
hardening (the confirmed-success narrowing): Claude Opus 4.8
(`claude-opus-4-8`, Anthropic). Both via the Claude Code CLI with
extended thinking + tool use; root-caused against the live import.
## 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
- [x] My branch name describes the change and contains no internal
ticket id
- [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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Company Import (#10507, hardened in #10523) lets a user upload a
company package on the Import page
> - The page expanded the user's `.zip` into a files map and POSTed it
as ONE inline JSON body — ~40MB for a real company because attachment
blobs get base64-inflated
> - On Paperclip Cloud that body travels browser → harness proxy →
tenant, where it truncated in transit → body-parser 400 → the browser
saw "Failed to fetch", and nothing imported
> - Two compounding causes: the giant inline body itself, and the board
async opt-in riding an `x-paperclip-cloud-*` header that the Cloud
harness strips as anti-spoofing (so async never engaged and the import
held one fragile synchronous connection)
> - This pull request uploads the raw compressed `.zip` as a multipart
request (about a third the size, already compressed) parsed server-side
into the same bundle the importer consumes, and moves the async opt-in
to a proxy-safe `?async=1`
> - The benefit is that a large-company import actually completes
through Cloud: a small compressed upload, a real async job that survives
dropped connections
## Linked Issues or Issue Description
- Refs #10507 / #10523 (Import/Export and its hardening). No open issue;
problem described above (large-company browser import through a proxy:
inline JSON body truncates → 400 → "Failed to fetch"; async opt-in
header stripped by the front door → async never engages).
## What Changed
- **Multipart zip transport.** The Import page uploads the raw `File` as
`multipart/form-data` (field `package`, import options in a JSON `meta`
field); the server unzips it into `{ rootPath, files }` and runs the
exact existing preview/import logic. The `application/json` inline path
is byte-identical for CLI/programmatic callers. Bare `application/zip`
(meta via `?meta=`) is also accepted for programmatic use.
- **Shared node zip reader.** `packages/shared/src/portability-zip.ts`
(node-only subpath, not re-exported to the browser bundle — same pattern
as `portability-hash.ts`); the CLI's `zip.ts` becomes a thin re-export.
Identical codec (STORE + DEFLATE via `inflateRawSync`, rejects data
descriptors/zip64).
- **Proxy-safe async signal.** `wantsAsyncImport` = `?async=1` (board
browsers, survives the harness) OR the existing
`x-paperclip-cloud-async-import` header (cloud tenants, set
server-side). The UI async client now uses `?async=1`. Backward
compatible.
- **Size + preflight.** New `PORTABLE_ZIP_UPLOAD_LIMIT_BYTES = 128MB`;
the inline 56MB preflight no longer gates the zip path (it shows the
compressed size instead). Async submit/poll/resume, the duplicate-guard
fingerprint (now over the resolved bundle), pause-on-import,
progress/error panels, and activation all apply to the multipart path.
- OpenAPI documents json + multipart + zip bodies and the `async` query
param.
## Verification
- Full typecheck chain (shared, server, ui, cli) clean.
- 152 tests across 8 files: new `portability-zip.test.ts`
(STORE/DEFLATE/base64-blob byte-exact round-trip, truncation throws,
data-descriptor rejection); `company-portability-routes.test.ts` +7
(multipart import+preview equals the inline bundle; async multipart
202→poll→success; board async via `?async=1` with no cloud header;
cloud-tenant async via header; sync fallback with neither; truncated-zip
400, nothing imported); `CompanyImport.test.tsx` asserts the local zip
sends the raw File and the inline preflight no longer blocks;
`openapi-routes.test.ts` green.
- NOT yet measured: the end-to-end browser upload through the live Cloud
harness — verified on staging after deploy before closing out.
## Risks
- Import semantics unchanged — only transport changed; the JSON inline
path is byte-identical, the cloud-tenant header async path untouched.
Multipart parsing is server-side (memory-bound: a ~13MB zip → ~30MB
files map, fine on the server).
- The bare `application/zip` path is programmatic-only and covered by
content-type dispatch but not a dedicated route test (the multipart path
is).
## Model Used
- Claude Fable 5 (`claude-fable-5`, Anthropic), Claude Code CLI,
extended thinking + tool use; root-caused against live logs/DB and the
harness proxy source.
## 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 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Company Import/Export (#10507) moves whole companies between
instances as portability bundles
> - Real-world use on a large company (1,418 issues, ~10.6k comments)
surfaced a cluster of related failures: the import took hours and the
browser connection died while the server kept running, a retry silently
produced a second partial import, the progress/error UI gave no durable
signal, and a cloud-tenant user couldn't even open the companies
afterward
> - Root cause of the slowness: importBundle inserted every issue,
comment, and document as a separate round-trip to a network Postgres —
an N+1-over-network pattern
> - This pull request hardens the whole import path: durable
progress/error UI, an async server-side job so imports survive dropped
connections (with a duplicate-submit guard), a fail-closed guard against
incomplete payloads, and batched inserts that cut a large import from
hours to minutes
> - The benefit is that migrating a real, large company actually
completes, is legible while it runs, and can't half-import twice
## Linked Issues or Issue Description
- Refs #10507 (the Import/Export feature this hardens). Supersedes
#10513 (the progress/error-UI piece, folded in here). No open issue;
problem described above (large-company import: slow, connection-fragile,
silently duplicable, opaque UI).
## What Changed
- **Batched inserts (perf):** importBundle pre-generates entity ids in
JS and inserts in chunked multi-row statements, so children no longer
wait on parents' generated ids. A 1,418-issue import drops from ~15,600
insert statements to **82** (190×); benchmark below. Import semantics —
collision handling, pause-on-import,
label/blocker/monitor/attachment/embedded-asset handling, blob sha
verification — are unchanged (full portability suite green).
- **Async import jobs for board sessions:** the existing cloud-tenant
async job path opens to board sessions with per-actor job keys; the
import page submits, polls, and resumes watching after a reload or
dropped connection instead of holding one fragile request. A
non-terminal job blocks a duplicate submit (409 returns the running
job), preventing the double-import.
- **Fail-closed completeness guard:** an optional `expectedFileCount` on
inline imports; the server rejects (422 `import_payload_incomplete`) a
body carrying fewer files than declared, so a re-framed/short payload
fails loudly instead of half-importing.
- **Durable progress/error UI (was #10513):** persistent progress panels
with size-aware copy, persistent error panels with retry guidance, and
inline explanation when the preview button is disabled;
request-lifecycle guards so stale previews/imports can't publish or
detach.
## Verification
- `pnpm -r` typechecks (shared, server, ui) clean.
- `company-portability.test.ts` (76) +
`company-portability-routes.test.ts` (30) green — the import correctness
net — plus new `CompanyImport.test.tsx` async/resume/409 coverage and a
new batching regression test (a 50-issue import issues <50 issue-insert
statements; rows land unchanged).
- **Batching benchmark (embedded Postgres):** at 1,418 issues × 7
comments × 1 doc — 82 insert statements vs ~15,598 one-per-row (190×),
~1s wall-clock; a row-verifying run at that scale imports all 1,418
issues / 9,926 comments / 1,418 documents with unique identifiers and no
warnings (no rows dropped by chunking). Over a network DB the round-trip
reduction is the hours→minutes lever.
- What is NOT directly measured here: wall-clock against a real network
Postgres (that happens on a staging deploy); the local timing is
network-free.
## Risks
- Batching is the load-bearing change: it rewrites the import write
path. Mitigated by the unchanged 106-test correctness suite, a new
scale/row-integrity test, and per-writer transactions (a failure rolls
back its table group; not a single outer transaction across writers —
noted, correctness preserved).
- Async jobs are in-memory (lost on server restart → pollers 404 and can
resubmit); matches the pre-existing cloud-tenant job semantics.
- `expectedFileCount` is optional (older callers unaffected); over-count
is allowed, only under-count fails closed.
## Model Used
- Claude Fable 5 (`claude-fable-5`, Anthropic), Claude Code CLI,
extended thinking + tool use; implementation across Fable 5 subagents
with live diagnosis against a running instance.
## 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 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - A company accumulates real state — issues, labels, blockers,
documents, work products, monitors, attachments, agents, routines — and
people need to move that state between instances: self-hosted to cloud,
cloud back to self-hosted, or plain backups
> - The experimental, flag-gated Cloud Sync transport (#6548) tried to
solve this host-to-host: the source pushed into a receiver over HTTPS
with a cross-instance consent/token handshake, which required the
destination to be publicly reachable and broke for common self-hosted
topologies (plain-HTTP LAN/VPN origins); the receiver half never landed
upstream at all
> - Meanwhile the portability bundle and the existing export/import
pages already move companies offline with none of those networking
constraints — but silently dropped labels, blockers, issue documents,
work products, monitors, and every attachment
> - This pull request removes the host-to-host transport and makes
Import/Export the single data-movement path: the pages become
first-class company-settings destinations, exports declare exactly what
they do not carry, and bundle schemaVersion 6 now carries all of the
above, with attachments as content-addressed sha256 blobs verified
before a single row is written
> - The benefit is a migration and backup flow that works between any
two instances with no reachability requirements, no cross-instance auth,
and no silent data loss
## Linked Issues or Issue Description
- Refs #6548 — the original Cloud Sync sender this PR supersedes and
removes.
- Related, not duplicates: #1697 (goals in the portability manifest —
orthogonal field addition), #954 (an earlier import/export +
skill-visibility proposal predating the current portability bundle).
- No open issue describes this directly, so in brief (feature-request
shape): **Problem** — moving a company between instances silently lost
labels (imports with label references actually hard-failed), blocker
relations, issue documents, work products, monitor state, and all
attachments, and the alternative Cloud Sync transport required the
destination to be publicly reachable over HTTPS plus a consent
handshake, which failed for typical self-hosted setups. **Desired
behavior** — one Import/Export flow in company settings that produces a
portable bundle carrying all of that data, tells the operator up front
what it cannot carry, imports with automations paused, and offers real
one-click activation afterwards.
## What Changed
- New export fidelity report (`GET
/api/companies/:companyId/export/fidelity`) + an "Export fidelity" panel
on the Export page listing anything a bundle will not include (now only:
approvals, cost history, activity history)
- Imports accept `pauseAutomations`; imported agents and routines land
paused, the import result reports created routines, and the Import page
ends in an activation panel that actually resumes selected
agents/activates routines
- Export and Import pages promoted into the company-settings nav; the
Cloud Upstream wizard, ux-lab page, and API client removed; the old
settings route redirects to Export
- Host-to-host transport removed: upstream-sync/receiver-client routes
and services, CLI `cloud connect`/`cloud push` + keypair store, the
shared upstream transfer contract, and the `enableCloudSync` flag;
migration `0196` drops the two experimental `cloud_upstream_*` sender
tables
- Bundle schemaVersion 6: labels (definitions + per-task names, remapped
by name on import), blocker relations (`blockedBy` slugs,
cycle-tolerant), issue documents (`tasks/<slug>/documents/<key>.md`),
work products (system refs nulled), monitors (notes/scheduledBy
restored, imported un-armed)
- Attachments travel as content-addressed `blobs/<sha256>` entries
(deduped; comment-scoped attachments re-link via comment index); every
blob is hash-verified **before any write**, so a corrupted bundle cannot
leave a partially imported company; both zip codecs now round-trip
extensionless/binary entries byte-exactly; the Import page preflights
the inline body limit and offers continue-without-attachments
- v5 (and older) bundles still import, with an informational warning;
bundles newer than v6 are rejected cleanly
- Docs: board-operator import/export guide, CLI README, README/ROADMAP
updated
## Verification
- `pnpm -r` typechecks (shared, db incl. migration numbering/safety
checks, server, ui, cli) and `pnpm check:token-gates` — clean
- Vitest: full server + shared sweep 4,888 passed / 1 skipped, with the
only 3 failures being pre-existing on `master` (2×
heartbeat-workspace-branch-containment, 1× workspace-runtime auto-port;
reproduced identically with this change stashed); ui + cli suites green;
the embedded-Postgres export-fidelity suite applies the full migration
chain including the new `0196` against a fresh database
- Live end-to-end on a scratch instance: seeded a company with labels, a
blocker pair, an issue document, a work product, a monitor, an agent, a
routine, and two binary attachments (one comment-scoped) → export →
import into a fresh company → labels remapped to new ids, blocker edge
and document restored, monitor un-armed with notes intact, attachments
byte-identical (sha256-compared through the API), agents/routines paused
→ activation panel resumed them; a v5-shaped bundle imported with only
the info warning; flipping one byte in a blob made the import 422 with
**zero** rows created
- Reviewer repro: create a company with a labeled issue + attachment →
Settings → Export → download → Settings → Import on another
company/instance → watch the preview, apply with "start paused", then
activate
## Risks
- Migration `0196` drops
`cloud_upstream_connections`/`cloud_upstream_runs` — experimental tables
behind a default-off flag; their connection/run history is intentionally
discarded
- Breaking removals are all of experimental, flag-gated surface:
`/api/upstream-sync/*` + `/api/cloud-upstreams/*` routes, `paperclipai
cloud connect|push`, and the `enableCloudSync` flag (stale keys in
stored instance settings parse harmlessly)
- Import remains non-atomic on mid-apply errors generally (pre-existing
behavior); the new blob verification specifically moved ahead of all
writes so tampered bundles cannot create partial state
- GitHub-sourced imports do not fetch `blobs/*` and skip attachments
with a warning
## Model Used
- Claude Fable 5 (`claude-fable-5`, Anthropic), via Claude Code CLI with
extended thinking, tool use, and subagent orchestration; implementation
and review split across Fable 5 subagents, with live end-to-end
verification against a running instance
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents and their work
> - Project pages give operators a dense task list for understanding
what has changed recently
> - A purely chronological list makes the transition from fresh work to
aging work difficult to scan
> - The existing activity feed already uses a quiet labeled divider to
communicate a recency boundary
> - This pull request applies that familiar pattern to task lists at the
one-day and one-week boundaries
> - The benefit is faster age-based scanning without adding filters,
badges, or repeated metadata to every row
## Linked Issues or Issue Description
### Subsystem affected
`ui/` — React + Vite board UI.
### Problem or motivation
Operators scanning a project task list cannot quickly see where recently
created or updated work gives way to tasks that are more than a day or a
week old.
### Proposed solution
Insert subtle, accessible “Older than a day” and “Older than a week”
separators when a task list is sorted newest-first by creation or update
time.
### Alternatives considered
Per-row age badges would repeat state and add noise; persistent
age-based groups would interfere with the list's existing grouping
controls. Lightweight boundary markers preserve the current ordering and
interaction model.
### Roadmap alignment
This is a tightly scoped board-UI polish change and does not duplicate a
roadmap milestone.
### Additional context
The visual treatment follows the existing activity-feed recency
separator pattern. A public GitHub search found no duplicate or related
open issue or pull request.
## What Changed
- Added rolling one-day and one-week recency buckets for created/updated
timestamps.
- Rendered token-compliant, accessible separators only for newest-first
date sorts and only when visible rows cross a boundary.
- Traversed expanded nested rows in their exact visible order and
emitted every crossed boundary when adjacent rows skip an age bucket.
- Added component and helper coverage for sequential boundaries, skipped
buckets, expanded nested rows, and the no-separator same-bucket case.
## Verification
- `pnpm exec vitest run ui/src/components/IssuesList.test.tsx` — 42
tests passed.
- `pnpm check:token-gates` — all token gates clean.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/ui build` — passed (with existing build
warnings only).
## Risks
- Low risk: the change is presentation-only and limited to list mode
when sorting `created` or `updated` descending.
- Boundaries use rolling 24-hour and 7-day windows rather than
calendar-day boundaries, matching the age-based labels.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5 family (the runtime did not expose a more specific
model build or context-window size), with reasoning, repository tool
use, code execution, and GitHub CLI access.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents for work
> - Operators regularly pass through full-page loading states while
authentication and company context resolve
> - Those states currently render as small bare text, which is easy to
miss and does not reinforce Paperclip's visual identity
> - A shared loading component gives these transitions one accessible,
consistent representation
> - This pull request introduces an animated paperclip loader and uses
it at the existing full-page loading boundaries
> - The benefit is a clearer, calmer loading experience with
reduced-motion and screen-reader support
## Linked Issues or Issue Description
### Subsystem affected
`ui/` — React + Vite board UI
### Problem or motivation
Full-page authentication, access-gate, and company-context waits use
small bare `Loading…` text that is visually weak and inconsistent.
### Proposed solution
Use one large centered paperclip loader at those boundaries, drawing the
SVG with `currentColor` so it follows the active theme.
### Alternatives considered
Keeping text-only states or adding a generic spinner would preserve less
of Paperclip's product identity and would continue duplicating loading
markup.
### Roadmap alignment
This is tightly scoped UI polish and does not duplicate a planned
roadmap capability.
### Additional context
Internal coordination task PAP-15760 requested this focused change.
## What Changed
- Added `AnimatedPaperclipIcon`, a theme-aware SVG whose stroke draws in
a loop.
- Added `PaperclipLoading`, a large full-viewport centered loader with
`role="status"` and an `sr-only` `Loading…` label.
- Added a static fully drawn fallback under `prefers-reduced-motion:
reduce`.
- Replaced bare loading text in `CloudAccessGate`, the Auth session
check, and three company-context redirects.
- Used token-safe Tailwind utilities throughout the component.
- Added focused coverage for the status semantics and the Auth layout
height override.
## Verification
- `pnpm check:token-gates`
- `pnpm -C ui exec tsc -b`
- `pnpm -C ui exec vitest run
src/components/AnimatedPaperclipIcon.test.tsx`
- `pnpm -C ui exec vitest run src/App.test.tsx
src/App.cases-routing.test.tsx` — 8/8 tests passed
- Visually checked light and dark loading states; the screenshot below
shows both themes.

## Risks
- Low risk: this changes presentation only at existing loading branches.
- Motion-sensitive users receive a static, fully drawn paperclip through
the reduced-motion media query.
- Screen readers retain a concise loading announcement through the
status role and visually hidden label.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5 (exact service build ID and context-window size
are not exposed in this environment), with reasoning, repository
inspection, shell tool use, code execution, and image inspection.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used with all deployment details
available to this environment
- [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 found none
- [x] I have described the issue in-PR following the feature-request
fields
- [x] I included the task-mandated internal parent reference and no
private instance URL
- [x] I preserved the task-mandated existing branch name without
renaming it
- [x] I have run scoped tests locally and they pass
- [x] I added focused component coverage for the new loading state
- [x] No documentation update is required for this presentation-only
change
- [x] I have considered and documented the 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The agent detail page lets operators inspect one agent and run
lifecycle actions from that context
> - Terminated agents are removed from the normal active-agent surface,
so the current detail route may no longer be fetchable after termination
> - Before this change, terminating an agent from its detail page
invalidated agent queries while leaving the browser on the now-stale
detail route
> - That refetch could surface an "Agent not found" error even though
the terminate action itself succeeded
> - The shared action button already owns the terminate mutation, so it
can notify detail-page callers when termination succeeds
> - This pull request redirects the detail page back to the agents list
after a successful terminate action
> - The benefit is that operators land on a valid route and the Back
button does not return them to the stale terminated-agent detail route
## Linked Issues or Issue Description
No direct public GitHub issue or PR was found for this detail-page
termination flow.
### What happened?
After terminating an agent from its detail page, the UI could remain on
that agent's detail route and show an "Agent not found" error after
query invalidation/refetch.
### Expected behavior
Once termination succeeds, the operator should leave the now-stale
detail page and land on a valid agents view.
### Steps to reproduce
1. Open a non-built-in agent detail page.
2. Use the overflow actions menu to terminate the agent.
3. Observe the post-termination route/error state.
### Paperclip version or commit
Current `master` before this PR.
### Deployment mode
Browser UI behavior, independent of a specific deployment mode.
Duplicate search: searched public GitHub issues and PRs for `agent not
found terminate`, `terminate agent detail`, and `Agent not found`; no
direct duplicate or viable in-flight PR was found.
## What Changed
- Added an optional `onTerminateSuccess` callback to
`AgentActionButtons`, fired only after the shared terminate mutation
succeeds.
- Wired `AgentDetail` to replace-navigate to `/agents/all` after
successful termination.
- Extended `AgentActionButtons` coverage for the terminate success path,
including API args, callback payload, and query invalidations.
## Verification
- `corepack pnpm exec vitest run
ui/src/components/AgentActionButtons.test.tsx`
- `corepack pnpm check:token-gates`
- `git diff --check origin/master..HEAD`
- Local diff scan for obvious tokens, credential filenames, and email
addresses found no matches.
## Risks
Low risk. The new callback is optional, only fires for successful
terminate actions, and preserves existing behavior for other
`AgentActionButtons` callers.
## Model Used
OpenAI Codex, GPT-5-based coding agent (`gpt-5`), tool use enabled for
repository inspection, editing, local verification, and GitHub CLI
operations. Context window details were not exposed by the runtime.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to coordinate
AI-agent companies
> - The board UI opens issue details from lists, quicklooks, and the
inbox
> - Those navigation paths already have enough issue data to paint the
header immediately, but the comment feed still waits for its first
request
> - That delay makes repeat navigation feel cold and can briefly show an
empty thread before comments arrive
> - This pull request centralizes the comment page shape, prefetches
issue detail plus the first comment page, and renders a reserved
skeleton while an uncached page is loading
> - The benefit is faster, stable warm navigation without coupling this
change to the separate aggregate issue-detail API work
## Linked Issues or Issue Description
- **Subsystem affected:** `ui/` — React + Vite board UI
- **Problem or motivation:** Opening an issue from an already-loaded
list still waits for the first comments request and may flash the
empty-thread state, making warm navigation feel slower than necessary.
- **Proposed solution:** Prefetch the issue-detail snapshot and first
comments page from every issue navigation entry point, reuse one
page-size constant for prefetch and render queries, and show the
existing chat skeleton until the uncached initial page resolves.
- **Alternatives considered:** Relying only on detail-query prefetch
leaves comments cold; bundling this with the aggregate issue-detail
endpoint would make the UI improvement harder to review and land
independently.
- **Roadmap alignment:** `ROADMAP.md` has no overlapping
issue-navigation initiative. This is a focused board responsiveness
improvement.
- **Related pull requests:** #10409 establishes the issue-detail
performance baseline; #10414 reduces server-side issue-detail request
overhead.
## What Changed
- Added one shared issue-comment page-size constant used by rendering
and prefetching.
- Added first-page comment prefetching and a combined navigation
prefetch helper.
- Wired quicklook, issue-list keyboard navigation, and inbox navigation
to warm both caches.
- Kept the existing chat skeleton visible while the first uncached
comment page loads.
- Added focused cache behavior tests for comment and combined navigation
prefetching.
## Verification
- `vitest run ui/src/lib/prefetchIssueComments.test.ts` — 3 tests
passed.
- `tsc -b ui` — passed.
- `pnpm check:token-gates` — passed.
## Risks
- Low risk: this adds background prefetch requests on intentional issue
navigation/hover paths. React Query stale-time deduplication prevents
repeat requests while the cache is fresh.
- The change intentionally remains independent of the separate aggregate
`getView` work and composes with it through the same query keys.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex coding agent, exact model `gpt-5.6-sol`; context-window
size was not exposed; reasoning mode with repository tool use and code
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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents for work
> - The `codex_local` adapter runs OpenAI's Codex CLI through direct CLI
and ACP execution lanes
> - The adapter defaulted to the bare `gpt-5.6` alias while the bundled
ACP Codex version lacked GPT-5.6-family metadata
> - Default and legacy-configured runs therefore emitted
fallback-metadata warnings and could use generic context limits
> - This pull request upgrades the bundled Codex ACP dependency, selects
the concrete `gpt-5.6-sol` model, and normalizes the legacy alias in
both execution lanes
> - The benefit is correct model metadata without hiding genuine stderr
or transcript warnings
## Linked Issues or Issue Description
Related public PRs: Refs #9342, Refs #9352, and Refs #9382. This PR is
narrower: it upgrades bundled Codex metadata and normalizes the legacy
bare alias in both execution lanes.
**Bug report**
### What happened
Default `codex_local` runs, and agents still configured with the bare
`gpt-5.6` model, print a model-metadata fallback warning and use generic
context-window limits.
Root cause: the ACP lane bundled a Codex release predating
GPT-5.6-family metadata, while Paperclip's default and advertised model
used the bare `gpt-5.6` alias for which Codex publishes no metadata.
### Expected behavior
A default Codex run resolves to a concrete model slug with published
metadata and does not emit a fallback-metadata warning.
### Deployment mode
Self-hosted/local `codex_local` adapter.
## What Changed
- Upgraded `@agentclientprotocol/codex-acp` from `^1.1.0` to `^1.1.4`
- Changed `DEFAULT_CODEX_LOCAL_MODEL` from `gpt-5.6` to `gpt-5.6-sol`
- Removed the bare alias from advertised models and listed concrete
GPT-5.6 Fast-mode variants
- Added `normalizeCodexModel()` and applied it in both CLI and ACP
execution lanes
- Updated adapter docs, Storybook fixtures, and regression tests
- Preserved warning visibility; no stderr, transcript, or log filtering
changed
## Verification
- `pnpm --filter @paperclipai/adapter-codex-local typecheck`
- `pnpm check:token-gates`
- `cd packages/adapters/codex-local && pnpm exec vitest run` — 205 tests
passed
- `cd server && pnpm exec vitest run
src/__tests__/adapter-models.test.ts` — 17 tests passed
- Confirmed the PR diff excludes `pnpm-lock.yaml` and
`.github/workflows/**` as required by repository policy
- Confirmed `.github/workflows/pr.yml` regenerates and uploads the PR
lockfile artifact before downstream `pnpm install --frozen-lockfile`
steps
## Risks
Low risk. The behavior change is scoped to `codex_local` model
selection. Existing concrete model IDs pass through unchanged; only the
legacy bare `gpt-5.6` alias is rewritten. Dependency resolution may
select a newer compatible `codex-acp` release within the declared range,
so CI remains the final compatibility gate.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Original implementation: Anthropic Claude Opus 4.8 (`claude-opus-4-8`,
1M context, tool use and code execution)
- Conflict resolution and PR preparation: OpenAI GPT-5.5 (`gpt-5.5`,
Codex CLI coding agent, high-reasoning tool use and code execution;
host-managed context window)
## 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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
— branch name is fixed by the assigned execution workspace and cannot be
renamed in-place
- [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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The UI depends on `lexical` for rich text editing in user-facing
flows
> - Dependabot detected a newer `lexical` release with bug fixes and
security hardening
> - A dependency-only bump is the smallest safe way to pick up those
upstream fixes
> - This pull request updates `lexical` from 0.46.0 to 0.48.0
> - The benefit is lower maintenance risk and a smaller security/support
gap without changing app logic
## Linked Issues or Issue Description
- No public issue exists for this maintenance update.
- Related public PR: Refs #9885.
## What Changed
- Bumped `lexical` from 0.46.0 to 0.48.0.
- Refreshed the lockfile entries for the dependency update.
## Verification
- GitHub Actions checks on PR #10299 passed.
- No local code changes were needed for this dependency-only update.
## Risks
- Low risk overall because this is a dependency-only update.
- Upstream editor behavior could still shift subtly; CI and the
dependency bump itself are the primary safeguard.
## Model Used
- OpenAI GPT-5 (Codex), tool-using agent.
## 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
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [i18next](https://github.com/i18next/i18next) from 26.3.1 to
26.3.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/i18next/i18next/releases">i18next's
releases</a>.</em></p>
<blockquote>
<h2>v26.3.6</h2>
<ul>
<li>fix: allow TypeScript 7 in the optional <code>typescript</code> peer
dependency range (<code>^5 || ^6 || ^7</code>). With
<code>typescript@7.0.2</code> in a project, <code>npm install</code>
failed with an <code>ERESOLVE</code> peer conflict. The published types
are TS7-compatible as-is: every <code>test/typescript</code> suite
produces identical results under 6.0 and 7.0.2. Reported in <a
href="https://redirect.github.com/i18next/react-i18next/issues/1927">react-i18next#1927</a>,
thanks <a
href="https://github.com/andikapradanaarif"><code>@andikapradanaarif</code></a>.</li>
</ul>
<h2>v26.3.5</h2>
<ul>
<li>fix: <code>$t()</code> nesting options blocks that span multiple
lines are now parsed. <code>nest()</code> decided where the nested key
ends by testing <code>match[1]</code> with <code>/{.*}/</code>, whose
dot does not cross line breaks — so a <code>$t(key, { ... })</code>
options object containing a newline was treated as having no options,
mis-split as formatters, and the nested lookup ran without its options
(placeholders stayed unresolved). The nesting regexp itself already
matches newlines inside <code>$t(...)</code>; adding the <code>s</code>
(dotAll) flag makes multiline options behave like the single-line form.
Thanks <a href="https://github.com/spokodev"><code>@spokodev</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2440">#2440</a>).</li>
<li>fix: <code>getUsedParamsDetails</code> (the <code>returnDetails:
true</code> path) no longer mutates the passed <code>replace</code>
object. It wrote <code>count</code> straight onto
<code>options.replace</code> so the returned <code>usedParams</code>
would include it — a caller reusing one <code>replace</code> object
across <code>t()</code> calls then carried a stale <code>count</code>
into later interpolations (e.g. a previous call's <code>count: 5</code>
rendered instead of the current call's value). The details are now built
from a copy; <code>usedParams</code> still includes <code>count</code>.
Thanks <a href="https://github.com/spokodev"><code>@spokodev</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2441">#2441</a>).</li>
<li>fix: with the default <code>skipOnVariables: true</code> +
<code>escapeValue: true</code>, a <code>{{placeholder}}</code> carried
inside an interpolated value now stays literal even when the value
contains escapable characters. The skip logic advanced the regex
<code>lastIndex</code> by the raw value length, but the escaped text
written into the string is longer, so <code>lastIndex</code> landed
inside the inserted value and a trailing <code>{{placeholder}}</code> in
it got interpolated — leaking another in-scope variable that should have
stayed literal (values without escapable characters were already skipped
correctly). The advance now uses the escaped length that is actually
written, and the regex-safe <code>$</code>-doubling is applied only at
the <code>String.replace</code> call so it can't distort the length
arithmetic. Thanks <a
href="https://github.com/spokodev"><code>@spokodev</code></a> (<a
href="https://redirect.github.com/i18next/i18next/pull/2442">#2442</a>).</li>
</ul>
<h2>v26.3.4</h2>
<ul>
<li>fix(security): <code>deepExtend</code> (used by
<code>addResourceBundle(..., deep, overwrite)</code>) no longer recurses
into inherited properties. It checked key existence with the
<code>in</code> operator, which walks the prototype chain, so a source
key matching an inherited built-in (e.g. <code>hasOwnProperty</code>,
<code>toString</code>) caused recursion into the shared
<code>Object.prototype</code> function and, with <code>overwrite:
true</code>, could overwrite e.g.
<code>Object.prototype.hasOwnProperty.call</code> with a non-callable
value — corrupting a shared built-in process-wide (DoS). Existence is
now checked with <code>Object.prototype.hasOwnProperty.call</code>, so
such keys are copied as plain own data instead. This complements the
existing <code>__proto__</code>/<code>constructor</code> guard and is
also strictly more correct for an own-property merge. Only affects
applications that pass attacker-controlled data with <code>deep:
true</code> and <code>overwrite: true</code>; no standard
backend/integration does this. Distinct from CVE-2026-48713 /
CVE-2026-48714 (different packages, <code>setPath</code> mechanism).
Thanks to zx (Jace) for the responsible disclosure.</li>
</ul>
<h2>v26.3.3</h2>
<ul>
<li>fix(types): selector <code>t($ => $.arr, { returnObjects: true,
context })</code> on a JSON array of <strong>heterogeneous</strong>
objects now preserves each element's full shape (e.g. <code>{ transKey1:
string; transKey2: string }[]</code>) instead of collapsing to a union
of partial element types. Two type-level causes: (1)
<code>FilterKeys</code> evaluated the whole array element type at once,
so <code>keyof (A | B)</code> only saw the keys common to every element
— it now distributes over the object union and filters each element
independently; (2) when TypeScript merges mismatched array element types
it injects phantom optional <code>undefined</code> keys (e.g.
<code>transKey1_withContext?: undefined</code> on elements that don't
define it), which the context-detection helpers mistook for real context
variants — they now skip keys typed as <code>undefined</code>. Also adds
a dedicated <code>context</code> + <code>returnObjects: true</code>
selector overload using <code>const Fn</code> +
<code>ReturnType<Fn></code>, so <code>Target</code> is no longer
collapsed to <code>unknown</code> via <code>ApplyTarget</code>. Resolves
Problem 1 of <a
href="https://redirect.github.com/i18next/i18next/issues/2398">#2398</a>
(Problem 2 was already fixed on master). Thanks <a
href="https://github.com/sauravgupta-dotcom"><code>@sauravgupta-dotcom</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2438">#2438</a>).
Fixes <a
href="https://redirect.github.com/i18next/i18next/issues/2398">#2398</a>.</li>
</ul>
<h2>v26.3.2</h2>
<ul>
<li>fix: chained formatters with a parenthesised option that contains
the format separator (e.g. <code>join(separator: ', ')</code>) now work
at <strong>any</strong> position in the chain, not just first.
Previously the comma-in-parens reassembly only repaired
<code>formats[0]</code>, so <code>{{v, uppercase, join(separator: ',
')}}</code> split the <code>join(...)</code> option on the inner comma
and never rejoined it, producing corrupt output. Replaced the
first-position-only repair with a position-independent pass that
re-joins fragments until each open paren closes. Thanks <a
href="https://github.com/spokodev"><code>@spokodev</code></a> (<a
href="https://redirect.github.com/i18next/i18next/pull/2437">#2437</a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/i18next/i18next/blob/master/CHANGELOG.md">i18next's
changelog</a>.</em></p>
<blockquote>
<h2>26.3.6</h2>
<ul>
<li>fix: allow TypeScript 7 in the optional <code>typescript</code> peer
dependency range (<code>^5 || ^6 || ^7</code>). With
<code>typescript@7.0.2</code> in a project, <code>npm install</code>
failed with an <code>ERESOLVE</code> peer conflict. The published types
are TS7-compatible as-is: every <code>test/typescript</code> suite
produces identical results under 6.0 and 7.0.2. Reported in <a
href="https://redirect.github.com/i18next/react-i18next/issues/1927">react-i18next#1927</a>,
thanks <a
href="https://github.com/andikapradanaarif"><code>@andikapradanaarif</code></a>.</li>
</ul>
<h2>26.3.5</h2>
<ul>
<li>fix: <code>$t()</code> nesting options blocks that span multiple
lines are now parsed. <code>nest()</code> decided where the nested key
ends by testing <code>match[1]</code> with <code>/{.*}/</code>, whose
dot does not cross line breaks — so a <code>$t(key, { ... })</code>
options object containing a newline was treated as having no options,
mis-split as formatters, and the nested lookup ran without its options
(placeholders stayed unresolved). The nesting regexp itself already
matches newlines inside <code>$t(...)</code>; adding the <code>s</code>
(dotAll) flag makes multiline options behave like the single-line form.
Thanks <a href="https://github.com/spokodev"><code>@spokodev</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2440">#2440</a>).</li>
<li>fix: <code>getUsedParamsDetails</code> (the <code>returnDetails:
true</code> path) no longer mutates the passed <code>replace</code>
object. It wrote <code>count</code> straight onto
<code>options.replace</code> so the returned <code>usedParams</code>
would include it — a caller reusing one <code>replace</code> object
across <code>t()</code> calls then carried a stale <code>count</code>
into later interpolations (e.g. a previous call's <code>count: 5</code>
rendered instead of the current call's value). The details are now built
from a copy; <code>usedParams</code> still includes <code>count</code>.
Thanks <a href="https://github.com/spokodev"><code>@spokodev</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2441">#2441</a>).</li>
<li>fix: with the default <code>skipOnVariables: true</code> +
<code>escapeValue: true</code>, a <code>{{placeholder}}</code> carried
inside an interpolated value now stays literal even when the value
contains escapable characters. The skip logic advanced the regex
<code>lastIndex</code> by the raw value length, but the escaped text
written into the string is longer, so <code>lastIndex</code> landed
inside the inserted value and a trailing <code>{{placeholder}}</code> in
it got interpolated — leaking another in-scope variable that should have
stayed literal (values without escapable characters were already skipped
correctly). The advance now uses the escaped length that is actually
written, and the regex-safe <code>$</code>-doubling is applied only at
the <code>String.replace</code> call so it can't distort the length
arithmetic. Thanks <a
href="https://github.com/spokodev"><code>@spokodev</code></a> (<a
href="https://redirect.github.com/i18next/i18next/pull/2442">#2442</a>).</li>
</ul>
<h2>26.3.4</h2>
<ul>
<li>fix(security): <code>deepExtend</code> (used by
<code>addResourceBundle(..., deep, overwrite)</code>) no longer recurses
into inherited properties. It checked key existence with the
<code>in</code> operator, which walks the prototype chain, so a source
key matching an inherited built-in (e.g. <code>hasOwnProperty</code>,
<code>toString</code>) caused recursion into the shared
<code>Object.prototype</code> function and, with <code>overwrite:
true</code>, could overwrite e.g.
<code>Object.prototype.hasOwnProperty.call</code> with a non-callable
value — corrupting a shared built-in process-wide (DoS). Existence is
now checked with <code>Object.prototype.hasOwnProperty.call</code>, so
such keys are copied as plain own data instead. This complements the
existing <code>__proto__</code>/<code>constructor</code> guard and is
also strictly more correct for an own-property merge. Only affects
applications that pass attacker-controlled data with <code>deep:
true</code> and <code>overwrite: true</code>; no standard
backend/integration does this. Distinct from CVE-2026-48713 /
CVE-2026-48714 (different packages, <code>setPath</code> mechanism). See
advisory <a
href="https://github.com/i18next/i18next/security/advisories/GHSA-6jcc-5g8w-32mx">GHSA-6jcc-5g8w-32mx</a>,
CVSS 5.9 (<code>CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:H</code>).
Thanks to zx (Jace) <a
href="https://github.com/manus-use"><code>@manus-use</code></a> for the
responsible disclosure.</li>
</ul>
<h2>26.3.3</h2>
<ul>
<li>fix(types): selector <code>t($ => $.arr, { returnObjects: true,
context })</code> on a JSON array of <strong>heterogeneous</strong>
objects now preserves each element's full shape (e.g. <code>{ transKey1:
string; transKey2: string }[]</code>) instead of collapsing to a union
of partial element types. Two type-level causes: (1)
<code>FilterKeys</code> evaluated the whole array element type at once,
so <code>keyof (A | B)</code> only saw the keys common to every element
— it now distributes over the object union and filters each element
independently; (2) when TypeScript merges mismatched array element types
it injects phantom optional <code>undefined</code> keys (e.g.
<code>transKey1_withContext?: undefined</code> on elements that don't
define it), which the context-detection helpers mistook for real context
variants — they now skip keys typed as <code>undefined</code>. Also adds
a dedicated <code>context</code> + <code>returnObjects: true</code>
selector overload using <code>const Fn</code> +
<code>ReturnType<Fn></code>, so <code>Target</code> is no longer
collapsed to <code>unknown</code> via <code>ApplyTarget</code>. Resolves
Problem 1 of <a
href="https://redirect.github.com/i18next/i18next/issues/2398">#2398</a>
(Problem 2 was already fixed on master). Thanks <a
href="https://github.com/sauravgupta-dotcom"><code>@sauravgupta-dotcom</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2438">#2438</a>).
Fixes <a
href="https://redirect.github.com/i18next/i18next/issues/2398">#2398</a>.</li>
</ul>
<h2>26.3.2</h2>
<ul>
<li>fix: chained formatters with a parenthesised option that contains
the format separator (e.g. <code>join(separator: ', ')</code>) now work
at <strong>any</strong> position in the chain, not just first.
Previously the comma-in-parens reassembly only repaired
<code>formats[0]</code>, so <code>{{v, uppercase, join(separator: ',
')}}</code> split the <code>join(...)</code> option on the inner comma
and never rejoined it, producing corrupt output. Replaced the
first-position-only repair with a position-independent pass that
re-joins fragments until each open paren closes. Thanks <a
href="https://github.com/spokodev"><code>@spokodev</code></a> (<a
href="https://redirect.github.com/i18next/i18next/pull/2437">#2437</a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="e1c60d4dd2"><code>e1c60d4</code></a>
26.3.6</li>
<li><a
href="04da43e08c"><code>04da43e</code></a>
fix: allow typescript 7 in optional peerDependencies range
(react-i18next#1927)</li>
<li><a
href="8eed4accc6"><code>8eed4ac</code></a>
build</li>
<li><a
href="573ae73568"><code>573ae73</code></a>
26.3.5</li>
<li><a
href="cc54b05b5c"><code>cc54b05</code></a>
docs(changelog): 26.3.5 — multiline $t() options, replace mutation,
escaped-l...</li>
<li><a
href="3180d67291"><code>3180d67</code></a>
fix: skip interpolation of placeholders inside escaped values (<a
href="https://redirect.github.com/i18next/i18next/issues/2442">#2442</a>)</li>
<li><a
href="d16f5a2da7"><code>d16f5a2</code></a>
fix: stop mutating the passed replace object when returning details (<a
href="https://redirect.github.com/i18next/i18next/issues/2441">#2441</a>)</li>
<li><a
href="bed56c1159"><code>bed56c1</code></a>
fix: parse $t() nesting options block that spans multiple lines (<a
href="https://redirect.github.com/i18next/i18next/issues/2440">#2440</a>)</li>
<li><a
href="c19e45864f"><code>c19e458</code></a>
docs(changelog): link GHSA advisory for deepExtend fix</li>
<li><a
href="7bb87d09f9"><code>7bb87d0</code></a>
docs(changelog): reference security advisory for deepExtend fix</li>
<li>Additional commits viewable in <a
href="https://github.com/i18next/i18next/compare/v26.3.1...v26.3.6">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps
[@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query)
from 5.101.2 to 5.101.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/TanStack/query/releases">@tanstack/react-query's
releases</a>.</em></p>
<blockquote>
<h2><code>@tanstack/react-query-devtools</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies []:
<ul>
<li><code>@tanstack/query-devtools</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</li>
<li><code>@tanstack/react-query</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</li>
</ul>
</li>
</ul>
<h2><code>@tanstack/react-query-next-experimental</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies []:
<ul>
<li><code>@tanstack/react-query</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</li>
</ul>
</li>
</ul>
<h2><code>@tanstack/react-query-persist-client</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies []:
<ul>
<li><code>@tanstack/query-persist-client-core</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</li>
<li><code>@tanstack/react-query</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</li>
</ul>
</li>
</ul>
<h2><code>@tanstack/react-query</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies []:
<ul>
<li><code>@tanstack/query-core</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</li>
</ul>
</li>
</ul>
<h2><code>@tanstack/react-query-devtools</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies []:
<ul>
<li><code>@tanstack/query-devtools</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</li>
<li><code>@tanstack/react-query</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</li>
</ul>
</li>
</ul>
<h2><code>@tanstack/react-query-next-experimental</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies []:
<ul>
<li><code>@tanstack/react-query</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</li>
</ul>
</li>
</ul>
<h2><code>@tanstack/react-query-persist-client</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies []:
<ul>
<li><code>@tanstack/query-persist-client-core</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</li>
<li><code>@tanstack/react-query</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</li>
</ul>
</li>
</ul>
<h2><code>@tanstack/react-query</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="7e3c822a10"><code>7e3c822</code></a>]:</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md">@tanstack/react-query's
changelog</a>.</em></p>
<blockquote>
<h2>5.101.4</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies []:
<ul>
<li><code>@tanstack/query-core</code><a
href="https://github.com/5"><code>@5</code></a>.101.4</li>
</ul>
</li>
</ul>
<h2>5.101.3</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="7e3c822a10"><code>7e3c822</code></a>]:
<ul>
<li><code>@tanstack/query-core</code><a
href="https://github.com/5"><code>@5</code></a>.101.3</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="86bb8a6fb2"><code>86bb8a6</code></a>
ci: Version Packages (<a
href="https://github.com/TanStack/query/tree/HEAD/packages/react-query/issues/11094">#11094</a>)</li>
<li><a
href="181ea826cb"><code>181ea82</code></a>
ci: Version Packages (<a
href="https://github.com/TanStack/query/tree/HEAD/packages/react-query/issues/11089">#11089</a>)</li>
<li><a
href="6d55b0759b"><code>6d55b07</code></a>
test({react,preact}-query): use the '.then()' convention consistently
(<a
href="https://github.com/TanStack/query/tree/HEAD/packages/react-query/issues/11085">#11085</a>)</li>
<li><a
href="44f38df5de"><code>44f38df</code></a>
test({react,preact,solid}-query/useInfiniteQuery): inline the shared
'fetchIt...</li>
<li><a
href="d1558c1491"><code>d1558c1</code></a>
test({react,preact}-query/usePrefetchQuery): inline the
'generateQueryFn' fac...</li>
<li><a
href="99690d18b7"><code>99690d1</code></a>
test({react,preact}-query/usePrefetchInfiniteQuery): inline single-use
helper...</li>
<li><a
href="10770f0720"><code>10770f0</code></a>
test({react,preact}-query/usePrefetchInfiniteQuery): inline the shared
'Suspe...</li>
<li><a
href="dbd5a86e95"><code>dbd5a86</code></a>
test({react,preact}-query/usePrefetchQuery): inline the shared
'Suspended' co...</li>
<li><a
href="b955f60d79"><code>b955f60</code></a>
test({react,preact}-query/useSuspenseQuery): assert the 'loading'
fallback is...</li>
<li><a
href="b9c657e20b"><code>b9c657e</code></a>
test({react,preact}-query/usePrefetchQuery): assert the 'Loading...'
fallback...</li>
<li>Additional commits viewable in <a
href="https://github.com/TanStack/query/commits/@tanstack/react-query@5.101.4/packages/react-query">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps
[react-resizable-panels](https://github.com/bvaughn/react-resizable-panels)
from 4.12.1 to 4.12.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bvaughn/react-resizable-panels/blob/main/CHANGELOG.md">react-resizable-panels's
changelog</a>.</em></p>
<blockquote>
<h2>4.12.2</h2>
<ul>
<li><a
href="https://redirect.github.com/bvaughn/react-resizable-panels/issues/726">726</a>:
Updated inline documentation to clarify size units.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a1eeb7aefd"><code>a1eeb7a</code></a>
4.12.1 -> 4.12.2</li>
<li>See full diff in <a
href="https://github.com/bvaughn/react-resizable-panels/compare/4.12.1...4.12.2">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Cloud-managed instances authenticate tenant users through a
trusted-header path (`resolveCloudTenantActor`) that deliberately never
grants `instance_admin`, so every tenant user is company-scoped
> - On a dedicated (single-owner) managed instance that leaves the
paying owner unable to administer their own instance: instance settings,
the environments admin surface, and the custom sandbox image flow are
all instance-admin gated (the environments UI can't even show the
provider/image of the platform sandbox because the restricted read view
blanks `config` entirely)
> - Re-granting the old blanket `instance_user_roles` row would repeat
the mistake the shared-pool hardening fixed: DB rows go stale, resurrect
via restores, and elevate through every auth path
> - This pull request elevates only the stack `owner`, computed per
request at the trusted-header boundary behind a new managed-tier feature
flag, and ships that elevation together with code floors on the
platform-owned surfaces an instance admin must not control on a managed
instance
> - The benefit is that dedicated-stack owners can administer their own
instance while platform credentials, execution policy, backups, and
runtime code-install stay platform-owned, and self-hosted behavior is
unchanged
## Linked Issues or Issue Description
No public GitHub issue exists for this change; the underlying issue is
described here following the feature-request template.
### Problem or motivation
- On cloud-managed instances, tenant users resolved from trusted headers
are always company-scoped. For dedicated instances with a single paying
owner, the owner cannot reach any instance-admin surface of their own
instance (instance settings, environments administration, custom image
setup), and the restricted environment read view hides even structural
fields like the sandbox provider and image.
- The previous hardening intentionally removed blanket elevation (and
purges stale `instance_user_roles` rows on every trusted-header
authentication). That protection must not regress for shared
multi-tenant pools.
### Proposed solution
- Owner-only, computed, flag-gated elevation plus code floors on
platform-owned surfaces, in one PR so the elevation can never ship
without the floors.
### Alternatives considered
- Re-inserting an `instance_user_roles` row for owners (the
pre-hardening model): rejected — DB rows go stale, survive restores, and
elevate through every auth path; #7525 removed exactly this.
- Widening only the environments read view without any elevation:
rejected — it fixes one screen but still leaves a dedicated-instance
owner unable to administer instance settings or custom images.
- Elevating additional stack roles (`member`/`admin`/`support`):
rejected — only the owner has an ownership claim over the whole
instance; other roles stay company-scoped.
### Roadmap alignment
- Extends the shipped "Cloud deployments" roadmap work (multi-tenant
isolation, company-scoped cloud tenants, managed-instance bootstrap)
without overlapping planned core items, and leaves self-hosted behavior
unchanged.
## What Changed
- **New feature key** `enableOwnerInstanceAdmin` (`packages/shared`):
boolean flag in `instanceExperimentalSettingsSchema`, catalog tier
`managed`, `cloudDefault: true`, `selfHostedDefault: false`. Inert on
self-hosted instances — the elevation path only exists behind the cloud
tenant trust token.
- **Computed elevation** (`server/src/middleware/auth.ts`):
`resolveCloudTenantActor` now returns `isInstanceAdmin: true` only when
the trusted-header stack role is `owner` **and** the flag is enabled.
The flag is resolved through the instance-settings service so the
managed-config overlay applies (the control plane can disable elevation
fleet-wide without touching tenant databases; a DB row edit or restore
cannot resurrect it). Resolution fails closed on settings read errors.
The `instance_user_roles` never-insert and the per-request stale-row
purge are byte-identical. `member`/`admin`/`support` stack roles stay
company-scoped.
- **Authorization guard split**
(`server/src/services/authorization.ts`): the blanket-allow now trusts
the actor's *computed* `isInstanceAdmin` flag (only the attested
resolver can set it for `cloud_tenant` actors) while keeping the
`instance_user_roles` DB lookup excluded for `cloud_tenant` — a stale or
hand-inserted role row still elevates nothing.
- **Floor F1 — platform environment credentials**
(`server/src/routes/environments.ts`): on cloud-managed instances,
platform-provisioned environment rows (`managedByPaperclip` marker, plus
the legacy managed-Kubernetes marker) use a single floored view for
every reader on all environment routes (list, get, create, update,
delete responses): `envVars` are never echoed and credential-shaped
`config` keys (reusing the managed-config
`SECRET_LIKE_CONFIG_KEY_PATTERN`) are dropped — for **all** actors
including instance admins — while structural config (provider, image,
template, region, …) and the managed markers stay visible. This also
fixes the environments UI for managed sandboxes, which previously lost
the provider/image entirely in the restricted view. The floor also
covers writes: `PATCH /environments/:id` and `DELETE /environments/:id`
on a platform-provisioned row are rejected (403,
`environment_platform_managed`) for every actor including instance
admins, and the guard binds to the persisted row's markers so a patch
cannot strip the managed marker to lift the floor. The one recovery path
is a metadata-only PATCH that solely clears the marker keys
(null/false), for rows stamped through the old unrestricted API before
the markers became reserved — and it never applies to a row whose slot
markers are live platform state: the single local row
(`environments_local_driver_idx`), which `ensureLocalEnvironment` adopts
and stamps on cloud-managed instances from every caller (company
creation, the heartbeat, run orchestration), and the single marked
sandbox row (`environments_managed_sandbox_idx`) while a managed-sandbox
bootstrap path is configured (managed-config `environments` section or
`PAPERCLIP_EXECUTION_MODE=kubernetes`) and the provisioner therefore
adopts and refreshes it on every boot. Clearing a live slot row's
markers would let the next write reclassify it as tenant-managed and
bypass the floor; conversely, when no sandbox provisioning path is
configured the platform holds no claim on any sandbox row, so a platform
marker there is stale by definition and the recovery patch applies.
Every marker outside a live slot is clearable, so no legacy row is ever
locked permanently. Custom-image setup and probes on the platform
sandbox stay available to instance admins — those are the owner-facing
flows this elevation exists for. The marker keys themselves are
reserved: client create/update payloads that set `managedByPaperclip` or
`managedKubernetesSandbox` are rejected (422,
`environment_platform_marker_reserved`) on cloud-managed instances, so a
tenant row can never be stamped platform-provisioned through the API and
self-locked behind the write floor (the provisioner writes markers at
the service layer, not through these routes). Tenant-created
environments are otherwise unaffected.
- **Floor F2 — executionMode**
(`server/src/routes/instance-settings.ts`): on cloud-managed instances,
`PATCH /instance/settings/general` rejects writes that would change
`executionMode` (403, `execution_mode_platform_managed`). Same-value
echoes pass so settings forms that submit the full general-settings
object keep working. The boot-time execution-policy bootstrap path is
untouched (it calls the service directly).
- **Floor F3 — manual database backups**
(`server/src/routes/instance-database-backups.ts`): floored off on
cloud-managed instances (403, `database_backups_platform_managed`);
backups are platform-owned there, and the result would also echo a
server-side filesystem path.
- **Floor F4 — adapter code install** (`server/src/routes/adapters.ts`):
`POST /adapters/install` and `POST /adapters/:type/reinstall` are
floored off on cloud-managed instances (403,
`adapter_install_platform_managed`). Adapter packages execute in the
server process, so a runtime install would let an instance admin read
the platform trust anchors out of the process environment. This mirrors
the existing bundled-only plugin install floor; adapter code on managed
instances comes bundled with the platform image.
## Instance-admin surface audit
Before widening who can hold `isInstanceAdmin`, every
instance-admin-gated surface in `server/src` was enumerated and reviewed
for whether its response or side effects could echo process environment
values or platform credentials (tenant trust token, JWT signing keys,
database connection strings, provider API keys): 29 distinct gate
definitions covering ~90+ call sites, in four groups — sole
instance-admin gates (12), instance-admin-or-company-permission gates
(10), response-shaping/scope-widening sites (6), and the central
`allow_instance_admin` short-circuit in the authorization service (58
`decide()` call sites).
Findings and dispositions:
- **Environment read/write responses** exposed platform sandbox
`envVars`/credential-shaped config to instance admins → closed by floor
F1.
- **Manual backup trigger** echoed a server filesystem path and triggers
a platform-owned operation → closed by floor F3.
- **Adapter install/reinstall** loads externally fetched code into the
server process (indirect, complete env exposure) → closed by floor F4.
The sibling plugin-install path already had a bundled-only floor on
managed instances and needed no change.
- **Token-minting surfaces** (gateway tokens, custom-image
terminal/connection tokens) mint credentials scoped to the instance's
own resources, not platform trust anchors → acceptable for an
owner-admin of a dedicated instance; unchanged.
- All remaining gated surfaces return ordinary instance-scoped business
data; none echo `process.env` or platform secrets directly. OAuth client
secrets are referenced by env-var *name* only; SSH private keys are
stored as secret refs before persistence and are not echoed.
Operational note for managed platforms: this model assumes the process
environment of a managed instance holds only that instance's own
credentials. Platform operators should keep provider credentials
per-instance (never fleet-shared) since an instance admin ultimately
controls in-process code on their own instance.
## Verification
- `pnpm vitest run server/src/middleware/cloud-tenant-actor.test.ts` —
resolver matrix: owner × flag on/off, flag via managed overlay
(on-over-DB-off and off-over-DB-on), member/admin/support × flag on,
no-token self-hosted, fail-closed settings read, purge still runs and no
role row is ever inserted (14 tests).
- `pnpm vitest run server/src/__tests__/authorization-service.test.ts` —
computed flag elevates a `cloud_tenant` actor; a stale
`instance_user_roles` row still never does; `session` actors unchanged
(full suite, embedded Postgres).
- `pnpm vitest run server/src/__tests__/environment-routes.test.ts` —
F1: no secret echo to admins on get/list, structural config visible to
restricted readers, platform-row PATCH/DELETE rejected for admins
(including a marker-stripping patch), marker-clear recovery allowed for
stale legacy rows and for a marked sandbox row when no provisioning path
is configured, but refused on the managed local row and on the sandbox
slot row under a managed-config `environments` entry or the forced
kubernetes execution mode, client marker-stamping creates/patches
rejected, tenant rows still readable and writable, self-hosted
read+write regression (60 tests).
- `pnpm vitest run server/src/__tests__/environment-service.test.ts` —
`ensureLocalEnvironment` adopts a pre-existing local row on
cloud-managed instances (marker stamped, other metadata preserved,
idempotent — no rewrite on re-ensure) and leaves self-hosted rows
untouched (22 tests, embedded Postgres).
- `pnpm vitest run server/src/__tests__/instance-settings-routes.test.ts
server/src/__tests__/instance-database-backups-routes.test.ts` — F2
change-vs-echo matrix incl. self-hosted regression; F3 floor for both
admin shapes (32 tests).
- `pnpm vitest run server/src/__tests__/adapter-routes-authz.test.ts` —
F4 floor; self-hosted install/reinstall behavior unchanged (existing
cases).
- `pnpm vitest run server/src/__tests__/first-admin-claim.test.ts
server/src/__tests__/bootstrap-claim-routes.test.ts
server/src/__tests__/managed-config.test.ts
server/src/__tests__/health.test.ts
server/src/__tests__/instance-settings-managed-overlay.test.ts
server/src/services/managed-environments.test.ts
server/src/services/execution-policy-bootstrap.test.ts` — first-admin
bootstrap gate and managed-config behavior unchanged (91 tests).
- `pnpm vitest run packages/shared/src/feature-catalog.test.ts` —
catalog/schema sync tests cover the new key (selfHostedDefault must
equal the schema default).
- `pnpm run typecheck` — all 31 workspace projects clean.
## Risks
- Self-hosted behavior is unchanged: every floor binds to
`isCloudManagedInstance()` (tenant trust token present), the new flag
defaults off with no elevation path, and regression tests pin the
self-hosted branches.
- The elevation is fail-closed and stateless: turning the flag off
(managed overlay or DB) de-elevates on the next request; there is no
role row to clean up and restores cannot resurrect elevation.
- On a cloud-managed instance a pre-existing unmarked local row is
adopted (stamped `managedByPaperclip`) by the next ensure and becomes
platform-owned — the intended managed-product semantic: the platform
owns the single local slot. Self-hosted instances are untouched.
- F1 widens restricted readers' view of platform-provisioned rows from
fully blanked `config`/`metadata` to structural-only `config` plus
markers. Platform-delivered config is guaranteed secret-free by the
managed-config contract (secret-shaped keys fail startup), and the floor
re-drops secret-shaped keys defensively.
- One extra instance-settings read per trusted-header request for
owner-role actors (the resolver already performs several queries per
request).
## Model Used
Claude Fable 5 (Anthropic) — model id `claude-fable-5`, extended
thinking enabled, agentic tool use via Claude Code; read-only explore
subagents on the same model were used for the surface audit sweep.
## 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
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to organize
and operate AI-agent companies.
> - Agent behavior depends partly on the bundled Paperclip core skill
synchronized into each runtime.
> - The existing database and runtime plumbing already supports
immutable skill-version snapshots and per-agent version selections, but
no product workflow exposed that capability.
> - Replacing the live bundled skill globally would make champion
adoption risky and difficult to compare across agents.
> - This pull request adds an experimental, instance-level beta-skills
gate plus a repository release registry, immutable seeded releases,
enforcement, and a per-agent release picker.
> - The benefit is controlled per-agent evaluation of frozen core-skill
releases while the default-off path remains behaviorally unchanged.
## Linked Issues or Issue Description
### Subsystem affected
Cross-cutting: `server/`, `ui/`, `packages/db`, and `packages/shared`.
### Problem or motivation
Paperclip needs a safe way to evaluate improved versions of its core
operating skill without globally replacing the live default. Today the
version-snapshot and per-agent pin plumbing exists, but operators cannot
use it. A global replacement would make regressions difficult to contain
and would prevent controlled comparisons across agents.
### Proposed solution
Add a default-off instance experiment that exposes immutable, named
core-skill releases. When enabled, operators can pin each agent to a
seeded release; when disabled, every agent resolves the live default
while saved pins remain intact. Validate pinned writes at the API
boundary, gate reads at runtime, and expose the selection in the agent
Skills tab.
### Alternatives considered
- **Replace the bundled core skill globally:** rejected because it
changes every agent at once and provides no rollback/isolation boundary.
- **Ship releases as separate skills:** rejected because releases are
versions of one core capability, not independently enabled skills.
- **Store release snapshots only outside the repository:** rejected
because repository provenance and hashes make builds reproducible and
reviewable.
### Roadmap alignment
This extends the Skills Manager / Skill Studio direction in `ROADMAP.md`
by making core-skill versions operable per agent. It does not duplicate
another open implementation PR; GitHub searches found no related
`enableBetaSkills` change.
### Additional context
The feature remains experimental and default off. The V7 champion was
selected through a multi-model evaluation process, and the frozen
release contents are verified by SHA-256 below.
## What Changed
- Added the default-off instance-level `enableBetaSkills` experimental
flag.
- Added `skills-releases/paperclip/` with the ordered release registry
and frozen `v0` plus `v7-roster` snapshots.
- Added release metadata to `company_skill_versions` and idempotent
release seeding. The migration was planned as `0191`, then renumbered to
`0192` because current `master` claimed `0191` before final rebase.
- Added read-time gating and write-time validation so disabled instances
always resolve the live default and reject pinned-version writes.
- Added the per-agent Release picker in the agent Skills tab, including
responsive layout and beta-pin state.
- Kept `EDITS.md` out of the release registry and PR diff.
### V7 Adoption Evidence
- Paid roster: 6 models, 94-case suite.
- Result: 553/564 pass-within-2, mean 92.17/94, versus the P2 baseline
of 544/564.
- Reference model improved 84→91; maximin improved 84→90.
- Final report:
https://pages.paperclip.ing/skills/optimization/paperclip/pap-14624-p3-final-20260721/
### Provenance
- `v7-roster` is the Phase 1 champion plus additions-only edits
E107–E112. Per-edit rationale remains in the evals repository at
`source/v7-roster/EDITS.md` and is deliberately excluded from this PR.
- `v0` is the `skills/paperclip` tree from commit `ea66ea81`.
- Champion selection was accepted on July 21, 2026 via board card
`9c304fc2` (PAP-14624 G3).
- This delivery mechanism was accepted on July 24, 2026 via plan
revision `2367abd2` (PAP-14858).
### QA Evidence
- P4 QA matrix comment `b7f40522-4e9b-4a3a-9821-28e86fe1a987`: all 6
acceptance criteria passed.
- Automated QA matrix: 166 tests passed with 0 failures, including real
filesystem materialization and full SHA-256 assertions.
- UI QA exercised the real agent Skills tab at desktop and mobile widths
with the experimental flag both on and off.
## Verification
- `pnpm check:token-gates`
- Focused beta-release matrix: 169 tests passed across shared
validators, server services/routes/heartbeat behavior, instance settings
UI, and release picker UI.
- `pnpm -r typecheck`
- `pnpm build`
- `pnpm test:run`: server and UI partitions passed; one CLI doctor test
inherited temporary AWS credentials from the agent heartbeat and
expected no static credentials. The isolated rerun with
`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`
unset passed 8/8.
- V7 SHA-256:
- `SKILL.md`:
`53ab290489684cbf116fdd1406a95f6b6f53c9c36358b1bf8bfeae481e253575`
- `references/cases.md`:
`3b821f59064a7761091020a14819a8d787131f24029748563d6c0e1be7e6eaec`
- `references/workflows.md`:
`69747bd6e05f7e3673d1e67b07ff295df1869c05e1fd029804d5fa9177db92cd`
- Confirmed 49 changed files, no `pnpm-lock.yaml`, no workflow changes,
and no `EDITS.md`.
## Risks
- **Migration:** low-to-moderate risk. Three nullable columns and one
partial unique index are added idempotently; existing rows remain valid.
- **Behavior:** low risk while the flag is off because read-time
resolution forces the live default and saved pins are preserved but
inactive.
- **Frozen content:** release snapshots intentionally diverge from
future live skill edits; provenance and hashes make that divergence
explicit and reproducible.
- **UI:** low risk. The picker only renders for the bundled core skill
when the experimental flag is enabled and seeded releases exist.
> This extends the existing Skills Manager / Skill Studio direction
described in `ROADMAP.md`; it does not duplicate another open
implementation PR. The GitHub PR search found no related
`enableBetaSkills` change.
## Model Used
- OpenAI Codex using `gpt-5.5` with reasoning and
terminal/code-execution tools; context-window size is not exposed by
this runtime. Earlier implementation commits also record Claude Opus 4.8
assistance where applicable.
## 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
- [ ] I have not referenced internal/instance-local Paperclip issues or
links (required governance identifiers are included above; no internal
URL is included)
- [ ] My branch name describes the change and contains no internal
Paperclip ticket id (the approved delivery plan mandated this shared
branch name)
- [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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies and their work.
> - Artifacts and documents are first-class outputs, but users need a
personal way to keep important documents easy to find.
> - Existing resource memberships already model per-user starred
projects and agents with company scoping and activity logging.
> - Documents lacked the equivalent membership model, route, and
artifact filtering behavior.
> - The shared membership contract also needs to remain safe for
existing UI project/agent mutation helpers when documents become a
recognized resource type.
> - This pull request extends the existing resource-membership system
with per-user document stars and a starred artifacts view.
> - The benefit is a company-scoped, idempotent server foundation for a
dedicated starred-documents experience without weakening authorization
or artifact filtering semantics.
## Linked Issues or Issue Description
### Problem / Motivation
Board users cannot star individual documents, and the company artifacts
API cannot return only the current user's starred documents.
### Proposed Solution
Add company/user-scoped document memberships, a board-only document star
route, document membership data in the shared contract, and a
`starred=true` artifacts filter.
### Alternatives Considered
A document column was rejected because stars are per-user; a separate
star API was rejected because projects and agents already use resource
memberships.
### Roadmap Alignment
This extends the existing Artifacts & Work Products roadmap area and
does not duplicate another open pull request found in the repository
search.
## What Changed
- Added the `document_memberships` schema and migration with
company/user/document uniqueness and starred ordering.
- Extended shared resource-membership and artifact-query contracts for
documents and `starred=true`.
- Added company-scoped document star/unstar service and board-only route
behavior with activity logging.
- Added starred document artifact filtering, including user-authored
documents, document kinds, cursor ordering, and incompatible-kind
handling.
- Preserved idempotency under concurrent star requests and synchronized
UI membership defaults/helpers with the expanded contract.
- Added focused shared, route, service, and UI regression coverage.
## Verification
- `pnpm exec vitest run packages/shared/src/resource-memberships.test.ts
server/src/__tests__/company-artifacts-service.test.ts
server/src/__tests__/resource-memberships-routes.test.ts`
- `pnpm exec vitest run ui/src/components/SidebarAgents.test.tsx
ui/src/components/SidebarProjects.test.tsx
ui/src/components/SidebarStarredProjects.test.tsx`
- `pnpm --filter @paperclipai/db check:migrations`
- `pnpm -r typecheck`
- `pnpm test:run`
- `pnpm build`
## Risks
- The migration adds a new membership table and non-concurrent indexes;
migration safety gates pass with the repository's established policy.
- The starred artifacts query intentionally returns only documents and
relaxes the normal agent-authored/system-kind predicates for documents
the current user explicitly starred.
- Document membership mutations remain board-user-only; agent callers
receive no document-star capability.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex CLI; runtime model ID and context-window size were not
exposed to this session. Reasoning, repository tool use, code execution,
and test execution were enabled.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the control plane people use to create and govern
AI-agent companies
> - Agent creation persists runtime configuration that controls which
model profiles future runs may select
> - Adapters can expose a `cheap` profile, and existing creation paths
implicitly left that profile available when operators made no choice
> - That made a newly created agent eligible for a lower-cost model
without an explicit operator opt-in
> - The UI also dropped an explicit opt-in when the operator selected
the adapter's default cheap model rather than a custom model ID
> - Codex additionally hardcoded `gpt-5.3-codex-spark` into its cheap
profile and static fallback model list, making Paperclip choose an
auth-dependent model rather than requiring an operator choice
> - This pull request makes new-agent creation disable an available
cheap profile by default while preserving explicit opt-in from the UI or
API
> - The Codex cheap profile now remains available for explicit
configuration but supplies no model default, so an unconfigured cheap
request stays on the primary model
> - The benefit is predictable model quality for new agents and an
intentional, auditable choice before lower-cost routing is enabled
## Linked Issues or Issue Description
**Problem**
New agents created with an adapter that exposes a `cheap` model profile
can inherit that profile without the operator explicitly enabling it. In
the UI, enabling the adapter-default cheap model is also omitted because
runtime configuration is only written when a custom model ID is present.
**Expected behavior**
- New agents default an available `cheap` model profile to `{ enabled:
false }` when the caller does not specify it.
- Explicit API configuration remains authoritative.
- UI opt-in persists even when the adapter default model is used.
- Codex does not advertise or automatically select
`gpt-5.3-codex-spark`; operators must explicitly configure any
lower-cost Codex model.
**Related public work**
- Refs #4881, which introduced cheap model profiles for local adapters.
- Supersedes the default-selection portions of #8032 and #10004 by
removing the Codex model default instead of replacing it with another
hardcoded model.
## What Changed
- Detect whether the selected adapter exposes a `cheap` model profile
during agent creation and hiring.
- Persist `runtimeConfig.modelProfiles.cheap.enabled = false` only when
the caller did not explicitly configure the profile.
- Preserve UI cheap-profile opt-in when using the adapter's default
model by writing an empty adapter config.
- Remove `gpt-5.3-codex-spark` from the Codex static model list.
- Keep the Codex `cheap` profile explicitly configurable while giving it
an empty adapter config, so Paperclip never chooses a cheap Codex model
automatically.
- Verify that a Codex cheap request without an explicit model leaves the
primary model unchanged.
- Extend server route and UI runtime-config tests for default-disable
and explicit-opt-in behavior.
## Verification
- `env -u PAPERCLIP_IN_WORKTREE -u PAPERCLIP_WORKTREE_NAME -u
PAPERCLIP_CONFIG -u PAPERCLIP_HOME -u PAPERCLIP_INSTANCE_ID -u
PAPERCLIP_CONTEXT pnpm exec vitest run
packages/adapters/codex-local/src/index.test.ts
packages/adapters/codex-local/src/server/codex-args.test.ts
server/src/__tests__/adapter-models.test.ts
server/src/__tests__/adapter-registry.test.ts
server/src/__tests__/heartbeat-model-profile.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Result: 7 test files passed, 105 tests passed.
- GitHub `Typecheck + Release Registry` check passed on the final head.
- `git diff --check public-gh/master...HEAD`
## Risks
- Low behavioral risk: only newly created or hired agents are
normalized; existing agents are unchanged.
- Explicit `cheap` profile settings remain untouched, including explicit
opt-in.
- Codex users who explicitly opt into the cheap lane must choose a
model; requests without a configured override intentionally continue on
the primary model.
- Adapter profile discovery is now awaited during creation, adding a
small amount of adapter metadata lookup work.
- The source branch name is automation-provided and retained as required
by the task, so it does not satisfy the preferred public branch naming
convention.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI `gpt-5.4` via Codex CLI, with reasoning, repository editing,
terminal execution, and GitHub/Paperclip tool access. The runtime did
not expose a context-window size.
## 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)
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators review decisions and turn useful outcomes into training
examples
> - The training library belongs to the Decisions surface, but its
routes and entry point appeared as a separate top-level destination
> - That mismatch made the training icon feel disconnected and left
internal links pointing at legacy `/training` URLs
> - This pull request nests the training library and inspector under
`/decisions/training`, keeps legacy URLs working through redirects, and
uses one route helper everywhere
> - The benefit is a clearer Decisions workflow without breaking
existing bookmarks or links
## Linked Issues or Issue Description
Refs: #9718
Refs: #9779
**Problem:** The Decisions page exposed training as a labeled header
action while the training library lived at the unrelated top-level
`/training` route. Links across the UI duplicated that legacy path.
**Expected behavior:** Training should be an icon action alongside the
other Decisions controls, and training library/record URLs should live
under `/decisions/training` while old URLs continue to redirect.
**Steps to reproduce:**
1. Open the Decisions page.
2. Observe the separate labeled Training button in the page heading.
3. Open a training record and note the top-level `/training/...` URL.
**Version/commit:** Current `master` before this PR.
**Deployment mode:** All UI deployment modes.
## What Changed
- Move the training library and inspector routes under
`/decisions/training`.
- Redirect legacy `/training` and `/training/:id` URLs to the new
locations.
- Add `decisionTrainingHref()` and use it for training links and
navigation.
- Place the Training icon action with the Decisions filter and sort
controls.
- Add focused tests for library and record URL generation.
## Verification
- `pnpm exec vitest run ui/src/lib/decisionTraining.test.ts`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/ui build`
- `pnpm check:token-gates`
## Risks
- Low risk: the change is limited to client-side routes and navigation.
- Existing `/training` bookmarks remain supported through replacement
redirects.
- No API, database, migration, dependency, or workflow changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex using GPT-5.4 with reasoning, repository tool use, shell
execution, and code/test verification.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies.
> - Its secrets subsystem can resolve external references such as AWS
Secrets Manager values without copying those values into Paperclip
custody.
> - Operators also need to rotate a referenced secret's value while
preserving the same provider reference for consumers inside and outside
Paperclip.
> - Previously, external-reference rotation could only retarget
metadata, and secret detail sheets were driven by local component state
rather than shareable navigation state.
> - This pull request adds an optional provider write capability,
implements AWS Secrets Manager write-through rotation, and exposes
capability-aware rotate modes in the UI.
> - It also makes secret and each-user definition detail sheets
URL-driven and adds a copy-link action.
> - The benefit is that operators can update the canonical external
value safely while keeping AWS rotation tracking intact, and they can
share or navigate directly to secret details.
## Linked Issues or Issue Description
### Subsystem affected
Cross-cutting (`server/`, `ui/`, and `packages/shared`).
### Problem or motivation
External-reference secrets can follow a provider-managed value, but
Paperclip could not write a replacement value back to providers that
support it. Operators had to leave Paperclip, update the value
separately, and then return without an auditable Paperclip rotation
record. Secret detail sheets also could not be shared or restored
through browser history because their selection lived only in component
state.
### Proposed solution
Add an optional `updateExternalSecretValue` provider capability and
surface it as `supportsExternalValueWrites`. Implement AWS writes with
`PutSecretValue` while leaving the resolution `versionId` unset so
future reads continue following `AWSCURRENT`. Add write-value and
retarget modes to the rotate dialog for capable providers. Drive secret
detail selection from `?secret=` / `?definition=` query parameters and
provide a copy-link action.
### Alternatives considered
Converting an external reference into a Paperclip-managed secret would
break consumers that depend on the existing provider reference. Pinning
reads to the newly written AWS version would prevent later out-of-band
rotations from flowing through. Keeping sheet selection only in React
state would not support browser Back or shareable links.
### Roadmap alignment
This extends the completed “Secrets Manager with per-agent access”
roadmap capability; it does not duplicate a separate planned roadmap
item. Public GitHub searches found no duplicate or closely related issue
or PR.
### Additional context
The PR includes focused provider, service, and UI render coverage.
Cutter also generated previews for the deep-linked detail sheet and
capability-aware rotate modes.
## What Changed
- Added optional external-value write support to the secret provider
contract and provider descriptors.
- Implemented AWS Secrets Manager write-through with `PutSecretValue`,
audit material, and compensation when persistence fails after the
provider write.
- Allowed `secretService.rotate()` value updates for external references
while rejecting ambiguous value-plus-retarget combinations and
unsupported providers.
- Added capability-aware “Write new value” and “Change reference” rotate
modes with updated custody and action copy.
- Made secret and each-user definition detail sheets source their
selection from URL query parameters, compose with folder paths, close
through browser history, and expose a copy-link action.
- Added provider, service, and UI render coverage for write-through,
rollback, capability messaging, dialog modes, and deep links.
## Verification
- `pnpm vitest run
server/src/__tests__/aws-secrets-manager-provider.test.ts` — 18 passed.
- `pnpm vitest run server/src/__tests__/secrets-service.test.ts` — 75
passed.
- `pnpm vitest run ui/src/pages/Secrets.render.test.tsx` — 31 passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — passed with all gates clean.
## Risks
- External value writes affect the canonical provider secret and
therefore all consumers of that AWS secret; the UI explicitly labels
this custody behavior.
- A provider write can succeed before Paperclip persistence fails. The
service records the written version and AWS support includes
compensation coverage to restore the prior value where possible;
unrecoverable failures return explicit audit-safe error details.
- URL-driven sheet state changes navigation behavior; render tests cover
deep links, Back/close behavior, and composition with folder query
state.
- No database migration or breaking API requirement is introduced;
providers without the optional capability retain reference-only
behavior.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex using exact model ID `gpt-5.6-sol`, high reasoning mode,
Codex CLI `0.142.5`, with repository, shell, Git, GitHub CLI, and
code-execution tools. The runtime did not expose a context-window size.
- Earlier implementation commits were assisted by Anthropic `Claude
Fable 5` as recorded in their commit trailers; the exact backend model
ID and context-window size were not preserved in the workspace metadata.
## 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 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, or
confirmed no documentation change is required
- [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
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Scheduled routines provide recurring control-plane work without
manual intervention.
> - The activity gate (`activity_gate_policy` / `activity_gate_scope`)
lets a scheduled routine skip a tick when nothing has happened since its
last run, so watcher-style routines stay asleep while the system is
settled instead of burning tokens every tick.
> - The scheduler, database columns, and the create/update API for those
fields already landed (see #9438), but there was no way for an operator
to actually set the policy from the routine editor, and the runs list
rendered gated skips as bare "skipped" rows with no "why".
> - This pull request adds the editor control and the run-row labels:
the Delivery section gets an "Advanced run policy" picker plus a scope
selector, and skipped runs explain why they were skipped.
> - The benefit is that the activity gate becomes discoverable and
usable end-to-end from the UI, closing the loop on the feature the API
already supports.
## Linked Issues or Issue Description
- Refs #8534 — activity gate for scheduled routines.
- Builds on #9438 (merged) which exposed the activity-gate create/update
API and the `Routine` response fields this UI reads and writes. This PR
is the editor/UI companion to that API work.
## What Changed
- **Routine editor — Advanced run policy control**
(`ui/src/components/routine-sections/editable-sections.tsx`): the
Delivery section gains a `RadioCardGroup` to choose between *Run on
every scheduled tick* (default) and *Skip when there's been no activity
since the last run*. When gating is enabled, a second scope picker
(*Company-wide* / *This project*) appears. The control is **disabled
with an explanatory hint** — rather than hidden — when the routine has
no schedule trigger, since the gate only affects scheduled ticks
(webhook/manual/API fires are themselves activity and always run). This
keeps the capability discoverable.
- **Edit-draft plumbing**
(`ui/src/components/routine-sections/context.tsx`,
`ui/src/pages/RoutineDetail.tsx`): `activityGatePolicy` /
`activityGateScope` flow through the edit draft, the delivery section's
dirty-field detection, the save payload, and revision restore, matching
how `concurrencyPolicy` / `catchUpPolicy` are handled.
- **Run-history skip reasons** (`ui/src/lib/routine-run-display.ts`):
skipped run rows now render a human-readable "why" —
`no_external_activity` → "Skipped — no activity since last run", plus
labels for `paused` and `worktree_execution_cutoff` — instead of a bare
status.
- Storybook fixture updates for the new routine fields, plus focused
run-display coverage for the skipped-run labels.
## Verification
- `pnpm exec vitest run ui/src/lib/routine-run-display.test.ts` — 11
tests passing.
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `pnpm check:token-gates` — all token gates clean.
- `pnpm --filter @paperclipai/ui build` — production build succeeds.
- Current-head GitHub CI for `9f9af8ecccfadcdc4a3afab313aafad81cbd1112`
— all checks pass (Storybook visual check skipped by workflow policy).
## Risks
- **Low risk.** UI-only change; no schema/migration and no server
changes (the API and columns already shipped in #9438). Fields are
optional and default to the pre-feature behavior (`always` / `company`),
so existing routines are unaffected. The scope picker only renders when
gating is turned on, and the whole control is inert without a schedule
trigger.
## Model Used
- Claude Opus 4.8 (`claude-opus-4-8`, 1M context), extended thinking,
with tool use (repo edit + shell verification).
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents and boards coordinate through issue-thread interactions
(request_confirmation, ask_user_questions, suggest_tasks, …) that wait
as `pending` cards until someone resolves them
> - Two lifecycle gaps existed: an interaction's creator could not take
back a card it no longer stands behind, and interactions left `pending`
on issues that reached a terminal status lingered forever as
live-looking approval requests
> - Stale pending cards mislead humans (they look actionable), distort
attention/liveness signals, and in the worst case invite acting on a
proposal whose issue is already closed or cancelled
> - This pull request adds an explicit withdraw route for pending
interactions and automatically expires pending interactions when their
issue reaches a terminal status (including a catch-up sweep for issues
closed before this change)
> - The benefit is that interaction cards now faithfully reflect
reality: only genuinely actionable requests stay pending, and creators
can retract requests that events have overtaken
## Linked Issues or Issue Description
Fixes#5787
Refs #7403
Related prior PRs found while searching for duplicates (all overlap
partially; none combine both lifecycle paths or the route-level
authorization used here): #6709 and #7312 (creator-withdraw attempts),
#8169 (terminal expiry), #8081 and #5137 (generalized cancel/expire
endpoints), #6094 (stale confirmation auto-resolve). Related merged
context: #9568 (agent cancel for ask_user_questions), #10119 (tolerating
legacy `withdrawn_by_creator` result outcomes — the reader side of the
outcome this PR writes).
## What Changed
- New route `POST /issues/:id/interactions/:interactionId/withdraw` that
resolves a `pending` interaction to status `withdrawn` with a structured
result (`outcome: "withdrawn"`, optional trimmed `reason`), stamps
`resolvedBy*`/`resolvedAt`, touches the issue, logs activity, and emits
resolved-interaction telemetry
- Withdrawal authorization: board users, the interaction's creator
agent, or the issue's current assignee agent (assignees additionally
pass the standard issue-mutation gate); task-watchdog runs are
explicitly rejected, and authorization-boundary plus low-trust
control-plane checks apply
- Withdrawing an already-resolved interaction returns `409`;
unknown/cross-issue/cross-company interaction ids return `404`
- New service method `expirePendingInteractionsForTerminalIssue`: when
an issue transitions to a terminal status, all of its `pending`
interactions are resolved to `expired` with `outcome: "issue_closed"`,
guarded by a `status = 'pending'` predicate so concurrent resolutions
are not overwritten
- The same expiry runs as a catch-up when interactions are listed on an
already-terminal issue, so cards stranded by issues closed before this
change also get cleaned up; expired request_confirmations are logged
with a distinguishing source
- Shared package: new `withdrawIssueThreadInteractionSchema` validator,
`WithdrawIssueThreadInteraction` type, and `withdrawn` / `issue_closed`
result-outcome support for all interaction kinds (kind-aware result
shapes for `ask_user_questions` and `request_item_verdicts`)
- UI helper `ui/src/lib/issue-thread-interactions.ts` recognizes the new
outcomes for card rendering
- Docs: bundled skill API reference updated with the withdraw endpoint
- Review follow-up: terminal expiry moved from the HTTP route hooks into
`issueService.update`'s status-transition block, so direct service
callers (tree control, recovery, pipelines, status cards) expire pending
cards too; the list-endpoint catch-up remains for issues closed before
this change
- Review follow-up: withdrawing or issue-close-expiring a
`request_confirmation` also settles its linked `tool_action_requests`
row (withdraw -> `cancelled`, issue closed -> `expired`), so a parked
tool call cannot stay approvable after its card is gone
- Review follow-up: interaction cards render dedicated copy for the new
outcomes ("Withdrawn" with the reason, "Expired when issue closed")
instead of falling through to superseded-by-comment / stale-target
variants; withdrawn plan reviews badge as "Withdrawn" rather than
"Changes requested"
## Screenshots
Card states rendered from a local ux-lab harness with mocked data ([full
gallery](https://pages.paperclip.ing/pr-10251-interaction-withdrawal-cards/)):
| Light | Dark |
| --- | --- |
| 
| 
|
## Verification
- `pnpm --filter @paperclipai/shared build` — clean tsc
- `cd server && npx vitest run
src/__tests__/issue-thread-interaction-routes.test.ts` — 22 tests pass,
including new coverage for: creator-agent withdraw success,
non-creator/non-assignee agent 403, watchdog-run 403, double-withdraw
409, and board-user withdraw
- `cd server && npx vitest run
src/services/issue-thread-interactions.test.ts` — 4 tests pass,
including terminal-issue expiry writing `issue_closed` results and
leaving already-resolved interactions untouched
- `cd ui && pnpm typecheck` — clean
- `cd server && npx vitest run src/__tests__/issues-service.test.ts` —
includes a new embedded-Postgres test proving a direct
`issueService.update` terminal transition expires pending interactions
and writes the activity-log entry
- `cd ui && npx vitest run
src/components/IssueThreadInteractionCard.test.tsx` — 32 tests,
including new coverage for withdrawn / issue-closed confirmation and
question cards
- `cd server && npx tsc --noEmit` — matches the pre-existing repo error
baseline exactly (no new errors)
- Manual: `POST /issues/:id/interactions/:interactionId/withdraw` with
`{"reason":"superseded"}` as the creator agent resolves the card to
`withdrawn`; closing an issue with a pending confirmation flips it to
`expired` with `outcome: "issue_closed"`
## Risks
- Interactions on terminal issues now auto-expire (including
retroactively via the list-time catch-up), so consumers that expected to
resolve a pending interaction on a closed issue will get `409`; this is
the intended semantics and matches how the attention feed already wants
to treat dead cards
- New result outcomes (`withdrawn`, `issue_closed`) are written to
stored results; readers were already made tolerant of these outcome
strings in #10119, so mixed-version reads are safe
- No schema/migration changes; per-row conditional updates (`status =
'pending'`) avoid clobbering concurrent resolutions
- Withdrawal is a new mutation surface, but it is strictly narrower than
existing resolve paths (board, creator, or assignee only; watchdog runs
blocked)
## Model Used
Claude Fable 5 (`claude-fable-5`, Anthropic Mythos-class tier) with
extended thinking and agentic tool use (Claude Code harness); commit
authored in a Paperclip-managed engineering session.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The inbox helps operators scan and act on tasks that need attention
> - Inbox task rows currently include external-object summary markers
alongside the core task information
> - Those markers add a column of visual noise that is not needed for
inbox triage
> - External-object data must remain available to the inbox filters even
when the row marker is removed
> - This pull request stops passing external-object summaries into inbox
rows and adds regression coverage
> - The benefit is a cleaner inbox while preserving external-object
filtering behavior
## Linked Issues or Issue Description
- Refs #4556
- **Problem:** Inbox task rows display external-object summary markers
that operators do not need for triage.
- **Expected behavior:** Inbox rows omit the external-object marker,
while filters that depend on external-object summaries continue to work.
## What Changed
- Removed the external-object summary prop from inbox task rows.
- Added a regression test that provides external-object summary data and
confirms the inbox row does not render its marker.
- Kept external-object summary loading intact for inbox filtering.
## Verification
- `pnpm exec vitest run ui/src/pages/Inbox.test.tsx` — 18 tests passed.
- `pnpm check:token-gates` — reproduces five pre-existing `#9627`
color-literal violations; this PR adds no token values or new gate
violations.
## Risks
- Low risk: the change removes one optional presentation prop from the
inbox row call site and leaves filtering data flow unchanged.
- Regression coverage verifies summary data no longer produces the
removed inbox marker.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.4, tool-enabled coding agent with shell and code
execution; reasoning enabled; context-window size not exposed by the
runtime.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Scheduled routines provide recurring control-plane work without
manual intervention
> - The new activity gate can suppress scheduled runs when no external
work occurred
> - The core scheduler and database support landed without a public
create/update contract
> - Agents, operators, and managed plugins need validated fields plus
discoverable semantics to opt in safely
> - This pull request exposes the activity gate through routine APIs,
revisions, plugin contracts, tests, and skill documentation
> - The benefit is backward-compatible control over idle scheduled work
without losing activity-triggered follow-up
## Linked Issues or Issue Description
- Refs #8534
## What Changed
- Added shared activity-gate policy and scope enums with create/PATCH
validation.
- Persisted activity-gate fields through routine creation, updates,
revision snapshots, pipeline snapshots, and revision restores.
- Defaulted legacy revision snapshots during restore and added
regression coverage for pre-field snapshots.
- Extended managed-plugin routine declarations, production
reconciliation, and the SDK test harness to preserve non-default gate
settings.
- Added end-to-end API coverage for create/PATCH/list/detail
round-trips, defaults, and invalid enum rejection.
- Documented schedule-only semantics, activity windows,
own-run/read-action exclusions, scopes, and an hourly quiet-night
watcher example.
## Verification
- `pnpm exec vitest run packages/shared/src/validators/routine.test.ts
server/src/__tests__/routines-service.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run packages/shared/src/validators/plugin.test.ts
packages/plugins/sdk/tests/testing-actions.test.ts
server/src/__tests__/plugin-managed-routines.test.ts
server/src/__tests__/routines-service.test.ts -t 'activity
gate|preserves declared activity gate settings|resolves routine agent
and project refs'`
- `pnpm exec vitest run ui/src/lib/workspace-routines.test.ts
ui/src/pages/Routines.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/plugin-sdk typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
- GitHub CI: all final-head checks green; Storybook visual regression
skipped by path rules.
- Greptile: 5/5 with no unresolved review threads.
## Risks
- Low risk: defaults remain `always` and `company`, preserving existing
routine behavior and old revision snapshots.
- Managed plugin manifests can now declare the same validated gate
settings as the public routine API; omitted values retain core defaults.
- Revision snapshots now include the new fields so policy changes are
not lost or treated as no-ops during restore.
> For core feature work, checked `ROADMAP.md`: this extends the existing
Scheduled Routines roadmap item and does not duplicate a separate
planned capability.
## Model Used
- OpenAI GPT-5.5 via Codex CLI, with repository tool use and code
execution; context-window size was not exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Status cards summarize changing company work and watch issues so
later changes can produce useful deltas
> - A summary can explicitly reference issues that are important to the
update even when those issues do not match the card's configured queries
> - Previously, those referenced issues were not retained in the watched
set, so their later status, assignee, or comment changes could be missed
> - The watched snapshot must avoid artificial additions or removals
caused only by a summary changing which issues it references
> - This pull request resolves issue references when a summary is
written, persists them, and joins them to the watched snapshot with
stable delta semantics
> - The benefit is that status cards continue tracking the exact issues
their latest update called out while keeping follow-up updates relevant
and non-duplicative
## Linked Issues or Issue Description
### Pre-submission checklist
- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on the latest released version of Paperclip (or can reproduce
on `master`).
- [x] I have confirmed the error originates in Paperclip itself — not in
my agent adapter, API provider, or local configuration.
### What happened?
When a status-card summary explicitly referenced an issue by identifier
or `/issues/<uuid>` URL, that issue was not automatically retained in
the card's watched set unless it independently matched a configured
query. Later status, assignee, or comment changes to an issue
highlighted by the latest update could therefore be omitted.
### Expected behavior
References in the latest summary should resolve only within the card's
company, appear in dry runs and the watched-issues UI, count and
fingerprint like query matches, and enter or leave the watched set
without artificial added/removed deltas already represented by the
summary change.
### Steps to reproduce
1. Create a status card whose query does not match a second issue in the
same company.
2. Write a summary that references the second issue by identifier or
issue URL.
3. Inspect the card's watched count or Watched issues tab.
4. Change the referenced issue's status, assignee, or comments and run
the next update.
5. Before this change, the referenced issue is absent from the watched
snapshot and its later change does not produce the expected delta.
### Paperclip version or commit
- Reproduced on `master` before this PR (base commit `762ce5b4ef`).
### Deployment mode
- Local dev (`pnpm dev`), built from source.
### Agent adapter(s) involved
- Not adapter-specific (core bug).
### Database mode
- Embedded Postgres test environment; the schema change uses standard
PostgreSQL JSONB.
### Access context
- Board (human operator).
## What Changed
- Added migration `0191` and schema support for persisted
`status_cards.mentioned_issue_ids`.
- Resolved summary references by issue identifier or `/issues/<uuid>`
URL within the status card's company when summaries are written.
- Joined mentioned issues into watched counts and fingerprints so later
status, assignee, and comment changes generate normal update deltas.
- Suppressed artificial added/removed deltas when the latest summary
starts or stops mentioning an issue.
- Added `mentionedIssues` to dry-run responses and a “Mentioned in the
latest update” group in the Watched issues tab.
- Updated the summarizer prompt to explain that referenced issues
automatically join the watched set.
- Added focused server and UI coverage for reference resolution,
snapshot behavior, deltas, API responses, and rendering.
## Verification
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/status-cards.test.ts
src/__tests__/status-card-update-engine.test.ts` — 31 tests passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/StatusCards/StatusCardTile.test.tsx` — 11 tests passed.
- Earlier implementation verification also passed database/shared/server
typechecks, UI `tsc -b`, the broader StatusCards UI test set, and
embedded-Postgres migration application.
### Visual Verification
- Greptile T-Rex ran Playwright browser checks successfully and captured
the Status Card drawer Watched tab showing the new “Mentioned in the
latest update” grouping:
https://app.greptile.com/trex/runs/15796101/artifacts
## Risks
- The migration adds a nullable JSONB column and is backward-compatible;
existing cards have no mentioned issues until their next summary write.
- Reference extraction is company-scoped to prevent cross-company issue
association.
- Watched counts and future fingerprints change for cards whose latest
summaries reference issues; tests cover additions, removals, and
suppression of spurious deltas.
- This targeted status-card fix does not introduce a new roadmap
subsystem or external integration.
## Model Used
- Anthropic Claude Fable 5 (Paperclip model alias; exact underlying
provider model ID and context window were not recorded in the
implementation task metadata), with extended reasoning, tool use, and
code execution.
- OpenAI Codex coding agent (runtime model identifier and context window
not exposed to this task) prepared the PR, rebased the branch, and ran
focused verification with terminal tool use.
## 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)
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Routines page is part of the operator UI for scheduled routine
management
> - The grouped-by-folder view was not presenting folder sections inline
in the main pane
> - That made the folder grouping mode harder to scan and hid the
separation between custom folders, Unfiled routines, and built-in
routines
> - This pull request updates the Routines page rendering so folder
groups appear as inline sections and built-in routines still split into
their own section afterward
> - The benefit is that grouped routines stay readable and the page
matches the intended folder organization
## Linked Issues or Issue Description
No corresponding public GitHub issue exists, so the problem is described
directly below following the bug template.
### What happened
On the Routines page, selecting Group → Folder flattened the grouped
list into a single "All routines" section with only the separate
built-in routines section below it.
### Expected behavior
Group → Folder should render one inline section per folder, keep
routines with no folder in an Unfiled section, and preserve the separate
built-in routines section after the custom folder groups.
### Steps to reproduce
1. Open the Routines page.
2. Change grouping to Folder.
3. Observe the main pane.
4. The routine list is flattened instead of grouped into folder-labeled
inline sections.
### Paperclip version / commit
Current PR head: `a8e384c838e362de3437c7a88bc7aa38b10fd9c0` on
`fix/routine-folder-grouping`.
### Deployment mode
Local development workspace for the Paperclip app UI.
## What Changed
- Updated the Routines page rendering so grouped folders render as
inline sections instead of flattening into a single list.
- Kept routines without a folder grouped under Unfiled.
- Preserved the built-in routines section after custom folder groups.
- Added and updated tests for the folder-grouped rendering behavior.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/Routines.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
## Risks
- Low risk: the change is localized to the Routines page rendering and
its test coverage.
- The main behavioral risk is accidental grouping regressions if future
routine-grouping logic changes without updating the tests.
## Model Used
OpenAI Codex, GPT-5, tool-use enabled, 256k-context class model.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Harold Kim <harold@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies and their ongoing work.
> - Status cards turn a standing question into recurring,
agent-generated summaries on the board.
> - The existing setup split intent across a watch prompt and separate
update instructions, which made creation and later behavior harder to
understand.
> - A status card should have one durable source of truth for both
deciding what to watch and telling the summarizer what each update must
contain.
> - This pull request makes the card prompt that source of truth,
simplifies creation to one step, and lets operators choose the running
agent immediately.
> - The benefit is a smaller mental model, fewer configuration modes,
and consistent update instructions throughout the card lifecycle.
## Linked Issues or Issue Description
Status cards currently require operators to express the same intent in
two places: the watch prompt and optional update instructions with
append/replace/none modes. This feature simplifies the experimental
status-card workflow so a single prompt defines both the watch query and
every generated update. The create flow must also support selecting the
responsible agent without a second setup step.
Related prior status-card work: #10101.
## What Changed
- Use the status card's single prompt to compile the watch query and
directly instruct every summary update.
- Add migration `0190_status_card_single_prompt` to remove
`status_cards.instructions_mode` and `status_cards.instructions`.
- Add `agentId` to `createStatusCardSchema`, validate company
membership, and default new cards to the built-in Summarizer.
- Replace the two-step create flow with one prompt-and-agent dialog and
extract a shared `SummarizerAgentSelect` for create/settings surfaces.
- Remove the extra-instructions settings section, reset incremental
history when the prompt changes, and rename the board page to "Status".
- Update the bundled `status-card-query` skill and board-operator
documentation, then regenerate the skills catalog manifest.
## Verification
- Server status-card suites: 29/29 passing.
- UI `StatusCards` suites: 22/22 passing.
- Skills catalog suite: 20/20 passing.
- `tsc -b` passes for server, UI, shared, and database packages.
- `pnpm check:migrations` passes.
- Light and dark mode screenshots cover the new create dialog and
settings tab.
## Risks
- Migration `0190` intentionally drops existing separate instruction
text. Existing card prompts remain and become the update instructions
under the new model; status cards are experimental and feature-flagged.
- Prompt edits now reset the incremental summary chain and trigger a
full rebuild, which is intentional because the prompt is also the update
contract.
- Agent selection is company-scoped; invalid agent ids return a
validation error rather than creating a misrouted card.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Implementation: Anthropic Claude via the `claude_local` adapter, agent
label "Claude Fable 5"; extended reasoning, tool use, and code
execution. The exact provider model id and context-window value were not
retained in the task metadata.
- PR preparation: OpenAI GPT-5.4 through Codex CLI, with reasoning,
repository inspection, GitHub CLI, and Paperclip API tool use.
## 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)
- [ ] 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
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies.
> - Operators need a board-level way to monitor a changing slice of
company work without repeatedly rebuilding filters or reading raw task
threads.
> - Existing summaries are useful snapshots, but they do not provide a
dedicated query-backed card with refresh policy, change tracking, update
history, and per-update cost visibility.
> - The capability needs to be safe to evaluate before it becomes part
of the default product surface.
> - This pull request adds end-to-end experimental Status Cards, from
schema and query compilation through update orchestration and operator
UI.
> - The entire feature is gated behind the `enableStatusCards`
experimental toggle, including its route and sidebar entry.
> - The benefit is a governed, inspectable way to keep focused
operational rollups current while preserving explicit controls over
refresh frequency and spend.
## Linked Issues or Issue Description
### Subsystem affected
Cross-cutting (`packages/db`, `packages/shared`, `server/`, `ui/`, and
bundled skills/docs).
### Problem or motivation
Operators cannot currently define a reusable natural-language view of
company work, compile it into an inspectable query, and keep its summary
current as matching issues change. Rebuilding filters and rereading task
threads makes board-level monitoring repetitive and hides the
relationship between source changes, refresh cost, and the resulting
summary.
### Proposed solution
Add experimental Status Cards that compile operator intent into a query,
summarize matched work, record each update, expose
manual/interval/reactive refresh policies and costs, and preserve the
last good result across stale, updating, paused, and error states. The
capability is off by default and fully gated behind `enableStatusCards`,
including its route and navigation entry.
### Alternatives considered
- Extend existing one-off summaries: rejected because status cards
require persistent query provenance, refresh policy, update history, and
card-specific cost controls.
- Add a dashboard-only filter widget: rejected because it would not
provide governed background refresh, an update ledger, or an inspectable
compile pipeline.
- Ship the surface by default: rejected in favor of an experimental
toggle while behavior and operator value are evaluated.
### Roadmap alignment
This advances Paperclip’s board-level execution visibility and
output-first product goals. `ROADMAP.md` was checked and no duplicate
status-card initiative was found.
### Additional context
No related open PR was found in the public GitHub search for status
cards. The PR-only design wireframes were removed from the repository
after review; the published prototype remains external to the production
source tree.
## What Changed
- Added company-scoped status-card schema, CRUD APIs, compile
provenance, update ledger, shared contracts, validators, and OpenAPI
coverage.
- Added the text-to-query compile pipeline, bundled `status-card-query`
agent skill, query versioning, and authorized write-back flow.
- Added the experimental board, create flow, lifecycle tiles,
detail/settings/debug drawers, archived view, routing, navigation, and
instance setting.
- Added a change-gated update engine with manual, interval, and reactive
refresh policies, trigger selection, active hours, and daily token caps.
- Added per-update token/cost recording, today and lifetime rollups, and
policy-derived cost previews.
- Added operator documentation and agent-authoring hardening for compile
and update behavior.
- Added PR-prep integration coverage for settings/startup wiring and
replaced raw UI values with design-system tokens.
- Removed the PR-only `design/pap-15023-status-cards` wireframe
artifacts so the repository contains only production feature assets.
## Verification
- `pnpm -r typecheck` — passes on the PR head; includes `ui` `tsc -b`
passing. The UI compile gate was also independently recorded as passing
at `6d7f3cf96b` on July 23, 2026.
- `pnpm build` — passes.
- `pnpm check:token-gates` — passes with all three gates clean.
- `pnpm test:run` — 2,880 tests passed and 1 skipped; the sole failure
was an unrelated 10-second `afterAll` database-cleanup timeout in
`execution-workspaces-service.test.ts`.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/execution-workspaces-service.test.ts` — passes on
immediate focused rerun (25/25).
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/instance-settings-service.test.ts
src/__tests__/server-startup-feedback-export.test.ts` — passes (31/31).
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/StatusCards/StatusCardSettingsForm.test.tsx
src/pages/StatusCards/StatusCardTile.test.tsx
src/pages/StatusCards/format.test.ts src/lib/status-card-state.test.ts`
— passes (26/26).
- Recorded pre-PR QA: compile-pipeline e2e PASS; full lifecycle and cost
QA PASS; security re-review PASS after write-back hardening; UX
approved.
- `pnpm exec vitest run packages/db/src/status-card-migrations.test.ts`
— passes; reapplies migrations `0185`–`0189` against an already-migrated
embedded Postgres database.
- `pnpm --filter /db check:migrations` — passes migration numbering and
safety checks.
- `pnpm --filter /db typecheck` — passes.
- Merged current `origin/master` on July 24, 2026 with no conflicts;
migrations `0185`–`0189` remain unclaimed on master.
## Risks
- The feature introduces five database migrations and a new background
update path; all new DDL is repeat-safe after partial application,
migration numbering/safety checks pass, and update execution is
company-scoped and change-gated.
- Natural-language compilation can produce invalid or overly broad
queries; compile provenance, query validation, debug visibility, and
version history make failures inspectable and recoverable.
- Reactive or interval refresh could increase spend; active hours, max
refresh frequency, daily token caps, per-update cost records, and
budget-paused states bound and expose that risk.
- The branch name contains an internal execution identifier because it
is a fixed handoff branch; it was intentionally not renamed or rebased
per the release handoff instructions.
- Overall rollout risk is limited because the route, navigation,
services, and UI are disabled by default behind `enableStatusCards`.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex using GPT-5.5 with reasoning, repository tool use, shell
execution, GitHub CLI, and test/build execution. The runtime did not
expose a context-window size.
## 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; the fixed execution-workspace
identifier is documented as an authorized handoff exception
- [x] I have run tests locally and they pass, with the one cleanup
timeout passing on focused rerun
- [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
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The issue-detail UI shows recovery cards and blocked/parked notices
when a task loses its next step — a run finished with no disposition, a
task is stranded, work is blocked behind other tasks, or an assigned
item sits in the backlog
> - That copy was written in the scheduler's internal vocabulary —
"Corrective wake queued", "Graph Liveness", "lost a live action path",
"the responsible" — which describes Paperclip's internals rather than
the user's situation
> - Users seeing these cards report having no idea what the card means
or what they are supposed to do
> - This pull request rewrites the user-facing copy in plain language
and adds explicit calls to action that match the options in the card's
Resolve menu
> - The benefit is that a non-expert operator can read a recovery or
blocked notice and immediately understand what happened and which action
to take next
## Linked Issues or Issue Description
No public GitHub issue exists for this; describing the problem here
(bug-report format):
- **What happened:** Recovery action cards and blocked notices render
internal jargon, e.g. the headline "Paperclip detected this task lost a
live action path. A recovery owner needs to act.", the chip "Corrective
wake queued", the kind label "Graph Liveness", and phrases like
"Comments still wake the responsible". Status values also appear as raw
code literals (`in_progress`, `todo`).
- **Expected behavior:** These notices should tell a normal user, in
plain language, what happened and what to do next (retry the task, mark
it done, send it for review, or record a blocker).
- **Impact:** Operators stall on tasks that only need a simple
disposition because the UI doesn't tell them that's what is being asked.
Related prior work: #9417 (merged) made the reopen-suppressed blocked
message explicit; this PR extends the same plain-language treatment to
the rest of the recovery and blocked-notice copy.
## What Changed
- Recovery card headlines for `missing_disposition`,
`stranded_assigned_issue`, and `issue_graph_liveness` now say what
Paperclip found and name the concrete next steps ("try the task again,
mark it done, or send it for review") matching the card's Resolve menu.
- The `issue_graph_liveness` kind label "Graph Liveness" is now "Task
Needs Next Step", and the "Wake" metadata row is now "Follow-up".
- Wake-policy chips describe actual behavior: "An agent will be asked to
choose the next step" (was "Corrective wake queued"), "Board will
decide", "Manual follow-up needed", "Repair needed before retry", "Check
scheduled".
- Blocked/waiting/parked notices say "the assignee" instead of "the
responsible" / "responsible agent", and "notify" instead of "wake".
- The still-needs-a-next-step notice drops raw `in_progress` code
literals and keeps a plain-language option list (mark done or cancelled,
send for review, record what is blocking it, delegate follow-up).
- Parked-backlog notice renders "To do / In progress" as plain labels
instead of code literals.
- Component tests updated to pin the new copy and the successful-run
example options.
## Verification
- `cd ui && npx vitest run
src/components/IssueRecoveryActionCard.test.tsx
src/components/IssueBlockedNotice.test.tsx
src/components/IssueAssignedBacklogNotice.test.tsx
src/components/IssueChatThread.test.tsx` — 4 files, 126 tests, all
passing.
- Copy-only review: the diff touches display strings, one label map
entry, and test assertions; no control flow, props, or identifiers
change.
## Risks
- Low risk — user-facing strings and test updates only. No behavior,
API, or schema changes. The only functional surface is that anything
keying off the displayed text (e.g. screenshots, external docs) will
show the new wording.
## Model Used
- Claude (Anthropic) — Claude Fable 5, model ID `claude-fable-5`,
extended thinking enabled, running in Claude Code with agentic tool use.
## 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 (no
docs reference this copy)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The experimental settings page renders one interactive toggle per
feature from the settings API
> - On managed instances some values are enforced by the hosting control
plane, and the API now reports those keys as managed
> - Rendering enforced values as live toggles misleads users: the click
appears to work, and the value silently snaps back
> - This pull request renders managed keys as locked toggles with a
"Managed by Paperclip Cloud" badge and guards the handlers so no PATCH
can be emitted
> - The benefit is UI honesty on managed instances, with self-hosted
responses rendering exactly as before
## Linked Issues or Issue Description
Builds on #10058 — renders the per-key `managedKeys` metadata #10058
adds to settings responses (typing shared from #10058 at rebase).
No public issue exists; `feature_request` template fields:
- **Problem or motivation:** on managed instances users see fully
interactive toggles for settings the control plane enforces; changes
appear to apply and never do, with no explanation.
- **Proposed solution:** disabled toggle + badge + guarded handler
driven by the settings response's managed-key metadata; the ~17 uniform
setting cards are extracted into one shared component with copy,
aria-labels, and patch payloads preserved verbatim.
- **Alternatives considered:** hiding managed settings entirely (users
lose sight of the effective value and why it is fixed); tooltip-only
hints on still-active toggles (doomed PATCHes are still emitted and
stripped server-side).
- **Roadmap alignment:** supports the in-progress "Cloud deployments"
milestone in `ROADMAP.md`.
## What Changed
- When the settings API reports a feature key as managed (`managedKeys`
from the managed-config overlay), the experimental settings page renders
that toggle disabled with a badge and a guarded handler, so a click can
never emit a PATCH. Previously, managed-instance users saw fully
interactive toggles they could never actually change. Self-hosted
responses (no `managedKeys`) render exactly as before.
- The ~17 copy-pasted uniform setting cards are extracted into one
`ExperimentalToggleCard` component with titles, descriptions, footnotes,
aria-labels, and patch payloads preserved verbatim; the two bespoke
cards get inline managed handling (the managed auto-recovery toggle also
cannot open its preview dialog).
- `ui/src/api/instanceSettings.ts` response typing now uses the shared
`InstanceExperimentalSettingsWithManaged` / `ManagedSettingMetadata`
types from #10058; `ui/src/pages/InstanceExperimentalSettings.tsx`
locked rendering + card extraction; tests.
## Verification
- 24 page tests (20 existing unmodified + 4 new: locked badge with no
PATCH while unmanaged keys stay editable; managed auto-recovery opens no
dialog; an open recovery preview closes with no PATCH when a refresh
marks auto-recovery managed; self-hosted unaffected): `pnpm --filter
@paperclipai/ui exec vitest run
src/pages/InstanceExperimentalSettings.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck` clean
## Risks
- Low risk. UI-only change; no server or API behavior changes.
Self-hosted responses carry no `managedKeys`, so the page renders
exactly as before there. The card extraction preserves copy,
aria-labels, and patch payloads verbatim, covered by the 20 pre-existing
page tests passing unmodified.
## Model Used
Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use;
independently peer-reviewed by a second AI agent before push
## 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
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators run those agents against paid model providers, so the web
UI has a Costs surface that reports spend and quota utilisation
> - Those figures are drawn as horizontal bars by the shared `QuotaBar`
component (`ui/src/components/QuotaBar.tsx`), consumed by
`BillerSpendCard` and `ProviderQuotaCard`
> - `QuotaBar` renders its fill as a plain `<div>` whose CSS width is
the only encoding of the percentage — no `role`, no value attributes, no
accessible name
> - A screen reader therefore announces nothing at all for these bars,
so the spend and quota numbers they convey are unavailable to
assistive-technology users (WCAG 2.1 SC 4.1.2, Name/Role/Value)
> - The same gap is being closed for the other bars in this area by
#1805 (BudgetPolicyCard) and #1869 (ProviderQuotaCard's inline bars);
`QuotaBar` is the remaining shared component with no ARIA semantics
> - This pull request adds the standard ARIA progressbar attributes to
`QuotaBar`'s fill element, reusing the `label` prop the component
already takes
> - The benefit is that every progress bar on the Costs screen exposes
its name and current value to assistive technology, with no visual or
behavioural change for sighted users
## Linked Issues or Issue Description
No existing GitHub issue — the problem is described in-PR below,
following [`bug_report.yml`](.github/ISSUE_TEMPLATE/bug_report.yml).
Related PRs from the same accessibility sweep (each covers a *different*
component, so these are companions rather than duplicates — all three
are currently open):
- Refs #1805 — ARIA attributes for the BudgetPolicyCard progress bar
- Refs #1869 — ARIA attributes for the ProviderQuotaCard inline bars
**What happened?**
On the Costs screen, the spend/quota bars rendered by `QuotaBar` (via
`BillerSpendCard` and `ProviderQuotaCard`) are non-semantic `<div>`
elements. Screen readers skip them entirely: no role, no value, no label
is announced, so the percentage information is available only visually.
**Expected behavior**
Each bar should be exposed as a progress bar with an accessible name and
its current value — e.g. announced as "Weekly spend: 45%, progress bar".
**Steps to reproduce**
1. Run the app and open the Costs page.
2. Expand any provider or biller card so a quota/spend bar is visible.
3. Navigate to the bar with a screen reader (VoiceOver, NVDA, or Chrome
DevTools → Accessibility pane).
4. Observe that the fill element has no role, no value, and no
accessible name.
**Paperclip version or commit**
Reproduces on `master`; `ui/src/components/QuotaBar.tsx` has carried no
ARIA attributes since the component was introduced.
**Deployment mode**
Not deployment-specific — the missing markup is in the shipped
component. Verified in local dev (`pnpm dev`).
**Agent adapter(s) involved**
Not adapter-specific (core UI).
## What Changed
- `ui/src/components/QuotaBar.tsx`: added `role="progressbar"` to the
fill `<div>`.
- Added `aria-valuenow={Math.round(clampedPct)}` with
`aria-valuemin={0}` / `aria-valuemax={100}`, using the already-clamped
percentage so the reported value can never fall outside 0–100.
- Added an `aria-label` of the form `<label>: <pct>%`, reusing the
existing `label` prop for the accessible name.
- No changes to props, styling, layout, or rendering logic: 1 file, 5
added lines, 0 deleted.
## Verification
- Manual: open Costs → expand a provider/biller card, inspect the bar in
Chrome DevTools → Accessibility pane. The fill node now reports role
`progressbar`, value `45`, min `0`, max `100`, and name "Weekly spend:
45%".
- Manual: with VoiceOver/NVDA, the bar announces "Weekly spend: 45%,
progress bar" instead of being skipped.
- Visual regression check: the bar is unchanged for sighted users — only
ARIA attributes were added, no class or style changes.
- CI (lint, typecheck, build, tests) is green on this branch.
- No unit test is added: the change is a set of static ARIA attributes
on one element, and `QuotaBar` currently has no test file. Happy to add
one if maintainers would like coverage here.
## Risks
Low risk. Presentation-only accessibility metadata on a single element;
no props, state, or styling change, and no other component is touched.
The one debatable point is that the percentage appears in both
`aria-label` and `aria-valuenow`, so some screen readers may announce it
twice; both forms are valid, and the label is kept because it carries
the bar's name alongside the value. Happy to drop the percentage from
the label if reviewers prefer the terser announcement.
## Model Used
<!-- @bluzername: please replace this line with the provider + exact
model ID (and context window / reasoning mode if relevant), or "None —
human-authored". Required by CONTRIBUTING.md. -->
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [ ] 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
- [ ] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable
- [ ] I have updated relevant documentation to reflect my changes (no
docs cover this component's markup)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - People increasingly drive Paperclip from a phone, so the UI ships a
mobile layout alongside the desktop one
> - `PageTabBar` is the shared component behind the tab strip on nearly
every detail page — AgentDetail, ProjectDetail, RoutineDetail,
IssueDetail, Inbox, Costs
> - On desktop it renders a Radix `TabsList`, whose `TabsTrigger`s carry
their own accessible names; on mobile it swaps to a native `<select>`
> - That `<select>` had no accessible name at all, so screen readers
announced it only as "popup button" — a user could not tell what the
control switches between
> - Because the component is shared, the gap reproduced on every mobile
page that uses tabs rather than on one screen
> - This pull request adds `aria-label="Page section"` to the mobile
`<select>`
> - The benefit is that mobile screen-reader users get the same
orientation desktop users already get from the tab triggers, from a
one-line change with no visual or behavioral impact
## Linked Issues or Issue Description
No existing public issue covers this, so the problem is described in-PR
following `.github/ISSUE_TEMPLATE/bug_report.yml`:
- **What happened** — On a mobile viewport, the `PageTabBar` `<select>`
had no `aria-label`, no `<label>` association, and no visible text of
its own. VoiceOver/TalkBack announce it as an unlabeled "popup button".
- **Expected behavior** — The control announces what it switches
between, matching the accessible naming the desktop `TabsTrigger`s
already provide.
- **Steps to reproduce** — 1. Open any detail page with tabs (agent,
project, routine, issue). 2. Narrow the viewport to mobile width so the
tab strip collapses to a `<select>`. 3. Focus the `<select>` with a
screen reader. 4. Observe that no purpose is announced.
- **Version / commit** — head `ef92d1c`, branch
`fix/page-tab-bar-mobile-a11y`.
- **Deployment mode** — Any. The change is UI-only and client-side.
**Related prior PR:** #1532 (closed unmerged on 2026-03-23) made this
same one-line change to `ui/src/components/PageTabBar.tsx` as part of a
~100-file batch. This PR is the focused standalone version of that fix.
## What Changed
- Added `aria-label="Page section"` to the mobile `<select>` in
`ui/src/components/PageTabBar.tsx`. One line added; no other files
touched.
## Verification
- **Automated:** `pnpm -C ui test` and the repo CI gates (lint,
typecheck, build) — CI is currently green on `ef92d1c`.
- **Manual:** Open any tabbed detail page, narrow the viewport until the
tab strip becomes a `<select>`, and focus it with VoiceOver (macOS/iOS)
or TalkBack (Android). It now announces "Page section, popup button"
instead of an unlabeled "popup button".
- **Inspector check:** In devtools, the `<select>` node's computed
accessible name is "Page section" (previously empty).
## Risks
Low risk. `aria-label` on a `<select>` is a presentation-free attribute:
it changes nothing about layout, styling, DOM structure, event handling,
or the desktop code path, which is untouched. No migration, no API
change, no new dependency. The only debatable point is wording — "Page
section" is a generic name shared by every call-site (see the note
below).
## Model Used
Not specified by the original author, and not recoverable from the
commit metadata (no `Co-Authored-By` or model trailer on `ef92d1c`).
@bluzername — please replace this line with the provider, model
ID/version, and any relevant capability details, or "None —
human-authored".
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [ ] I have specified the model used (with version and capability
details) — see above; needs the author
- [ ] 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
(`fix/page-tab-bar-mobile-a11y`) and contains no internal Paperclip
ticket id or instance-derived details
- [ ] I have run tests locally and they pass
- [ ] I have added or updated tests where applicable — no test added;
the change is a static attribute with no branching behavior
- [ ] I have updated relevant documentation to reflect my changes — not
applicable
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
currently 4/5, see below
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---
### Maintainer note on the open Greptile comment
This description was restructured to the repository PR template by a
maintainer; the code and the author's intent are unchanged. Unchecked
boxes above are ones only @bluzername can attest to.
Greptile's one remaining comment asks for a `selectAriaLabel` prop so
call-sites could override the label. We think the hardcoded label is
correct here and match existing practice: shared components whose
meaning is fixed own their label internally (`ThemeToggle.tsx`), while
components whose label depends on the data they render take it as a prop
(`CopyText.tsx`'s `ariaLabel`). This `<select>` always means "which page
section", at every call-site, so a prop no caller would set would be
unused API surface.
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies.
> - Agents already receive selected company secrets through `env.*`
bindings at run launch, but environment injection is ambient,
long-lived, and not suitable for every secret consumer.
> - The existing binding and secret-access-event models already provide
company-scoped authorization and per-resolution audit seams.
> - Agents need an explicit way to discover only the secrets granted to
them and fetch a value on demand without exposing the wider company
catalog.
> - That capability must remain run-bound, preserve low-trust token
carve-outs, and make every value read visible in both security and
operator audit trails.
> - This pull request adds an `access.*` delivery namespace, two
run-bound agent routes, dual audit logging, documentation, and an
operator grants editor.
> - The benefit is least-privilege, revocable, auditable secret access
while preserving existing env injection behavior.
## Linked Issues or Issue Description
No pre-existing public issue. Related work:
- Refs #9797 — existing in-sheet agent access UI that this PR extends to
distinguish env and API delivery.
- Refs #9918 — complementary searchable-agent picker improvement for the
same secrets sheet.
- Refs #9530 — related company-wide metadata catalog proposal; this PR
intentionally exposes only the authenticated run's granted aliases and
values.
**Problem / motivation:** Agents can currently consume secrets only
through process environment injection. This keeps values resident for
the run, does not support on-demand consumers, and cannot provide a
discrete operator-visible activity event for each agent-initiated read.
**Proposed solution:** Treat `company_secret_bindings` as the source of
truth for agent secret grants. Keep `env.KEY` as env delivery and add
`access.ALIAS` for API-only delivery; an env binding also implies read
access because the value is already present in the agent process. Add
run-bound list/fetch endpoints that derive scope from the authenticated
heartbeat run and never accept caller-selected overlays.
**Alternatives considered:** A company-wide agent-readable catalog was
rejected for this value path because it increases reconnaissance and
does not prove a per-secret grant. Reusing the ephemeral
environment-probe resolver was rejected because it lacks binding
enforcement. Approval-gated reads and user-scoped secrets remain
deferred beyond v1.
**Roadmap alignment:** This extends the completed **Secrets Manager with
per-agent access** roadmap capability from launch-time env injection to
explicit run-bound API delivery without duplicating a separate planned
initiative.
## What Changed
- Added `access.*` agent binding validation and a dedicated run-bound
resolver that combines `secrets:read` authorization with binding-context
enforcement.
- Added `GET /api/agents/me/secrets` for minimal granted metadata and
`POST /api/agents/me/secrets/:key/value` for on-demand value fetches
with `Cache-Control: no-store`.
- Preserved the existing denials for low-trust review agents,
task-bridge credentials, and skill-test tokens; standard long-lived
agent API keys cannot call the run-bound routes.
- Added dual audit behavior: value attempts write `secret_access_events`
and `activity_log` (`secret.value.read`), while metadata listing writes
the lighter `secret.access.listed` activity event.
- Kept env compatibility: `env.*` remains injected at launch and also
implies API read for the same bound agent; `access.*` never becomes an
environment variable.
- Added the agent-settings **Secret access** editor plus
delivery-mode/alias surfacing on the Secrets page, with focused UI tests
and tokenized layout styles.
- Updated OpenAPI, shared types, agent-facing skill documentation, and
API reference documentation.
### UI Screenshots
P3 produced and reviewed three screenshots using mock data; images are
intentionally not committed to the repository:
- `secret-access-editor.png` — agent settings grant editor.
- `secret-access-light.png` — Secrets-page delivery surfacing in light
mode.
- `secret-access-dark.png` — Secrets-page delivery surfacing in dark
mode.
The source attachments are retained with the implementation task and
linked in the internal handoff; the public page publisher was
unavailable in the PR-prep runtime.
## Verification
- `pnpm exec vitest run
server/src/__tests__/agent-secrets-routes.test.ts
server/src/__tests__/secrets-service.test.ts
server/src/__tests__/secrets-routes.test.ts
ui/src/lib/secret-delivery.test.ts
ui/src/components/AgentSecretAccessEditor.test.tsx` — 5 files, 122 tests
passed.
- Security follow-up: `pnpm exec vitest run
server/src/__tests__/agent-secrets-routes.test.ts
server/src/__tests__/secrets-service.test.ts` — 2 files, 73 tests passed
after active-run and version-consistency fixes.
- Final-head CI: all feature, typecheck, build, e2e, security, and
review gates pass; `General tests (server (1/3))` remains red after one
rerun because unrelated `heartbeat-retry-scheduling.test.ts` cleanup
deletes `heartbeat_runs` before referenced `activity_log` rows.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — feature-local arbitrary-value violations
fixed; command still reports five unchanged `#9627` literals outside
this PR.
- End-to-end QA passed all eight acceptance criteria: grant/list, fetch,
dual audit, env-implies-read, denial matrix, revocation, UI rendering,
and env-injection regression. Evidence:
https://github.com/paperclipai/paperclip/pull/9921#issuecomment-5027455492
- Security review returned PASS-with-required-changes; the
implementation uses the required dedicated binding-enforcing resolver,
run-bound JWT restriction, run-derived overlays, minimal metadata, and a
resolver redaction-registration hook. Evidence:
https://github.com/paperclipai/paperclip/pull/9921#issuecomment-5027455382
## Risks
- A compromised agent can exfiltrate any secret explicitly granted to
it; explicit company-scoped/run-scoped grants, revocation, and audit
reduce but cannot remove that inherent capability risk.
- The resolver invokes a redaction-registration hook before returning
values, but the current route has no persistent cross-request per-run
redaction registry. Paperclip-owned later comments/events therefore
cannot yet guarantee automatic scrubbing of a deliberately copied
fetched value; QA classified this as non-blocking residual hardening.
- Audit-event insertion currently fails open if the security-event
insert itself fails; the operator activity event provides partial
redundancy, but a future hardening change should define fail-closed
behavior for value delivery.
- This PR overlaps `ui/src/pages/Secrets.tsx` with #9918 and may require
a straightforward rebase after that PR moves.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, `gpt-5.3-codex`, with reasoning, repository tool use,
terminal execution, Paperclip API access, and GitHub CLI capabilities.
Context-window size is not exposed by the runtime.
- Anthropic Claude Opus 4.8 with 1M context and tool use assisted with
the UI implementation commit.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Connections is the subsystem that defines which external apps and
MCP-style integrations operators can browse, configure, and run
> - The v3 schema core in #9958 added stable connection identities, auth
metadata, and grant-aware contracts, but the app catalog still used the
older gallery shape
> - The product needs a richer, typed AppDefinition catalog so browsing
and setup can render provider-specific auth and configuration
requirements consistently
> - This pull request moves the Wave 1 app catalog onto generated
AppDefinition data and carries that shape through shared types, server
lookup paths, and app connection UI
> - The benefit is that follow-up runtime and wizard work can build
against one catalog contract instead of local-only mock/gallery data
## Linked Issues or Issue Description
Refs #9958.
No public GitHub issue exists for this branch. This is the catalog layer
for the Connections v3 stack after the schema-core foundation in #9958.
## What Changed
- Adds generated AppDefinition data for the Wave 1 catalog and ingestion
reporting.
- Replaces the legacy tool app gallery exports with
AppDefinition-centered shared contracts, validators, and tests.
- Updates server tool-access lookup behavior to use the AppDefinition
catalog.
- Updates app connection UI surfaces and tests to consume
AppDefinition-backed catalog data.
- Documents the catalog ingestion workflow in the connector playbook.
## Verification
- `pnpm run preflight:workspace-links`
- `pnpm exec vitest run packages/shared/src/app-definitions.test.ts
packages/shared/src/app-definitions-url.test.ts
ui/src/pages/apps/AppsConnect.test.tsx
server/src/__tests__/tool-access-service.test.ts`
## Risks
- Medium: this changes the catalog contract used by shared, server, and
UI app connection surfaces.
- Catalog data quality matters because generated definitions now drive
browse/setup display.
- Follow-up runtime and wizard PRs must rebase on this branch or on
master after this lands.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex coding agent with repository tool use.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies and their governed access to external systems.
> - Connected Apps build on the existing Apps and MCP gateway substrate
so companies can configure reusable, auditable integrations.
> - The current connection record does not yet have a stable public
address, explicit ownership/auth method fields, or subject-specific
credential grants.
> - Without that schema core, later OAuth, per-user authorization, token
brokering, triggers, and connector-service phases cannot enforce tenant
and subject boundaries consistently.
> - This pull request adds the forward-compatible Connections v3 schema
core while preserving the existing connection lifecycle and directly
migrating the remote MCP transport name.
> - The benefit is a company-scoped, least-privilege foundation for
one-click integrations without bypassing Paperclip secrets, profiles,
rules, or audit controls.
## Linked Issues or Issue Description
No matching public issue was found.
**Problem**
Paperclip's current app connections need a durable identity and
authorization substrate before Connected Apps can safely support
multiple setup methods, per-user credentials, provider tenants, and
managed connector services. The existing schema only models a single
connection-level credential set and uses legacy transport terminology.
**Proposed solution**
Add a stable company-scoped connection UID, explicit
ownership/auth/transport fields, a subject-aware `connection_grants`
table, and multi-key credential annotations. Backfill existing
connections and workspace grants in a reversible migration, then update
shared/server/UI contracts to the new `mcp_remote` transport name.
**Related work**
- Related foundation: #9534
- Roadmap: Connected Apps (one-click integrations)
## What Changed
- Added company-scoped connection `uid`, `ownership`, `authKind`, and
canonical transport fields across database, shared contracts,
validators, services, and UI fixtures.
- Added `connection_grants` with workspace/user subject rules, provider
tenant metadata, credential secret refs, revocation state, company
scoping, and uniqueness constraints.
- Added migration `0182_connections_v3_schema_core` to backfill stable
UIDs, rename `remote_http` to `mcp_remote`, infer auth kinds, create
default workspace grants, and support rollback coverage.
- Added multi-key credential annotations and updated gateway/access
services without changing the existing lifecycle behavior.
- Updated the connection glossary, connector playbook, and security
threat model for the new identity, grant, and relay boundaries.
- Added explicit test UIDs to direct database fixtures so the new
non-null invariant is exercised across affected server suites.
## Verification
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm exec vitest run server/src/__tests__/tool-access-service.test.ts
server/src/__tests__/tool-gateway-service.test.ts
server/src/__tests__/tool-gateway.test.ts
server/src/__tests__/heartbeat-runtime-skills.test.ts
server/src/__tests__/tool-oauth-legacy-backfill.test.ts
server/src/__tests__/tool-access-policy-service.test.ts
server/src/__tests__/heartbeat-runtime-mcp-servers.test.ts
packages/db/src/connections-v3-schema-core-migration.test.ts
packages/shared/src/validators/tool-access.test.ts --config
vitest.config.ts` — 9 files, 218 tests passed.
- Latest-head GitHub Actions: build, typecheck, general/serialized
suites, backup/worktree restore coverage, both e2e shards, canary,
policy, and security scans pass.
- Greptile: 5/5 with zero unresolved threads.
- `pnpm check:token-gates` remains red only on five pre-existing `#9627`
color literals outside this change.
## Risks
- **Migration risk:** UID backfill and default-grant creation touch
every existing connection. The migration uses company-scoped uniqueness,
deterministic legacy UIDs with ID suffixes, and seeded up/rollback
coverage.
- **Authorization risk:** Grant rows carry credential references.
Constraints enforce workspace-vs-user subject shape, company/connection
lookup indexes, one default grant per connection, and one user grant per
connection/subject. Security review is requested specifically for this
design.
- **Compatibility risk:** `remote_http` is renamed directly to
`mcp_remote`; all repository call sites and fixtures are updated in the
same change.
- **Future-phase risk:** Subject-bound token issuance, triggers, and
connector-service relay verification remain fail-closed requirements
documented for later phases; this PR does not expose those capabilities.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex CLI coding agent. The runtime did not expose an exact
underlying model ID or context-window size; capabilities used include
repository inspection, code editing, shell execution, test execution,
Git/GitHub CLI operations, and structured reasoning.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Bumps
[radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui)
from 1.6.0 to 1.6.4.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md">radix-ui's
changelog</a>.</em></p>
<blockquote>
<h2>1.6.4</h2>
<ul>
<li>Fixed a regression where importing primitives from the root
<code>radix-ui</code> entry point erased every primitive's types to
<code>any</code>.</li>
</ul>
<h2>1.6.3</h2>
<h3>Dialog</h3>
<ul>
<li>Fixed broken ARIA references in Dialogs where title or description
elements are not rendered.</li>
</ul>
<h3>Slider</h3>
<ul>
<li>Fixed a bug where <code>onValueCommit</code> was not called when a
slider thumb was dragged across another thumb.</li>
</ul>
<h3>Toast</h3>
<ul>
<li>Fixed <code>Toast</code> removing non-focused toasts when pressing
<code>Escape</code>.</li>
</ul>
<h3>Tooltip</h3>
<ul>
<li>Fixed a bug where <code>Tooltip.Content</code> children were mounted
to the DOM twice.</li>
</ul>
<h3>Other updates</h3>
<ul>
<li>Fixed overriding inline animation style in
<code>Popper.Content</code>.</li>
<li>Improved tree-shaking so bundlers can drop unused components.
Component parts are now marked <code>/* @__PURE__ */</code> and use
named render functions instead of <code>Component.displayName =
...</code> assignments, which previously prevented dead-code elimination
with some bundlers.</li>
<li>Widened <code>virtualRef</code> prop type to allow
<code>RefObject<Measurable | null></code> in popover
components.</li>
<li>Fixed dev-only checks with conditional exports to drop dev-warnings
from production builds.</li>
<li>Added per-primitive subpath entry points so each primitive can be
imported directly, eg. <code>import { Accordion } from
'radix-ui/accordion'</code> or <code>import * as Accordion from
'radix-ui/accordion'</code>. This mirrors the namespaced exports
available from the root <code>radix-ui</code> entry point.</li>
<li>Fixed a bug where updating a <code>Checkbox</code>,
<code>Switch</code>, or <code>RadioGroup</code> value programmatically
(eg. a "select all" control) while inside a
<code><form></code> would dispatch a <code>click</code> event from
the hidden bubble input that propagated to ancestor <code>onClick</code>
handlers.</li>
</ul>
<h2>1.6.2</h2>
<h3>Other updates</h3>
<ul>
<li>Added CSS custom properties for Navigation Menu item indicators'
translate values.</li>
<li>Fixed a bug in Dismissable Layer causing background nested popovers
to close all layers on outside click</li>
<li>Fixed runtime errors for <code>Form.Message</code>,
<code>Form.Control</code>, <code>Form.Label</code> and
<code>Form.ValidityState</code> that are correctly rendered outside of
<code>Form.Field</code> components</li>
<li>Fixed a bug in form control components to ensure their values are
updated when their associated form's is reset. This affects
<code>RadioGroup</code>, <code>Slider</code>, <code>Select</code>, and
<code>Switch</code>.</li>
<li>Fixed menu items, tab triggers, toolbar links, and select items
intercepting <code>Space</code>/<code>Enter</code> keys that originate
from focusable descendants.</li>
<li>Fixed a bug where calling an event handler without an argument would
throw, preventing successive event handlers from being called. This
affected all components that accept event handlers with internal
implementations.</li>
<li>Fixed a bug in Context Menu to ensure that the menu properly
re-anchors to the latest pointer position when re-triggered in its open
state.</li>
<li>Fixed stale <code>onEscapeKeyDown</code>/<code>onDismiss</code>
handlers on React 19.2.</li>
<li>Fixed items in a Roving Focus Group not being auto-focused on mount
within a Focus Scope component.</li>
<li>Fixed a regression in Dismissable Layer originating from a <a
href="https://redirect.github.com/react/react/pull/34831">bug in React's
<code>useEffectEvent</code></a>.</li>
<li>Fixed <code>--radix-scroll-area-corner-width</code> and
<code>--radix-scroll-area-corner-height</code> not resetting to
<code>0</code> when a corner is removed. Previously these values would
stick around and leave a permanent gap on the remaining scrollbar.</li>
<li>Fixed a bug in Slider where stepping with the keyboard would skip a
valid value when the current value is off the step grid. Stepping now
snaps to the next step-aligned value in the direction of travel,
matching native <code><input type="range"></code>
behavior.</li>
</ul>
<h2>1.6.1</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radix-ui">compare
view</a></li>
</ul>
</details>
<details>
<summary>Attestation changes</summary>
<p>This version has no provenance attestation, while the previous
version (1.6.0) was attested. Review the <a
href="https://www.npmjs.com/package/radix-ui?activeTab=versions">package
versions</a> before updating.</p>
</details>
<br />
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open-source app people use to manage AI agents for
work
> - The `ui` package contains React components that drive the agent
scheduling and workspace configuration UX
> - Dependabot PR #9895 bumped `@radix-ui/react-*` from 1.6.0 → 1.6.4,
which changed the internal effect-scheduling order inside Dialog and
Select primitives
> - Two `workspaces-a` CI tests started failing:
`RoutineRunVariablesDialog` and `editable-sections / TriggersSection`
> - Root cause for both: radix 1.6.4's changed scheduling pushes a
cascaded state update one render-tick later, and each test asserted
before that tick landed
> - This PR fixes the two affected surfaces at the source (one
production fix, one test fix) so the radix bump can land cleanly
> - The benefit is unblocking PR #9895 without compromising test
fidelity or production correctness
## Linked Issues or Issue Description
Refs #9895 — this PR fixes the two `workspaces-a` test failures that
blocked the radix-ui 1.6.0 → 1.6.4 Dependabot bump.
**Root cause:** radix-ui 1.6.4 changed the internal effect-scheduling
order inside its Dialog and Select primitives, pushing certain cascaded
state updates one render-tick later than before. Two tests each asserted
against the intermediate state, before the deferred tick landed.
**Affected tests (both now pass at radix 1.6.0 AND 1.6.4):**
1. `editable-sections / TriggersSection` — `ScheduleEditor`
disabled-button assertion
2. `RoutineRunVariablesDialog` — workspace branch propagation assertion
Production behavior is unchanged and correct in both cases (verified via
tracing).
## What Changed
- **`ui/src/components/ScheduleEditor.tsx`** — `onValidityChange` is now
called synchronously inside the custom-cron `onChange` handler, not only
via the passive `useEffect` below. Previously there was a one-render
window where an invalid draft still read as valid to the parent (button
enabled); this closes that window. The effect call is preserved as a
safety net for other entry paths; only the `onChange` path is new.
- **`ui/src/components/RoutineRunVariablesDialog.test.tsx`** — the
post-mount settle loop now waits for the branch value to actually appear
in an `<input>` (`value === "pap-1634-routine-branch"`) rather than
exiting as soon as the workspace card mounts. The card reports its
branch name through an effect callback that triggers a follow-up render;
the old loop exited one tick too early. Iteration cap raised from 10 →
20 to give the extra tick room.
This PR intentionally does **not** bump radix-ui — that stays in #9895.
## Verification
```sh
# TypeScript — clean at radix 1.6.0 (master):
tsc -p ui/tsconfig.json
# Targeted vitest:
npx vitest run ui/src/components/RoutineRunVariablesDialog.test.tsx
npx vitest run ui/src/components/editable-sections
npx vitest run ui/src/components/ScheduleEditor
# Full ui suite — green with radix 1.6.4 installed locally (371 files / 3035 tests):
npx vitest run --project ui
# check-forbidden-tokens — clean
```
All of the above pass at **both** radix 1.6.0 (current master) and
1.6.4.
## Risks
Low risk. The production change (`ScheduleEditor.tsx`) adds a
synchronous call to an already-injected `onValidityChange` prop — same
value, earlier in the same event cycle. No new state, no new effects, no
API changes. The test change tightens an assertion (waits longer, checks
a more specific condition) rather than relaxing one.
## Model Used
Claude Sonnet (Anthropic) — Paperclip agent workflow; model family
claude-sonnet-4-x with tool use and extended reasoning enabled.
## 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
Co-authored-by: Harold Kim <harold@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies, and
humans onboard into companies via invite links.
> - Some invite types (`company_join` with
`requires_company_admin_approval`) require an admin to approve the join
request after the invitee submits it.
> - While the requester waits, the invitee sees the
`AwaitingJoinApprovalPanel` in `InviteLanding`, which describes where
the admin needs to go to approve the request.
> - That panel rendered the destination — "Company Settings → Access" —
as two clickable `<a href="/company/settings/access">` links, even
though the surrounding copy is plainly addressed to the admin ("Ask
**them** to visit ..."), not the requester.
> - First-time invitees naturally click the only underlined link on the
screen, are sent to `/company/settings/access`, hit the "No company
access" panel (they have no membership yet), and conclude the invite
flow is broken.
> - This PR removes the navigation by rendering both "Company Settings →
Access" references as plain styled text (`<p>` / `<span>`), so the
guidance stays visible but cannot be followed by the requester.
> - The benefit is that the post-submit invite experience matches the
copy's intent — guidance for the admin, not navigation for the
requester.
Fixes#6784.
## What Changed
- `ui/src/pages/InviteLanding.tsx` — In `AwaitingJoinApprovalPanel`,
replace the two `<a href={approvalUrl}>Company Settings → Access</a>`
elements with `<p>` and `<span>` containing the same text. Remove the
now-unused `approvalUrl` constant.
- `ui/src/pages/InviteLanding.test.tsx` — Update the existing "pending
approval page" test: it previously asserted two anchor tags pointing at
`/company/settings/access`; it now asserts **zero** anchors while the
text "Company Settings → Access" still appears twice (in the "Approval
page" box and inline in the "Ask them to visit ..." sentence). Renamed
the test description from "...linked access instructions" to
"...non-clickable access instructions" to reflect the contract.
## Verification
```
pnpm vitest run ui/src/pages/InviteLanding.test.tsx
```
Result: 8 / 8 pass, including the updated "shows the pending approval
page with the company icon and non-clickable access instructions" case.
Manual reproduction (master @ `242a2c2f`,
`deploymentMode=authenticated`, `bind=lan`, embedded Postgres):
1. As instance admin, generate a `company_join` invite that requires
admin approval.
2. In a fresh browser profile, open the invite link.
3. Fill **Create your account** and submit.
4. The "Request to join \<company\>" panel appears.
5. Hover the "Company Settings → Access" mentions — no underline, no
link cursor; clicking does nothing. The text is still readable and the
surrounding copy ("Ask them to visit ...") still conveys the instruction
to the requester.
Before / after screenshots: see issue #6784 — the "before" state lands
users on `/company/settings/access` which renders "No company access".
After this PR the guidance is informational only.
## Risks
Low. UI-only change confined to one function in `InviteLanding.tsx` plus
its matching test. No API contracts, routes, or data shapes are
modified. The removed `approvalUrl` constant was only referenced by the
two anchor elements.
## Model Used
- Anthropic Claude Opus 4.7 (`claude-opus-4-7`, 1M context, extended
thinking enabled).
- Tools: file editing, Bash, Playwright reproduction against a
self-hosted Paperclip instance running master @ `242a2c2f`, and the
Paperclip monorepo's own Vitest suite for verifying the test update.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots (will attach in PR thread)
- [x] I have updated relevant documentation to reflect my changes (no
docs files needed updating)
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Bumps [react-i18next](https://github.com/i18next/react-i18next) from
17.0.9 to 17.0.10.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md">react-i18next's
changelog</a>.</em></p>
<blockquote>
<h2>17.0.10</h2>
<ul>
<li>fix(warnings): the <code>useTranslation</code> and
<code>Trans</code> "You will need to pass in an i18next
instance" warnings now match the <code>useSSR</code> wording,
mentioning the props/context alternatives and the most common
unexplained cause at scale: duplicate react-i18next copies in monorepo
setups. The <code>Trans</code> variant also referenced the internal
<code>i18nextReactModule</code> name; it now points to the public
<code>initReactI18next</code> API.</li>
<li>feat(warnings): development-only warning
(<code>SUSPENDED_WHILE_LOADING</code>, logged once) right before
<code>useTranslation</code> suspends while translations are loading.
With the default <code>useSuspense: true</code> and no
<code><Suspense></code> boundary this previously surfaced as a
blank screen or a cryptic React error; the warning now names both fixes
(add a <code><Suspense></code> boundary or set
<code>react.useSuspense: false</code>). No-op in production builds; the
<code>process.env.NODE_ENV</code> check is wrapped so runtimes without a
<code>process</code> global (raw ESM in the browser, some edge runtimes)
stay silent instead of throwing.</li>
<li>ci: weekly workflow typechecking the test suite against
<code>@types/react@next</code> / <code>@types/react-dom@next</code>, so
the next React major's type changes (like the React 18
<code>TFunctionResult</code>/children wave) surface before user
reports.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3b71c2766c"><code>3b71c27</code></a>
17.0.10</li>
<li><a
href="57c3500e6a"><code>57c3500</code></a>
build</li>
<li><a
href="c62476f4d1"><code>c62476f</code></a>
chore: sync package-lock with i18next ^26.2.0 devDependency bump</li>
<li><a
href="0126bd1cad"><code>0126bd1</code></a>
improve instance warnings (monorepo hint) + dev-only suspense warning +
weekl...</li>
<li>See full diff in <a
href="https://github.com/i18next/react-i18next/compare/v17.0.9...v17.0.10">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents and their work
> - Operators rely on task detail properties and header actions to scan
linked resources and make quick decisions
> - External GitHub objects used a long pull-request label and repeated
mention counts that added visual noise without adding state
> - The task-detail star action also rendered as a labelled outline
button, unlike the compact star controls used elsewhere
> - These inconsistencies made dense task-detail surfaces slower to scan
and broke Paperclip's content-first visual language
> - This pull request shortens the GitHub pull-request label, removes
duplicate mention-count decoration, and aligns the detail star action
with the icon-only control pattern
> - The benefit is a calmer, more consistent task-detail experience with
accessible labels preserved for assistive technology
## Linked Issues or Issue Description
No matching public GitHub issue or open pull request was found.
**What happened?**
On the task detail surface, linked GitHub pull requests were labelled
`Github Pull Request` and could show a repeated `×N` mention count. The
detail-header star control used a labelled outline button rather than
the compact icon-only star pattern.
**Expected behavior**
Linked pull requests should use the concise `Github PR` label without
duplicate mention-count decoration, and the detail star action should
render as an accessible icon-only ghost button consistent with
neighboring controls.
**Steps to reproduce**
1. Open a task with a linked GitHub pull request mentioned more than
once.
2. Inspect the external-object property label and value row.
3. Inspect the star action in the task detail header.
**Paperclip version or commit:** `230126d80b` (`master` at preparation
time)
**Deployment / installation:** Local development, built from source.
**Scope:** Core UI; not adapter-specific, database-related, or
configuration-related.
## What Changed
- Render GitHub pull-request property labels as `Github PR`.
- Remove repeated external-object mention-count decoration from property
values.
- Render detail-header star controls as icon-only ghost buttons while
preserving `aria-label`, pressed, busy, error, and tooltip states.
- Expand component coverage for concise labels, duplicate-count
suppression, visual variants, and accessible star actions.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/IssueProperties.test.tsx
src/components/StarToggle.test.tsx` — 54 tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- Visual review (before/after plus normal, starred, pending, and error
states):
45cb492185/star-toggle-review.html
- `pnpm check:token-gates` — reports five existing `#9627` violations in
files unchanged by this PR; the same values are present on `master`.
## Risks
- Low risk: changes are limited to task-detail presentation and tests.
- The star action remains fully accessible through its existing ARIA
label and tooltip, but it no longer displays visible text.
- External-object mention counts remain available in data; only the
redundant property-row decoration is removed.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.3 Codex, tool-enabled coding agent with repository
and shell access.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the control plane operators use to coordinate AI-agent
companies and review work needing attention.
> - The Inbox is the operator-facing surface that aggregates tasks
requiring attention across server state and shared client polling.
> - Archiving a task optimistically removed it, but ordinary background
activity and stale polling responses could make it reappear seconds
later.
> - The server therefore needs to distinguish genuine user-attention
events from routine agent/system activity.
> - The client also needs a bounded local archive guard across every
Inbox query path while the server mutation and in-flight polls settle.
> - This pull request fixes both resurrection paths and adds
race-focused regression coverage.
> - The benefit is stable archive behavior without hiding a genuine
archive failure after reconciliation or reload.
## Linked Issues or Issue Description
### Pre-submission checklist
- [x] Searched existing open and closed issues and pull requests; no
duplicate implementation was found.
- [x] Reproduced on `master` before this branch.
- [x] Confirmed this is a Paperclip core bug, not adapter or provider
behavior.
### What happened?
Archiving an Inbox task hid it optimistically, then background refresh
activity could insert it back into the list seconds later.
### Expected behavior
A successfully archived task remains hidden during normal polling. A
genuine failed archive may become visible again after reconciliation or
reload.
### Steps to reproduce
1. Open Inbox with a visible task.
2. Archive the task.
3. Wait for shared polling or routine agent activity to refresh task
data.
4. Observe the archived task reappear without a hard page refresh.
### Paperclip version or commit
`master` before this branch.
### Deployment mode
Built from source using the local development application.
### Installation method
Built from source (`pnpm`).
### Agent adapter(s) involved
Not adapter-specific; this is a core Inbox bug.
### Database mode
Not database-mode-specific.
### Access context
Board (human operator).
### Additional context
The failure had independent server and client causes: routine activity
could resurface archived rows server-side, while stale shared-poll
responses could bypass optimistic client removal.
## What Changed
- Restrict server-side Inbox resurfacing to explicit user-attention
events rather than any issue activity write.
- Add a bounded client-side archive guard with confirmation, failure
restoration, and cache reconciliation behavior.
- Apply the guard to Inbox rendering, badge counts, optimistic cache
updates, and shared-poll result application.
- Classify the generic compact Inbox query so stale shared-poll data
cannot bypass the guard.
- Add server visibility-matrix tests and UI race-condition regression
tests.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/hooks/useSharedPolling.test.ts src/lib/inboxArchiveCache.test.ts
src/pages/Inbox.test.tsx` — 25 passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/issues-service.test.ts` — 107 passed.
- Branch rebased cleanly onto current `origin/master` before push.
## Risks
- Low-to-moderate behavioral risk: resurfacing is intentionally
narrower, so the server tests cover human comments, mentions,
interactions, and status transitions that must still regain attention.
- The client guard is bounded and cleared on mutation failure, limiting
the risk of hiding a task whose archive did not persist.
- No schema, migration, public API, workflow, dependency-lock, or
visual-token changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Anthropic Claude via Claude Code (`claude_local`; prior
implementation/review run, exact underlying model ID and context window
were not retained in the handoff metadata), with repository tool use and
test execution.
- OpenAI `gpt-5.5` via Codex CLI for final review repair and PR
preparation, with reasoning, repository editing, GitHub tooling, and
code 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/described the result 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 and contains no internal
Paperclip task identifier
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation where needed; no
documentation change is required for this bug fix
- [x] I have considered and documented risks above
- [x] All Paperclip-authored commits include the required co-author
trailer
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Secrets UI lets operators navigate slash-delimited secret names
as folders
> - PR #9913 shipped create-in-folder behavior for company and per-user
secrets
> - The production behavior is already on master, but several
create-in-folder interaction paths lack retained regression coverage
there
> - Without that coverage, prefix composition, derived keys, prefix
removal, and staged empty folders could regress unnoticed
> - This pull request adds focused render tests without changing
production behavior
> - The benefit is safer maintenance of the folder-based secrets
workflow with a small, reviewable patch
## Linked Issues or Issue Description
- Refs #9913
## What Changed
- Added render coverage for creating a company secret from a folder and
deriving its key from the full slash-delimited name.
- Added coverage for per-user secret prefixes and exposing the full name
when the prefix chip is removed.
- Added coverage for inline folder-name validation and URL-backed
staging of an empty folder.
## Verification
- `env -u PAPERCLIP_IN_WORKTREE -u PAPERCLIP_WORKTREE_NAME -u
PAPERCLIP_CONFIG -u PAPERCLIP_HOME -u PAPERCLIP_INSTANCE_ID -u
PAPERCLIP_CONTEXT pnpm exec vitest run
ui/src/pages/Secrets.render.test.tsx` — 29 tests passed.
- `pnpm check:token-gates` — reports five existing `#9627` literals in
unrelated files; this PR changes no token-gated component code and
introduces no new violation.
## Risks
- Low risk: test-only change with no production, API, schema, migration,
dependency, or runtime behavior changes.
- The tests exercise existing DOM interactions and may need updates if
the Secrets creation UI copy or controls intentionally change.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex CLI managed coding agent with repository inspection, code
execution, Git/GitHub, and Paperclip API tools. The managed harness does
not expose the exact underlying model ID or context-window size.
## 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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
— the assigned execution branch name is fixed by the task
- [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 — no
documentation change is needed for test-only coverage
- [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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the control plane people use to manage AI-agent
companies.
> - Operators need to grant secrets to specific agents safely and
efficiently.
> - The secrets access sheet previously used a native select while
similar agent-assignment surfaces use a searchable picker.
> - That inconsistency makes finding an agent slow and error-prone as
companies grow.
> - This pull request reuses the shared agent picker behavior for
single-agent secret access grants.
> - The benefit is a consistent, searchable selection experience without
changing secret-access semantics.
## Linked Issues or Issue Description
Refs #9797
**Problem / motivation:** The in-sheet agent access form introduced in
#9797 renders every grantable agent in a native select. In companies
with many agents, operators cannot filter by name or title and the
experience differs from other agent-selection surfaces.
**Proposed solution:** Add a single-select variant beside the existing
`AgentMultiSelect`, then use it in the secret access grant form.
**Alternatives considered:** Keeping a native select would preserve less
code but would not scale or align with existing searchable agent
selection.
**Roadmap alignment:** This is a focused usability fix for an existing
core control-plane surface and does not overlap an unstarted roadmap
initiative.
## What Changed
- Added `AgentSelect`, a searchable single-agent popover that filters by
agent name and title.
- Replaced the secret access form's native select with the shared
searchable picker.
- Added focused coverage for filtering, selecting, callback behavior,
and popover closure.
## Verification
- `pnpm exec vitest run ui/src/components/AgentMultiSelect.test.tsx` —
passes (3 tests).
- `pnpm --filter @paperclipai/ui typecheck` — passes.
- `pnpm check:token-gates` — branch adds no violations; the command
currently fails on five unchanged `#9627` literals already present on
`master`.
- Manual: open Secrets, choose a secret, add agent access, filter by
agent name or title, select the result, and grant access.
## Risks
- Low risk: the change is limited to agent selection UI and preserves
the existing grant request payload.
- The new picker depends on the existing popover/input primitives and
resets its filter when closed.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5-series coding agent (exact runtime model ID and
context-window size are not exposed), with reasoning, repository tool
use, terminal execution, and GitHub CLI capabilities.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators store company and user secrets under human-readable names
> - Slash-delimited names already express useful hierarchy, but the
Secrets page previously rendered them as one flat list
> - Large secret collections therefore became harder to scan, navigate,
and create within consistently named groups
> - A client-derived folder model preserves the existing server contract
while making those names navigable
> - This pull request adds folder browsing, URL-addressable paths,
global search, and create-in-folder behavior without adding folder
records
> - The benefit is a more scalable secrets workflow with no migration or
API compatibility risk
## Linked Issues or Issue Description
Slash-delimited secret names such as `dev/github/oauth/clientid`
currently appear as raw flat rows. This change treats name prefixes as
client-side navigation folders on the main Secrets tab while leaving
stored names, API contracts, validation, and the database unchanged.
- Wireframes and interaction specification:
https://pages.paperclip.ing/pap-14698-secrets-folders/
- Folder paths are derived only from secret names; there is no
server-side folder entity or data-model change.
## What Changed
- Added pure secret-path utilities for normalized segments, breadcrumbs,
nested folder listings, counts, and leaf/path rendering.
- Added a Folders/Flat view to the main Secrets tab with folder-first
sorting, breadcrumbs, empty-folder states, filters, and global search
results.
- Added URL navigation through `?path=<normalized/path>` so deep links,
reload, browser Back, and new-tab folder navigation work.
- Persisted the preferred view in `localStorage` under
`paperclip.secrets.viewMode`; an explicit `?path=` deep link takes
precedence for that visit.
- Added create-in-folder behavior with a removable prefix chip, staged
New folder paths, inline segment validation, and full-name key
derivation.
- Kept My secrets flat while rendering slash-delimited names with muted
paths and emphasized leaves.
- Added unit/render coverage for path helpers, folder navigation, and
create-in-folder behavior.
## Verification
- `pnpm exec vitest run ui/src/pages/secrets/secret-path.test.ts
ui/src/pages/Secrets.render.test.tsx` — 35 tests passed.
- `pnpm -r typecheck` — passed.
- `pnpm test:run` — server suite passed 2,707 tests and UI suite passed
3,022 tests; one unrelated CLI doctor test was environment-sensitive
because this agent inherited AWS variables.
- `env -u AWS_PROFILE -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY -u
AWS_SESSION_TOKEN -u AWS_REGION -u AWS_DEFAULT_REGION pnpm exec vitest
run cli/src/__tests__/secrets.test.ts` — 8 tests passed.
- `pnpm build` — passed.
- `pnpm check:token-gates` — feature files are clean; the command
reports five existing `#9627` color literals outside this diff.
## Risks
- Low data risk: folders are derived client-side from existing names,
with no schema, migration, API, or stored-name changes.
- URL behavior changes only the main Secrets tab and uses the additive
`?path=` contract.
- View preference is browser-local and scoped to the
`paperclip.secrets.viewMode` key.
- Renaming or deleting the last secret under an open prefix
intentionally leaves the user on an empty-folder state instead of
redirecting.
- “Move to folder…” bulk prefix rename remains deferred because it is a
multi-secret mutation with separate conflict and partial-failure
semantics.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI `gpt-5.5` via Codex CLI for PR preparation, verification,
GitHub/Paperclip tool use, and codebase analysis; the managed harness
does not expose the active context-window size.
- Anthropic Claude Opus 4.8 (1M context) and Claude Fable 5 assisted
earlier implementation/design commits, as recorded in their commit
trailers; both used repository and code-editing tools.
## 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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
— the assigned shared execution branch name is fixed for this work
- [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
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to supervise
AI-agent companies.
> - Operators need task execution state to be visible where they read,
reply to, and manage an issue.
> - Scheduled monitor state was easy to miss because it lived in a
description-area card and used inconsistent, mostly static time copy.
> - The task page, composer, retry card, and properties panel therefore
needed one shared monitor-state and countdown language.
> - This pull request adds shared live-ticking time utilities, moves
monitor status into a top-of-page banner and composer strip, and
redesigns the properties row with complete read-only details.
> - The benefit is that operators can immediately understand when an
agent resumes, why it is waiting, and how to act without hunting across
the page.
## Linked Issues or Issue Description
Issue monitors can schedule a future agent check, retry, or wake, but
the UI did not present that state consistently or prominently. The
existing description-area activity card competed with issue content, the
composer did not explain that replying wakes the agent early, compact
properties copy truncated important details, and relative times did not
share a live two-unit formatter.
This change makes scheduled monitor state visible and consistent across
the issue header, reply composer, properties panel, and scheduled-retry
card. Related prior server-side recovery visibility work: #9629
(distinct scope).
## What Changed
- Added shared two-unit monitor ETA/offset formatters and a live-ticking
countdown hook, including compact absolute-time rules for Today,
weekday, and cross-year dates.
- Replaced the description-area monitor activity card with a top-of-page
status banner and added an inline composer strip that explains replies
wake the agent before the scheduled check.
- Redesigned the properties Monitor row as readable two-line copy with
attempt state, due/overdue/cleared wording, click-to-edit behavior, and
a hover/tap details tooltip.
- Adopted the shared two-unit formatting in the scheduled-retry card and
added focused coverage for monitor formatting, state transitions,
visibility, and controls.
- Added the approved wireframe package and published reference:
https://pages.paperclip.ing/pap-14557-monitor-visibility/
## Verification
- `pnpm vitest run ui/src/lib/issue-monitor.test.tsx
ui/src/components/IssueMonitorBanner.test.tsx
ui/src/components/IssueProperties.test.tsx` — 3 files, 74 tests passed.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed with existing Vite optimization/chunk-size
warnings.
- UX review approved the rendered real components across desktop/mobile,
light/dark, and the scheduled/retrying/due/overdue/cleared/none state
matrix.
- QA passed 6/6 criteria in Chromium, including a live countdown
transition without refresh. Review evidence included the P2 banner
state-matrix screenshot and the P3 properties-row and details-tooltip
screenshots, plus a dark-mode capture.
- `pnpm check:token-gates` currently reports five pre-existing `#9627`
comment literals on `origin/master`; this branch introduces none of
those literals or any new token violation.
## Risks
- Low-to-moderate UI behavior risk: monitor copy and placement change
across several issue surfaces, but all derive from one shared state
builder and focused tests cover the state matrix.
- Countdown rendering wakes once per minute while a visible monitor is
scheduled; the hook is limited to active monitor surfaces and stops when
hidden.
- No database, API contract, migration, telemetry, or authorization
behavior changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, `gpt-5.6-sol`; context-window metadata was unavailable
in this runtime; reasoning, repository tool use, command execution, and
test execution enabled. Earlier implementation commits were assisted by
Claude Fable 5 and Claude Opus 4.8 (1M context), as credited in their
commit trailers.
## 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
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents and the credentials those agents need for work.
> - The secrets UI is responsible for making credential creation
understandable and for showing where credentials are used.
> - The create flow previously exposed an editable generated key too
early, used an unnatural field order, and rendered uneven provider tab
rows.
> - The detail sheet also lacked an in-context way to see and manage
which agents reference a selected secret.
> - This pull request makes the create dialog follow a predictable
name-to-value flow and adds agent access management directly to the
secret details sheet.
> - The benefit is a clearer secrets workflow with fewer accidental key
edits and less navigation when granting or revoking agent access.
## Linked Issues or Issue Description
**Subsystem affected:** `ui/` — React + Vite board UI
**Problem or motivation:** Creating a secret currently makes its
generated key look immediately editable, places fields in an awkward
keyboard order, and applies provider-tab overrides that produce uneven
rows. After creation, operators cannot inspect or change agent access
from the selected secret's detail sheet.
**Proposed solution:** Generate the key from the path-style name and
keep it read-only until an explicit Edit action; place Value directly
after Name; use the standard tab sizing; and add an Agent access section
that reads and updates `secret_ref` / `user_secret_ref` environment
bindings through agent adapter configuration.
**Alternatives considered:** Keeping access management only on agent
configuration screens was rejected because it hides a secret-centric
question—“which agents can use this?”—and requires repetitive
navigation. Keeping the key always editable was rejected because the
generated value should be the safe default.
**Roadmap alignment:** No matching item was found in `ROADMAP.md`; this
is a focused usability and access-management improvement to the existing
secrets surface.
**Additional context:** GitHub search found no duplicate PR for this
dialog and in-sheet access change. PR #9321 also mentions user-secret
resolution but addresses unrelated skills-route behavior.
## What Changed
- Auto-generate the create-secret key from Name, keep it read-only by
default, and expose an explicit Edit action.
- Use a path-style Name placeholder (`/dev/foo/bar`) and place Value
immediately after Name for natural keyboard navigation.
- Remove tab sizing/whitespace overrides that caused uneven provider-tab
row heights.
- Add an Agent access section to the Details tab that lists referencing
agents and grants or revokes `secret_ref` / `user_secret_ref`
environment bindings in place.
- Cover company secrets and each-user definitions with focused render
tests.
## Verification
- `pnpm exec vitest run ui/src/pages/Secrets.render.test.tsx` — 17/17
passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — changed files are clean; the
repository-wide command currently reports five pre-existing `#9627`
violations in unrelated files (`Sidebar.tsx`, `Inbox.tsx`,
`IssueDetail.tsx`, `Issues.tsx`, and `Routines.tsx`).
- Additional secrets verification completed during implementation: 35/35
adjacent secrets tests passed, and the flow was checked in a real
browser in light and dark themes.
## Risks
- Agent access mutations update adapter environment configuration, so
malformed legacy env entries could affect how a binding is displayed or
revoked.
- Each-user definitions use `user_secret_ref` rather than `secret_ref`;
focused tests cover selecting the correct binding type.
- No database or API contract changes are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Anthropic Claude Fable 5 assisted with the implementation using
repository-aware code editing and test execution.
- OpenAI GPT-5.5 via Codex CLI assisted with PR preparation, branch
hygiene, focused verification, GitHub operations, and review/check
loops.
## 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
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Its web UI is a long-lived single-page app; users leave tabs open
for hours/days
> - We chased multi-GB tab memory growth through several fixes (#9569,
#9624, #9627, #9701) that cut real churn — but the footprint kept
climbing
> - A heap-snapshot diff on a 12-hour tab finally showed the true cause:
of 13.2M heap nodes, **12.1M were `PerformanceMeasure` objects** — React
19.2 emits a `performance.measure()` per component render for its
DevTools "Performance Tracks" and never clears them
> - These are native objects, so `performance.memory` never reported
them (it read a flat ~74 MB while the real heap was ~308 MB), which is
why our earlier heap/DOM sampling looked stable while the footprint
ballooned
> - This pull request periodically clears the User Timing measure
buffer, since nothing in the app consumes it
> - The benefit is that long-lived tabs stop accumulating millions of
native `PerformanceMeasure` objects, eliminating the remaining unbounded
growth
## Linked Issues or Issue Description
No public GitHub issue exists; describing inline per CONTRIBUTING.md →
"Link Issues or Describe Them In-PR", following the bug report template.
This is the root-cause fix for the memory growth chased in #9569 / #9624
/ #9627 / #9701.
**What happened?**
Long-lived browser tabs grew to multiple GB of memory footprint over
hours/days. A heap snapshot of a 12h tab showed **13.2M nodes, of which
12.1M were `PerformanceMeasure`** (vs ~1.0M total nodes on a fresh tab).
Live capture showed **~340 `performance.measure()` calls/sec**, named
after React components with a `detail.devtools` payload — React 19.2's
"Performance Tracks". Nothing ever clears them, so they accumulate
without bound.
**Expected behavior**
A long-lived tab should not accumulate millions of `PerformanceMeasure`
entries. Idle/long-running tabs should hold a bounded footprint.
**Steps to reproduce**
Open the app (React 19.2 production build), leave a tab open with normal
activity, and run `performance.getEntriesByType('measure').length`
periodically — it climbs unbounded (12.3M after ~12h).
`performance.clearMeasures()` drops it to near-zero and frees the
memory.
**Paperclip version or commit**
Branch `fix/react-perf-measure-leak`, off `master` (after #9701).
**Deployment mode**
Local dev (`pnpm dev` build served by the dev instance), web UI. React
19.2.7. Not adapter-specific.
## What Changed
- **`ui/src/lib/perf-measure-reaper.ts` (new)** —
`startPerfMeasureReaper(intervalMs = 10_000)` clears
`performance.clearMeasures()` on a timer and returns a stop function. It
never calls `getEntriesByType('measure')` (which would materialize the
huge buffer). Honors a `window.__paperclipKeepPerfMeasures = true`
opt-out so a developer recording a React Performance Track in DevTools
can keep the entries.
- **`ui/src/main.tsx`** — start the reaper at app boot.
- Only *measures* are cleared — React's tracks pass explicit start/end
times and leave no marks, and the app doesn't use the User Timing API at
all (verified), so nothing else is affected.
## Verification
- **Root cause proven** by heap-snapshot diff: fresh tab ~1.0M nodes vs
12h tab 13.2M nodes, 12.1M of them `PerformanceMeasure`; live capture
showed ~340 measures/sec with `detail.devtools` and React component
names.
- **Fix proven live**: `performance.clearMeasures()` on the aged tab
dropped the buffer from **12,418,266 → 1,500** and reclaimed the memory
(heap `perf.memory` 308 → 269 MB, plus the ~12M native objects, which
are the bulk of the footprint).
- `vitest`: `perf-measure-reaper.test.ts` — interval clearing, `stop()`,
opt-out flag, and no-API no-op. All pass.
- `tsc -b` clean.
## Risks
Very low, client-only.
- Clearing the User Timing measure buffer only affects the DevTools
Performance panel's React track *history*; normal users never consume
it. Developers who want to record it can set
`window.__paperclipKeepPerfMeasures = true`.
- Only `performance.clearMeasures()` is called (not `clearMarks`), so
any mark-based timing elsewhere is untouched; a grep confirmed the app
makes no `performance.mark()/measure()` calls of its own.
- Adds exactly one 10s interval (negligible), and `clearMeasures()` does
not materialize the buffer.
Note: this is a React 19.2 upstream behavior (its performance tracks are
emitted in the production build and never cleared). If React later gates
or clears them, this reaper can be removed.
## Model Used
- **Provider:** Anthropic, via the Claude Code CLI.
- **Model:** Claude Opus 4.8 (`claude-opus-4-8`).
- **Reasoning mode:** Extended thinking enabled.
- **Capabilities used:** tool use (shell, file editing), the Chrome
DevTools MCP to reproduce and profile, and a streaming heap-snapshot
parser to identify the `PerformanceMeasure` accumulation.
## 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 (continues #9701; no duplicates)
- [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 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 considered and documented any risks above
- [ ] I have updated relevant documentation to reflect my changes (N/A —
no user-facing docs; rationale documented inline)
- [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)
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies and supervise governed work.
> - Decisions capture high-value operator judgment, and the
decision-training foundation merged in #9702 freezes that evidence for
later evaluation and learning.
> - Operators still need the UI from the closed stacked PR #9718 to
intentionally capture examples and inspect the resulting dataset.
> - GitHub automatically closed#9718 when its stacked base branch was
deleted after #9702 merged, leaving the server foundation on `master`
without the corresponding UI.
> - This pull request restores the final UI and its still-required
supporting API fields directly on current `master`, while excluding the
obsolete migration and duplicated server-foundation diffs.
> - The benefit is a reviewable replacement PR that preserves the
completed decision-training workflow without replaying stale stack
history.
## Linked Issues or Issue Description
- Refs #9718
- Refs #9702
## What Changed
- Restored the top-level `/training` library and record inspector with
search, filters, JSONL export, notes editing, and evidence tabs.
- Restored the Decisions-row training affordance and capture drawer,
including preview, provenance, deletion, cache refresh, and approval
consistency behavior.
- Restored the shared types and focused server support needed by the UI
without reintroducing decision-training migrations or the already-merged
server foundation.
- Restored focused UI and attention-service tests from the final #9718
state.
- Credit to the authors and reviewers of #9718; this recovery
transplants their final reviewed delta after the stacked base deletion.
## Verification
- `pnpm exec vitest run ui/src/pages/Training.test.tsx
ui/src/components/DecisionTrainingDrawer.test.tsx
ui/src/components/AttentionQueueRow.test.tsx
server/src/__tests__/attention-service.test.ts
server/src/__tests__/decision-training.test.ts` — 5 files, 48 tests
passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts` — 3
tests passed; confirms exact route/OpenAPI parity for the restored
preview endpoint.
- `pnpm check:token-gates` — the restored files are clean; the
repository-wide command currently reports five pre-existing false
positives where comments reference GitHub issue `#9627` as if it were a
color literal.
## Risks
- Low migration risk: this PR contains no database migrations and is
based directly on current `master`.
- The main behavioral risk is cache invalidation across Decisions and
Training views; focused tests cover capture, update, deletion, row
state, and approval refresh behavior.
- The token-gate baseline remains red on unrelated `#9627` comment
references; this PR does not modify those files.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.3-Codex, reasoning with repository/tool access and
code execution. Context window not exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies and their work
> - Human approvals, issue interactions, and execution decisions already
capture high-value decision moments
> - Those moments are currently transient and cannot be reused as stable
evaluation or training examples
> - Reusable examples need a server-owned, immutable snapshot so later
comments or runs cannot leak into the recorded state
> - Human notes need to remain editable and auditable without changing
the captured state
> - This pull request adds the database model, snapshot capture service,
API, export format, and attention-feed enrichment for decision training
> - The benefit is a durable, inspectable foundation for evaluating
whether agents can reproduce good human decisions from only the context
available at decision time
## Linked Issues or Issue Description
### Subsystem affected
Cross-cutting (`server/`, `packages/db`, and `packages/shared`).
### Problem or motivation
Paperclip has no durable dataset for converting human decisions into
evaluation-ready examples. Teams need to capture pending or resolved
decisions with the exact issue context, comments, runs, and repository
evidence available at a cutoff, while preventing future context from
leaking into the example.
### Proposed solution
Store immutable, schema-versioned snapshots anchored to durable
interaction, approval, or execution-decision records; keep notes
separately editable with history; expose human-only CRUD, list, and
JSONL export APIs.
### Alternatives considered
Client-generated snapshots were rejected because they duplicate cutoff
logic and cannot reliably enforce no-leakage boundaries. Automatic
outcome backfill was deferred so captured examples remain faithful to
what was known at capture time.
### Roadmap alignment
Supports the roadmap direction of turning completed work and decision
patterns into reusable organizational knowledge.
### Additional context
The implementation records explicit commit-resolution confidence
(`exact`, `nearest_run`, `workspace`, or `none`) so downstream
evaluation can distinguish evidence quality.
## What Changed
- Added the `decision_training_examples` schema and idempotent migration
with company, issue, and source/author indexes.
- Added shared types for decision-training records, notes history, and
versioned snapshots.
- Added a single server-side snapshot capture path with inclusive
comment cutoffs, pre-cutoff run capture, durable decision payloads, and
explicit commit-resolution confidence.
- Added create, list, detail, notes-only update, delete, and JSONL
export routes with human-only write authorization and activity logging
that skips no-op note submissions.
- Added per-user `trainingExampleId` enrichment to attention items.
- Added focused embedded-Postgres tests for cutoff boundaries,
post-cutoff leakage, immutable snapshots, human-only writes, duplicate
prevention, notes history, attention enrichment, and export shape.
- Updated UI test and Storybook attention-item factories for the new
required `trainingExampleId` contract.
## Verification
- `pnpm exec vitest run server/src/__tests__/decision-training.test.ts`
— 10 tests passed.
- `pnpm --filter @paperclipai/db typecheck` — passed, including
migration numbering and safety checks.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
## Risks
- The migration adds a new table and indexes only; it does not rewrite
existing rows or install resolve-time hooks.
- Snapshot JSON can grow with long comment threads and run histories; v1
intentionally favors complete, inspectable examples over aggressive
truncation.
- Commit SHA resolution is evidence-based and records `exact`,
`nearest_run`, or `none` so downstream consumers can account for
confidence.
- The API is additive, but future UI work must continue to treat the
snapshot as immutable and use notes-only updates.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex using `gpt-5.3-codex`, with repository tool use, terminal
execution, and code-editing capabilities; context-window size is not
exposed by the runtime.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to organize,
govern, and understand AI-agent work
> - Operators need concise, current status views across projects and
execution workspaces without manually reading every issue and run
> - Paperclip already has auditable issues, documents, built-in agents,
routines, and live run events, but no first-class summary-slot workflow
connecting those systems
> - A built-in Summarizer can generate status prose through ordinary
governed tasks while summary slots provide stable, revisioned
destinations for that output
> - The UI needs to show current summaries, generation progress,
failures, revisions, and streaming draft status in the places operators
already work
> - This pull request adds the end-to-end summary-slot data, API, agent,
orchestration, and UI surfaces behind an experimental setting
> - The benefit is decision-oriented status context that remains
company-scoped, auditable, retryable, and inexpensive by default
## Linked Issues or Issue Description
No public GitHub issue exists for this feature.
**Problem**
Operators currently have to reconstruct project and workspace status by
reading many issues, runs, and comments. This makes it hard to identify
decisions, review queues, recent work, and the next event worth
watching.
**Proposed capability**
Add an experimental summary system with revisioned summary slots for
projects and workspaces, a paused-by-default built-in Summarizer agent,
governed generation tasks, live draft status, and reusable UI cards.
**Expected behavior**
- Summary data remains company-scoped and revisions remain auditable.
- Generation runs through normal issue/agent orchestration and
deduplicates active requests.
- Only the linked built-in Summarizer generation task can author a slot
revision.
- Operators can generate, retry, inspect revisions, and follow draft
progress from project and workspace views.
- The feature remains opt-in and background generation remains paused by
default.
## What Changed
- Added summary-slot schema, idempotent migrations, shared contracts,
validators, API paths, and service tests.
- Added company-scoped summary-slot routes for reading revisions,
requesting generation, and guarded Summarizer writes with activity
logging.
- Added terminal generation finalization, failure reasons, assignment
wakeups, and orchestration integration.
- Added the paused-by-default built-in Summarizer bundle, low-cost
runtime defaults, status-summarization skill, and stale-summary routine.
- Added summary cards, revision selection, retry/configuration states,
live draft streaming, transcript chunk handling, and project/workspace
integrations.
- Updated Claude local parsing for streamed status output and expanded
server, adapter, shared, database, catalog, and UI coverage.
## Verification
- `pnpm -r typecheck`
- `pnpm exec vitest run packages/db/src/summary-slots-schema.test.ts
packages/shared/src/summary-slot.test.ts
server/src/__tests__/summary-slot-routes.test.ts
server/src/__tests__/summary-slots.test.ts
server/src/__tests__/built-in-agents.test.ts
ui/src/components/SummarySlotCard.test.tsx
ui/src/components/SummarySlotCard.status.test.tsx
ui/src/components/useSummaryDraftStream.test.tsx
ui/src/lib/summary-draft-stream.test.ts
ui/src/lib/run-log-chunks.test.ts
ui/src/context/LiveUpdatesProvider.hook.test.tsx` — 113 tests passed
- `pnpm test:run` — server and UI suites passed; one CLI AWS doctor test
was affected by inherited `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`,
and passed when those host credentials were removed
- `pnpm exec vitest run cli/src/__tests__/secrets.test.ts` with
inherited AWS credential variables removed — 8 tests passed
- `pnpm build`
- `pnpm check:token-gates` currently reports nine `#9627` comment
references introduced by current `master`; none are in this PR diff
## Risks
- Database risk is limited by incrementally ordered, idempotent
migrations and migration safety checks.
- Summary generation creates normal issues/runs, so misconfiguration can
produce failed slots; the UI exposes retryable failure reasons and agent
configuration entry points.
- Streaming draft parsing depends on the documented `STATUS:` protocol;
final persisted revisions remain the source of truth.
- The feature is experimental, opt-in, and its built-in routine is
paused with no background token spend by default.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI GPT-5.3 Codex with reasoning, repository tool use, code
execution, GitHub CLI, and Paperclip control-plane integration. Earlier
branch commits also record Claude model co-authorship where applicable.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Inbox lets operators scan task state and distinguish unread
activity at a glance
> - Read and unread rows should keep the same status-and-title alignment
so the list remains easy to scan
> - The mark-read dot previously participated in flex layout, which
added an extra leading column and visibly indented unread rows
> - Parent rows also needed special handling because their collapse
chevron occupies the normal leading gutter
> - This pull request moves the unread dot out of desktop flex flow,
preserves a consistent leading spacer, and positions parent-row dots
after the chevron
> - The benefit is consistent alignment for read, unread, leaf, nested,
and collapsible parent rows without hiding tree controls
## Linked Issues or Issue Description
### What happened?
In the Inbox, an unread row rendered its status icon and title farther
right than an equivalent read row because the mark-read dot occupied its
own flex column. On a collapsible parent row, the dot also competed with
the leading chevron.
### Expected behavior
Read and unread Inbox rows keep identical status/title alignment while
preserving the unread affordance and any tree-expansion controls.
### Steps to reproduce
1. Run Paperclip from `master` and open the Inbox at desktop width.
2. Compare otherwise-equivalent read and unread leaf rows.
3. Expand a task tree containing an unread depth-zero parent.
4. Observe the unread row indentation and the dot competing with the
parent chevron.
### Paperclip version or commit
Reproduced against the pre-fix `master` parent of this PR.
- **Deployment mode:** Local dev (`pnpm dev`).
- **Installation method:** Built from source.
- **Agent adapters involved:** Not adapter-specific; this is a core
Inbox UI bug.
- **Database mode:** Not database-related.
- **Access context:** Board (human operator).
- **Additional context:** No logs or configuration are involved; the
regression is visual layout behavior covered by focused component/page
tests.
## What Changed
- Render the desktop unread dot as an absolute overlay so it does not
consume row width; retain the existing in-flow behavior on mobile.
- Add an `unreadDotPlacement` option so depth-zero collapsible parents
place the dot after the leading chevron.
- Reserve the same leading spacer for read and unread non-chevron Inbox
rows.
- Expand `IssueRow` and Inbox tests to cover leaf alignment, nested
rows, parent chevrons, fading state, and mobile behavior.
## Verification
- `pnpm exec vitest run ui/src/components/IssueRow.test.tsx
ui/src/pages/Inbox.test.tsx` — 2 files, 30 tests passed.
- `pnpm check:token-gates` — all three token gates clean across 630
scanned files.
- Browser QA (desktop 1280px and mobile 375px) — PASS: read/unread
desktop content measured at identical x positions; nested guides, parent
chevron hit target, fading state, and mobile mark-read hit targets
verified.
## Risks
- Low risk: the change is isolated to Inbox row presentation and has
focused regression coverage.
- The main visual risk is breakpoint-specific placement of the mark-read
dot; tests explicitly cover desktop absolute positioning and mobile
in-flow positioning.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex `gpt-5.6-sol`, high reasoning effort, with
repository/tool execution. The runtime did not expose a context-window
size.
- Original implementation commit also records assistance from Claude
Opus 4.8.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to coordinate
AI-agent work.
> - Execution workspaces provide the local service loop where operators
start, stop, restart, inspect, and open workspace services.
> - The existing header exposed those actions through separate controls
whose position and labeling changed across runtime states.
> - That movement made the most common development actions harder to
scan and easier to misclick, especially with multiple services or long
URLs.
> - The design therefore uses one fixed-geometry, state-aware control
bar and keeps service-specific detail behind a compact disclosure.
> - This pull request adds that control surface, maps existing runtime
data and every pending mutation into it, and integrates it into the
execution-workspace header without changing server contracts.
> - The benefit is a calmer, predictable service-control loop across
stopped, transitional, running, unhealthy, failed, multi-service, and
narrow-width states.
## Linked Issues or Issue Description
### Subsystem affected
`ui/ — React + Vite board UI`
### Problem or motivation
Execution-workspace service actions move and change shape as runtime
state changes, while URLs and multi-service status compete for header
space. During bulk actions, operators also need every targeted service
to show its transitional state immediately.
### Proposed solution
Use one fixed-geometry, state-aware service control bar in the workspace
header. Map existing runtime records into a stable status, URL, and
actions model, and track each in-flight bulk request independently until
it settles.
### Alternatives considered
Keeping the separate quick-control buttons was rejected because their
geometry changes by state. Showing every service inline was rejected
because it makes the header too wide; per-service detail remains in a
compact disclosure and the Services tab.
### Roadmap alignment
Reviewed `ROADMAP.md`; this focused execution-workspace UI improvement
does not duplicate a listed roadmap initiative.
### Additional context
The published design and state viewer is available at
https://pages.paperclip.ing/pap-14233-workspace-service-controls/.
## What Changed
- Added `WorkspaceServiceControlBar`, a fixed-geometry responsive
control for single- and multi-service runtime states.
- Added 15 Storybook states covering running, stopped, transitions,
unhealthy, failed, disabled, long-URL, mobile, and multi-service
behavior.
- Replaced `WorkspaceRuntimeQuickControls` in the execution-workspace
header with adapters that map live services and all pending requests
into the new control model.
- Added focused unit coverage for service-entry construction, bulk
pending overlays, request resolution, clipboard feedback, and header
integration.
## Verification
- `cd ui && NODE_ENV=development pnpm vitest run
src/components/WorkspaceServiceControlBar.test.tsx
src/components/WorkspaceRuntimeControls.test.tsx
src/pages/ExecutionWorkspaceDetail.test.tsx` — 29 tests passed.
- `NODE_ENV=development pnpm --dir ui typecheck` — passed.
- `pnpm check:token-gates` — all token gates clean.
- Reviewed the Storybook captures for all primary states.
## Risks
- Low-to-moderate UI risk: service controls depend on adapter mapping
from existing runtime records; focused tests cover single-service and
bulk-action mapping and integration paths.
- Multi-service bulk actions intentionally apply to all eligible
services, while per-service actions remain in the disclosure.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, exact model ID `gpt-5.3-codex`; runtime-managed context
window; coding/reasoning mode with repository, terminal, Git, GitHub
CLI, and test execution tools.
## 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
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The issue detail chat lets operators send messages while an issue
run is live
> - Messages sent during a live run are shown as queued and can expose
an interrupt action
> - The UI only treated running runs as interruptible, even though
queued runs can also own the pending message
> - That mismatch hid the interrupt button after sending a message while
the agent run was still queued
> - This pull request treats queued and running issue runs as
interruptible and preserves the exact target run on optimistic and
persisted comments
> - The benefit is that operators can immediately interrupt the queued
run their message is waiting behind
## Linked Issues or Issue Description
- **What happened:** Sending a message while an issue-owned agent run
was in `queued` state showed the message as pending but did not make the
interrupt action available.
- **Expected behavior:** A message queued behind either a queued or
running issue run should retain that run as its interrupt target and
expose the interrupt control.
- **Steps to reproduce:** Open an in-progress issue with an issue-owned
run still queued, send a chat message, and inspect the queued message
actions.
- **Version/commit:** Reproduced against the pre-change `master` UI
behavior.
- **Deployment mode:** Paperclip board UI with a queued issue execution
run.
## What Changed
- Generalized issue-run resolution from running-only to
queued-or-running interruptible runs.
- Used the interruptible run consistently for optimistic queue metadata,
persisted comment decoration, cancel controls, and targeted
interruption.
- Added a regression test that sends a message behind a queued run and
verifies the exact run is cancelled.
## Verification
- `pnpm exec vitest run ui/src/pages/IssueDetail.test.tsx` — 44 tests
passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — repository-wide gate currently reports nine
pre-existing `#9627` comment literals; this patch adds no token literals
or gate violations.
## Risks
- Low risk: the behavior change is limited to selecting queued
issue-owned runs as valid interrupt targets in the existing chat flow.
- Cancellation remains targeted by run ID, and the regression test
verifies the queued run ID is preserved through optimistic and persisted
comment states.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI `gpt-5.4` via Codex CLI; context-window size is not exposed by
this runtime; reasoning-enabled with repository, shell, GitHub CLI, and
code-execution tools.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies and their work
> - The inbox is a per-user attention view, so archiving an item must
not alter the underlying issue, assignment, or status
> - Agents can help responsible users tidy resolved work only when the
action is company-scoped, reversible, policy-controlled, and fully
attributable
> - The database and authorization foundations landed in #9654 and
#9658, but the end-to-end archive routes, audit details, agent workflow
guidance, and operator UI still need to ship together
> - Separate stacked PRs #9659 and #9661 made the complete behavior
harder to review and land as one coherent capability
> - This pull request consolidates the remaining server,
shared-contract, documentation, skill, and UI work on top of current
master
> - The benefit is a single reviewable change that lets agents safely
archive responsible-user inbox items and lets users control or undo that
behavior
## Linked Issues or Issue Description
### Subsystem affected
Cross-cutting inbox management across shared contracts, server
authorization/routes/services, shipped agent skills, and the board UI.
### Problem or motivation
Agents may complete work whose issue remains in the responsible user's
Mine inbox. Existing board-user archive behavior does not provide the
agent-facing policy endpoints, target resolution, heartbeat-run
attribution, typed denials, conservative workflow guidance, or UI needed
for safe agent-managed cleanup.
### Proposed solution
Allow authorized agents to archive or unarchive responsible-user inbox
items under the user's open, allowlist, or disabled policy; preserve
actor/agent/run attribution in issue detail and activity records; expose
policy controls and agent archive attribution in the UI; and document
conservative cleanup rules for agents and PR gardening.
### Alternatives considered
- Reuse generic issue mutation permissions: rejected because inbox state
belongs to a target user and requires user-scoped authorization.
- Automatically archive every completed or closed item: rejected because
completion signals can still require human review or a decision.
- Keep the backend and UI as separate stacked PRs: superseded by this
consolidated PR so the complete user-visible behavior can be reviewed
and verified together.
### Related work
- Builds on merged foundations #9654 and #9658.
- Supersedes the remaining stacked changes in #9659 and #9661.
- `ROADMAP.md` has no overlapping inbox archive or inbox authorization
initiative.
## What Changed
- Added shared inbox-agent policy types and validators plus
company-scoped self-service policy routes and OpenAPI coverage.
- Enabled agent archive/unarchive mutations with responsible-user
targeting, policy enforcement, typed failures, attribution, idempotency,
and detailed activity auditing.
- Returned agent archive attribution in issue detail and documented
reversible inbox cleanup semantics in the implementation spec and
Paperclip skill.
- Added conservative PR-gardening inbox tidy guidance that keeps GitHub
access read-only and avoids archiving work that still needs human
action.
- Added the Profile settings policy control and Issue Properties
attribution/unarchive UI with focused component coverage and narrow-pane
handling.
## Verification
- `pnpm exec vitest run
server/src/__tests__/inbox-archive-routes.test.ts
server/src/__tests__/inbox-agent-policy-routes.test.ts
server/src/__tests__/authorization-service.test.ts
server/src/__tests__/openapi-routes.test.ts
ui/src/components/InboxAgentPolicyControl.test.tsx
ui/src/components/IssueProperties.test.tsx` — 110 passed.
- `pnpm --filter @paperclipai/db exec vitest run
src/inbox-archive-agent-policies-migration.test.ts` — 1 passed.
- `node --test
.agents/skills/pr-gardening/scripts/pr-gardening.test.mjs` — 9 passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — changed files are clean; the
repository-wide command currently reports nine unrelated pre-existing
`#9627` literals outside this PR's diff.
## Risks
- Agent inbox mutations broaden an existing endpoint path, so
authorization and target resolution must remain fail-closed; focused
route and authorization tests cover allowed and denied paths.
- Archive state affects only the responsible user's inbox presentation
and remains reversible; it does not mutate issue status, assignment, or
visibility.
- The UI policy defaults to the existing open behavior, while allowlist
and disabled modes can reduce agent access.
- This PR intentionally builds on #9654 and #9658 and contains no new
migration number or modification to an already-applied migration.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex using GPT-5.4, medium reasoning, repository tool use,
shell execution, code review, and 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
(`feat/inbox-agent-archive-complete`) 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the control plane where operators configure and inspect
AI-agent work.
> - An issue can override the assigned agent's primary model for that
task.
> - The issue-properties UI labeled that per-task setting as `Custom ·
<model>`, which could be read as a property of the model rather than a
replacement of the agent default.
> - Operators need the UI to distinguish the agent's primary model from
an issue-specific override at both the collapsed summary and selection
point.
> - This pull request renames the lane presentation to `Override` and
adds concise provenance text without changing the stored lane value or
adapter configuration behavior.
> - The benefit is that operators can immediately understand which model
will run and why it differs from the agent default.
## Linked Issues or Issue Description
No matching public GitHub issue was found. Related implementation work:
#9700 moved Codex ACPX model configuration to startup and is already on
`master`; #9355 also addresses ACP session-config rejection behavior.
Those PRs concern execution behavior, while this PR is limited to
clarifying the issue-level override UI.
Bug report details:
- **What happened?** The issue-properties Model row displayed `Custom ·
<model>` for a task-level
`assigneeAdapterOverrides.adapterConfig.model`. Operators could misread
`Custom` as describing the model itself and could not see that the value
replaced the agent's primary model for this issue.
- **Expected behavior:** The UI should explicitly identify a task-level
model as an override and explain that it replaces the agent's primary
model for the issue.
- **Steps to reproduce:** Configure an agent with a primary model, set a
different model on an issue, and inspect the Model row and model picker
in Issue Properties.
- **Paperclip version or commit:** Reproduced on the pre-change branch
derived from current `master`.
- **Deployment mode:** Local development or any deployment using the
board UI.
- **Installation method:** Built from source.
- **Agent adapters involved:** Adapter-agnostic; any adapter exposing
model selection.
- **Database mode:** Not database-related.
- **Access context:** Board operator viewing issue properties.
- **Relevant logs or output:** Not applicable; this is a presentation
ambiguity.
- **Relevant config:** An issue-level
`assigneeAdapterOverrides.adapterConfig.model` differing from the
assigned agent's primary model.
- **Additional context:** The internal lane identifier remains `custom`;
only user-facing copy and explanatory text change.
- **Privacy checklist:** No private instance links, internal ticket IDs,
secrets, usernames, or local paths are included.
## What Changed
- Renamed the collapsed issue model label from `Custom · <model>` to
`Override · <model>` and added a provenance tooltip.
- Renamed the model-picker lane from `Custom` to `Override` and added
explanatory subtext at the selection point.
- Updated the Issue Properties component test to assert the new label.
- Added an isolated Storybook fixture for the task model override state
so visual review has a stable target.
## Verification
- `pnpm exec vitest run ui/src/components/IssueProperties.test.tsx` — 43
tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `git diff --check public-gh/master...HEAD` — passed.
- `pnpm check:token-gates` — the changed files are clean, but the
repo-wide command currently reports nine unrelated `#9627` comment
references already present on `master` as color literals.
## Risks
- Low risk: this changes display strings and explanatory copy only;
override storage, lane identifiers, API contracts, and execution
behavior are unchanged.
- The global token-gate false positive may also appear in CI until the
unrelated `#9627` references on `master` are allowlisted or the scanner
ignores issue-number comments.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent assisted this PR preparation using medium
reasoning, repository-aware shell execution, Git/GitHub tooling, and
local test/typecheck execution. The hosted runtime did not expose an
exact model ID or context-window size to this session.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to organize
and govern AI-agent companies
> - Company skills are durable resources that users browse, import,
assign, and maintain over time
> - A flat skill list plus tags does not provide a stable location or
hierarchy for personal, company, project-imported, and bundled skills
> - Folder paths need to be canonical, company-scoped, safe to move, and
preserved across re-imports without changing skill IDs
> - The `/skills` UI also needs traversal, breadcrumbs, move/create
flows, and a dedicated My Skills namespace that work on desktop and
mobile
> - This pull request adds the folder data model and APIs, reserved-root
lifecycle, project import behavior, and the folder-first skills
experience
> - The benefit is a predictable filesystem-like organization model
while tags remain available for cross-cutting classification
## Linked Issues or Issue Description
Refs #9619 — the reviewed folder foundation was intentionally closed and
folded into this combined feature PR.
Refs #9026 — earlier flat-folder attempt superseded by this integrated
implementation.
Refs #3281 — related skill organization proposal; this PR uses canonical
persisted folders rather than deriving groups from skill keys, and does
not add hidden-skill behavior.
**Feature request**
- **Problem:** Skills currently lack a canonical hierarchical location,
making personal skills, project imports, bundled skills, and
company-authored skills difficult to traverse and manage at scale.
- **Proposed behavior:** Add nested company-scoped folders with stable
paths, reserved My/Projects/Bundled roots, subtree queries, safe
move/create operations, and a folder-first `/skills` library UI.
- **Import behavior:** New project scans file skills under
`projects/<project-slug>`; later imports update content without
overriding a user-selected folder.
- **Alternatives considered:** Tags alone remain useful for
cross-cutting classification, but they do not provide canonical
location, nesting, reserved namespaces, or stable import placement.
- **Roadmap alignment:** Extends the completed Skills Manager and
Scheduled Routines capabilities without duplicating an active roadmap
item.
## What Changed
- Adds `folders` persistence for routine and skill folders, nested
canonical paths, parent/slug/system-key fields, migration backfills, and
reapply-safe migrations `0174`–`0175` after current master migrations.
- Adds company-scoped folder CRUD, cycle/depth/namespace validation,
reserved My/Projects/Bundled lifecycle, item moves, subtree filtering,
and folder paths on skill results.
- Preserves project-import placement: first import files into the
project folder, while re-import keeps user-owned placement and stable
skill IDs.
- Adds the `/skills` folder tree rail, tags facet, breadcrumbs,
subfolder browser, move/new-folder dialog, canonical detail location,
inline tag editing, and folder-aware Studio creation.
- Keeps bundled skills read-only even when their source metadata is
incomplete by detecting the reserved Bundled folder and hiding
selection/move actions.
- Extends routine folder UI and OpenAPI coverage, and adds regression
tests across migrations, services, routes, tree helpers, pages, and
Studio creation.
## Verification
- `pnpm exec vitest run
packages/db/src/nested-skill-folders-migration.test.ts
server/src/__tests__/folders-routes.test.ts
server/src/__tests__/folders-service.test.ts
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/routines-service.test.ts
ui/src/components/folders/FolderControls.test.tsx
ui/src/components/folders/SkillFolderTree.test.tsx
ui/src/components/folders/skill-folder-tree.test.ts
ui/src/pages/CompanySkills.test.tsx ui/src/pages/Routines.test.tsx
ui/src/pages/SkillStudio.test.tsx
ui/src/lib/company-skill-routes.test.ts ui/src/lib/skill-create.test.ts`
— 13 files, 192 tests passed.
- `pnpm exec vitest run ui/src/pages/CompanySkills.test.tsx
ui/src/components/folders/SkillFolderTree.test.tsx` — 2 files, 20 tests
passed after preserving the existing PR's bundled-skill fixes.
- `pnpm -r typecheck` — passed for all workspace packages.
- `pnpm test:run` — passed in an isolated CI-like environment with
inherited Paperclip runtime identity and static AWS credential variables
removed.
- `pnpm build` — production build passed for all workspace packages.
- Greptile iteration 2 — 5/5 confidence with zero unresolved threads on
commit `ff2d67aa71`.
- Latest-head GitHub checks — all success, neutral, or skipped; PR is
mergeable with a clean merge state.
- `pnpm check:token-gates` — reports nine existing `#9627` comment false
positives already present on `master`; this PR introduces no new token
violation.
## Risks
- **Migration/backfill:** `0174` creates the foundation and `0175` adds
nested/reserved semantics. Both are ordered after current master
migration `0173`, are covered by numbering/safety checks, and are
designed to be reapply-safe.
- **Reserved namespaces:** My, Projects, and Bundled roots are
service-managed. Regression coverage prevents namespace squatting,
cross-company folder use, bundled writes, cycles, and excessive depth.
- **Behavioral change:** Project scans choose a project folder only on
initial creation; existing skills deliberately retain their current
folder during refresh.
- **UI scope:** The folder rail applies to the Installed library;
Catalog retains the discovery-oriented category sidebar.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI `gpt-5.5` in Codex CLI, medium reasoning mode; runtime did not
expose a context-window value. Used repository/file tools, terminal
execution, Git/GitHub operations, test execution, and code editing.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Fixes#3759
## Thinking Path
> - Paperclip orchestrates AI agents and issue workflows, so the comment
composer has to stay stable under normal typing.
> - The affected subsystem is the shared markdown comment editor used
across issue and workflow surfaces.
> - That editor decorates mention links after Lexical updates the
editable DOM.
> - The current mention decoration effect recreates its
`MutationObserver` whenever `value` changes, which happens on every
keystroke.
> - That observer also reacts to the DOM mutations produced by mention
decoration itself, creating unnecessary observer churn in Chrome.
> - This pull request keeps one observer instance alive, batches
decoration work into `requestAnimationFrame`, and disconnects the
observer while decoration writes run.
> - The benefit is lower observer churn while preserving the existing
mention chip behavior.
## What Changed
- Removed `value` from the mention-decoration observer effect dependency
list so the observer is not recreated on every external value update.
- Batched mention decoration with `requestAnimationFrame` and
temporarily disconnected the observer while DOM decorations are applied
to avoid self-triggered feedback loops.
- Added a regression test that verifies external value changes do not
recreate the mention decoration observer.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/ui exec tsc --noEmit`
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/MarkdownEditor.test.tsx` currently fails before test
collection on the existing repo baseline with `TypeError: undefined is
not an object (evaluating 'z.string')` from
`packages/shared/src/adapter-type.ts`
## Risks
- Low risk. The change is scoped to the mention decoration observer
lifecycle and keeps the existing decoration logic intact.
- The main behavior change is deferring decoration to the next animation
frame instead of running immediately on every observed mutation.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with local tool use in the
Codex CLI environment.
## 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)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Its web UI keeps live views fresh with a live-events websocket plus
React Query, and #9627 began replacing polling with event-sourcing
(pushing data into the cache)
> - After the churn fixes (#9624) and #9627 shipped, a long-lived tab's
memory footprint was still climbing, so I re-profiled with the Chrome
DevTools MCP
> - The dominant remaining churn is React Query re-arming a polled
query's `refetchInterval` timer on **every** observer notification — and
our live-event handlers `setQueryData(liveRuns(companyId))` on nearly
every event, so each pushed update re-arms every `liveRuns` observer's
timer (the sidebar is always mounted)
> - #9627 event-sourced the live-runs data but left the now-redundant
`refetchInterval` in place, so we did half the fix — the poll is pure
waste and the thing re-arming timers
> - This pull request removes `refetchInterval` from the event-sourced
company live-runs queries so the frequent cache writes have no timer to
re-arm
> - The benefit is that the steady-state timer churn on the
most-observed resource collapses, so the off-heap footprint stops
climbing
## Linked Issues or Issue Description
No public GitHub issue exists; describing inline per CONTRIBUTING.md →
"Link Issues or Describe Them In-PR", following the bug report template.
Continues #9569 / #9624 / #9627.
**What happened?**
With the earlier fixes deployed, a browser tab left open on the app kept
growing its memory footprint. MCP profiling showed ~100+ `setInterval`
create/clear cycles per 5s on an aged tab (vs ~16 fresh), all from React
Query's refetch-interval timers being re-armed on every `setQueryData`
to the frequently-written `liveRuns(companyId)` query.
**Expected behavior**
A resource whose data is pushed (event-sourced) should not also poll;
cache writes should not repeatedly re-arm interval timers. Idle tabs
should hold a bounded footprint.
**Steps to reproduce**
Open a tab with agents streaming, leave it open, and instrument
`setInterval`/`clearInterval`: the churn rate climbs and traces to
`QueryObserver.updateTimers` (`refetchInterval`) for `liveRuns`,
re-armed by every live-event cache write.
**Paperclip version or commit**
Branch `perf/drop-live-runs-refetch-interval`, off `master` (after
#9627).
**Deployment mode**
Local dev (`pnpm dev`), web UI. Core UI live-updates plumbing; not
adapter-specific.
## What Changed
- Set `refetchInterval: false` on every site that polls the **plain**
`queryKeys.liveRuns(companyId)` query (event-sourced by #9627):
`Sidebar`, `SidebarAgents`, `Issues`, `Inbox`, `IssueDetail`
(companyLiveRuns), `ProjectDetail` (×2), `Routines`,
`ExecutionWorkspaceDetail`, `bridge-init`.
- Removed the now-unused `useVisibilityRefetchInterval` interval
vars/imports in `Issues`, `Inbox`, `IssueDetail`.
- **Left variant-key sites polling on purpose** — `Agents` page
(`[...liveRuns, "agents-page"]`) and `ActiveAgentsPanel` (`[...liveRuns,
scope, …]`) are NOT event-sourced by #9627 (different exact cache key),
so dropping their poll would make them stale. Those are a later phase.
- Freshness for the converted queries now comes from event-sourcing
(#9627) + its reconnect reconcile; the initial mount fetch and cross-tab
publish (`usePublishSharedQueryData`) still happen.
## Verification
- MCP profiling identified the churn: the single churning callback is
React Query's `refetchInterval` timer, re-armed by
`setQueryData(liveRuns)` on live events.
- `vitest`: all affected suites pass (`Sidebar`, `SidebarAgents`,
`Issues`, `Inbox`, `IssueDetail`, `ProjectDetail`, `Routines`,
`ExecutionWorkspaceDetail`, `LiveUpdatesProvider`) — 153 tests.
- Updated two `SidebarAgents` linger-window tests: they advanced fake
timers to the *exact* linger-expiry boundary and had relied on
poll-induced re-renders to flush. The linger self-schedules its own
`setTimeout`, so the tests now cross the boundary with a small margin +
an explicit flush (no product change).
- `tsc -b` clean.
- End-to-end footprint reduction should be re-measured against a rebuilt
bundle with the same instrumentation.
## Risks
Low, client-only.
- `liveRuns(companyId)` freshness now depends entirely on event-sourcing
+ reconnect reconcile (both from #9627). If an event path is missed, the
reconnect handler refetches once; durable replay is a planned later
phase.
- Variant-key run lists (Agents page, ActiveAgentsPanel) are unchanged
and still poll, so they don't regress.
- Issue-scoped run queries (`issues.liveRuns/activeRun/runs`) are
**not** touched here — they aren't event-sourced yet and are a separate
phase.
## Model Used
- **Provider:** Anthropic, via the Claude Code CLI.
- **Model:** Claude Opus 4.8 (`claude-opus-4-8`).
- **Reasoning mode:** Extended thinking enabled.
- **Capabilities used:** tool use (shell, file editing), and the Chrome
DevTools MCP to re-profile the live instance and pinpoint the
`refetchInterval` timer churn.
## 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 (a perf/plumbing change)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (continues #9627; no duplicates)
- [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 considered and documented any risks above
- [ ] I have updated relevant documentation to reflect my changes (N/A —
no user-facing docs; rationale documented inline)
- [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)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The control plane records a successful-run handoff when productive
work ends without a durable next-step disposition
> - That handoff state was derived only from the latest activity event,
without checking whether a corrective run or wake was currently alive
> - As a result, actively progressing issues could still show a
high-severity missing-disposition alarm and blocked-inbox row
> - The same stale required event could also remain indefinitely when a
later successful run correctly skipped recovery because another valid
continuation path already existed
> - This pull request makes the derived state liveness-aware, suppresses
attention only while the live path exists, and resolves stale required
events on valid-path skips
> - The benefit is that productive work stays calm while genuine stalls
still resurface automatically when liveness disappears
## Linked Issues or Issue Description
- **Bug:** An issue whose latest successful-run handoff event is
`required` continues to report a missing disposition even while a
heartbeat run, scheduled retry, or queued/deferred/claimed wake is
actively targeting that issue.
- **Expected behavior:** The API should expose current continuation
liveness, the blocked inbox should suppress the alarm only while that
path remains live, and a later successful run that skips recovery
because a valid path exists should durably resolve the stale event.
- **Related but distinct:** #9370 changes disposition freshness at
detection time; #8748 adds an explicit policy opt-out. This PR preserves
detection/escalation policy and fixes read-time/current-liveness state.
## What Changed
- Extended `SuccessfulRunHandoffState` with `hasLiveContinuation` and
optional `liveRunId` evidence.
- Added bounded liveness hydration for required handoff states using
active heartbeat-run and wake-request signals.
- Suppressed `missing_disposition` blocked-inbox rows only while a run,
scheduled retry, or live wake targets the issue.
- Added durable `issue.successful_run_handoff_resolved` logging when
handoff detection skips because another valid continuation path owns the
next action.
- Added focused regressions for live/absent derived state, self-healing
attention suppression, valid-path skip classification, and
resolved-event logging.
- Updated UI normalization and fixtures for the shared contract without
changing rendering behavior.
## Verification
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm vitest run
server/src/services/recovery/successful-run-handoff.test.ts
server/src/__tests__/issue-list-assignee-filter-routes.test.ts
server/src/__tests__/issue-blocker-attention.test.ts` — 56 passed
- `pnpm vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "queues one
finish-handoff wake when a successful run leaves in-progress work
without a next action"` — 1 passed
- `git diff --check`
## Risks
- Low risk: no schema or migration changes, and detection, bounded
correction attempts, and escalation behavior are unchanged.
- Liveness lookups are limited to issues whose latest handoff state is
`required`; blocked-inbox suppression reuses rows already loaded by that
query path.
- Suppression is read-time and self-healing: when the run or wake stops,
the alarm returns on the next fetch.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex using `gpt-5.4`, tool-enabled software-engineering
workflow with repository, shell, test, Git, GitHub, and Paperclip
control-plane access. Context-window size is not exposed by this
runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Inbox is the primary triage surface; it supports both mouse
hover and `j`/`k` keyboard navigation over a flat, expandable list of
rows
> - Hover and keyboard selection are meant to be a single shared
"cursor": hovering a row and then pressing `j`/`k` should continue from
the hovered row, not jump elsewhere
> - A prior hover-perf rewrite moved the hovered row into a numeric
`hoveredIndexRef` and nulled it whenever the list reshaped; because the
inbox polls constantly, any refresh between hovering and pressing a key
dropped the hovered index
> - With the hovered index dropped, the next keypress fell back to
selection index `0`, stranding the cursor at the top of the list instead
of continuing from the hovered row
> - This pull request tracks the hovered row's stable identity (a nav
key) alongside the numeric index and re-anchors it by key across
reshapes, mirroring the existing keyboard-selection reconciliation
> - The benefit is that mouse hover and keyboard navigation stay in sync
exactly as intended, even while the inbox is polling
## Linked Issues or Issue Description
**Bug report**
- **What happened:** In the Inbox, hovering a row with the mouse and
then pressing `j`/`k` (or another keyboard shortcut) does not continue
from the hovered row. If the list refreshes (which happens on the
inbox's constant polling) in the moment between hovering and pressing a
key, keyboard selection snaps back to the top row instead.
- **Expected behavior:** The keyboard cursor should be in sync with the
hovered row — pressing `j`/`k` after hovering should move relative to
the row the mouse is over.
- **Steps to reproduce:**
1. Open the Inbox with several rows.
2. Hover the mouse over a row partway down the list.
3. Wait for (or trigger) a background poll/refresh of the list.
4. Press `j` or `k`.
5. Observe selection jumps to the top of the list instead of continuing
from the hovered row.
- **Root cause:** The `[flatNavItems]` effect nulled `hoveredIndexRef`
on every list reshape. Since hover also clears the keyboard selection
band to `-1`, the fallback selection index resolved to `0`.
## What Changed
- Hoisted `navEntryKey` to module scope so it can compute a stable,
index-independent identity for a nav row from both the hover handler and
the reshape effect.
- Added `hoveredNavKeyRef` to track the hovered row's stable key
alongside the existing numeric `hoveredIndexRef`, set whenever the
pointer selects a row.
- On list reshape, re-anchor the hovered index by key (find the row with
the same key) instead of unconditionally dropping it; only drop the
hover when the row is actually gone. This mirrors the existing
`selectedIndex` key-based reconciliation.
- Added a unit test that hovers a row, reshapes the list via a simulated
poll, presses `j`, and asserts selection continues from the hovered row.
## Verification
- `pnpm --filter ./ui exec vitest run src/pages/Inbox.test.tsx` → 15/15
passing, including the new hover→`j`/`k` sync test and the existing
keyboard-nav tests.
- The new test explicitly covers the reshape-during-hover path that the
prior test suite had deferred to live/e2e verification.
## Risks
- Low risk. The change is confined to the Inbox's in-memory
hover/selection bookkeeping (two refs and one effect); it adds no new
renders (hover still paints via CSS `:hover`) and touches no data
fetching, routing, or persistence. Behavior is unchanged when the list
does not reshape; when it does, the hover now follows the same row
instead of being dropped.
## Model Used
- Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, used with
extended thinking and tool use (file edits, running the UI test suite
locally).
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - When an agent's task is `blocked`, humans often comment on the issue
thread expecting it to reopen and resume
> - If the issue stays `blocked` because an unresolved (not-done)
blocker still gates the reopen path, the UI said nothing — the human
"sent a message and nothing happened" (a real silent-failure report)
> - That silence makes the product feel broken even though the gate is
working as designed
> - This pull request adds Rule C copy to the existing
`IssueBlockedNotice` surface: when a comment won't reopen a blocked
issue, it explains why and names the deepest unresolved blocker leaf
with its status
> - The benefit is that the human immediately understands the task is
*paused, not stuck*, and knows exactly which task to act on to unblock
it
## Linked Issues or Issue Description
<!-- No public GitHub issue — describing the underlying problem inline
(path B), following the feature issue template. -->
**Problem or motivation**
A human comments on a `blocked` issue expecting it to move back to
`todo` and resume. When the reopen gate keeps it blocked because an
unresolved (not-`done`) blocker remains, nothing in the UI communicates
this, so the user perceives a dropped message ("I sent a message and
nothing happened").
**Proposed solution**
Reuse the existing amber `IssueBlockedNotice` surface (the designated
blocked/recovery surface — no new component). When a message won't
reopen a blocked issue, the notice states that it won't reopen yet,
names the unresolved blocker leaf with its status (e.g. "Still blocked
by PAP-XXXXX (in progress)"), and reassures that it reopens
automatically once the blocker is done. UI-only; no server change.
**Alternatives considered**
Adding a server signal (a reopen-suppressed reason on the comment/notice
payload) was considered but rejected as unnecessary — the
unresolved-blocker set is already available client-side, so the copy is
derived in the component. Done-but-pending-finalize blockers are `done`,
so they fall out of the unresolved set into the standard reopen (Rule B)
path and are correctly not shown as reopen-suppressed.
## What Changed
- `ui/src/components/IssueBlockedNotice.tsx`: added reopen-suppressed
messaging for `blocked` issues that still have unresolved blockers — a
lead sentence ("a message won't reopen it yet, then it reopens
automatically"), the named unresolved blocker leaf with its status, and
an "and N other task(s)" summarization when multiple blockers remain.
- `ui/src/components/IssueBlockedNotice.test.tsx`: added/updated tests
covering the single, nested-chain (deepest-leaf), multiple-blocker, and
empty-blocker states, plus the not-a-reopen-case (`in_progress`) path.
- `ui/storybook/stories/issue-blocked-notice.stories.tsx`: new Storybook
stories rendering each notice state for copy review.
## Verification
- `cd ui && tsc -b` — typecheck clean (previously failed TS2322 on the
story meta; fixed by a default `args`).
- Vitest: `IssueBlockedNotice.test.tsx` green (single / nested /
multiple / empty / in-progress states).
- Storybook stories rendered at 1440×900 for all four states;
screenshots posted on the tracking issue.
- UXDesigner reviewed the notice copy and signed off (no copy edits
required).
## Risks
Low risk. UI-only, additive copy on an already-shipped amber notice
surface; no server or schema changes. The reopen behavior itself is
unchanged — this only explains the existing gate. Worst case is copy
wording, which had a design review.
## Model Used
Claude — claude-opus-4-8 (Opus 4.8), extended thinking, with tool use /
code execution in the Paperclip harness.
---
- [x] I searched the GitHub PR list for similar/duplicate PRs before
opening this one.
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Each company workspace in the web UI is mounted under a URL prefix
(e.g. `/NEU/company/...`), and `Link` from `@/lib/router` applies that
prefix automatically via `applyCompanyPrefix`
> - Company Settings rendered its Org Chart, Export, Import, and Cloud
Upstream buttons as raw `<a href>` anchors, which drop the prefix, so
those pages 404 on prefixed instances (#2910);
`CompanyExport.filePathFromLocation` also failed to locate
`/company/export/files/` inside prefixed URLs, breaking export file
previews
> - #2951 fixed the Settings links with `<Link to>` plus tests, but the
sandbox-settings work in #4415 reverted the links back to `<a href>`,
silently reintroducing the bug (#6647)
> - This pull request restores the prefix-aware `<Link>` for all four
Settings links, normalizes the pathname with `toCompanyRelativePath()`
before matching the export-files marker, and adds regression tests
covering every route so the fix cannot be lost again
> - The benefit is that export/import/org-chart/cloud-upstream
navigation and export file previews work on every prefixed deployment
## Linked Issues or Issue Description
Fixes: #6647
Refs #2910 (original report: `/company/export` → prefix `COMPANY` → not
found)
Refs #2951 (original fix with `<Link to>` + tests — merged, then lost)
Refs #4415 (sandbox settings PR that reverted Settings back to `<a
href>`)
## What Changed
- **`ui/src/pages/CompanySettings.tsx`**: use `Link` from `@/lib/router`
for the Org Chart, Export, Import, and Cloud Upstream buttons (replacing
raw `<a href>`)
- **`ui/src/pages/CompanyExport.tsx`**: resolve file paths from prefixed
URLs by normalizing with `toCompanyRelativePath()` before matching
`/company/export/files/`
- **`ui/src/lib/company-routes.test.ts`**: regression tests for
export/import/cloud-upstream/org prefix rewriting, double-prefix
prevention, and export file URL normalization
## Verification
```bash
pnpm vitest run ui/src/lib/company-routes.test.ts
```
Manual:
1. Open `http://localhost:3100/NEU/company/settings` (or your company
prefix).
2. Click **Export** / **Import** — URL should stay under
`/:prefix/company/...`.
3. On export, select a file — URL should be
`/:prefix/company/export/files/...` and preview should load.
The change is navigation-target-only (no visual/layout changes), so
before/after is shown as the resolved URLs:
| Link | Before (prefix dropped → not found) | After |
|------|-------------------------------------|-------|
| Export | `/company/export` | `/NEU/company/export` |
| Import | `/company/import` | `/NEU/company/import` |
| Org Chart | `/org` | `/NEU/org` |
| Cloud Upstream | `/company/settings/cloud-upstream` |
`/NEU/company/settings/cloud-upstream` |
## Risks
Low — same approach as #2951; only navigation/parsing, no API changes.
## Model Used
- Original implementation: authored by @qbamca in Cursor (agentic
editor; the session's exact model ID was not recorded)
- Follow-up commit (merge-conflict resolution) and this description
update: Claude Fable 5 (Anthropic, `claude-fable-5`, extended thinking,
agentic tool use), operated by the Commit Capital triage team
## 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 (no
doc changes required — behavior matches documented routing)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending re-review of the conflict-resolution commit)
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Jakub Mikiciuk <jmikiciuk@igus.net>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source control plane operators use to manage
AI-agent companies.
> - Operators can store runtime secrets in provider vaults such as AWS
Secrets Manager.
> - The server already preserves sanitized AWS failure details,
including the failed operation, required IAM capability, region, and
safe recovery options.
> - The create-secret dialog reduced that structured response to a
generic message, leaving operators unable to understand or fix failed
AWS writes.
> - This pull request keeps the safe structured error through the UI and
presents concise, actionable diagnostics without exposing raw AWS
principals or account details.
> - The benefit is that operators can correct IAM access or link an
existing AWS secret immediately instead of debugging an opaque failure.
## Linked Issues or Issue Description
No public issue was found for this exact UI gap.
Bug report:
- What happened: creating a Paperclip-managed value in AWS Secrets
Manager could fail with a generic dialog error even though the API
returned safe, actionable provider details.
- Expected behavior: the dialog should identify the AWS operation,
required IAM capability, region, provider vault, and safe alternative
while keeping raw cloud-provider details redacted.
- Steps to reproduce: configure an AWS Secrets Manager provider vault
without `secretsmanager:CreateSecret`, then create a Paperclip-managed
secret using that vault.
- Paperclip version/commit: current `master` before this PR.
- Deployment mode: Paperclip server with an AWS Secrets Manager provider
vault.
Related prior server-side propagation work:
- Refs #9161
## What Changed
- Preserve the structured `ApiError` returned by failed create-secret
mutations instead of reducing it to a string.
- Render AWS-specific, sanitized diagnostics with the required IAM
capability, region, provider vault, operation, and external-reference
recovery option.
- Add a full dialog render regression test that verifies actionable
details appear and raw AWS ARN/account information does not.
## Verification
- `pnpm exec vitest run ui/src/pages/Secrets.render.test.tsx` — 1 file
passed, 15 tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — all gates clean.
- `git diff --check public-gh/master...HEAD` — passed.
### Visual Verification
QA verified both states in Chromium at head `357cd2271` using the real
`Secrets` component and confirmed that raw AWS account, ARN,
assumed-role, and provider exception details are absent from the
rendered DOM.
**AWS access-denied diagnostics**

**Generic non-AWS fallback**

## Risks
Low risk. The change only affects failed create-secret presentation in
the UI; successful secret creation, API contracts, schema, and
migrations are unchanged. The structured details are server-sanitized,
and the regression test confirms raw AWS principal/account data is not
rendered.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI `gpt-5.4` via Codex CLI, with tool-enabled repository editing,
shell execution, Git, GitHub, and Paperclip API access. Reasoning mode
and exact context-window size were not exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to run
AI-agent companies.
> - Scheduled routines support catch-up policies when the server resumes
after missed cron ticks.
> - The existing capped replay policy dispatched once per missed tick,
which can flood the board after downtime for frequent schedules.
> - Sub-hourly routines usually need one prompt catch-up execution
rather than historical per-tick replay, while hourly-or-slower schedules
may rely on the existing behavior.
> - This pull request coalesces missed sub-hourly ticks into one
execution and keeps the slower-schedule behavior unchanged.
> - The benefit is bounded recovery work without changing the semantics
of lower-frequency scheduled routines.
## Linked Issues or Issue Description
### What happened?
When a scheduled routine using `enqueue_missed_with_cap` resumes after
several missed sub-hourly cron ticks, Paperclip dispatches one catch-up
execution for every missed tick. Those executions arrive in a
same-second burst and can flood the board with duplicate-looking work.
### Expected behavior
Sub-hourly schedules should advance past all missed ticks but dispatch
exactly one catch-up execution. Hourly-or-slower schedules should retain
capped per-tick replay.
### Steps to reproduce
1. Build Paperclip from `master` and create a routine with a sub-hourly
cron schedule and `catchUpPolicy: enqueue_missed_with_cap`.
2. Set its persisted `nextRunAt` far enough in the past to cover several
scheduled occurrences.
3. Run routine catch-up processing.
4. Observe multiple catch-up dispatches instead of one coalesced
execution.
### Paperclip version or commit
Reproduced on `master` before this PR.
### Deployment mode
Built from source in local development with embedded PGlite.
## What Changed
- Classify sub-hourly cadence from timezone-aware scheduled occurrences,
avoiding daily multi-minute false positives while supporting schedules
restricted to active days.
- Coalesce all missed sub-hourly ticks into one catch-up dispatch while
advancing `nextRunAt` to the next future occurrence.
- Preserve capped per-tick replay for hourly-or-slower schedules.
- Clarify the catch-up policy labels in both routine editing surfaces.
- Add regression coverage for both the coalesced and preserved
behaviors.
## Verification
- `pnpm exec vitest run server/src/__tests__/routines-service.test.ts
--testNamePattern='coalesces multiple missed sub-hourly ticks|continues
replaying each missed hourly tick|continues replaying missed ticks for
daily schedules with multiple minute values|coalesces sub-hourly
schedules restricted to weekdays'` — 4 passed.
- `pnpm check:token-gates` — all gates clean.
- `git diff --check origin/master...HEAD` — clean.
## Risks
- Low-to-moderate behavioral risk: sub-hourly routines using
`enqueue_missed_with_cap` now intentionally receive one recovery
execution instead of one per missed tick.
- Hourly-or-slower schedules retain their previous capped replay
behavior, limiting the compatibility surface.
- No schema, migration, workflow, or lockfile changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex CLI with GPT-5.5, medium reasoning, code execution and
repository tool use; the runtime did not expose a context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies and review work that needs human attention.
> - The Decisions page condenses approvals, failed runs, reviews, and
issue interactions into a scannable attention queue.
> - Some decision rows already include screenshot evidence, but the
collapsed thumbnail stack is too small for meaningful inspection.
> - Image-only rows were not expandable because expansion was previously
reserved for inline decision resolvers.
> - Reviewers therefore had to leave the Decisions page before they
could understand the visual evidence attached to a row.
> - This pull request makes rows with images expandable and presents a
readable, linked gallery while preserving the compact collapsed view.
> - The benefit is faster evidence review without sacrificing queue
density or existing inline-resolution behavior.
## Linked Issues or Issue Description
No public GitHub issue currently tracks this feature.
### Subsystem affected
`ui/` — React + Vite board UI
### Problem or motivation
Screenshot evidence in Decisions rows is only visible as small
overlapping thumbnails, and non-inline rows cannot expand to show it.
This makes visual review unnecessarily slow and forces reviewers to
navigate away from the queue.
### Proposed solution
Treat active rows with images as expandable, render the first three
images at a readable size in an expanded gallery, and link images plus
any remaining-image affordance to the related issue.
### Alternatives considered
Always rendering large images would make the queue difficult to scan;
opening the issue immediately preserves density but prevents in-context
review. An explicit expandable gallery keeps both behaviors available.
### Roadmap alignment
`ROADMAP.md` does not list overlapping Decisions image-gallery work.
This is a focused improvement to the existing review surface rather than
a new product area.
### Additional context
The Storybook variants document both the collapsed thumbnail treatment
and deterministic expanded gallery state for reviewer inspection.
## What Changed
- Allow active Decisions rows with screenshot evidence to expand even
when they have no inline resolver.
- Keep compact thumbnails in collapsed rows and render up to three
larger, linked images when expanded.
- Add an accessible remaining-image tile that links to the related issue
when more screenshots exist.
- Add component coverage for image-only expansion and the
remaining-image issue link.
- Add collapsed and expanded image-gallery Storybook variants, including
deterministic initial expansion.
## Verification
- `pnpm exec vitest run ui/src/components/AttentionQueueRow.test.tsx`
- `pnpm check:token-gates`
- `pnpm --dir ui typecheck`
- `pnpm --dir ui build-storybook`
- QA visual verification (light + dark, no defects):
https://github.com/paperclipai/paperclip/pull/9532#issuecomment-4964769485
## Risks
- Low risk: the change is isolated to Decisions row rendering and
Storybook fixtures.
- Rows with images gain a new expansion interaction, but existing inline
resolver behavior and deep links remain intact.
- The gallery intentionally limits the in-row preview to three images to
avoid unbounded row height.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Anthropic Claude Opus 4.8 assisted with the original implementation
and tests.
- OpenAI `gpt-5.4` via Codex CLI assisted with current-master rebase
integration, verification, and PR preparation. The runtime
context-window size is not exposed; capabilities used include reasoning,
repository tool use, code execution, and 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to coordinate
AI-agent companies and their work.
> - Its recovery subsystem detects stranded issue execution and decides
whether to retry, escalate, or request operator intervention.
> - The existing recovery path used a mostly generic owner ladder and
generic execution contract, so transient failures could wake a manager
who then performed the deliverable instead of repairing and returning
the task.
> - Provider quota failures also entered the same takeover path even
when the correct action was to wait for capacity and retry the original
assignee.
> - Recovery actions already retain the source owner and evidence needed
to choose a cause-specific route, render a scoped contract, and measure
whether work was handed back.
> - This pull request adds a cause-keyed recovery playbook, propagates
its contract through every built-in adapter, and makes resolved recovery
actions return work to the original owner by default.
> - The benefit is bounded self-recovery that preserves task ownership,
avoids needless management takeover, and makes recovery outcomes
observable.
## Linked Issues or Issue Description
No matching public GitHub issue was found.
Related recovery work was reviewed but is not duplicated here: #9630
restores bounded recovery continuations, #8807 changes one
assignee-ranking case, and #9404 records runtime-failure transition
evidence. This change instead introduces cause-specific routing and
recovery contracts across the recovery lifecycle.
### What happened?
When an issue became stranded, recovery generally selected an owner
through the same fallback ladder and rendered the normal execution
contract. That made the recovery wake look like ordinary deliverable
work, even when the correct action was to retry the original agent,
repair its runtime, or wait for a provider quota reset.
### Expected behavior
Recovery should select a response by failure cause, tell the recipient
to recover rather than complete the deliverable, suppress takeover wakes
for provider quota waits, and return repaired work to its original
assignee unless the recovery owner explicitly completes it.
### Actual behavior
Recovery could escalate transient failures to management, omit the
cause-specific next action from the wake, and leave the recovery owner
assigned after the runtime problem was resolved.
### Impact
The generic path creates avoidable management work, ownership churn, and
budget consumption while obscuring whether recovery successfully
returned work to the responsible agent.
## What Changed
- Added cause-keyed routing for process loss, missing disposition,
provider quota limits, Codex output inactivity, workspace validation
failures, and fallback recovery causes.
- Added recovery-scoped wake rendering that replaces the generic
execution contract with the failure summary, original assignee, attempt
count, next action, and cause-specific playbook instruction.
- Propagated the structured recovery contract through all built-in
adapter execution paths, including Hermes local and gateway adapters.
- Added provider-quota wait monitoring so capacity failures schedule the
original assignee instead of enqueueing a takeover wake.
- Added hand-back behavior and `handed_back` / `owner_completed` outcome
accounting when recovery actions are resolved.
- Added focused routing, renderer, quota-monitor, and hand-back
regression coverage plus implementation-spec documentation.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-workspace-branch-containment.test.ts
server/src/__tests__/issue-recovery-actions.test.ts`
- 4 test files passed; 194 tests passed.
- Targeted `pnpm --filter ... typecheck` across
`@paperclipai/adapter-utils`, `@paperclipai/shared`,
`@paperclipai/server`, `@paperclipai/ui`, and all nine changed adapter
packages.
- 13 affected workspace packages passed typecheck.
- `pnpm check:token-gates`
- All UI token gates passed.
## Risks
- Recovery routing behavior changes for stranded work, so an incorrectly
classified cause could select a different recipient than before;
fallback causes retain the existing management ladder.
- Provider quota detection depends on structured failure evidence and
conservative text matching; unmatched failures continue through fallback
recovery.
- Adapter prompt plumbing changes across built-ins, covered by shared
renderer tests and compile-time call signatures.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex with exact model ID `gpt-5.6-sol`, using reasoning, tool
use, and code execution. The runtime does not expose its configured
context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies.
> - Company skills make reusable agent behavior discoverable and
editable from one place.
> - Projects already contain skill directories, but operators had to
import each skill path manually.
> - Copying those skills would break the desired write-through workflow
between Skill Studio and the source project.
> - The server therefore needs a safe preview/select/import contract
that only accepts rediscovered, workspace-contained candidates.
> - The UI needs a guided project picker that explains reference
semantics, handles conflicts, and remains usable on mobile.
> - This pull request adds that end-to-end project skill import flow
with authorization, tenant-scope, traversal, and symlink regression
coverage.
> - The benefit is faster bulk onboarding while keeping project files as
the single source of truth.
## Linked Issues or Issue Description
**Feature request**
**Problem:** Importing several skills already stored in a Paperclip
project requires operators to discover and submit each local path
individually. This is slow, hides which well-known directories were
searched, and makes conflict/already-imported states difficult to
evaluate before mutation.
**Proposed solution:** Add an “Import skills from project” flow that
previews skills from well-known directories, lets operators selectively
import eligible candidates, and stores local-path references so Skill
Studio edits write through to the project files.
**Alternatives considered:** Copying files into company-managed skill
storage was rejected because it creates divergent copies. Trusting
client-supplied paths was rejected because imports must be constrained
to server-rediscovered, workspace-contained candidates.
**Additional context:** GitHub duplicate search found no existing issue
or PR for this exact workflow. Refs #3799 for related skill-import
inventory behavior; this PR does not claim to close that issue.
## What Changed
- Extend `scan-projects` with backward-compatible preview and
selective-import modes, typed validation, candidate statuses, and
OpenAPI coverage.
- Discover project skills under `skills`, `.agents/skills`,
`.claude/skills`, `.codex/skills`, `.cursor/skills`, `.opencode/skills`,
and `.gemini/skills`.
- Re-discover selections server-side, enforce company/project/workspace
scope, and reject traversal or symlink escapes before creating
`local_path` references.
- Add the Skills-page menu entry and responsive project import dialog
with project selection, grouped candidates, select all/deselect all,
conflicts, empty/error/403 states, and import results.
- Add route, service, and component regressions for preview
authorization, cross-tenant selections, traversal/symlink safety,
selection counts, grouping, and result semantics.
### Screenshots
**Choose a project**

**Review discovered skills**

**Mobile selection footer**

**Import result**

## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/company-skills-routes.test.ts
ui/src/pages/skills/ImportSkillsFromProjectDialog.test.tsx` — 3 files,
81 tests passed.
- `pnpm check:token-gates` — all token gates clean.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- Security review passed after adding tenant-scope and
unauthorized-preview regressions; UX re-review approved desktop/mobile
surfaces; QA passed all seven acceptance areas including write-through
editing, deduplication, conflicts, empty state, and permission denial.
## Risks
- Files remain referenced in project workspaces, so moving or deleting a
source directory can make an imported skill unavailable; the UI
explicitly communicates the reference behavior.
- New well-known directory scans may discover more candidates than older
versions, but preview mode prevents mutation until the operator confirms
a selection.
- The endpoint remains backward compatible: omitting `mode` preserves
the prior full-import behavior.
- No schema migration or telemetry event changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Anthropic Claude Opus 4.8 with tool use/code execution assisted with
the UI implementation and UX polish. OpenAI Codex CLI with tool use/code
execution assisted with server implementation, security fixes,
regression coverage, integration, and PR preparation; the runtime did
not expose Codex's exact backing model ID or context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Its web UI keeps live views fresh with React Query, coordinated by a
live-events websocket (`/api/companies/:id/events/ws`) and cross-tab
polling
> - We already cut the worst live-update churn (#9569, #9624), but the
deeper issue is that even the "push" path is *push-the-signal,
pull-the-data*: a websocket event triggers `invalidateQueries` → an HTTP
refetch
> - The company live-runs list (`queryKeys.liveRuns`) is the
most-observed resource — the sidebar renders it on nearly every page —
so its refetch is the most ambient source of churn, fired on every
`heartbeat.run.queued` / `heartbeat.run.status` event
> - Those events already carry enough (`runId`, `status`) to update the
cached list directly, so this pull request event-sources that list
instead of refetching it
> - The benefit is that the always-observed live-runs list stops
refetching on run lifecycle events — the first concrete step of the
push-over-poll redesign
## Linked Issues or Issue Description
No public GitHub issue exists; describing inline per CONTRIBUTING.md →
"Link Issues or Describe Them In-PR", following the bug report template.
This continues the memory/CPU-churn work from #9569 and #9624.
**What happened?**
Live agent-run tabs accrue high idle CPU and off-heap memory because
live-update events cause HTTP refetches. Profiling showed the company
live-runs list — observed on almost every page via the sidebar — being
refetched on every run status change, one of the most frequent ambient
refetches.
**Expected behavior**
A websocket event that already carries the changed data should update
the cached list directly, without an HTTP round-trip, so the
always-observed live-runs list does no refetch on routine run lifecycle
events.
**Steps to reproduce**
Open the app with agents running and watch the network panel: `GET
/api/companies/:id/live-runs` fires on each `heartbeat.run.status` /
`heartbeat.run.queued` event even though the event payload already
describes the change.
**Paperclip version or commit**
Branch `perf/live-runs-event-sourced`, off `master` (after #9624).
**Deployment mode**
Local dev (`pnpm dev`), web UI. Core UI live-updates plumbing; not
adapter-specific.
## What Changed
- **`ui/src/lib/live-runs-cache.ts` (new)** — pure `removeRunFromList` /
`patchRunStatusInList` helpers for the cached `LiveRunForIssue[]`.
- **`ui/src/context/LiveUpdatesProvider.tsx`** — on
`heartbeat.run.queued` / `heartbeat.run.status`, patch
`liveRuns(companyId)` in place instead of invalidating it:
- terminal status → remove the run from the list,
- status change on a run already in the list → update it in place,
- a genuinely new run (can't be reconstructed from the event) → fall
back to a single `invalidateQueries` refetch.
- Removed the blanket `liveRuns` invalidation from
`invalidateHeartbeatQueries`.
- On websocket **reconnect**, refetch `liveRuns` once to reconcile
events missed while disconnected (durable replay is a later phase).
- Other resources these events invalidate (`dashboard`, `costs`,
`sidebarBadges`, `agents.list`, agent detail) are unchanged — they're
lower-frequency / less-often-observed and are follow-up phases. This
keeps the change scoped and **client-only** (no server changes).
## Verification
- `vitest`: new `live-runs-cache.test.ts` (remove/patch/no-op/undefined)
and new lifecycle-handler cases in `LiveUpdatesProvider.test.ts`
(terminal→remove, present→patch, new→needs-refetch) via
`__liveUpdatesTestUtils`. All existing `LiveUpdatesProvider` tests still
pass (33 total across the two files).
- `tsc -b` clean.
- Runtime: the event-sourced path is covered by unit tests; end-to-end
refetch reduction should be re-measured against a rebuilt bundle with
the network panel / MCP instrumentation.
## Risks
Low, and client-only.
- **Staleness across a dropped connection:** an event missed while the
socket is down isn't replayed yet, so the reconnect handler refetches
`liveRuns` once to reconcile. Durable event replay (Last-Event-ID) is a
planned later phase; until then reconnect-reconcile covers the gap.
- **New-run fallback:** a genuinely new run still triggers one refetch
(it can't be reconstructed from the event alone), so no new runs are
missed.
- Aggregate resources (dashboard/costs/badges) are untouched and still
invalidate (already coalesced), so their behavior is unchanged.
Follow-up phases (from the design discussion): event-source
`activity`/comments and the remaining class-B resources; give pure-poll
resources events and drop their intervals; add durable event sequence +
reconnect replay; and a shared bus (Postgres `LISTEN/NOTIFY`) only when
the API tier scales to >1 replica.
## Model Used
- **Provider:** Anthropic, via the Claude Code CLI.
- **Model:** Claude Opus 4.8 (`claude-opus-4-8`).
- **Reasoning mode:** Extended thinking enabled.
- **Capabilities used:** tool use (shell, file editing), sub-agent
fan-out to inventory the client polling and server push infrastructure,
and the Chrome DevTools MCP to reproduce/profile the churn that
motivated this redesign.
## 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 (a perf/plumbing change, not planned core feature
work)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (continues #9569 / #9624; no duplicates)
- [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 considered and documented any risks above
- [ ] I have updated relevant documentation to reflect my changes (N/A —
no user-facing docs; rationale documented inline)
- [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: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Its web UI keeps live views (issue threads, run transcripts,
dashboard) fresh via React Query polling plus a live-events websocket,
coordinated across tabs with a `BroadcastChannel` layer
> - Long-lived tabs viewing live agent runs grew to multi-GB memory
footprints while their JS heap stayed ~60–200 MB — so the memory is
off-heap (Blink/native + committed allocator arenas), not a classic JS
leak
> - Live profiling of a reproduced 2.7 GB / 66 MB tab showed 15–30% idle
CPU, ~3 fetches/sec across overlapping poll loops, and ~8 `setInterval`
create/clear cycles per second whose rate grew ~7× as the tab aged —
relentless allocation churn that inflates committed memory the OS never
reclaims, amplified across tabs by the cross-tab fan-out
> - This pull request cuts that churn at its four largest sources
(invalidation storm, per-instance 1 s timers, redundant polling,
unbounded streamed-run set)
> - The benefit is that idle tabs do far less periodic work, so their
off-heap footprint stops ballooning over a long session
## Linked Issues or Issue Description
No public GitHub issue exists; describing inline per CONTRIBUTING.md →
"Link Issues or Describe Them In-PR", following the bug report template.
**What happened?**
Browser tabs viewing live agent runs grew to 8–16 GB memory footprint
over a long session (multiple tabs open), while each tab's "live" JS
heap stayed only ~150–250 MB. Every idle tab also burned 15–30% CPU.
Tabs eventually approached the ~4 GB V8 heap ceiling / OS pressure and
could crash.
**Expected behavior**
Tabs viewing live runs should hold a bounded footprint and do minimal
work while idle, regardless of how long they stay open or how many tabs
are open.
**Steps to reproduce**
Open several issue/run tabs that have agents actively streaming and
leave them open for a while. Watch Chrome's Task Manager: Memory
Footprint climbs into the GBs while "JavaScript Memory" stays small, and
CPU stays high on idle tabs. Reproduced in ~90 minutes: a tab reached
2.7 GB footprint on a 66 MB JS heap, and the per-second timer-churn rate
was ~7× higher on a 90-minute-old tab than a fresh one.
**Paperclip version or commit**
Branch `fix/live-updates-churn`, off `master`.
**Deployment mode**
Local dev (`pnpm dev`), web UI. Not adapter-specific — core UI
live-updates plumbing (observed with `claude_local` / `codex_local`
runs).
## What Changed
- **`ui/src/lib/query-invalidation-batcher.ts` (new)** —
`createInvalidationBatcher` throttles + de-dupes React Query
invalidations into one flush per ~300 ms, and
`createCoalescingQueryClient` wraps the client via a `Proxy` so only
`invalidateQueries` is batched (optimistic `setQueryData` writes stay
immediate). Wired into `LiveUpdatesProvider`, which previously
invalidated synchronously on every websocket event.
- **`ui/src/hooks/useSecondTick.ts` (new)** — one shared, ref-counted,
page-wide 1 s ticker. `useLiveElapsed` in `IssueChatThread` now uses it
instead of a per-instance `setInterval` that forced a full-thread
re-render every second per live element.
- **`ui/src/components/transcript/useLiveRunTranscripts.ts`** — when the
realtime websocket is enabled, the recurring log poll backs off to a 30
s safety-net cadence instead of polling every 2 s on top of the live
stream. Added a marker for the durable poll→push rearchitecture.
- **`ui/src/lib/issueChatTranscriptRuns.ts`** —
`resolveIssueChatTranscriptRuns` now caps the streamed run set
(live/active runs always kept; most-recent linked runs fill up to 20) so
a large run history can't open a live-transcript poll per historical
run.
- **`ui/src/main.tsx`** — explicit `gcTime` so cross-tab-published cache
entries for unobserved resources are collected promptly.
- Tests for the batcher, shared ticker, and run cap.
## Verification
- `vitest`: new suites `query-invalidation-batcher.test.ts` (batcher
collapses 20 invalidations → 1 flush; keeps distinct keys/variants;
dispose cancels; proxy passes non-invalidate methods through),
`useSecondTick.test.tsx` (single ref-counted timer, stops when idle),
`issueChatTranscriptRuns.test.ts` (cap keeps newest + live). All pass.
- Existing affected suites pass: `LiveUpdatesProvider` (23),
`IssueChatThread` (), `useLiveRunTranscripts`,
`AgentDetail.instructions` — 109 tests across affected files.
- `tsc -b` clean.
- Behavior confirmed by live profiling before the change (2.7 GB / 66 MB
tab, ~8 interval churns/sec growing 7× with age). Runtime churn
reduction should be re-measured against a rebuilt bundle with the same
instrumentation.
## Risks
Low-to-moderate; all changes reduce work rather than add features.
- **Invalidation batching** delays live-driven refetches by up to ~300
ms. Optimistic `setQueryData` writes (e.g. the visible issue's new
comment) remain immediate, so foreground updates still feel instant;
only the safety-net refetch is throttled. Non-live invalidations (user
actions, mutations) are unaffected — they use the real client.
- **Poll back-off** relies on the websocket as the live source when
realtime is enabled; a 30 s fallback poll still covers gaps/reconnects
(both the transcript hook and `LiveUpdatesProvider` also
auto-reconnect).
- **Run cap (20)** means an issue with a very large run history streams
live transcripts only for its live/active + 20 most-recent runs; older
runs still open normally via their run pages.
- Downstream test fallout (timing-sensitive tests around
invalidation/polling) may need adjustment — flagged intentionally for
follow-up.
Durable follow-up (out of scope, marked in code): replace transcript/run
polling with server push (SSE/websocket deltas) so idle tabs do no
periodic work at all.
## Model Used
- **Provider:** Anthropic, via the Claude Code CLI.
- **Model:** Claude Opus 4.8 (`claude-opus-4-8`).
- **Reasoning mode:** Extended thinking enabled.
- **Capabilities used:** tool use (shell, file editing), sub-agent
fan-out for codebase analysis, and the Chrome DevTools MCP to reproduce
and profile the memory/CPU churn on a live instance.
## 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 (a bug/perf fix, not planned core feature work)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (none found)
- [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 considered and documented any risks above
- [ ] I have updated relevant documentation to reflect my changes (N/A —
no user-facing docs; rationale documented inline)
- [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: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip uses company skills to make agent capabilities reusable
across an organization.
> - Skill operations currently mix capability availability with
permission checks, which creates avoidable setup friction and
inconsistent denial handling.
> - The policy contract needs to remain open by default while allowing
company-scoped restrictions for governed deployments.
> - Core owns the canonical policy actions, persistence, evaluation, API
behavior, safe import boundaries, and generic denial/read-only UI.
> - Enterprise policy-editor implementation belongs in the separate
`paperclip-ee` repository and is intentionally excluded from this PR.
### Problem or motivation
Company skill operations can encounter permission dead ends even when no
explicit restriction has been configured, and import-source
classification can drift between policy evaluation and execution.
### Proposed solution
Define eight canonical skill policy actions, default all actions to
allowed, persist company-scoped restrictions, expose policy evaluation
APIs, normalize import sources at the boundary, and update Skill Studio
to present actionable restriction states without embedding Enterprise
Edition implementation in the core repository.
### Alternatives considered
Keeping capability checks distributed across routes and UI surfaces was
rejected because it duplicates policy logic and makes denial behavior
inconsistent. Shipping the Enterprise policy editor in this repository
was rejected because `paperclip-ee` is a separate repository and must
receive its own PR.
### Roadmap alignment
Extends the completed **Skills Manager** roadmap area by adding coherent
governance and removing workflow dead ends.
### Additional context
The core API contract remains suitable for a separate Enterprise Edition
editor, but this PR contains no `paperclip-ee` package or EE-specific UI
integration code.
## What Changed
- Added the company skill policy contract to product and implementation
documentation, including the open-by-default rule, eight canonical
actions, decision shape, and core/EE ownership boundary.
- Added the company-scoped policy schema, migration `0170`, shared
validators, policy service, REST routes, OpenAPI coverage, and focused
server tests.
- Hardened import policy enforcement by normalizing import sources and
keeping source classification consistent between policy evaluation and
execution.
- Updated core Skill Studio behavior to remove generic permission dead
ends and show actionable policy/platform denial states only when an
operation is actually denied.
- Removed the `plugin-paperclip-ee` package, Docker wiring, EE
discovery/deep-link helpers, and EE-specific UI tests/stories from this
PR so that implementation can be submitted separately to the EE
repository.
- Preserved open-by-default behavior when no explicit company
restriction exists.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/skill-studio/SkillPolicySurfaces.test.tsx
src/lib/skill-policy-denial.test.ts` — 20/20 passed.
- `pnpm --filter @paperclipai/ui exec tsc --noEmit` — passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/worktree-config.test.ts` — 12/12 passed.
- `pnpm check:token-gates` — passed with all gates clean.
- `git diff --check` — passed.
- `git diff --name-only origin/master | rg
'paperclip-ee|ee-skill-policy'` — no matches.
## Risks
- Migration `0170` introduces company policy persistence; rollout
depends on the migration applying before policy routes are exercised.
- Open-by-default is an intentional behavioral policy: deployments
expecting implicit denials must configure explicit restrictions.
- Import normalization is security-sensitive and should retain focused
review.
- The separate EE editor must stay contract-compatible with the core
policy API as policy actions evolve.
## Model Used
- OpenAI Codex CLI, runtime model identifier and context-window size not
exposed by this execution environment; reasoning, repository tool use,
shell execution, and code review capabilities enabled.
## 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 available to this runtime)
- [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 or
described the result 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 and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run focused 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 on the latest head
- [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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Evyatar Bluzer <bluzername@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Governed MCP access spans contracts, runtime enforcement, adapters,
UI surfaces, and operator verification
> - The parity reference PR #9534 is too large for effective automated
or human review
> - The feature therefore needs a linear stack whose individual diffs
stay below the 100-file review limit
> - This pull request is split 7/8 and focuses on Apps/Gateways UI and
feature-flagged activation
> - The benefit is a standalone, testable review boundary while
preserving byte-for-byte parity at the top of the stack
## Linked Issues or Issue Description
- Related parity reference: #9534
- Problem: The standalone UI foundation needs feature-flagged routes,
navigation, app detail flows, gateways, Storybook scenarios, and QA
configuration.
- Proposed solution: Adds Apps/Gateways pages and components,
navigation/route activation, remaining page integrations, Storybook
stories, and QA Vite configuration.
- Alternatives considered: keeping #9534 as one 403-file review, or
rewriting the feature to manufacture seams; both were rejected in favor
of path extraction plus compile-driven boundary moves.
- Roadmap alignment: this advances the existing governed MCP/tool-access
work already represented by #9534; it does not introduce a separate
roadmap initiative.
- Stack position: base branch is
`pap10341-split/06-ui-tools-foundation`.
- Merge policy: merge bottom-up, in order, only after the complete
eight-PR stack has been reviewed and the top-of-stack parity gate
remains empty.
- Requested review: UXDesigner sanity pass on Apps/Gateways flows and
flag-off behavior; Greptile on every PR.
## What Changed
- Adds Apps/Gateways pages and components, navigation/route activation,
remaining page integrations, Storybook stories, and QA Vite
configuration.
- Keeps this PR below 100 changed files and independently typecheckable.
- Preserves the final tree from #9534 when combined with the other seven
stack levels.
## Verification
- `pnpm typecheck`
- `pnpm check:token-gates` — all gates clean
- Focused UI Vitest run with `NODE_ENV=test` — 17 files, 147 tests
passed
## Risks
- Navigation or flag regressions could expose incomplete experiences;
activation remains controlled by existing experimental settings.
- Stack risk: merging out of order can expose incomplete layers;
mitigate by following the documented bottom-up merge policy.
- Parity risk: later edits to an intermediate branch can drift from
#9534; mitigate by re-running the empty top-of-stack diff before merge.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, exact model ID `gpt-5.4`; runtime-managed context
window; medium reasoning with repository, shell, Git, GitHub CLI, and
code-execution tools enabled.
## 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] Internal references are omitted except the execution-plan link
explicitly required for this coordinated split stack
- [x] My branch name describes the change 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Stack Coordination
- Internal execution plan:
[PAP-13874](/PAP/issues/PAP-13874#document-plan)
- Parity reference: #9534
- Stack: #9556 → #9557 → #9558 → #9559 → #9560 → #9561 → #9562 → #9563
- Merge bottom-up only after full-stack review and an empty parity diff
at #9563.
## UI Evidence
QA captured these from the live Garden MCP split stack at 1440px and
verified clean rendering:



---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Governed MCP access spans contracts, runtime enforcement, adapters,
UI surfaces, and operator verification
> - The parity reference PR #9534 is too large for effective automated
or human review
> - The feature therefore needs a linear stack whose individual diffs
stay below the 100-file review limit
> - This pull request is split 6/8 and focuses on UI API, shared
components, and Tools/Profile surfaces
> - The benefit is a standalone, testable review boundary while
preserving byte-for-byte parity at the top of the stack
## Linked Issues or Issue Description
- Related parity reference: #9534
- Problem: Operators need typed clients and administration surfaces that
compile independently before navigation exposes them.
- Proposed solution: Adds UI APIs, hooks, libraries, shared components,
Tools/Profiles pages, and the plugin settings consumer required by the
new company-scoped API.
- Alternatives considered: keeping #9534 as one 403-file review, or
rewriting the feature to manufacture seams; both were rejected in favor
of path extraction plus compile-driven boundary moves.
- Roadmap alignment: this advances the existing governed MCP/tool-access
work already represented by #9534; it does not introduce a separate
roadmap initiative.
- Stack position: base branch is
`pap10341-split/05-runtime-integration`.
- Merge policy: merge bottom-up, in order, only after the complete
eight-PR stack has been reviewed and the top-of-stack parity gate
remains empty.
- Requested review: UXDesigner sanity pass on Tools/Profile surfaces;
Greptile on every PR.
## What Changed
- Adds UI APIs, hooks, libraries, shared components, Tools/Profiles
pages, and the plugin settings consumer required by the new
company-scoped API.
- Keeps this PR below 100 changed files and independently typecheckable.
- Preserves the final tree from #9534 when combined with the other seven
stack levels.
## Verification
- `pnpm typecheck`
- `pnpm check:token-gates` — all gates clean
- Focused UI Vitest run with `NODE_ENV=test` — 28 files, 183 tests
passed
## Risks
- Large dead-code UI additions can drift from activation routes; PR 7
supplies the registration layer and top-level parity catches omissions.
- Stack risk: merging out of order can expose incomplete layers;
mitigate by following the documented bottom-up merge policy.
- Parity risk: later edits to an intermediate branch can drift from
#9534; mitigate by re-running the empty top-of-stack diff before merge.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, exact model ID `gpt-5.4`; runtime-managed context
window; medium reasoning with repository, shell, Git, GitHub CLI, and
code-execution tools enabled.
## 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] Internal references are omitted except the execution-plan link
explicitly required for this coordinated split stack
- [x] My branch name describes the change 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Stack Coordination
- Internal execution plan:
[PAP-13874](/PAP/issues/PAP-13874#document-plan)
- Parity reference: #9534
- Stack: #9556 → #9557 → #9558 → #9559 → #9560 → #9561 → #9562 → #9563
- Merge bottom-up only after full-stack review and an empty parity diff
at #9563.
## UI Evidence
QA captured these from the live Garden MCP split stack at 1440px and
verified clean rendering:



---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Its server and UI test suites protect company-scoped plugin access
and instance settings behavior
> - Recent governed-access contracts intentionally added company
invocation scope and new experimental-setting defaults
> - Four existing tests were not updated consistently with those
contracts, causing current-master CI failures unrelated to the changes
under review
> - The runtime behavior is intentional, so changing production code
would weaken the new authorization and settings contracts
> - This pull request aligns the stale tests with current behavior and
removes one UI assertion accidentally pulled forward from a later
stacked feature
> - The benefit is a focused, low-risk repair that restores master CI
without changing application behavior
## Linked Issues or Issue Description
- **Bug:** Current master has four regression failures in plugin
authorization, plugin execution-workspace bridging, instance settings
normalization, and experimental settings UI tests.
- **Expected behavior:** Tests provide required company/invocation
scope, use the governed object-shaped secret reference contract, include
all current defaults, and only assert UI controls implemented at this
stack level.
- **Actual behavior:** Tests exercised obsolete request shapes or
expected a later-stack Apps toggle that is not present on current
master.
- **Reproduction:** Run the four test files listed in the Verification
section on master before this commit.
## What Changed
- Updates plugin config authorization coverage to include company scope
and an object-shaped `secret_ref` binding.
- Supplies invocation company scope to execution-workspace host-client
tests.
- Adds `enableApps` and `enableSmokeLab` to normalized settings
expectations.
- Removes the premature Apps toggle UI test introduced without its
later-stack implementation.
## Verification
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/plugin-execution-workspace-bridge.test.ts
server/src/__tests__/instance-settings-service.test.ts
ui/src/pages/InstanceExperimentalSettings.test.tsx` — 73 tests passed.
- `pnpm exec vitest run
packages/plugins/sdk/tests/host-client-factory.test.ts
server/src/__tests__/plugin-secrets-handler.test.ts
server/src/__tests__/instance-settings-routes.test.ts
ui/src/lib/instance-settings.test.ts` — 39 tests passed.
- `git diff --check` — passed.
## Risks
- Low risk: test-only changes with no production runtime, schema, API,
or UI behavior changes.
- The removed Apps toggle assertion should return in the later stacked
change that introduces the actual control.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, exact model ID `gpt-5.4`; runtime-managed context
window; medium reasoning with repository, shell, GitHub CLI, and
code-execution tools enabled.
## 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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Governed MCP access spans contracts, runtime enforcement, adapters,
UI surfaces, and operator verification
> - The parity reference PR #9534 is too large for effective automated
or human review
> - The feature therefore needs a linear stack whose individual diffs
stay below the 100-file review limit
> - This pull request is split 2/8 and focuses on database schema and
shared governance contracts
> - The benefit is a standalone, testable review boundary while
preserving byte-for-byte parity at the top of the stack
## Linked Issues or Issue Description
- Related parity reference: #9534
- Problem: The governed access model needs additive persistence and
synchronized shared types before server enforcement can compile.
- Proposed solution: Adds migrations 0148–0169, tool-access and Smoke
Lab schema, shared types/validators/gallery helpers, and the minimal
compile-required contract consumers identified by boundary testing.
- Alternatives considered: keeping #9534 as one 403-file review, or
rewriting the feature to manufacture seams; both were rejected in favor
of path extraction plus compile-driven boundary moves.
- Roadmap alignment: this advances the existing governed MCP/tool-access
work already represented by #9534; it does not introduce a separate
roadmap initiative.
- Stack position: base branch is `pap10341-split/01-demo-servers`.
- Merge policy: merge bottom-up, in order, only after the complete
eight-PR stack has been reviewed and the top-of-stack parity gate
remains empty.
- Requested review: QA for migrations/validators; Greptile on every PR.
## What Changed
- Adds migrations 0148–0169, tool-access and Smoke Lab schema, shared
types/validators/gallery helpers, and the minimal compile-required
contract consumers identified by boundary testing.
- Keeps this PR below 100 changed files and independently typecheckable.
- Preserves the final tree from #9534 when combined with the other seven
stack levels.
## Verification
- `pnpm typecheck` — passed, including migration numbering and safety
checks
- `pnpm --filter @paperclipai/db test` — passed
- `pnpm --filter @paperclipai/shared test` — passed
## Risks
- Migration or contract mistakes could affect every upper layer; all
migrations are additive/idempotent and compile consumers are included in
this boundary.
- Stack risk: merging out of order can expose incomplete layers;
mitigate by following the documented bottom-up merge policy.
- Parity risk: later edits to an intermediate branch can drift from
#9534; mitigate by re-running the empty top-of-stack diff before merge.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, exact model ID `gpt-5.4`; runtime-managed context
window; medium reasoning with repository, shell, Git, GitHub CLI, and
code-execution tools enabled.
## 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] Internal references are omitted except the execution-plan link
explicitly required for this coordinated split stack
- [x] My branch name describes the change 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Stack Coordination
- Internal execution plan:
[PAP-13874](/PAP/issues/PAP-13874#document-plan)
- Parity reference: #9534
- Stack: #9556 → #9557 → #9558 → #9559 → #9560 → #9561 → #9562 → #9563
- Merge bottom-up only after full-stack review and an empty parity diff
at #9563.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents stream their run output live into the web UI, viewed per-run
in the `AgentDetail` transcript viewer
> - Browser tabs holding these views were sitting at 8–16 GB of memory
footprint while their JS heap stayed at ~256 MB — a 60×+ gap, meaning
the cost is in retained DOM / render objects, not JS objects
> - The `LogViewer` in `AgentDetail.tsx` kept every streamed
stdout/stderr line and structured event in unbounded React state and
rendered each into a rich DOM block (the default "nice" mode has no
virtualization), so a run streaming for hours grew an unbounded live DOM
tree
> - The sibling `useLiveRunTranscripts` hook (used by `IssueChatThread`)
already bounds its buffers and virtualizes; `LogViewer` bypassed it and
managed its own uncapped state — that inconsistency is the bug
> - This pull request caps the live buffers and bounds the live DOM
render, aligning `LogViewer` with the already-bounded path
> - The benefit is that long-lived streaming tabs no longer grow without
bound, cutting multi-GB tabs back to a bounded footprint
## Linked Issues or Issue Description
No public GitHub issue exists; describing inline per CONTRIBUTING.md →
"Link Issues or Describe Them In-PR", following the bug report template.
**What happened?**
Chrome's Task Manager showed multiple long-lived Paperclip tabs
(agent-run / task views) each consuming 8–16 GB of memory footprint,
while each tab's JS heap stayed at only ~150–256 MB. Memory grew
monotonically the longer a run streamed.
**Expected behavior**
A tab viewing a live agent run should hold a bounded amount of memory
regardless of how long the run streams.
**Steps to reproduce**
Open an agent run with a long-running / high-volume stream in
`AgentDetail`, leave the tab open while output streams for an extended
period, and watch the tab's memory footprint climb without bound in
Chrome's Task Manager.
**Paperclip version or commit**
`3991a19a` (branch `fix/agent-run-transcript-memory`, off `master`).
**Deployment mode**
Local dev (`pnpm dev`), web UI. Not adapter-specific — core UI bug in
the shared transcript viewer.
## What Changed
- Add `ui/src/lib/live-log-buffer.ts`: a pure `appendCapped(prev,
additions, max)` helper plus caps `MAX_LIVE_LOG_LINES=5000`,
`MAX_LIVE_EVENTS=2000`, and `LIVE_TRANSCRIPT_RENDER_LIMIT=1500`, with
rationale documented in the module.
- Add `ui/src/lib/live-log-buffer.test.ts`: 6 unit tests (append,
trim-to-cap, oversized batch, exact-cap, no-mutation, referential
bail-out).
- `ui/src/pages/AgentDetail.tsx` (`LogViewer`): route all four
live-append sites (WebSocket log / progress / event, plus the poll
fallback) through `appendCapped`, and pass
`limit={LIVE_TRANSCRIPT_RENDER_LIMIT}` to `RunTranscriptView` for live
runs so the "nice" view mounts only the most recent blocks.
- The terminated-run "Load more log" pagination is deliberately left
**uncapped** (guarded by `isLive`), so no historical output is lost —
older output remains on the server and reachable there.
## Verification
- `vitest run src/lib/live-log-buffer.test.ts` → 6/6 pass.
- Existing suites `RunTranscriptView.test.tsx`,
`AgentDetail.instructions.test.tsx`, `useLiveRunTranscripts.test.tsx` →
22/22 pass.
- `tsc -b` (UI) → clean.
- Manual/behavioral: live runs tail the last ~1500 blocks; terminated
runs still render full history via "Load more log". Follow-up planned to
profile before/after with the Chrome DevTools MCP.
## Risks
Low risk. Changes only bound **in-memory state for live runs**; the
terminated-run paginated path is untouched (still uncapped, guarded by
`isLive`). No API, schema, or persistence changes. Worst case for a live
run is that only the most recent 5000 lines / 1500 rendered blocks are
visible in the tab — which is the intended "tail" behavior, and full
history remains on the server.
## Model Used
- **Provider:** Anthropic, via the Claude Code CLI.
- **Model:** Claude Opus 4.8 (`claude-opus-4-8`).
- **Reasoning mode:** Extended thinking enabled.
- **Capabilities used:** tool use (shell execution, file editing),
sub-agent fan-out for the codebase memory sweep, and the Chrome DevTools
MCP for the diagnosis phase.
## 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 (the ROADMAP "Memory" item is about company/agent
knowledge, unrelated to this browser-tab memory fix)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (none found among open PRs)
- [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 considered and documented any risks above
- [ ] I have updated relevant documentation to reflect my changes (N/A —
no user-facing docs affected; rationale is documented inline in
`live-log-buffer.ts`)
- [ ] All Paperclip CI gates are green (in progress at time of writing)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(the only open P2 was this missing template, which this update resolves;
awaiting re-review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Bumps [react-i18next](https://github.com/i18next/react-i18next) from
17.0.8 to 17.0.9.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md">react-i18next's
changelog</a>.</em></p>
<blockquote>
<h2>17.0.9</h2>
<ul>
<li>fix: allow TypeScript 7 in the optional <code>typescript</code> peer
dependency range (<code>^5 || ^6 || ^7</code>). With
<code>typescript@7.0.2</code> in a project, <code>npm install</code>
failed with an <code>ERESOLVE</code> peer conflict. Fixes <a
href="https://redirect.github.com/i18next/react-i18next/issues/1927">#1927</a>,
thanks <a
href="https://github.com/andikapradanaarif"><code>@andikapradanaarif</code></a>.</li>
<li>fix(types): <code><Trans t={t} ns="ns" …></code>
with a <code>t</code> from <code>useTranslation(['ns'])</code> now
typechecks under TypeScript 7. TS7 intersects the <code>Ns</code>
inference candidates coming from the <code>t</code> prop (<code>readonly
['ns']</code>) and the <code>ns</code> prop (<code>'ns'</code>) into an
unsatisfiable <code>'ns' & readonly ['ns']</code>, where TS6
resolved them. The <code>ns</code> prop on <code>TransProps</code>,
<code>TransSelectorProps</code> and
<code>IcuTransWithoutContextProps</code> now also accepts a single
namespace out of an array-typed <code>Ns</code> (<code>Ns | (Ns extends
readonly (infer S extends string)[] ? S : never)</code>) — which matches
runtime behavior and is unchanged under TS5/TS6.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8b4a9ea139"><code>8b4a9ea</code></a>
17.0.9</li>
<li><a
href="422bab13d4"><code>422bab1</code></a>
fix: support typescript 7 — widen peer range and fix Trans ns inference
under...</li>
<li><a
href="6e18aa95b5"><code>6e18aa9</code></a>
README: mention npx i18next-cli localize as the zero-to-localized
path</li>
<li>See full diff in <a
href="https://github.com/i18next/react-i18next/compare/v17.0.8...v17.0.9">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps
[@storybook/react-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-vite)
from 10.4.6 to 10.5.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/releases">@storybook/react-vite's
releases</a>.</em></p>
<blockquote>
<h2>v10.5.0</h2>
<h2>10.5.0</h2>
<blockquote>
<p><em>Foundational changes for new AI workflows</em></p>
</blockquote>
<p>Storybook 10.5 contains hundreds of fixes and improvements:</p>
<ul>
<li>⚡️ Angular-vite framework: Modern, fast dev, docs, and test
(preview)</li>
<li>🌈 Vitest initialGlobals: Test across themes, viewports, locales</li>
<li>🤖 Agentic review: AI-curated visual changesets and search results
(experimental)</li>
<li>⚛️ React docgen service: Unified metadata across MCP, Docs, and
Controls (experimental)</li>
<li>🧑💻 Claude / Codex plugins: One-click ADE integration
(experimental)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md">@storybook/react-vite's
changelog</a>.</em></p>
<blockquote>
<h2>10.5.0</h2>
<blockquote>
<p><em>Foundational changes for new AI workflows</em></p>
</blockquote>
<p>Storybook 10.5 contains hundreds of fixes and improvements:</p>
<ul>
<li>⚡️ Angular-vite framework: Modern, fast dev, docs, and test
(preview)</li>
<li>🌈 Vitest initialGlobals: Test across themes, viewports, locales</li>
<li>🤖 Agentic review: AI-curated visual changesets and search results
(experimental)</li>
<li>⚛️ React docgen service: Unified metadata across MCP, Docs, and
Controls (experimental)</li>
<li>🧑💻 Claude / Codex plugins: One-click ADE integration
(experimental)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9dafcd22ed"><code>9dafcd2</code></a>
Bump version from "10.5.0-beta.2" to "10.5.0" [skip
ci]</li>
<li><a
href="448db85e65"><code>448db85</code></a>
Bump version from "10.5.0-beta.1" to "10.5.0-beta.2"
[skip ci]</li>
<li><a
href="a4ce9790a7"><code>a4ce979</code></a>
Bump version from "10.5.0-beta.0" to "10.5.0-beta.1"
[skip ci]</li>
<li><a
href="f0bf138a0a"><code>f0bf138</code></a>
Bump version from "10.5.0-alpha.11" to
"10.5.0-beta.0" [skip ci]</li>
<li><a
href="fac05a5741"><code>fac05a5</code></a>
Bump version from "10.5.0-alpha.10" to
"10.5.0-alpha.11" [skip ci]</li>
<li><a
href="4057c4169f"><code>4057c41</code></a>
Bump version from "10.5.0-alpha.9" to
"10.5.0-alpha.10" [skip ci]</li>
<li><a
href="da84210b49"><code>da84210</code></a>
Bump version from "10.5.0-alpha.8" to
"10.5.0-alpha.9" [skip ci]</li>
<li><a
href="c347410b9f"><code>c347410</code></a>
Bump version from "10.5.0-alpha.7" to
"10.5.0-alpha.8" [skip ci]</li>
<li><a
href="d16ab3008a"><code>d16ab30</code></a>
React: Align react-docgen versions</li>
<li><a
href="c9a1ac9f72"><code>c9a1ac9</code></a>
Bump version from "10.5.0-alpha.6" to
"10.5.0-alpha.7" [skip ci]</li>
<li>Additional commits viewable in <a
href="https://github.com/storybookjs/storybook/commits/v10.5.0/code/frameworks/react-vite">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open source control plane people use to manage
AI-agent companies
> - Operators need to identify the exact build running from the
persistent account menu
> - Formal releases already have a concise public version, but source
builds include a long derived version string
> - The derived version identifies a commit but does not expose the
source branch or a direct path to inspect the code
> - Server Git metadata is auth-sensitive, so the UI must also refresh
it when the current session changes
> - This pull request shows linked branch and commit metadata for source
builds while preserving `v<version>` for formal releases
> - The benefit is faster build diagnosis with correct metadata across
sign-in and sign-out transitions
## Linked Issues or Issue Description
### Pre-submission checklist
- [x] I searched existing open and closed issues and found no duplicate
implementing this exact account-menu behavior.
- [x] The behavior reproduces on `master`.
- [x] The behavior originates in Paperclip's core UI, not an adapter,
provider, or local configuration.
### What happened?
Source builds displayed the full derived version, such as
`2026.626.0+58.git.518fc71ce`, without linking the operator to the
corresponding source branch or commit.
### Expected behavior
Source builds should show the concise branch and short commit SHA with
links to GitHub, while formal releases should continue showing their
public version. Auth transitions should refresh the health metadata that
supplies those Git details.
### Steps to reproduce
1. Run Paperclip from a commit after a release tag.
2. Open the account menu.
3. Inspect the build label beneath the user identity.
4. Sign in or out and reopen the menu.
### Paperclip version or commit
Any source build whose server version uses the
`<version>+<count>.git.<sha>[.dirty]` format.
### Deployment mode
Local dev (`pnpm dev`) or authenticated deployments.
### Installation method
Built from source.
## What Changed
- Detect source-derived version strings and render the source branch
plus seven-character commit SHA in `SidebarAccountMenu`.
- Link source branches and commits to the canonical
`paperclipai/paperclip` GitHub repository.
- Extend server Git metadata with the full SHA and expose it through
health/OpenAPI contracts.
- Refresh auth-sensitive health metadata after sign-in and every
sign-out entry point.
- Preserve the existing `v<version>` label for formal releases and add
focused regression coverage.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run src/pages/Auth.test.tsx
src/components/SidebarAccountMenu.test.tsx
src/components/SidebarServerInfo.test.tsx`
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/health.test.ts src/__tests__/server-info.test.ts`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
- `git diff --check public/master...HEAD`
## Risks
- Low risk: formal release rendering retains the existing fallback
behavior when the source-version pattern does not match.
- Source links assume the build came from the canonical public
repository; fork-only branches or commits may not resolve there.
- Health metadata is invalidated after auth transitions, adding one
bounded refetch so the displayed Git details match the new session.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex using GPT-5.4 with medium reasoning, repository/tool
access, shell execution, and code editing; context-window size was not
exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators tune instance behavior through Settings → Experiments,
where experimental features are toggled on and off
> - The task graph liveness auto-recovery experiment shows a
confirmation dialog (preview of what would be recovered) before it is
enabled
> - After confirming with "Enable only" or "Enable and run", the
dialog's Radix overlay and the `pointer-events: none` body lock were
left behind, dimming the page and blocking all interaction until a
refresh
> - The dialog was unconditionally mounted and only closed inside the
mutation's `onSuccess`, so the overlay teardown depended on the mutation
outcome and could race or never happen
> - This pull request closes the dialog before the mutation fires in
both confirm flows, clears the pending preview alongside the open flag,
and mounts the dialog conditionally so its overlay fully unmounts
> - The benefit is that enabling an experiment behaves like every other
settings change: the dialog goes away, the page stays interactive, and
errors surface in the page-level error banner instead of a dead UI
## Linked Issues or Issue Description
No public GitHub issue exists for this bug; description follows the bug
report template. Refs #4587 (the PR that introduced the configurable
liveness auto-recovery controls this dialog belongs to).
**What happened?** In Settings → Experiments, toggling on "Task graph
liveness auto-recovery" and confirming via "Enable only" left the whole
UI dimmed and unclickable. The dialog content disappeared, but the modal
overlay and the `pointer-events: none` lock on `<body>` remained until a
full page refresh.
**Expected behavior:** Confirming (or dismissing) the auto-recovery
dialog should close it completely and return the page to a fully
interactive state, with the toggle reflecting the new setting.
**Steps to reproduce:**
1. Open Settings → Experiments.
2. Toggle on "Task graph liveness auto-recovery"; the confirmation
dialog with the recovery preview appears.
3. Click "Enable only".
4. The dialog content disappears but the page stays dimmed and nothing
is clickable; refreshing restores the UI and shows the setting was
applied.
**Paperclip version or commit:** master @ 634ae12 · **Deployment mode:**
local instance · **Area:** UI only
(`ui/src/pages/InstanceExperimentalSettings.tsx`).
## What Changed
- Added a `closeRecoveryPreview()` helper that resets both
`previewDialogOpen` and `pendingPreview` together, and used it
everywhere the dialog closes (confirm flows, run-mutation success, and
user dismissal).
- "Enable only" and "Enable and run" now close the dialog *before*
firing the mutation, so overlay teardown no longer depends on the
mutation outcome; mutation errors roll back the optimistic toggle and
surface in the existing page-level error banner.
- The `RecoveryPreviewDialog` is now conditionally mounted
(`previewDialogOpen ? <RecoveryPreviewDialog … /> : null`), guaranteeing
the Radix overlay and body pointer-events lock are fully removed when
closed.
- Added a regression test that walks the real flow — toggle on → preview
dialog appears → "Enable only" — and asserts the update payload, that
the dialog text and `[data-slot="dialog-overlay"]` element are gone, and
that the toggle reads enabled.
Credit: the implementation commit was authored by Cody — thanks! This PR
packages that fix for upstream review.
## Verification
- `pnpm vitest run src/pages/InstanceExperimentalSettings.test.tsx` in
`ui/` — 17/17 tests pass, including the new regression test.
- Independently re-verified beyond the committed assertions: with a
temporary assertion (not committed), confirmed
`document.body.style.pointerEvents` is `none` while the dialog is open
and released after "Enable only" — the actual "can't interact with
anything" symptom, not just overlay DOM removal.
- Manual check: open Settings → Experiments, toggle the auto-recovery
feature, click "Enable only" — the dialog closes, the page stays
interactive, and the toggle shows enabled without a refresh.
## Risks
- Low risk: change is confined to one page component's dialog lifecycle;
no API, schema, or shared-package changes.
- Behavioral shift: the dialog now closes immediately on confirm instead
of staying open with a pending spinner until the mutation resolves.
Errors are still surfaced via the page-level error banner, and the
optimistic toggle rolls back on failure.
## Model Used
- Implementation commit authored by the AI coding agent "Cody"
(Anthropic Claude-based agent). Review, independent verification, and PR
preparation by Claude (Anthropic), model ID `claude-fable-5`, extended
thinking with tool use (code execution, test runs).
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Cody <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The web UI leans on a shared design-token + component system so
surfaces stay visually consistent as they grow
> - Two small inconsistencies had crept in: several distinct blues were
used to signal "live/running" agent state across the sidebar, task
header, and chat thread; and in the Inbox an unread task's mark-read dot
was pushing that row's status icon and title one column right of read
rows
> - Both read as "not quite aligned" in daily use and undercut the
polish of the lists work that just landed
> - This pull request consolidates the live/running blues onto one
shared recipe and stops the unread dot from indenting the row
> - The benefit is one consistent "live" blue everywhere and Inbox rows
that line up whether read or unread
## Linked Issues or Issue Description
No public GitHub issue exists for this work; describing inline per the
bug-report template.
- **Problem**: (1) the same concept — an agent actively working —
rendered in three visibly different blues: the sidebar `N live` dot, the
task-detail "Live" badge, and the chat-thread "RUNNING" badge each used
a different token/recipe. (2) In the Inbox, unread rows carry a leading
mark-read dot that occupies the chevron column, but a per-row spacer was
still rendering in that same column — so on unread rows the status icon
+ title were shifted one column (~24px) further right than read rows.
Most visible when grouped by workspace.
- **Steps to reproduce**: open the Inbox with a mix of read and unread
tasks (group by workspace). The unread rows' status icons sit further
right than the read rows'. Separately, compare the blue of the sidebar
`N live` dot, a task's "Live" header badge, and a chat "RUNNING" badge —
they don't match.
- **Expected behavior**: unread and read rows align on the same status
column, with the unread dot centered on the workspace group chevron; and
all three "live/running" affordances share one blue.
## What Changed
- Added a shared `liveBlueBadge` recipe in `ui/src/lib/status-colors.ts`
and pointed the task-detail **Live** badge (`IssueDetail.tsx`) and the
chat-thread **RUNNING** badge (`IssueChatThread.tsx`) at it; removed the
now-redundant `brandChipBadge` usage from the chat thread and a stray
`🔵` breadcrumb prefix.
- Changed the sidebar **`N live`** dot (`SidebarNavItem.tsx`) to the
same `blue-600 / dark:blue-400` as its adjacent label text.
- **Inbox** (`Inbox.tsx`): skip the per-row leading spacer when the
unread mark-read dot is present, so the dot alone fills the chevron
column. Unread rows' status icon + title now sit in the same column as
read rows, and the dot centers on the workspace group chevron.
- **Test** (`Inbox.test.tsx`): added a regression test asserting an
unread leaf row renders the mark-read dot and drops the spacer, while a
read row keeps the spacer.
## Verification
- `pnpm typecheck` — clean (all packages)
- `pnpm check:token-gates` — 3/3 CLEAN
- `cd ui && pnpm vitest run src/pages/Inbox.test.tsx` — 14/14 (includes
the new regression test)
- Full Storybook visual suite (514 stories, both themes) — green locally
(CI cannot run this suite yet — the baseline-manifest archive is
unpublished, a pre-existing condition from #9134)
- Manual (workspace-grouped Inbox, 2× dark): measured the unread badge
center at the same x as the workspace chevron (276 = 276) and the
unread-row status icon at the same x as read-row status icons (292 =
292). Before/after screenshots in a PR comment below.
## Risks
Low risk — presentation only. No data, routing, or state changes. The
blue consolidation is a token/class swap; the Inbox change removes a
redundant spacer element on unread rows only (read rows and
non-grouped/mobile views are unaffected). The unread-row behavior is
covered by the new unit test.
## Model Used
Claude (Anthropic), Opus 4.8 — model id `claude-opus-4-8`; extended
thinking + tool use, driving local verification (typecheck, token gates,
vitest, Playwright visual suite + pixel measurements).
## 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
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source control plane people use to coordinate
AI-agent companies
> - Human operators use the Decisions attention queue to review and
resolve work that needs them
> - Decision rows were composed as a fixed content column plus a
right-side controls column
> - At phone widths, timestamps, actions, menus, and evidence thumbnails
compressed the decision headline until it was barely readable
> - This pull request makes each row respond to its own container width
and stacks metadata, content, evidence, and actions on narrow surfaces
while preserving the dense desktop layout
> - The benefit is a useful, thumb-reachable Decisions workflow on
phones and narrow side panels without regressing wide-screen density or
scrolling performance
## Linked Issues or Issue Description
No public GitHub issue exactly matches this bug, so it is described here
using the bug-report fields.
**What happened**
Decision rows used a fixed two-column layout. On narrow screens, the
right-hand timestamp, overflow menu, decision buttons, and optional
thumbnails squeezed the headline into a truncated sliver.
**Expected behavior**
Decision headlines should remain readable on mobile, supporting context
should flow below the headline, and primary actions should remain easy
to tap. Wide rows should retain the compact desktop presentation.
**Steps to reproduce**
1. Open the Decisions / What needs me surface with populated attention
items.
2. Reduce the row container to a phone-width layout (approximately
390px).
3. Observe rows with multiple actions or evidence thumbnails.
**Paperclip version / deployment mode**
Current `master`, board UI in local or hosted deployments.
**Related public work found during dedup search**
- Refs: #9311 — original What needs me attention queue work.
- Refs: #9468 — recent Decisions scrolling performance work preserved by
this change.
## What Changed
- Reworked `AttentionQueueRow` into a container-query-driven vertical
stack on narrow surfaces, with the existing compact layout restored at
wide row widths.
- Made decision titles wrap to two lines, moved project/evidence context
below the headline, and promoted actions to full-width mobile tap
targets.
- Preserved upstream row memoization and `content-visibility` scrolling
optimizations while rebasing onto current `master`.
- Added three 390px Storybook scenarios covering populated rows,
type/detail variants, and snoozed/dismissed curtains.
- Updated the focused row test to assert the new thumbnail/context
alignment.
## Verification
- `pnpm exec vitest run ui/src/components/AttentionQueueRow.test.tsx` —
1 file passed, 16 tests passed.
- `pnpm check:token-gates` — all token gates clean.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/ui build-storybook` — completed
successfully.
- `git diff --check public/master...HEAD` — passed.
## Risks
- Low risk: the behavior is isolated to the Decisions row presentation
and its Storybook coverage.
- Container-query breakpoints could need future visual tuning for
unusual embedded widths, but the wide layout remains available at the
row-level breakpoint.
- The mobile layout increases row height by design in exchange for
readable content and usable actions.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex CLI coding agent. The exact model ID and context-window
size are not exposed to this runtime; reasoning, repository editing,
shell execution, and test execution capabilities were enabled.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators use the Decisions page to review an uncapped attention
feed across active, snoozed, and dismissed items
> - Large feeds mounted every row eagerly, and routine interactions
re-rendered the full queue
> - That made initial paint and scrolling progressively slower as
decision history accumulated
> - This pull request bounds rendering, stabilizes row props, and lets
off-screen rows skip layout and paint work
> - The benefit is a responsive Decisions page even for companies with
large attention histories
## Linked Issues or Issue Description
### What happened?
Opening `/decisions` for a company with a large attention history
eagerly mounted every visible-feed row. Expanding, selecting,
dismissing, snoozing, or restoring an item could also re-render the
entire queue.
### Expected behavior
The page should render a bounded initial window, progressively reveal
more rows near the scroll boundary, and avoid re-rendering unaffected
rows during interactions.
### Steps to reproduce
1. Populate a company with hundreds of attention items.
2. Open `/decisions`.
3. Scroll and interact with individual rows.
4. Observe increasing initial render, layout, paint, and interaction
cost on the previous implementation.
### Paperclip version or commit
Reproduced on `master` before this PR.
### Deployment and installation
Local development, built from source. This is a core UI issue, not
adapter- or database-specific.
### Additional context
Searched open public issues and PRs; no duplicate was found.
## What Changed
- Added a pure `planAttentionRenderRows` helper that allocates one
render budget across active groups and open snoozed/dismissed curtains
in document order.
- Render 50 rows initially and add 100 more when the Decisions page
approaches the scroll boundary.
- Memoized `AttentionQueueRow`, stabilized parent callbacks and inbox
dismissal actions, and passed row items through a shared expand
callback.
- Added `content-visibility: auto` and intrinsic containment so
accumulated off-screen rows avoid unnecessary layout and paint work.
- Added render-plan coverage and a regression test proving identical row
props do not re-render after a parent update.
## Verification
- `pnpm -C ui typecheck`
- `pnpm -C ui exec vitest run src/lib/attention.test.ts
src/components/AttentionQueueRow.test.tsx
src/components/Sidebar.test.tsx src/pages/Inbox.test.tsx` — 96 tests
passed
- `pnpm check:token-gates` — all gates clean
## Risks
- Low risk: the change is UI-only and does not alter API or database
contracts.
- The main behavioral risk is incorrect row-budget accounting across
collapsed groups or open curtains; the pure planner has focused tests
for ordering, truncation, and collapsed/closed sections.
- Progressive rendering means rows beyond the current budget are
intentionally absent until scrolling nears the boundary, matching the
existing Issues list pattern.
> This is a targeted performance fix and does not overlap planned core
feature work in `ROADMAP.md`.
## Model Used
- Anthropic Claude Fable 5 assisted with the implementation using
repository tools and code execution.
- OpenAI Codex `gpt-5.6-sol` prepared and verified the PR with high
reasoning effort, repository tools, shell execution, and
GitHub/Paperclip API access. The runtime did not expose a context-window
size.
## 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 (no
documentation changes were required for this UI-only behavior)
- [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
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Execution workspaces can run managed services that must report
reliable lifecycle and readiness state
> - Service startup previously waited for readiness before committing
the starting row, making concurrent control actions see stale state
> - Fixed service ports also needed clearer configuration and ownership
diagnostics to avoid cross-workspace collisions
> - This pull request persists startup state before readiness, validates
port ownership, and exposes configurable service ports in the workspace
UI
> - The benefit is dependable service controls and actionable
diagnostics when workspace runtimes start slowly or compete for ports
## Linked Issues or Issue Description
### What happened?
Slow-starting workspace services could remain invisible to concurrent
stop/restart controls until readiness completed, and fixed-port
conflicts lacked enough ownership context for safe repair.
### Expected behavior
A starting service is persisted immediately, control operations can
observe it, configured ports are editable, and conflicts identify the
owning process/workspace.
### Steps to reproduce
1. Configure a workspace service that delays binding its HTTP port.
2. Start the service and immediately request another control action.
3. Observe stale persisted state before this change.
4. Configure two workspaces for the same fixed port and observe limited
conflict diagnostics.
### Paperclip version or commit
`origin/master` at `02e2dd271`
### Deployment mode
Local dev; built from source; not adapter-specific; database-backed
workspace runtime state.
## What Changed
- Commit the `starting` runtime-service row before waiting for readiness
and transition it after the probe completes.
- Add port-owner inspection and cross-workspace conflict details to
local service supervision.
- Preserve configurable runtime service ports through workspace
configuration updates.
- Surface service-port editing and validation in the execution workspace
details UI.
- Add server and UI regression coverage for slow readiness, concurrent
controls, port persistence, and conflict diagnostics.
## Verification
- `vitest --project @paperclipai/server
src/__tests__/workspace-runtime.test.ts
src/__tests__/execution-workspaces-service.test.ts` — 118 tests passed.
- `vitest --project @paperclipai/ui
src/pages/ExecutionWorkspaceDetail.service-ports.test.ts` — 4 tests
passed.
- `node scripts/check-token-gates.mjs` — all token gates clean.
## Risks
- Moderate risk: changes touch workspace service lifecycle persistence
and local process/port inspection.
- No schema migration is required; tests exercise slow readiness,
concurrent control, persisted ports, and cross-workspace conflicts.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.3 Codex, reasoning with repository tool use and
code execution; context-window size was not exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The board coordinates repeated API polling across tabs to reduce
redundant requests
> - The shared polling coordinator retained cached result and
publication entries after the last subscriber left
> - Dynamic polling keys could therefore grow those maps for the
lifetime of the page
> - This pull request evicts inactive keys while preserving useful
short-lived handoff state and request deduplication
> - The benefit is bounded client memory without regressing cross-tab
polling behavior
## Linked Issues or Issue Description
### What happened?
Shared polling cached result/publication entries indefinitely after a
polling key no longer had subscribers.
### Expected behavior
Inactive keys are eventually removed, while recently published values
remain available long enough for normal subscriber handoff.
### Steps to reproduce
1. Create and unsubscribe many distinct shared polling keys in one page
lifetime.
2. Inspect the coordinator's cached results and publication timestamps.
3. Observe that the old maps retain every historical key.
### Paperclip version or commit
`origin/master` at `02e2dd271`
### Deployment mode
Local dev; built from source; not adapter-specific; not
database-related.
## What Changed
- Track inactive polling keys and schedule bounded cache eviction.
- Preserve cached data while a key is active or inside its retention
window.
- Cancel stale cleanup timers when polling resumes and clear coordinator
caches during disposal.
- Add focused fake-timer coverage for retention, resubscription, and
disposal behavior.
## Verification
- `vitest --project @paperclipai/ui src/lib/cross-tab-poll.test.ts` — 11
tests passed.
## Risks
- Low-to-moderate risk: eviction timing affects client polling
coordination.
- Tests cover the retention boundary, resumed subscriptions, and
coordinator cleanup to reduce regression risk.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.3 Codex, reasoning with repository tool use and
code execution; context-window size was not exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Company skills expose their source metadata in a narrow details
sidebar
> - Long filesystem paths and repository locators were truncated, hiding
the part operators often need to distinguish sources
> - The sidebar can preserve the complete value by wrapping at arbitrary
path boundaries instead of ellipsizing it
> - This pull request renders full source paths and repository labels
without widening the layout
> - The benefit is that operators can inspect and copy the actual skill
source from the UI
## Linked Issues or Issue Description
### Pre-submission checklist
- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I can reproduce this on `master`.
- [x] I have confirmed the behavior originates in Paperclip itself, not
an agent adapter, API provider, or local configuration.
### What happened?
Long company-skill source paths and repository locators were truncated
in the skill details sidebar.
### Expected behavior
The complete source value remains visible and wraps within the available
sidebar width.
### Steps to reproduce
1. Open a company skill whose source path is longer than the details
sidebar.
2. View the Source field.
3. Observe that the old UI replaces the middle or end of the value with
an ellipsis.
### Paperclip version or commit
`origin/master` at `02e2dd271`.
### Deployment mode
Local dev (`pnpm dev`).
### Installation method
Built from source (`pnpm dev` / `pnpm build`).
### Agent adapter(s) involved
None; this is a company-skills UI layout issue.
### Logs, configuration, or screenshots
Not applicable; the behavior is directly visible in the Source field.
### Additional context
The narrow sidebar should remain width-constrained. Wrapping
intentionally trades vertical space for full source inspectability.
## What Changed
- Replace source-path truncation with width-constrained arbitrary
wrapping.
- Apply the same wrapping behavior to linked repository/source labels.
- Add a regression test proving the full long path is rendered without
ellipsis.
## Verification
- `vitest --project @paperclipai/ui src/pages/CompanySkills.test.tsx` —
11 tests passed.
- `node scripts/check-token-gates.mjs` — all token gates clean.
## Risks
- Low risk: the change is limited to text layout in the company skill
details view.
- Very long unbroken values may make the Source section taller,
intentionally trading vertical space for inspectability.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.3 Codex, reasoning with repository tool use and
code execution; context-window size was not exposed by the runtime.
## 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
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators scan task state constantly, so the task **status**
vocabulary (backlog / todo / in progress / in review / done / blocked /
cancelled) has to read instantly
> - Those statuses render through one shared component, `StatusGlyph`,
whose icons were hand-rolled SVG geometry lifted from an internal spec
> - Hand-rolled glyphs are harder to reason about, drift from the rest
of the UI (which uses Lucide everywhere else), and mix fill/stroke
styles across statuses
> - This pull request swaps the hand-rolled geometry for named Lucide
icons — one clean, consistent icon family — with no change to colours,
sizing, or accessibility
> - The benefit is a status icon set that is consistent with the rest of
the app's iconography, trivially adjustable (change a mapping, not SVG
path math), and simpler to maintain
## Linked Issues or Issue Description
No existing public GitHub issue. Describing the change in-PR
(feature/polish):
**Problem / motivation.** The task status icons in `StatusGlyph` were
bespoke inline SVGs (a half-filled disc for *in progress*, a filled disc
+ knockout check for *done*, ring+bar for *blocked*, ring+slash for
*cancelled*, etc.). The rest of the UI uses [Lucide](https://lucide.dev)
icons, so the status set was the odd one out — and its mixed fill/stroke
shapes were harder to scan and to tweak.
**Proposed solution.** Map each status to a Lucide icon and render that
instead:
| Status | Lucide icon |
| --- | --- |
| backlog | `circle-dashed` |
| todo | `circle` |
| in_progress | `rotate-cw` |
| in_review | `circle-dot` |
| done | `circle-check` |
| blocked | `circle-minus` |
| cancelled | `ban` |
| in_queue (covered-blocked) | `circle-minus`, recoloured blue |
Colours (the `--status-task-icon-*` tokens), the `sm/md/lg` size scale,
`currentColor` recolouring, and the `role="img"` / `aria-label`
behaviour are all unchanged — only the shapes change.
**Alternatives considered.** Keeping the bespoke geometry (rejected:
inconsistent with the app and harder to maintain).
**Related PRs** (linked for reviewer context, not dependencies):
- Refs #8580 — the merged PR that established the current hand-rolled
status glyphs this PR restyles.
- Refs #8838 — open PR forwarding Radix trigger props through
`StatusGlyph`; touches the same component (no overlap with this change).
- Refs #1760 — open proposal to redesign the *cancelled* status icon
specifically; this PR moves cancelled to Lucide `ban`.
## What Changed
- `ui/src/components/StatusGlyph.tsx`: replaced the per-status
hand-rolled SVG `glyphBody()` geometry with a `status → Lucide icon` map
(`circle-dashed`, `circle`, `rotate-cw`, `circle-dot`, `circle-check`,
`circle-minus`, `ban`). Kept the token-driven colour wiring, size scale,
`currentColor` recolouring, a11y label handling, and the `in_queue` =
blocked-icon-recoloured-blue behaviour.
- `ui/src/components/StatusGlyph.test.tsx`: updated to lock the new icon
mapping (per-status Lucide class, size scale, colour var, `in_queue`,
a11y) instead of the old geometry.
Net: two files, +74 / −138 (the component got smaller). Because every
status surface (list, board, detail header, status picker,
sub-task/blocked-by pills, chips) routes through `StatusGlyph`, this
single-component edit covers them all.
## Verification
- `pnpm check:token-gates` → **3/3 clean** (no hardcoded
colour/spacing/font values introduced).
- `pnpm typecheck` → clean across all packages.
- `cd ui && pnpm vitest run` → **2509/2509 passing**, including the
updated `StatusGlyph` test.
- Manual: ran the worktree dev server and confirmed the new icons render
everywhere (task list, task detail, related-task chips, and the status
picker showing all seven).
**Storybook visual-regression note:** this is an intentional visual
change, so the status-icon stories will diff against the published
baseline. The baseline snapshots need to be regenerated and republished
by a maintainer (`pnpm test:storybook-visual:update` from a trusted
environment) as part of accepting this change — the visual-regression CI
check is expected to be red until then. No baseline is published in the
environment this PR was authored in, so that step is left to a
maintainer.
## Risks
- **Low risk / cosmetic.** No logic, data, or API changes — only the
rendered icon shapes. Colours, sizes, and accessibility labels are
unchanged.
- The most noticeable shifts are *in progress* (half-disc → rotating
arrow), *done* (solid disc+check → outline circle+check), and
*cancelled* (ring+slash → ban). These are deliberate.
- The only CI check expected to fail is the Storybook visual-regression
job, pending a maintainer baseline update (see Verification).
## Model Used
Claude Opus 4.8 (`claude-opus-4-8`), run in Claude Code with extended
thinking and tool use (file edits, local test runs, browser-driven
visual verification).
## 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
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Instance settings include an Environments section where operators
configure execution environments, each with an environment-variables
editor for run-time bindings
> - The editor showed a bare "Unsaved changes" banner that never said
which variables changed, sometimes appeared the moment a saved config
was opened (a lossy round-trip through the editor's emit rules made
clean values look dirty), and the environment form let you navigate away
without any confirmation, silently dropping the draft
> - Operators could not tell what was unsaved, distrusted the phantom
banner, and lost half-finished environment edits to a stray click — the
agent configuration page already confirms before discarding, so
environments behaved inconsistently
> - This pull request lists the new/edited/removed variable names under
the banner, normalizes both sides of the dirty comparison so saved
values no longer look dirty on open, and confirms before cancel, in-app
navigation, or tab unload while the form has unsaved changes
> - The benefit is that the banner is trustworthy and specific, and
unsaved environment edits can no longer be lost without an explicit
confirmation
## Linked Issues or Issue Description
Related (not fixed by this PR): #8930 introduced the current
environment-variables editor and its unsaved-changes banner; #9386 moved
environment create/edit from a modal to routed pages, which this PR's
navigation guard builds on.
No existing public issue for the defects themselves; described per the
bug report template:
**What happened?**
The environment-variables editor in Environments settings showed a bare
"Unsaved changes" banner with no indication of which variables changed.
For some saved configurations (names with surrounding whitespace,
incomplete secret references, duplicate names differing only by
whitespace) the banner appeared immediately on opening the edit form,
before any user input. Navigating away from the environment form —
cancel, an in-app link, or closing the tab — silently discarded the
draft with no confirmation.
**Expected behavior**
The banner should say which variables are new, edited, or removed; a
freshly opened saved configuration should show no banner; and leaving
the form with unsaved changes should require an explicit confirmation,
consistent with the agent configuration page.
**Steps to reproduce**
1. Open Settings → Instance settings → Environments and edit an
environment whose saved config round-trips lossily (e.g. an env var name
stored with trailing whitespace) — the "Unsaved changes" banner appears
with no user edits.
2. Add or edit a variable — the banner gives no hint of what is unsaved.
3. With a dirty draft, click any in-app link or Cancel — the draft is
dropped with no confirmation.
**Deployment mode**
Self-hosted (local development instance), reproducible on `master`.
## What Changed
- The unsaved-changes banner in `EnvironmentVariablesEditor` now renders
a change summary line — `New: … · Edited: … · Removed: …` — showing up
to three names per group with a `+N more` overflow and the full list in
a `title` tooltip. A rename shows as one addition plus one removal.
- Dirty detection normalizes both the committed value and the draft
through the same rules the editor uses when emitting values (trimmed
names, incomplete secret refs dropped, last-writer-wins on trimmed
duplicates), so a saved config that round-trips lossily no longer shows
a phantom banner on first open.
- The editor exposes an `onDirtyChange` callback and warns via
`beforeunload` while its local draft is dirty.
- The environment create/edit page (`CompanyEnvironments`) tracks a
payload-level baseline fingerprint of the form as initialized and treats
the page as having unsaved changes when the current form differs from it
or the editor draft is dirty. While dirty it confirms ("Discard unsaved
environment changes?") on Cancel, intercepts same-origin in-app link
clicks, and warns on tab unload.
## Verification
- `node_modules/.bin/vitest run
ui/src/pages/CompanyEnvironments.test.tsx
ui/src/components/environment-variables-editor/EnvironmentVariablesEditor.test.tsx`
— 48 tests pass, including new coverage for: the change-summary banner
text, no phantom banner for lossy round-trip values, beforeunload only
while dirty, cancel confirmation on the edit page, and unload/link-click
warnings after edits are staged into the form.
- `tsc -b` in `ui/` passes.
- Manual: edit an environment, add/edit/remove variables, observe the
summary line; click Cancel or an in-app link and observe the
confirmation; save and observe navigation proceeds without prompting.
## Risks
- Low risk, UI-only. The click interceptor is scoped to same-origin
anchor navigation while the environment form page has unsaved changes
and is removed on cleanup; modified-key/middle-button clicks and
external links are left alone.
- The dirty-normalization intentionally ignores differences the editor
could never persist (incomplete secret refs, untrimmed duplicate names);
those were previously reported as unsaved changes that could not be
saved away.
## Model Used
- Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code, with
extended thinking and tool use (code editing, 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Instance settings include an Environments section where operators
configure sandbox/SSH/local execution environments, including
interactive custom-image setup sessions with a browser terminal
> - The environment create/edit form was rendered inside a modal dialog,
so pressing Escape anywhere — including inside the embedded SSH terminal
while capturing a snapshot — closed the whole modal and destroyed the
in-progress session
> - Environment editing is a heavyweight, long-lived flow; losing it to
a reflexive Escape keypress is destructive and surprising
> - This pull request converts environment create/edit from a modal into
routed standalone pages, so Escape no longer dismisses the form
> - The benefit is that terminal sessions and half-completed edits
survive Escape, and the flow gets shareable URLs and normal back/forward
navigation
## Linked Issues or Issue Description
No existing public issue; described per the bug report template:
**What happened?**
While editing an environment's sandbox snapshot in the embedded SSH
terminal, pressing Escape (e.g. to exit a mode inside the terminal)
closed the entire environment edit modal, discarding the setup session
and any unsaved form state.
**Expected behavior**
Escape inside the terminal or form should not dismiss the environment
editor. A heavyweight flow like environment configuration should be a
standalone page where Escape behaves as expected within the focused
widget.
**Steps to reproduce**
1. Open Instance settings → Environments and edit a sandbox environment
2. Start a custom image setup session and focus the browser terminal
3. Press Escape
4. The modal closes and the session context is lost
## What Changed
- Converted the environment create/edit dialog in
`CompanyEnvironments.tsx` into routed pages at
`/company/settings/instance/environments/new` and
`/company/settings/instance/environments/:environmentId/edit`
- Registered the new routes in `App.tsx` and wired breadcrumbs for the
list/create/edit states
- Form state now initializes from the route (create vs edit) instead of
dialog open/close state, and successful saves navigate back to the
environments list
- Updated `CompanyEnvironments.test.tsx` and `CompanySettings.test.tsx`
to render through a router with the new routes and assert against the
routed form page instead of a dialog
## Verification
- `pnpm vitest run ui/src/pages/CompanyEnvironments.test.tsx
ui/src/pages/CompanySettings.test.tsx` — 22/22 passing
- `tsc --noEmit` on the `ui` package — clean
- Behavioral coverage: the updated tests exercise the routed create/edit
pages end to end (open edit via the list, interact with the
setup-session controls on the form page, save navigates back to the
list); with the form no longer in a dialog there is no Escape-close
handler to trigger
## Risks
- Low risk; UI-only routing change. Deep links into the old modal state
do not exist (the modal had no URL), so no redirects are needed
- The edit page resolves the environment from the route param; a
stale/unknown id falls back to the environments list
## Model Used
- Claude (Anthropic), model ID `claude-fable-5` (Fable 5), extended
thinking enabled, agentic tool use via Claude Code
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Cody <cody@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI renders agent/user-authored Markdown in task
descriptions, comments, and other work-thread surfaces
> - Those Markdown surfaces often live inside cards and containers that
constrain overflow
> - Ordered-list markers are painted outside the list content box, so
too little inline padding can clip multi-digit markers at the left edge
> - This pull request keeps the shared Markdown list gutter compact
while giving ordered lists enough marker space for two- and three-digit
counters
> - The benefit is that long numbered lists in board-facing Markdown
render correctly without widening unordered-list gutters or changing
API, data, or editor behavior
## Linked Issues or Issue Description
No public GitHub issue found.
Bug description:
- What happened: rendered Markdown ordered lists with multi-digit items
could show clipped marker digits when the list was flush against an
overflow-constrained container.
- Expected behavior: ordered-list markers such as `10.` and `100.`
should render fully in task descriptions and comments.
- Steps to reproduce: render a `.paperclip-markdown` ordered list with
at least 100 items inside a container that clips overflow and has no
extra left gutter.
- Paperclip version/commit: current `master` before this PR.
- Deployment mode: board UI, deployment-mode independent.
Related search result:
- Refs #2049 because it also touches rendered Markdown list
presentation, but it styles GFM task-list checkboxes and does not
address ordered-list marker clipping.
## What Changed
- Set the shared `.paperclip-markdown` list padding to a compact
`1.5rem` baseline for bullets and lists.
- Added an ordered-list-only `2.5rem` padding override so
outside-positioned multi-digit ordered-list markers have enough
inline-start room.
- Added a focused stylesheet regression test that verifies
unordered-list gutters stay compact while ordered lists keep the larger
marker gutter.
- Restored the exact maintainer-skill marker phrase expected by the
existing server skill utility contract test, fixing an unrelated
latest-head CI failure from current `master`.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/MarkdownListStyles.test.ts`
- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/ui build`
- `pnpm exec vitest run
server/src/__tests__/paperclip-skill-utils.test.ts`
## Risks
- Low risk: ordered lists in rendered Markdown get a larger left gutter;
unordered lists keep a smaller shared gutter.
- Low risk: the skill-doc marker change is text-only and matches the
existing server test contract.
- No database, API, migration, auth, adapter, or telemetry changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent, tool-enabled software-engineering
session. Context window size was not exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, where
operators need a reliable way to find and act on work awaiting their
input.
> - The attention and issue-thread interaction subsystems expose those
decision points across server APIs and the board UI.
> - The previous navigation and interaction presentation left these
actions fragmented and did not offer a controlled rollout for the
Decisions surface.
> - This branch adds the attention feed, richer interaction cards,
grouping, dismiss/snooze behavior, and a gated Decisions sidebar entry.
> - It also keeps experimental settings and API contracts synchronized,
with an idempotent migration for the new dismissal state.
> - This pull request delivers the complete, tested attention/Decisions
experience as one reviewable unit.
## Linked Issues or Issue Description
- Adds an operator-focused attention queue and Decisions experience:
grouped decision cards, semantic interaction actions, dismiss/snooze
handling, resilient interaction states, and an experimental flag to
control the Decisions navigation entry.
## Feature Context
### Problem or Motivation
Operators currently have to hunt across approvals, interactions, failed
runs, and budget alerts to find decisions that need their action.
### Proposed Solution
Provide a gated Decisions attention queue that groups actionable items,
supports direct resolution, and preserves operator control through
dismiss and snooze actions.
### Alternatives Considered
Keep separate, source-specific views only; this leaves cross-cutting
operator decisions fragmented and harder to prioritize.
### Roadmap Alignment
This improves the V1 control-plane operator workflow by making pending
governed actions discoverable in one company-scoped surface.
## What Changed
- Added server attention-feed services, routes, interaction handling,
dismiss/snooze support, and an idempotent `0145` inbox-dismissal
migration.
- Added shared attention, inbox-dismissal, and experimental-settings
contracts.
- Added Decisions/attention UI, interaction-card states, sidebar
badge/navigation integration, grouping, keyboard support, and Storybook
coverage.
- Added tests for attention behavior, thread interactions, settings
normalization, dismissals, and API behavior.
- Removed generated screenshots from the final PR diff and rebased the
branch onto current `master`.
## Verification
- `pnpm check:token-gates` — passed.
- `pnpm exec vitest run
packages/shared/src/issue-thread-interactions.test.ts
server/src/__tests__/attention-service.test.ts
server/src/__tests__/inbox-dismissals.test.ts
server/src/__tests__/issue-thread-interactions-service.test.ts
server/src/__tests__/issue-thread-interaction-routes.test.ts
ui/src/lib/attention.test.ts
ui/src/components/AttentionQueueRow.test.tsx
ui/src/components/IssueThreadInteractionCard.test.tsx
ui/src/pages/InstanceExperimentalSettings.test.tsx` — passed: 158 tests
across 9 focused files.
- GitHub Actions for `ad636f560`: build and typecheck/release-registry
have passed; remaining general-server and Greptile checks are in
progress.
## Risks
- Moderate: this is a cross-layer attention/interaction feature with a
new migration and navigation behavior.
- The `enableDecisions` experimental setting defaults to off, limiting
rollout impact.
- Existing dismissal data is backfilled to `dismiss`; the migration is
idempotent and uses guarded constraint creation.
> ROADMAP.md was checked; no duplicate planned core feature was
identified. Related open pull requests were searched before opening this
PR.
## Model Used
- OpenAI GPT-5.5 via Codex CLI, with tool use and local code execution.
Context-window size unavailable in this environment.
## 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 public PR branch name describes the change and contains no
internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally; focused tests pass and the remaining
unrelated AWS test failure is documented above
- [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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent configuration includes adapter-specific model settings and a
built-in adapter test action so operators can verify runtime
configuration before saving changes.
> - Cody/Codex-style local adapters can use an adapter default model
when the user clears the explicit model field.
> - The adapter test path still passed an object containing `model:
undefined` in some create/edit flows, which is different from omitting
the model and can break default-model behavior.
> - The previous fix was reverted because it also included an unrelated
skill documentation edit.
> - This pull request reapplies only the UI default-model test-config
fix, with no doc or skill changes.
> - The benefit is that testing Cody/Codex adapter settings with the
default model follows the same contract as saving default model
settings: no explicit model key is sent.
## Linked Issues or Issue Description
Bug report:
- Summary: Testing a Cody/Codex local agent after selecting the default
model could send an adapter config with an undefined model value instead
of omitting the model key.
- Expected behavior: Clearing the model to use the adapter default
should test with `adapterConfig: {}` unless another model is explicitly
selected.
- Actual behavior: The UI test-config path could preserve `model:
undefined`, causing the adapter test to fail instead of exercising the
default model.
- Related PRs: Reapplies the UI-only portion of #9361 after #9363
reverted the original PR.
## What Changed
- Exported and reused `omitUndefinedEntries` so adapter test config
payloads drop undefined adapter config entries before calling the test
endpoint.
- Hardened the current model display value so create-mode values that
are nullish or non-string do not crash the model selector/test flow.
- Added render coverage for editing a Codex agent back to the default
model and for testing a create form with the default model.
## Verification
- `pnpm exec vitest run
ui/src/components/AgentConfigForm.render.test.tsx`
- `pnpm check:token-gates`
- Confirmed `git diff origin/master --name-only` contains only:
- `ui/src/components/AgentConfigForm.render.test.tsx`
- `ui/src/components/AgentConfigForm.tsx`
- `ui/src/lib/agent-config-patch.ts`
## Risks
Low risk. The change only removes `undefined` adapter config entries
from the UI adapter-test payload and adds focused render coverage.
Explicit model values and other adapter config fields are preserved.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent, tool-use enabled. Context window size
not exposed in this runtime.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The agent detail page uses route references that can be based on an
agent's URL key.
> - Renaming an agent can change that URL key while the browser is still
on the old route.
> - After save or rollback, refetching the stale route reference can
render an "Agent not found" state even though the agent still exists.
> - This pull request redirects the detail page to the updated canonical
route when the saved agent's route reference changes.
> - The benefit is that agent renames keep users on the same
configuration workflow without landing on a stale URL.
## Linked Issues or Issue Description
- Refs #1848
- Related public search performed for agent rename/not-found issues and
PRs; no closer in-flight PR was found.
- Bug context: after saving a renamed agent or rolling back to a
revision with a different name-derived URL key, the agent detail page
could continue using the old URL and show "Agent not found".
## What Changed
- Added a small route-sync helper that compares the previous and updated
agent route refs after mutations.
- Redirects the agent detail page with `replace: true` when a save or
rollback changes the canonical route ref.
- Removes the stale detail-query cache entry so the old route reference
is not refetched after a rename.
## Verification
- Local outgoing patch scan for common secrets, private paths/emails,
and internal issue/link references: no matches.
- `corepack pnpm install --frozen-lockfile`
- `corepack pnpm --dir ui run typecheck`
- `corepack pnpm --dir ui exec vitest run
src/pages/AgentDetail.progress.test.ts src/App.test.tsx`
- `corepack pnpm check:token-gates`
## Risks
- Low risk: the redirect only runs when the updated agent resolves to a
different route ref than the current agent.
- If a future mutation response omits both URL key and name, the
existing route-ref fallback behavior still applies.
## Model Used
OpenAI Codex, GPT-5 coding agent via the local Codex adapter, with
tool-assisted repository inspection, shell execution, and GitHub API
use.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Sandbox environments can capture reusable custom images (provider
snapshots) so agents boot with pre-installed tools and CLI logins
> - The custom-image runtime fingerprint check included provider
secret-ref paths (e.g. the Daytona `apiKey`), while capture-time
fingerprinting excluded them, so any config carrying a credential never
matched its captured snapshot
> - As a result, agent config tests and environment probes silently
booted the provider base image instead of the snapshot, test sandboxes
were deleted before operators could inspect them, and any environment
save orphaned the snapshot without warning
> - The UI compounded the confusion by displaying an internal template
id that matches nothing in the provider dashboard
> - This pull request aligns runtime fingerprints with capture-time
exclusions, re-stamps fingerprints on saves that cannot affect the
snapshot (warning when they can), archives test/probe sandboxes instead
of deleting them, and surfaces the provider snapshot ref in the UI
> - The benefit is that custom images actually apply to config tests and
probes, survive unrelated config edits, and are debuggable against the
provider dashboard
## Linked Issues or Issue Description
No public GitHub issue exists for this; describing it in-PR per the bug
template. Related: Refs #9329 (saved-environment probe company context —
this branch carries an equivalent fix), Refs #8794 (introduced reusable
sandbox custom images).
**What happened?**
With a Daytona environment whose provider config stores the API key as a
secret reference and an active captured custom-image snapshot:
- Agent config tests and environment probes booted the provider base
image (`daytonaio/sandbox:0.8.0`) instead of the captured snapshot, so
CLI upgrades/logins baked into the snapshot were missing and the probe
reported "login required" and an outdated CLI.
- The environment card showed an internal template id (e.g.
`b5be03e1-ca5…`) that does not correspond to any snapshot name in the
provider dashboard, making the active image impossible to correlate.
- Test/probe sandboxes were deleted immediately after the run, so the
sandbox a test used could not be inspected afterwards.
- Saving the environment config (even fields unrelated to the image)
changed the stored fingerprint, silently detaching the snapshot with no
warning.
**Expected behavior**
Config tests and probes boot the captured snapshot when one is active;
the UI shows the provider-facing snapshot/template ref; test sandboxes
stay inspectable for a short window; unrelated config edits keep the
snapshot linked, and edits that genuinely invalidate it produce an
explicit warning.
**Steps to reproduce**
1. Configure a sandbox environment on Daytona with the API key stored as
a company secret reference.
2. Capture a custom image snapshot from the environment page and mark it
active (e.g. after installing/logging into a CLI in the setup sandbox).
3. Run the agent config test or an environment probe: the sandbox boots
the base image, not the snapshot, and the sandbox is deleted immediately
after the test.
4. Save the environment config with an unrelated field change: the
snapshot silently stops applying.
**Paperclip version or commit**
`master` at the merge-base of this branch.
**Deployment mode**
Self-hosted local instance (macOS, pnpm dev server) with the Daytona
sandbox provider plugin.
## What Changed
- Runtime custom-image fingerprint checks now exclude provider
secret-ref paths, matching capture-time exclusions, so configs carrying
credentials match their captured snapshots
(`environment-custom-image-runtime.ts`).
- Agent config tests and saved-environment probes force fresh,
non-reused sandboxes and pass company context so lease-backed probes can
resolve company secrets and boot the real snapshot
(`environment-probe.ts`, `routes/agents.ts`, `routes/environments.ts`).
- Test/probe sandboxes are released by archiving (stop + 60-minute
provider-side auto-delete) instead of immediate deletion, so operators
can inspect the exact sandbox a test used (Daytona plugin).
- On environment PATCH save, changes that cannot affect the captured
snapshot re-stamp the template's source fingerprint so the snapshot
stays linked; boot-source or provider-identity changes (new manifest
field `templateIdentityPaths`) mark the template detached and the save
response reports it (`environment-custom-images.ts`, shared plugin
types/validators).
- The custom-image overview exposes `activeTemplateMatchesConfig`; the
environments UI shows the provider snapshot/template ref (internal id
moved to a tooltip), warns via toast when a save detaches the snapshot,
and shows a persistent "Not in use" warning when the active template no
longer matches the saved config (`CompanyEnvironments.tsx`,
`api/environments.ts`).
## Verification
- `pnpm vitest run
server/src/__tests__/environment-custom-images-service.test.ts
server/src/__tests__/environment-probe.test.ts
server/src/__tests__/environment-routes.test.ts
server/src/__tests__/agent-test-environment-routes.test.ts` — server
coverage for fingerprint exclusions, re-stamp/detach on save, probe
company context, and fresh-sandbox test behavior.
- `pnpm vitest run
packages/plugins/sandbox-providers/daytona/src/plugin.test.ts` —
archive-on-release and snapshot ref handling.
- `pnpm vitest run ui/src/pages/CompanyEnvironments.test.tsx` — snapshot
ref display, detach toast, and "Not in use" warning.
- Manually verified end-to-end on a live self-hosted instance against
real Daytona: config test boots the captured snapshot (CLI login and
version persist), the test sandbox remains visible in the provider
dashboard as archived, and saving unrelated fields keeps the snapshot
applied.
## Risks
- Fingerprint exclusion widening: a provider credential rotation alone
no longer detaches a captured snapshot; that is the intended behavior
(the snapshot content does not depend on the credential), and
provider-identity fields (e.g. Daytona `apiUrl`) still detach via
`templateIdentityPaths`.
- Archived test sandboxes consume provider-side resources for up to
their auto-delete window instead of being freed immediately; bounded (60
minutes) and only for test/probe sandboxes.
- New optional manifest field `templateIdentityPaths` is
backward-compatible; providers that omit it keep current matching
behavior.
## Model Used
- Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use
via Claude Code / Claude Agent SDK.
## 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
## Thinking Path
> - Paperclip is the open-source control plane people use to manage AI
agents and their work.
> - Its scheduler, routines, and heartbeat services decide when agents
automatically begin work.
> - Experimental per-worktree execution is useful for isolated
development, but enabling it previously allowed automatic services to
consider an existing backlog.
> - A worktree activation must therefore create a durable eligibility
boundary rather than merely toggle execution on.
> - This pull request records an activation cutoff and applies it
consistently to automatic routine and heartbeat dispatch.
> - The result is that an enabled worktree executes only work created
after its own activation, while non-worktree behavior remains unchanged.
## Linked Issues or Issue Description
**Problem type:** Bug / safety regression
**Summary:** Enabling experimental run execution in an existing worktree
could start automatic scheduler, routine, watchdog, and heartbeat
activity for work created before that worktree was explicitly armed.
**Expected behavior:** A worktree that has execution enabled only
considers automatically dispatched work created on or after its
activation timestamp. Ambiguous activation state fails closed.
Non-worktree instances keep their existing behavior.
**Related public work:** Refs #8275 (runtime worktree policy gating);
this PR adds an activation-time boundary for automatic execution rather
than changing the general runtime policy.
## What Changed
- Persist a worktree execution activation timestamp and originating
instance ID; stamp them only when the experimental toggle changes from
disabled to enabled.
- Resolve activation state fail-closed when the cutoff is missing,
invalid, disabled, or belongs to another instance.
- Gate automatic routine scheduling, webhooks, watchdog activity, and
heartbeat selection at the activation cutoff; manual runs remain
available.
- Share the canonical worktree truthy-environment helper across routine
dispatch and agent inbox filtering.
- Add cutoff and truthy-runtime regression coverage, plus
experimental-settings UI states that explain armed and suppressed
execution.
## Verification
- `pnpm exec vitest run server/src/__tests__/routines-service.test.ts
server/src/__tests__/instance-settings-service.test.ts` — passes: 2
files, 60 tests.
- `pnpm --filter @paperclipai/server typecheck` — passes.
- Existing CI completed successfully before the follow-up review fixes;
this branch was rebased onto the latest `origin/master` before
retesting.
## Risks
- **Behavioral:** Automatic worktree execution is intentionally more
restrictive; pre-existing work is suppressed until newly created after
activation.
- **Operational:** A malformed or cross-instance activation record fails
closed, requiring an operator to disable and re-enable the experimental
toggle on the intended worktree.
- **Compatibility:** The worktree environment now accepts all canonical
truthy values (`1`, `true`, `yes`, and `on`) consistently; non-worktree
instances are unaffected.
- **Branch metadata:** This existing execution-workspace branch predates
the current naming rule and cannot be renamed under this task's
workspace contract; the code and PR title do not include internal ticket
references.
> `ROADMAP.md` was checked; this targeted execution-safety fix does not
duplicate planned core work.
## Model Used
- Anthropic Claude Code — assisted with the original implementation;
exact model identifier and context window were not recorded in the
repository metadata.
- OpenAI Codex CLI — assisted with PR preparation and review fixes;
exact model identifier and context window are not exposed in this
execution environment. Used with terminal tooling, code editing, and
targeted 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)
- [ ] 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The heartbeat/recovery subsystem decides whether an agent run has a
durable continuation path after the process stops.
> - External waits need stricter semantics than local background
watchers: a killed local process is not durable, while a first-class
blocker/monitor/scheduled wake is.
> - Without that distinction, recovery can repeatedly treat
adapter-failed continuations as live work and obscure the real reason a
task stopped.
> - This pull request adds explicit durable external-wait liveness
handling and documents the expected execution semantics.
> - It also improves operator-visible recovery evidence so invalid
external-wait paths explain why they were rejected.
> - The benefit is clearer recovery behavior, fewer duplicate
continuation recoveries, and a safer contract for monitor-backed
external waits.
## Linked Issues or Issue Description
- Refs #5978
- Related PRs: #4988, #7495, #8502
## What Changed
- Added durable external-wait liveness classification so
local/background watchers are not accepted as durable live paths after
the owning process exits.
- Preserved first-class blocker/monitor/scheduled wake paths as valid
external-wait continuations.
- Added backend regression coverage for killed watcher failure,
monitor-backed durable wait resumption, normal completion, blocker
behavior, and no duplicate recovery.
- Added adapter utility coverage for terminal cleanup behavior used by
local process adapters.
- Surfaced invalid external-wait recovery evidence in the recovery
action card and run ledger.
- Updated execution semantics documentation and the V1 implementation
contract.
## Verification
- `pnpm check:token-gates` passed.
- `pnpm -r typecheck` passed.
- `node scripts/run-vitest-stable.mjs --mode general --group
general-server` equivalent lane passed in CI-clean env: 238 files, 2164
tests passed, 1 skipped.
- `node scripts/run-vitest-stable.mjs --mode general --group
general-workspaces-a` passed in fully Paperclip-env-clean env: UI 305
files / 2430 tests; CLI 43 files / 230 tests.
- `node scripts/run-vitest-stable.mjs --mode general --group
general-workspaces-b` passed in fully Paperclip-env-clean env:
shared/db/adapters/plugin packages all green.
- `node scripts/run-vitest-stable.mjs --mode serialized` passed in fully
Paperclip-env-clean env: 107 serialized server suites green, including
84/84 heartbeat-process-recovery tests.
- `pnpm build` passed in fully Paperclip-env-clean env.
Notes: running `pnpm test:run` directly inside the Paperclip heartbeat
environment exposed local harness env contamination in existing tests
(`PAPERCLIP_CONFIG`, `PAPERCLIP_DB_BACKUP_DIR`, and
`PAPERCLIP_WORKTREE_START_POINT`). Re-running the same lanes with
inherited `PAPERCLIP_*` and port env removed produced the CI-equivalent
green results above.
## Risks
- Medium behavioral risk: this changes recovery classification for
stopped local external-wait processes, so adapters relying on unmanaged
background watchers must use blockers, monitors, scheduled wakes, or
explicit durable handoff instead.
- Low UI risk: recovery-card copy changes are covered by component tests
and Storybook screenshot QA.
- No database migration is included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5-based coding agent, tool-enabled terminal/code
execution. Exact context-window metadata was not exposed in the runtime.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Issue and item detail pages use a shared issue chat thread to show
comments, runs, activity, and interactions.
> - That thread still defaulted to landing on the latest comment when
messages first loaded.
> - On long issue/item pages, that default can yank the operator away
from the top of the page before they choose to inspect the newest
message.
> - Deep links to comment hashes can create the same kind of initial
viewport jump when they are used as generic navigation targets.
> - This pull request makes initial latest-comment and initial
thread-hash scrolling opt-in instead of default behavior.
> - The benefit is stable initial page position across issue-thread
surfaces while keeping the explicit Jump to latest control available.
## Linked Issues or Issue Description
No exact public GitHub issue was found for this bug.
Bug description:
- What happened: opening a page with a shared issue conversation thread
could automatically move the viewport toward the newest comment/thread
target.
- Expected behavior: ordinary page loads should keep the initial
viewport stable unless the user explicitly clicks Jump to latest.
- Steps to reproduce: open an issue or item detail page with a long
conversation thread and observe whether the page jumps to the newest
thread entry on initial load.
- Paperclip version/commit: reproduced while working on the current
`master` branch lineage.
- Deployment mode: local trusted/dev UI.
Related public thread/comment UX work: Refs #3916, Refs #7972, Refs
#8800.
## What Changed
- Changed `IssueChatThread` so initial latest-comment scrolling defaults
to off.
- Added a separate opt-in for initial thread-hash scrolling, also
defaulting to off.
- Preserved stale deleted-comment hash cleanup without scrolling the
page.
- Updated regression coverage so default initial load stays put, comment
hashes do not scroll by default, and manual Jump to latest still
scrolls.
## Verification
- `pnpm --filter @paperclipai/ui typecheck` passed on the clean PR
branch.
- `pnpm --dir ui exec vitest run src/pages/IssueDetail.test.tsx -t
"loads from the pending state into issue detail without changing hook
order"` passed on the clean PR branch.
- `pnpm --dir ui exec vitest run
src/components/IssueChatThread.test.tsx` was attempted on the clean PR
branch, but the file fails before changed assertions with the existing
`TypeError: act is not a function` test-harness issue across 58 tests;
14 tests passed.
- Static check: no `autoScrollToLatestOnInitialLoad={true}` or
`autoScrollToHashOnInitialLoad={true}` call sites remain in `ui/src`.
## Risks
Low risk. This only changes initial scroll defaults in the shared issue
thread. The main behavioral shift is that direct comment/thread hashes
no longer auto-scroll on first load unless a caller explicitly opts in;
the Jump to latest button and post-submit scroll behavior are unchanged.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex coding-agent runtime; exact context window not
exposed in this environment; tool-enabled repository inspection,
editing, testing, git, GitHub CLI, and Paperclip API usage.
## 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
- [ ] 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI includes an agents sidebar so operators can see which
agents are currently active.
> - That sidebar depends on live-run polling, heartbeat events, and
cross-tab cache sharing to stay current without overloading the API.
> - The sidebar was visually churning because agents could leave the
live section immediately after a run ended, while progress events and
cross-tab broadcasts kept forcing hot query updates.
> - This pull request stabilizes live sidebar membership and makes
shared polling broadcasts monotonic/deduplicated.
> - The benefit is a calmer operator sidebar that still reflects real
live state without flashing between stale and fresh snapshots.
## Linked Issues or Issue Description
No public GitHub issue exists for this operator-facing bug.
Bug summary:
- What happened: the agents sidebar could flash or reshuffle around
active agents while live-run and heartbeat data was updating.
- Expected behavior: active and recently-active agents should remain
visually stable, and cross-tab cache sharing should not overwrite
fresher data with older snapshots.
- Reproduction context: run Paperclip with multiple tabs or rapid
live-run/progress updates and watch the agents sidebar while agents
enter/leave live execution.
- Deployment mode: local/operator board UI.
Related PR:
- Supersedes #9357, which carried the same fixes on a branch/title/body
that were not suitable for public contribution hygiene.
## What Changed
- Restored the maintainer-only warning wording in the developer skill
guide so the existing server skill-utils CI gate passes on current
master.
- Added a 120-second linger window for streamlined sidebar agent rows so
an agent does not immediately disappear from the live section as soon as
its last run ends.
- Deferred the recent-agent fallback until there are no live or
lingering agents, while keeping the live badge tied only to
actually-live runs.
- Stopped broad live-runs/heartbeats/agents-list invalidation on every
run progress event, while preserving targeted agent-detail invalidation.
- Added producer timestamps to cross-tab shared polling result messages
so older-or-equal snapshots are dropped before `setQueryData`.
- Added per-resource broadcast dedupe/rate limiting so tabs do not
rebroadcast equivalent cached data in a loop.
- Added focused coverage for sidebar linger behavior, staggered
multi-agent linger expiry, live update invalidation scope, shared
polling timestamp handling, and cross-tab broadcast dedupe.
## Verification
Run locally on the rebased PR branch:
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/SidebarAgents.test.tsx
src/context/LiveUpdatesProvider.test.ts` — 44 tests passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/cross-tab-poll.test.ts src/hooks/useSharedPolling.test.ts` — 10
tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — exit code 0.
## Risks
- Low migration risk: the sidebar/polling changes are UI/client cache
behavior only, with no database or API contract changes.
- Sidebar visibility now intentionally lingers for 120 seconds after the
last live run; stale rows could remain briefly visible, but their live
badge is removed when they are no longer actually live.
- Cross-tab broadcasts are now more conservative; a missed publish
should be corrected by the next normal poll or accepted newer timestamp.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex via the Paperclip Codex coding-agent runtime; exact
API model identifier and context-window size are not exposed in this
environment. The agent used terminal/tool execution for repository
inspection, focused tests, branch preparation, and PR creation.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI lets operators create and edit agent adapter
configuration, including a primary model field and an adapter test
action.
> - For Cody and similar adapter forms, selecting the default model
means the model value is intentionally unset so the adapter can use its
default.
> - The adapter test path still allowed `model: undefined` to survive in
the generated adapter config, which could send an invalid test payload
instead of omitting the field.
> - This pull request normalizes create/edit adapter test config so
default-model selections omit `model` entirely.
> - The benefit is that testing an agent configured to use the adapter
default model exercises the same clean config shape that should be saved
and run.
## Linked Issues or Issue Description
No public GitHub issue was found for this local UI bug, so the problem
is described inline.
Bug description:
- What happened: using the adapter test action after choosing the
default model could include `model: undefined` in adapter config and
surface a UI/runtime error instead of testing with the adapter default.
- Expected behavior: choosing the default model should omit the `model`
field from adapter config so the adapter default is used.
- Steps to reproduce: edit a Codex/Cody-style agent with a concrete
model, switch the model selector to Default, then run the adapter Test
action.
- Paperclip version/commit: current `master` before this PR.
- Deployment mode: board UI, deployment-mode independent.
## What Changed
- Sanitized adapter test config assembly so undefined adapter config
entries are omitted before the test request is sent.
- Made create-mode current model display resilient when the model is
unset for adapter defaults.
- Added regression coverage for editing an existing agent from a
concrete model back to Default and testing it.
- Added regression coverage for create-mode testing with an
unset/default model.
- Hardened the developer skill wording used by the existing server skill
utility contract test.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/AgentConfigForm.render.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/paperclip-skill-utils.test.ts`
- GitHub PR checks on this branch are green, including Typecheck +
Release Registry, Build, General tests, e2e, verify, security scans, and
Greptile Review.
## Risks
- Low risk: this only removes undefined values from adapter test config
payloads, which aligns with the existing persisted patch behavior.
- Low risk: default-model display now treats unset create-mode model
values as an empty string.
- No database, API schema, migration, auth, or adapter runtime contract
changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected - check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent, tool-enabled software-engineering
session. Exact context window size was not exposed by the runtime.
## 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
---------
Co-authored-by: Cody <cody@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue thread UI renders live agent output from adapter run logs
and transcript parsing.
> - Some adapter streams emit many small or repeated token chunks, and
live UI updates can expose partial words, duplicated slices, or
transient markdown placeholders.
> - That makes active run updates look like gibberish even when the
underlying agent output is valid.
> - The fix needs to preserve raw logs while making the live thread view
stable, readable, and ordered.
> - This pull request adds monotonic run-log sequencing, safer live
transcript dedupe/order handling, markdown placeholder hiding, and
readable live text stabilization.
> - The benefit is a live issue thread that updates smoothly without
showing confusing partial parser artifacts.
## Linked Issues or Issue Description
No public GitHub issue exists yet, so this PR includes the bug details
inline.
### What happened?
Live run updates in the issue thread can show confusing repeated or
partial text while an adapter is streaming. The visible text appears to
lose parsing boundaries during active updates, especially with ACP-style
token deltas, so the live output can briefly render duplicated chunks,
incomplete words, or HTML-comment placeholders.
### Expected behavior
Live text should remain readable while preserving the underlying run
output for raw inspection.
### Steps to reproduce
1. Start a live agent run whose adapter emits small stdout token deltas.
2. Watch the issue thread while the run is still active.
3. Observe transient duplicated chunks, incomplete words, or markdown
placeholder artifacts in the live rendered text.
### Paperclip version or commit
Reproduced against current `master` before this PR branch.
### Deployment mode
Local dev issue-thread UI with live local adapter runs.
### Additional context
GitHub PR search for `live run streaming text markdown transcript` found
one broad merged PR, `#252` (“Dotta updates - sorry it's so large”), but
no targeted duplicate for this live streaming readability bug.
## What Changed
- Added per-run monotonic sequence numbers to persisted and live run-log
chunks.
- Dedupe and order live transcript chunks by sequence before falling
back to timestamp ordering.
- Hide markdown HTML comment placeholder text from rendered markdown
output.
- Smooth live issue-thread text updates so partial additions reveal at
readable word boundaries and sliding-window removals do not produce
gibberish.
- Added coverage for run-log ordering/deduping, markdown comment hiding,
live issue-thread stabilization, and Greptile-reviewed edge cases where
overlap rewrites could synthesize text or no-boundary additions could
stay hidden.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/issue-chat-messages.test.ts src/components/MarkdownBody.test.tsx
src/components/transcript/useLiveRunTranscripts.test.tsx` passed before
the review fix: 3 files, 86 tests.
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/issue-chat-messages.test.ts` passed after the review fix: 1
file, 30 tests.
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/issue-chat-messages.test.ts
src/components/transcript/useLiveRunTranscripts.test.tsx` passed after
the final Greptile overlap fix: 2 files, 40 tests.
- `pnpm check:token-gates` passed.
- Local PII/secret scan of touched files found only expected code/test
words such as `secret`, `token`, and redaction-related strings; no
literal credentials found.
- `pnpm -r typecheck` passed after restoring declared dependencies with
`CI=1 pnpm install --frozen-lockfile` and running with a short `TMPDIR`
because `tsx` IPC sockets fail under the long sandbox temp path.
- `pnpm build` passed with existing Vite CSS/font/chunk warnings.
- GitHub PR checks passed on head
`4c052dfe86aecb5feb73504e6b48843f68fce813`: build, typecheck/release
registry, server and workspace test shards, serialized server suites,
e2e, canary dry run, policy, review, Socket, Superagent, Snyk, and
verify.
- Greptile review passed on head
`4c052dfe86aecb5feb73504e6b48843f68fce813` with confidence score 5/5 and
no blocking issues found.
- `pnpm test:run` failed in unrelated server workspace tests on this
macOS local environment:
- `server/src/__tests__/heartbeat-workspace-branch-containment.test.ts`:
two assertions compare `/tmp/...` with `/private/tmp/...`.
- `server/src/__tests__/heartbeat-worktree-suppression.test.ts`:
expected one heartbeat run but observed two, followed by cleanup fallout
in the full run.
- Isolated rerun of those two server suites reproduced the same three
failures.
## Risks
- Low product risk for the UI changes: the readable smoothing only
affects active live-run display stabilization, not stored comments or
raw run logs.
- Moderate verification risk: local full Vitest did not pass because of
unrelated server workspace tests. Targeted tests for this change,
typecheck, token gates, and build passed.
- Run-log sequence fields are optional for compatibility with older log
rows that do not include `seq`.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5-based coding agent, tool-using local workspace
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 searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate
- [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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Skill Studio is the UI surface for editing skill package files,
including `SKILL.md`.
> - Markdown files are split into frontmatter fields plus a rich
markdown body editor.
> - The dirty-state guard intentionally ignores markdown editor
normalization during initial mount.
> - That guard was too narrow: rich markdown body edits could happen
before the file was marked as user-interacted, so edits did not reliably
enable Save.
> - This pull request broadens the user-interaction signals around the
markdown body editor and adds a regression test for saving body edits.
> - The benefit is that editing `SKILL.md` in Skill Studio now behaves
like normal file editing: changes show as unsaved and Save persists the
full markdown document.
## Linked Issues or Issue Description
No public GitHub issue exists. Inline bug description follows.
### What happened?
Editing a Skill Studio markdown body did not reliably mark the file
dirty, so the Save action could remain unavailable or fail to persist
the body edit.
### Expected behavior
User edits in the markdown body editor should mark the file unsaved and
allow saving the updated `SKILL.md` content.
### Steps to reproduce
1. Open a Skill Studio markdown file such as `SKILL.md`.
2. Edit the markdown body in the rich editor.
3. Observe whether the unsaved state appears and Save becomes enabled.
4. Save and reload the file.
### Paperclip version or commit
Reproduced on `master` before this fix.
### Deployment mode
Local dev (`pnpm dev`).
## What Changed
- Mark markdown body interaction on capture-phase key, pointer, paste,
drop, before-input, and input events around the rich editor.
- Preserve the existing guard that prevents MDXEditor mount-time
normalization from dirtying a clean file.
- Add a Skill Studio regression test that edits the markdown body,
observes the Unsaved state, enables Save, and verifies the saved
`SKILL.md` includes both frontmatter and the edited body.
## Verification
- `pnpm vitest run ui/src/pages/SkillStudio.test.tsx`
Manual reviewer path:
- Open a Skill Studio markdown file such as `SKILL.md`.
- Edit the body text in the rich markdown editor.
- Confirm the UI shows an unsaved state and the Save button is enabled.
- Save and confirm the updated markdown body persists.
## Risks
Low risk. The change only broadens interaction detection before applying
existing dirty-state logic, and the guard still prevents initial editor
normalization from marking an unopened file dirty.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex CLI using GPT-5, with repository file editing, shell
execution, and GitHub CLI tool use.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Codex local is the adapter subsystem that exposes OpenAI Codex CLI
model choices to agents and issue overrides.
> - OpenAI has GPT-5.6 Codex-capable models that should appear in
Paperclip's built-in Codex model list and refresh behavior.
> - Paperclip's server model listing falls back to the adapter metadata
and merges OpenAI refresh results with known Codex defaults.
> - This pull request updates the Codex default model metadata to
include GPT-5.6 options and adds regression coverage for fallback and
refresh paths.
> - The benefit is that operators can select the new Codex models
without relying on manual model IDs, and refresh behavior keeps known
GPT-5.6 options visible.
## Linked Issues or Issue Description
Refs #9322.
Refs #9342.
Refs #9346.
### Agent or provider
Codex CLI (OpenAI).
### Why this adapter is useful
OpenAI's GPT-5.6 Codex-capable models should be available in Paperclip's
Codex adapter defaults and model refresh path.
### How the agent is invoked
`codex`
## What Changed
- Changed the `codex_local` default model metadata from `gpt-5.5` to
`gpt-5.6`.
- Added `gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna` to the
built-in Codex adapter model list.
- Updated adapter and server model-listing tests to cover GPT-5.6
fallback and refresh behavior.
- Aligned Codex Fast mode support and helper text with the new `gpt-5.6`
default, while preserving GPT-5.5, GPT-5.4, and manual model ID support.
## Verification
- `git diff --check origin/master...HEAD`
- `pnpm exec vitest run packages/adapters/codex-local/src/index.test.ts
packages/adapters/codex-local/src/server/codex-args.test.ts
server/src/__tests__/adapter-models.test.ts
server/src/__tests__/adapter-model-refresh-routes.test.ts`
- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/adapter-codex-local typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
## Risks
Medium risk because changing `DEFAULT_CODEX_LOCAL_MODEL` from `gpt-5.5`
to `gpt-5.6` changes the adapter's default model selection for new blank
configurations. The model-list additions are otherwise low risk and
covered by adapter/server metadata tests. This PR intentionally overlaps
related PRs #9342 and #9346, so reviewers may prefer to close or fold it
into one of those branches.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent based on GPT-5, with shell, git, GitHub CLI,
and repository editing tool use. Exact served model ID and context
window were not exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents often work from reviewed plans that are approved through
issue-thread interactions.
> - Accepting a plan is not just a UI decision; it must reliably resume
the assignee so approved work continues.
> - A failed continuation wake could leave an approved plan stranded in
review with no durable retry or visible recovery path.
> - This pull request makes approved plan continuations retryable,
recoverable, and visible when resume fails.
> - The benefit is that operators can trust plan approval to either
resume the agent or produce an explicit actionable failure instead of
silent limbo.
## Linked Issues or Issue Description
No public GitHub issue exists. Inline bug report follows the repository
bug template.
### Pre-submission checklist
- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on the latest released version of Paperclip or can reproduce
on `master`.
- [x] I have confirmed the error originates in Paperclip itself, not in
my agent adapter, API provider, or local configuration.
### What happened?
When a plan-confirmation interaction was accepted, the assignee
continuation wake could fail before useful agent execution. In that case
the issue could remain in review even though the plan had been approved,
because the failed wake was fire-and-forget and there was no durable
retry or recovery path for accepted continuations.
### Expected behavior
Accepted plan continuations should either wake the assignee
successfully, retry bounded infrastructure failures, recover dropped
wakes, or surface an explicit failure state that operators can act on.
### Steps to reproduce
1. Create an issue with an assignee and a plan confirmation that wakes
the assignee on accept.
2. Accept the confirmation.
3. Simulate a pre-flight continuation failure, such as process loss
before agent start or workspace validation failure.
4. Observe that the approved issue can remain in review without an
active assignee wake or visible retry/failure state.
### Paperclip version or commit
Reproducible on `master` before this PR's retry/recovery changes.
### Deployment mode
Local dev (`pnpm dev`) and server-side recovery paths.
### Installation method
Built from source (`pnpm install`, `pnpm dev`, test runner).
### Agent adapter(s) involved
Not adapter-specific; this is a core continuation/recovery bug. The
tests cover local-agent failure shapes without relying on a
provider-specific API.
### Database mode
Embedded Postgres test database for verification. The affected logic is
database-backed and applies to normal Postgres deployments as well.
### Access context
Board accepts the interaction; agent execution resumes through the
assignee wake path.
### Relevant logs or output
No sensitive logs are needed. The regression tests simulate the failed
wake and recovery states directly.
### Relevant config
No special config is required beyond an assignee with wake-on-demand
enabled.
### Additional context
This PR also prevents a stale workspace-validation payload from
quarantining another issue's active workspace and prevents unrelated
successful runs from masking a continuation that never resumed.
### Privacy checklist
- [x] I have reviewed all pasted output for PII, usernames, file paths,
API keys, tokens, and company names, and redacted where necessary.
## What Changed
- Added bounded infrastructure retries for failed accepted-interaction
continuation wakes.
- Extended stranded issue recovery so dropped accepted-plan continuation
wakes are requeued.
- Recorded and rendered explicit resume-failure state on accepted
confirmation cards.
- Added clean-workspace fallback for workspace-validation failures while
preventing cross-issue workspace quarantine.
- Tightened recovery so unrelated successful runs do not mask an
accepted continuation that never resumed.
- Added focused server/UI coverage for retry scheduling, recovery,
visible failure state, and interaction card rendering.
## Verification
- `pnpm vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts` — 106 tests
passed.
- Earlier branch verification also covered the issue-thread interaction
card tests for the visible resume-failure UI.
- GitHub CI is green on the replacement PR head, and Greptile reports
5/5 with no blocking issues.
## Risks
- Medium behavioral risk: this changes recovery behavior for accepted
continuation interactions and workspace-validation retries.
- Mitigation: retries are bounded, scoped to same-company issue context,
and workspace quarantine now requires ownership by the issue being
retried.
- Existing stored confirmation results remain compatible because the new
resume-failure field is optional.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent, tool-enabled terminal workflow. The
runtime does not expose an exact context-window value to the agent.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Local CLI adapters can run against Paperclip-managed execution
environments instead of only the host filesystem.
> - The environment picker and environment capability API derive sandbox
support from shared adapter capability lists.
> - The Grok Build adapter is implemented as a local CLI adapter, but it
was missing from those shared environment capability lists.
> - That made Grok agents look local-only even when sandbox environments
were configured.
> - This pull request registers `grok_local` in the shared adapter
constants and remote-managed environment support path.
> - The benefit is that Grok Build agents can select the same local,
SSH, and sandbox environment overrides as other local CLI adapters.
## Linked Issues or Issue Description
No public GitHub issue exists for this bug. Inline bug report follows.
### Pre-submission checklist
- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on `master`.
- [x] I have confirmed the error originates in Paperclip itself, not in
the Grok adapter provider or local configuration.
### What happened?
When configuring a Grok Build local agent in the board UI, Paperclip did
not expose configured sandbox environments as selectable environment
overrides. The shared environment capability helper treated `grok_local`
as local-only because it was missing from the remote-managed local
adapter allowlist.
### Expected behavior
Grok Build should behave like other local CLI adapters: when
environments are enabled and a runnable sandbox environment exists, the
agent configuration form should show the environment override selector
and allow the sandbox to be selected.
### Steps to reproduce
1. Enable environments in instance experimental settings.
2. Configure at least one runnable sandbox environment.
3. Open the agent configuration form for a Grok Build local agent.
4. Observe that the sandbox environment is not offered as an override
before this fix.
### Paperclip version or commit
`master` before this PR.
### Deployment mode
Local dev (`pnpm dev`).
### Installation method
Built from source (`pnpm dev` / `pnpm build`).
### Agent adapter(s) involved
- Grok Build local adapter.
- Core bug in shared environment capability logic.
### Database mode
Not database-related.
### Access context
Board human operator.
### Relevant logs or output
No runtime error is emitted; the issue is a missing UI option caused by
shared capability metadata.
### Relevant config
No secret-bearing config required. Reproduction only needs environments
enabled and a runnable sandbox environment configured.
### Privacy checklist
- [x] I have reviewed all pasted output for PII, usernames, file paths,
API keys, tokens, company names, and redacted where necessary.
## What Changed
- Added `grok_local` to the shared built-in adapter type list.
- Added `grok_local` to the remote-managed adapter set used by
environment capability helpers.
- Added shared regression coverage for Grok local sandbox provider and
driver support.
- Added a UI render regression test that confirms Grok Build agents show
the environment override when a runnable sandbox exists.
## Verification
- `git diff --check`
- Changed-file secret scan with `rg` for common token/key patterns.
- `pnpm --filter @paperclipai/shared exec vitest run
src/environment-support.test.ts`
- `pnpm --dir ui exec vitest run
src/components/AgentConfigForm.render.test.tsx`
## Risks
- Low risk. This expands environment support for an existing local
adapter to match the local CLI adapter behavior already used by Claude,
Codex, Gemini, OpenCode, Cursor, and Pi.
- Operators still need at least one configured runnable sandbox
environment before a Grok agent has a sandbox option to select.
- This PR was created from a Paperclip execution workspace branch whose
name is runtime-provided; the PR body intentionally avoids internal
issue identifiers or instance-local links.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5 coding agent, tool-enabled terminal/code execution
environment.
## 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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board currently uses issues for execution, but longer-lived
content work needs a separate object that can survive beyond a single
task thread.
> - The Cases subsystem adds an experimental, company-scoped record for
content artifacts and their supporting metadata.
> - The backend needs durable storage, API routes, revision history,
issue linkage, and company-boundary enforcement before the UI can depend
on Cases.
> - The UI needs an opt-in navigation surface, list/detail views,
reference chips, and issue-page context so operators can inspect Cases
without making them the default workflow.
> - The agent-facing skills need a contract for creating and updating
Cases so automated content workflows can dogfood the feature.
> - This pull request ships that experimental end-to-end path behind the
`enableCases` flag.
> - The benefit is a first-class place to collect content work,
references, attachments, revisions, and related execution threads
without polluting the core issue model.
## Linked Issues or Issue Description
No public GitHub issue exists for this experimental feature.
Feature request fields:
### Problem
Content-oriented work such as release notes, announcements, docs, and
campaigns can span many execution issues, which makes the final artifact
hard to find and reason about after the execution thread moves on.
### Proposed solution
Add an experimental Cases object that is company-scoped, linked to
issues, queryable through the API, inspectable in the board UI, and
writable by agent workflows through documented conventions.
### Alternatives considered
Continue encoding content artifacts directly in issues or documents
only. That keeps the data model smaller, but it does not give operators
a stable artifact-centric view or a clean way to link related execution
history.
### Roadmap alignment
Checked `ROADMAP.md`; this PR does not duplicate an existing planned
core roadmap item.
## What Changed
- Added the `cases` data model, migration, schema exports, and
experimental `enableCases` instance setting.
- Added company-scoped Cases API routes for list/detail/update, issue
links, revisions, children, activity events, annotations, attachments,
and idempotent agent-oriented upserts.
- Scoped case and issue lookup helpers before access checks so
inaccessible cross-company identifiers resolve as not found rather than
leaking existence.
- Fixed case PATCH timestamp handling so non-status updates cannot
overwrite `completedAt` from a stale pre-transaction row snapshot.
- Moved Cases list type/status/project filters into the server request
before the server-side limit is applied, including multi-select filters
and no-project filtering.
- Added backend route coverage for creation, updates, idempotency, issue
linking, attribution, company-boundary enforcement, OpenAPI
registration, list filtering, timestamp patch behavior, and inaccessible
lookup regressions.
- Added the experimental Cases UI surface: sidebar entry, gated routes,
list filters/grouping, detail overview, activity, revisions, children,
attachments, and issue-page case rail.
- Added case reference rendering and company-prefixed case href
generation so case links resolve directly inside the active company
route.
- Added Paperclip skill documentation for agent workflows that create or
update Cases.
- Wired release-content skills to emit Cases for dogfooding.
- Rebased onto current `master` and renumbered the Cases migrations to
`0143`/`0144` after the latest upstream migration sequence.
## Verification
- Current PR head: `ecc13be0d`.
- Rebased on current `master` (`606aa4f266`) and pushed to the existing
PR branch.
- `git diff --check origin/master...HEAD` — passed before the first
update push; subsequent committed diffs were also checked with `git diff
--check` before commit.
- Guardrails checked: no `pnpm-lock.yaml` changes, no
`.github/workflows` changes, and changed-file count is below the
Greptile 100-file limit.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/cases-routes.test.ts
src/__tests__/instance-settings-service.test.ts
src/__tests__/openapi-routes.test.ts` — passed, 3 files / 26 tests
before review-fix commits.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/cases-routes.test.ts` — passed after each server-side
Greptile fix, latest 1 file / 15 tests.
- `pnpm --filter @paperclipai/server typecheck` — passed after the
timestamp and lookup fixes.
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/Cases.test.tsx src/pages/CaseDetail.test.tsx
src/pages/CompanySkills.test.tsx src/App.cases-routing.test.tsx` —
passed, 4 files / 30 tests before review-fix commits.
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/Cases.test.tsx` — passed after the list-filter fix, 1 file /
12 tests.
- `pnpm --filter @paperclipai/ui typecheck` — passed after the
list-filter fix.
- `pnpm check:token-gates` — passed after UI changes.
- Remote PR checks on head `ecc13be0d` are green: Paperclip CI, build,
typecheck, test matrix, e2e, Canary Dry Run, policy, commit review,
Superagent Security Scan, Socket, Snyk, and Greptile passed; Storybook
visual regression is skipped and security-review is neutral.
- Greptile Review: 5/5 confidence, zero unresolved Greptile threads.
## Risks
- Medium feature risk because this introduces a new experimental domain
object across database, server, shared contracts, skills, and UI.
- The feature is gated behind `enableCases`, which limits default
operator exposure while the model is exercised.
- Case links now prefer company-prefixed hrefs; the unprefixed redirect
remains for externally entered URLs.
- Cases list filtering now sends multi-select filters to the server
before limiting; the UI still applies the same local filters as a second
pass for ancestor/context rows.
- Migrations were renumbered on top of current master; the SQL uses
guarded `IF NOT EXISTS` / `ADD COLUMN IF NOT EXISTS` patterns where
relevant for safer replay.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex in the Paperclip local coding environment was used
for this PR curation, rebase verification, review-fix implementation,
push, and PR description update. The runtime exposes tool use and shell
execution; context-window size is not exposed by this Paperclip adapter.
Several implementation commits also include AI co-author trailers
recorded in git history.
## 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 searched the GitHub PR list for similar PRs and confirmed this
is not a duplicate
- [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)
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent setup depends on instruction files that are readable and
editable from the board UI
> - The instructions tab already receives server-side metadata
describing whether each file is Markdown
> - The UI was deciding Markdown editor usage primarily from the file
extension, which makes extensionless Markdown instruction files feel
like raw code
> - This pull request makes the instructions editor trust server
Markdown metadata for existing files and keep extension fallback only
for new unsaved files
> - The benefit is that AGENTS-style prose instructions render and edit
like prose while explicitly non-Markdown files still use the raw
textarea
## Linked Issues or Issue Description
- Refs #8201
- Refs #5652
- Refs #3427
- Refs #2068
- Related PRs: #2468, #2620
## What Changed
- Use server `markdown` metadata from instruction file details/summaries
to choose the prose Markdown editor for existing instruction files.
- Keep extension-based Markdown detection only for pending new files
before server metadata exists.
- Remove the monospace content styling from the Markdown editor path so
prose instructions read like normal text.
- Add focused tests for extensionless Markdown files, new `.md` files,
and `.md` files explicitly marked non-Markdown by the server.
## Verification
- `pnpm check:token-gates`
- `pnpm exec vitest run ui/src/pages/AgentDetail.instructions.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
## Risks
- Low risk. The editor selection now depends on server metadata for
existing files, so incorrect server metadata would choose the wrong
editor. The fallback still preserves extension-based behavior for newly
created unsaved files.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI GPT-5 via Codex coding agent, tool-enabled terminal workflow.
Context window details were not exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Company search is the primary way operators find issues, comments,
documents, artifacts, agents, and projects across a busy company
> - Search previously supported only a bare text query: no way to narrow
by status/assignee/project/label/date, no sort control, no typed
operators, and weak relevance/snippets meant hunting through noise
> - As companies accumulate tens of thousands of items, unfiltered
single-sort search stops scaling for day-to-day operator workflows
> - This pull request adds a full filtering model (filter bar, chips,
mobile sheet, URL state), sort modes, typed query operators (`status:`,
`assignee:`, `type:`, …) with command-palette parity,
relevance/snippet/deep-link improvements, zero-results recovery, and the
supporting shared validators, backend service work, and DB indexes
> - The benefit is that operators can go from a vague query to the exact
item in a couple of keystrokes, on desktop and mobile, with shareable
filtered-search URLs
## Linked Issues or Issue Description
No existing public GitHub issue; describing the underlying feature
request inline (per feature_request template):
- **Problem:** Company search accepted only a plain text query. Users
could not filter results by status, assignee, project, label, or
recency; could not change result ordering; and got no guidance when
filters emptied the result set.
- **Desired solution:** Structured search filters (UI controls + typed
query operators + URL parameters), selectable sort modes, better
relevance and snippets with exact deep links, and parity between the
search page and the command palette.
- **Alternatives considered:** Client-side filtering of unfiltered
results (does not scale past the fetch limit); a separate "advanced
search" page (splits the surface and duplicates state handling).
Related (not duplicate) PRs found while searching: #4848 (issue search
query planning), #8235 (search rate limiting).
## What Changed
- **Shared contract:** new search filter/sort/count/zero-results types
and validators in `packages/shared` (`validators/search.ts`, types
index).
- **Backend:** `server/src/services/company-search.ts` supports issue
filters, sort modes, per-filter option counts, snippets, artifact
visibility, and zero-results loosen suggestions; single-statement match
replaces per-scope scans and predicates are trigram-index compatible
(~3.7s → ~350ms on a live 14.8k-hit corpus).
- **DB:** migration `0142_company_search_sort_indexes.sql` adds the
supporting indexes.
- **Search page (`ui/src/pages/Search.tsx`):** filter bar, removable
chips, mobile filter sheet with result-count preview, sort menu, URL
round-tripping, zero-results recovery UI.
- **Query operators (`ui/src/lib/search-query-parser.ts`):** typed
operators parsed into filters, operator autocomplete, filter pills.
- **Command palette:** operator-aware parsing and full-search handoff.
- **Stale-operator fix (latest commit):** typed operator filters are no
longer folded into persistent URL-filter state, so deleting a token
(e.g. removing `status:blocked` from the input) actually removes the
filter from subsequent requests; filter-control edits materialize
control state and strip typed tokens so a removed chip cannot resurrect
from the input.
## Verification
- `cd ui && npx vitest run src/pages/Search.test.tsx` — 19 tests
including two new red→green regressions for the stale-operator paths
(both fail on the previous commit, pass now).
- `cd ui && npx vitest run src/components/CommandPalette.test.tsx` and
`cd server && npx vitest run
src/services/company-search-service.test.ts` — operator parity and
backend filter/sort/count coverage.
- `cd ui && npx tsc --noEmit` — clean.
- Manual: open `/search`, type `auth status:blocked`, confirm the status
filter applies; delete `status:blocked`, confirm results are unfiltered
again; drive the same filters from the filter bar/chips/mobile sheet and
confirm the URL round-trips (reload/back/forward preserves state).
- Full end-to-end QA pass (9/9 acceptance checks) against the wireframes
on desktop (1280px) and mobile (390px) with a live API and browser
automation.
## Risks
- Additive migration (indexes only, no data rewrites) — safe to roll
forward; index creation cost is paid once at migrate time.
- Search request shape gains optional parameters only; old clients keep
working.
- Behavioral shift: filter-control edits now strip typed operator tokens
from the query text (their values persist as filter state) — deliberate,
so removed filters stay removed.
- Ranking changes alter result ordering for existing queries; covered by
service tests and the QA pass.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Claude Fable 5 (`claude-fable-5`, Anthropic, extended thinking + tool
use) — stale-operator-filter fix, regression tests, PR preparation.
- GPT-5 Codex (`codex_local` adapter) and Claude Opus 4.6
(`claude-opus-4-6`) — earlier implementation phases (backend contract,
filter UI, operators, ranking) under agent orchestration.
## 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)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
— pre-existing branch name retained to avoid closing/reopening the PR
- [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 (no
user-facing docs affected)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending re-run on latest commit)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending re-review of the stale-filter fix)
- [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 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Execution workspaces are the bridge between Paperclip's control
plane and a local agent's checked-out repository state.
> - When a workspace is restored after a failed or interrupted run, the
recorded branch can disagree with the branch currently checked out on
disk.
> - A clean branch mismatch can be reconciled safely, but a dirty
mismatch needs a lossless path that does not discard uncommitted agent
work.
> - This pull request adds a quarantine-and-restore path that saves
dirty work to a rescue branch, restores the recorded branch, and exposes
the repair from the board UI and run page.
> - The benefit is that operators can recover wedged execution
workspaces without losing work or moving another live branch
unexpectedly.
## Linked Issues or Issue Description
No public GitHub issue exists for this workspace-recovery failure, so
this PR includes the bug report inline.
**What happened**
A git worktree-backed execution workspace could become wedged when
Paperclip expected one branch but found a different checked-out branch
with dirty tracked or untracked files. The existing safe repair path
refused the restore, leaving the source task blocked with no lossless
one-click recovery path.
**Expected behavior**
Paperclip should preserve dirty work before restoring the recorded
workspace branch. If another live workspace claims the checked-out
branch, or an attached runtime service is active, the repair should
refuse with clear operator-facing evidence instead of risking work loss
or file contention.
**Steps to reproduce**
Create a git worktree execution workspace whose persisted branch name
differs from the checked-out branch, add dirty tracked or untracked
files in that worktree, then trigger workspace validation or use the
branch reconcile endpoint. Before this change, the dirty mismatch
remained blocked because Paperclip had no quarantine restore mode.
**Paperclip version or commit**
Observed on the pre-fix workspace-recovery implementation. Verified on
this PR head after rebasing onto current `master`.
**Deployment mode**
Local trusted development/worktree deployments using git worktree
execution workspaces and optional workspace runtime services.
## What Changed
- Added dirty-worktree quarantine repair that creates a rescue branch,
commits dirty tracked and untracked files there, restores the recorded
branch, writes audit comments/activity, and preserves the live foreign
branch ref.
- Added `quarantine_restore` branch reconcile API support,
recovery-action resolution, source-task wake behavior, execution-review
preservation, claimant refusal, runtime-service refusal, and coverage
for the non-transactional git ordering.
- Added board UI controls for the repair action in the recovery card
plus a compact failed-run workspace recovery surface that uses the same
reconcile handlers.
- Hardened Greptile follow-up cases by best-effort restoring the
recorded branch after a mid-sequence rescue commit failure and by
refusing quarantine restore while attached runtime services are active.
## Verification
- `pnpm exec vitest run
server/src/__tests__/execution-workspaces-service.test.ts -t
"quarantine_restore"`
- `pnpm exec vitest run server/src/__tests__/workspace-runtime.test.ts
-t "workspace dirty quarantine branch repair"`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts -t
"repairs clean unrecorded branch drift|adopts unrecorded forward branch
drift"`
- `pnpm exec vitest run
server/src/__tests__/workspace-runtime-routes-authz.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- Earlier PR verification covered the route, service, heartbeat, UI
component, and run-page recovery surfaces; Cutter posted public preview
screenshots for the repair popover and run-page panel at
https://github.com/paperclipai/paperclip/pull/9297#issuecomment-4926934211.
- GitHub PR checks are green on
`dcac76b05f4cf6e1ee16544c2831d83c7857e475`.
- Greptile is 5/5 with zero annotations and no unresolved review threads
on `dcac76b05f4cf6e1ee16544c2831d83c7857e475`.
## Risks
- Moderate risk because the change intentionally runs git commands
against local worktrees; the implementation refuses dirty repair when
another claimant or active runtime service is detected and records
rescue refs for auditability.
- Compatibility / release-note callout for self-hosted operators:
existing instances that left `enableWorkspaceBranchReconcileForward`
unset now get automatic forward branch reconciliation during heartbeat
workspace recovery. Operators who want the previous advisory-only
behavior can set `experimental.enableWorkspaceBranchReconcileForward` to
`false`; dirty quarantine repair can likewise be disabled with
`experimental.enableWorkspaceDirtyQuarantineRepair: false`.
- If the git rescue succeeds but a later database write fails, the
worktree may already be restored while the recovery action remains open;
this ordering is documented in code because git side effects cannot
participate in the database transaction.
- UI risk is limited to the workspace recovery surfaces and covered by
component tests plus the existing Cutter visual preview.
## Model Used
OpenAI Codex coding agent based on GPT-5, with repository tool use,
shell execution, and local test execution. Earlier preserved commits on
this branch also show Claude Code / Claude Opus 4.8 assistance in their
commit metadata.
## 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] Branch naming exception documented: this PR preserves the existing
worktree branch requested for publication while keeping the PR title and
body public-facing
- [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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI keeps issue, agent, activity, and run state fresh
through polling across several pages and sidebar surfaces
> - When multiple components or browser tabs poll the same company data
at the same time, the API can receive bursts of duplicate issue-list
requests
> - Those duplicate requests increase database and server load without
returning meaningfully different data
> - This pull request adds server-side compression/coalescing plus
client-side visibility-aware and cross-tab shared polling
> - The benefit is lower request volume during normal board usage while
preserving fresh UI data for active users
## Linked Issues or Issue Description
No exact public GitHub issue was found for this request.
Problem:
- The board can issue redundant polling requests for the same issue-list
data from multiple UI surfaces and tabs.
- In busy operator sessions, those bursts can trigger request-storm
behavior and unnecessary issue-list load.
- Expected behavior is to reuse identical in-flight work server-side and
reduce hidden-tab or duplicate-tab polling client-side while preserving
normal refresh behavior.
Related public context found during duplicate search:
- #8206 covers a different board UI 404-storm scope.
- #5165 covers separate issue-list behavior around page-size truncation.
## What Changed
- Added API compression middleware foundation and a company/created-at
index for heartbeat run access.
- Added server-side issue-list request storm detection and identical
in-flight request coalescing.
- Added UI fetch metadata, visibility-aware polling, and request
deduplication for issue/activity/client calls.
- Added cross-tab shared polling primitives and wired them into the
sidebar, inbox, dashboard, issue, project, routine, and agent surfaces.
- Resolved the latest `master` migration collision by keeping upstream
`0140_built_in_managed_resources.sql` and renumbering this branch's
heartbeat-run index migration to
`0141_heartbeat_runs_company_created_at_index.sql`; the SQL uses `CREATE
INDEX IF NOT EXISTS` for idempotency.
- Stabilized server heartbeat cleanup tests exposed by the PR check
matrix.
- Fixed the Greptile compression follow-up by weakening strong ETags on
encoded JSON responses and bypassing compression for streamed/download
responses.
## Verification
- `pnpm exec vitest run ui/src/components/IssuesList.test.tsx
ui/src/pages/Inbox.test.tsx` — passed after resolving the latest
`master` conflict in `IssuesList.tsx` and updating the 200-result cap
expectations.
- `pnpm check:token-gates` — passed after the UI conflict resolution.
- `jq -e '.entries | length as $n | (map(.idx) | unique | length == $n)
and (map(.tag) | unique | length == $n)'
packages/db/src/migrations/meta/_journal.json` — passed after
renumbering the migration to `0141`.
- `pnpm exec vitest run server/src/__tests__/api-compression.test.ts` -
passed after the compression follow-up.
- `pnpm exec vitest run
server/src/__tests__/issue-list-assignee-filter-routes.test.ts` - passed
after the compression follow-up.
- `pnpm --filter @paperclipai/server typecheck` - passed after the
compression follow-up.
- Greptile Review for head `8dbddac41ec273fda404100b4981ddb912fad57b` -
passed after the latest conflict/migration fix; all Greptile review
threads are resolved.
- `pnpm exec vitest run server/src/__tests__/api-compression.test.ts
server/src/__tests__/issue-list-assignee-filter-routes.test.ts
ui/src/api/client.test.ts ui/src/api/issues.test.ts
ui/src/lib/polling.test.ts ui/src/lib/cross-tab-poll.test.ts
ui/src/pages/Inbox.test.tsx ui/src/components/SidebarProjects.test.tsx
ui/src/components/SidebarAccountMenu.test.tsx
ui/src/lib/issueDetailCache.test.ts` — passed.
- `pnpm exec vitest run ui/src/api/client.test.ts` — passed.
- `pnpm exec vitest run ui/src/pages/Inbox.test.tsx
ui/src/components/SidebarProjects.test.tsx
ui/src/components/SidebarAccountMenu.test.tsx
ui/src/lib/issueDetailCache.test.ts` — passed.
- `pnpm exec vitest run
server/src/__tests__/heartbeat-worktree-suppression.test.ts` — passed.
- `pnpm exec vitest run
server/src/__tests__/low-trust-red-team-routes.test.ts` — passed.
- `pnpm exec vitest run
server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts` —
passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- GitHub PR checks for head `8dbddac41ec273fda404100b4981ddb912fad57b`:
all GitHub Actions/status checks passed; Greptile, Superagent, Socket,
Snyk, build, typecheck/release registry, general tests, serialized
server suites, e2e, canary dry run, policy, and commitperclip review are
green; Storybook visual regression and security-review were
skipped/neutral by policy.
- Confirmed this branch does not include `pnpm-lock.yaml` or
`.github/workflows` changes.
## Risks
- Medium risk: issue-list coalescing changes request timing and cache
semantics for a hot API path.
- Medium risk: cross-tab polling uses browser coordination primitives,
so older or unusual browser environments need fallback behavior to stay
correct.
- Low migration risk: the new index migration is ordered after current
`master` and uses `CREATE INDEX IF NOT EXISTS`.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent, GPT-5-based model, tool-enabled with
shell/git execution. Exact hosted deployment identifier and
context-window size were not surfaced in the agent runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators use the issue list, blocked-work notices, pipeline body
documents, and issue documents to understand what agents are doing
> - Some of those surfaces had low-information details: live-work
blockers were visually easy to miss, revision history showed generic
actor labels, and selected active subtasks carried an extra accent
border
> - These details matter because Paperclip's default UI should make
agent state and work history legible without requiring users to inspect
raw logs
> - This pull request groups a small set of related operator UI polish
changes for those work-state and document-history surfaces
> - The benefit is clearer review context for blocked work, better
attribution in document revision history, and cleaner active-subtask row
styling
## Linked Issues or Issue Description
No public GitHub issue found during dedup search.
### Bug Report
**Pre-submission checklist**
- Searched existing open PRs/issues for `document revision authors` and
`blocked notice live work`; no duplicates found.
- Reproduced against the current `master` base for this PR.
- Confirmed this is core board UI behavior, not adapter/provider/local
configuration.
**What happened?**
Blocked live-work notices, document revision history, pipeline body
document revision history, and active subtask rows exposed technically
correct but low-signal UI details. Revision menus could show generic
`Board`/`Agent` labels instead of the specific author, and selected
active subtasks had an extra accent border.
**Expected behavior**
Operators should see clear blocked-live-work copy, recognizable revision
authors using available agent/user profile data, and visually consistent
selected task rows.
**Steps to reproduce**
1. Open an issue with a live-work blocker and inspect the blocked
notice.
2. Open an issue document revision menu with agent/user-authored
revisions.
3. Open a pipeline item body document revision menu with
agent/user-authored revisions.
4. Inspect a selected active subtask row in the issue list.
**Paperclip version or commit**
`8b6a06ee2` (`origin/master` at branch creation).
**Deployment mode**
Local dev / self-hosted board UI.
**Installation method**
Built from source.
**Agent adapter(s) involved**
Not adapter-specific (core board UI).
**Database mode**
Not database-related.
**Access context**
Board operator UI.
**Relevant logs or output**
Not applicable.
**Relevant config**
Not applicable.
**Additional context**
This PR is a small polish/fix contribution. `ROADMAP.md` allows tightly
scoped bugs and polish without roadmap-level coordination.
**Privacy checklist**
Reviewed the PR body for private instance references and omitted
internal ticket links.
## What Changed
- Polished the live-work blocked notice copy and tests so operators get
a clearer explanation of what is blocking progress.
- Show issue document revision authors with agent/user names and avatar
treatment instead of generic actor labels.
- Show pipeline body document revision authors with the same available
agent/user profile data.
- Removed the extra left accent border from selected active subtask
rows.
- Stabilized the document revision test helper for React runtimes where
`act` is not exported as a function.
## Verification
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD -- .github/workflows
pnpm-lock.yaml` produced no output.
- `pnpm check:token-gates` passed: all color literal, arbitrary bracket
value, and raw font-size gates clean.
- `pnpm exec vitest run ui/src/components/IssueBlockedNotice.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx
ui/src/components/IssueRow.test.tsx` passed: 3 test files, 32 tests.
- `pnpm --filter @paperclipai/ui typecheck` passed.
- Remote PR checks passed on head `3e18fe3f`: 22 complete, 0 pending, 0
failing.
- Greptile completed at 5/5 with no blocking issues after addressing the
pipeline revision author feedback.
## Risks
Low risk. The change is limited to board UI rendering and component
tests. The main risk is a subtle visual regression in issue/document
surfaces that are not covered by screenshots; the affected component
tests cover the intended copy, attribution, and row-style behavior.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent, GPT-5 model family, tool-enabled CLI session.
Exact runtime context window is not exposed by this execution
environment.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators need first-party agent capabilities for repeatable company
work, not just manually created one-off agents.
> - Built-in agents need to behave like normal company-scoped agents
while preserving approval gates, permissions, budgets, and audit trails.
> - Reflection and coaching work also needs bundled instructions, skill
content, and a routine so the feature can be installed and reset
predictably.
> - The API, database, UI, portability, and tests all need to agree on
the built-in lifecycle from not provisioned through setup, approval,
ready, paused, and reset.
> - This pull request adds built-in agent provisioning and the
Reflection Coach bundle end-to-end.
> - The benefit is a safer first-party path for Paperclip-managed agents
without bypassing the same governance model used for operator-created
agents.
## Linked Issues or Issue Description
No public GitHub issue was found for this exact built-in agent and
Reflection Coach bundle work.
Problem/motivation:
- Paperclip did not have a first-party built-in agent lifecycle for
product-owned agents.
- Bundled agent resources such as default instructions, skills, and
routines needed managed ownership and reset semantics.
- Approval-gated companies needed built-in setup to preserve requested
adapter, budget, manager, and permission state through board approval.
- The board UI needed clear built-in badges, setup affordances,
readiness state, and bundle status without exposing secrets.
Proposed solution:
- Add a company-scoped built-in agent registry,
provisioning/reset/reconcile/status APIs, and Reflection Coach bundled
resources.
- Track bundled managed resources in the database with idempotent
migration behavior.
- Reuse existing agent approval, authorization, budget, and activity-log
paths instead of creating a bypass.
- Add UI setup, badges, gates, bundle panels, and route coverage for
built-in agents.
Duplicate search:
- Searched GitHub PRs for `built-in agents Reflection Coach
repo:paperclipai/paperclip`; only this PR was returned.
- Searched GitHub issues for the same query; no public issues were
returned.
## What Changed
- Added built-in agent definitions, lifecycle state derivation,
provisioning, reset, reconcile, status, and routine-control routes.
- Added the `built_in_managed_resources` migration and schema exports
for bundled instructions, skill, and routine ownership.
- Added the Reflection Coach built-in bundle with default instructions,
skill catalog content, routine template, default permissions, and
managed-resource drift handling.
- Added approval-aware provisioning behavior that preserves requested
adapter config, budgets, manager assignment, and built-in permissions
through hire approval.
- Added authorization and mutation gates for built-in agent and skill
changes, including consented Reflection Coach change paths.
- Added UI surfaces for built-in agent setup, roster/detail badges,
readiness gates, bundle status, routine controls, and route filtering.
- Added company import/export and validator coverage for built-in
managed resources and low-trust/red-team presets.
- Addressed Greptile follow-ups for pending approval reconciliation,
consent-gate error propagation, config-read authorization fallback,
approval-path manager preservation, and non-model adapter provisioning.
## Verification
Local verification:
- `git diff --check public/master..HEAD` passed.
- `pnpm check:token-gates` passed with all gates clean.
- `pnpm exec vitest run
ui/src/components/ConfigureBuiltInAgentModal.test.tsx` passed: 1 file, 4
tests.
- `pnpm exec vitest run ui/src/components/EntityRow.test.tsx
ui/src/pages/Agents.test.tsx ui/src/components/BuiltInAgentGate.test.tsx
ui/src/components/ConfigureBuiltInAgentModal.test.tsx
ui/src/components/BuiltInBundlePanel.test.tsx
ui/src/pages/InstanceExperimentalSettings.test.tsx
ui/src/pages/Routines.test.tsx` passed: 7 files, 64 tests.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/built-in-agents.test.ts
src/__tests__/authorization-service.test.ts
src/__tests__/company-skills-routes.test.ts` passed: 3 files, 91 tests.
- `pnpm --filter @paperclipai/db check:migrations` passed.
- `pnpm -r typecheck` passed after the rebase; `pnpm --filter ui
typecheck` passed after the final UI review fix.
Remote verification on latest head
`1c61f693a4ec881d739022b0e75a8ca8bf8c2cd8`:
- Merge state: `CLEAN`.
- Greptile: `5/5`, zero unresolved Greptile threads.
- PR check rollup: all checks successful, neutral, or skipped as
expected.
- Passing gates include Build, Typecheck + Release Registry, all server
shards, all workspace shards, all serialized server suites, e2e, Canary
Dry Run, policy, review, verify, Socket, Superagent, and Snyk.
## Risks
- This adds a new managed-resource table and migration; the migration
uses idempotent create/add/index guards and passed migration safety
checks.
- Built-in agent provisioning touches approval and authorization paths;
tests cover pending approval preservation, stale retry rejection,
consent gates, and config-read fallback behavior.
- Reflection Coach creates managed instructions, skill, and routine
resources; drift/reset behavior is covered by service tests and redacted
API responses.
- Non-model adapter setup now provisions a `needs_setup` built-in row
before command/endpoint fields are complete; this matches the server
lifecycle and is covered by the setup modal regression test.
## Model Used
OpenAI Codex coding agent based on GPT-5. Exact hosted model ID,
context-window size, and reasoning-mode labels are not exposed in this
runtime; tool use, shell execution, GitHub CLI/API access, and local
code editing were enabled.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip's UI is governed by the design system merged in #9134 and
the component convergence in #9240 — one Card, one Badge, one nav row,
one `IssueRow`, a single multiplicative radius ladder.
> - With those primitives in place, the remaining rough edges were
interaction and alignment details on the surfaces people use every day:
the inbox, the task list, the sidebar, and the routine/task detail
pages.
> - Each item here was reported from live use and fixed against a
running instance, then verified by measurement (pixel alignment, frame
timing) rather than by eye alone.
> - The result is that the inbox and task lists now behave as one system
(same hover, keyboard nav, tree-guide, and archive language), list
nesting reads correctly, list hover is smooth, and the routine detail
page scrolls and aligns like the rest of the app.
## Linked Issues or Issue Description
No public GitHub issue exists for this work; describing per the feature
template.
- **Problem**: after the design-system foundation (#9134) and component
convergence (#9240), the inbox/task lists still had interaction and
alignment gaps — hover lag on long lists, keyboard navigation that only
partly matched between the two lists, workspace/parent nesting whose
guides and chevrons didn't line up, an inbox that sat offset from the
task list, and a routine detail page with an odd double-scroll and a
bespoke sub-nav.
- **Proposed behavior**: the inbox and task lists share one interaction
contract (hover, keyboard nav, collapse, archive), list nesting aligns
to the status column with clean chevrons, list hover is CSS-only (no
per-hover re-render), and the routine detail page uses a fixed
header/sub-nav with a single scrolling content region and a sub-nav that
matches the primary nav.
## What Changed
- **List hover performance**: hover is now painted purely by CSS
`:hover` and records the hovered row in a ref, instead of writing
list-selection state on every `mouseenter` (which re-rendered 100–300
non-memoized rows per hover). Keyboard nav reads the ref so it still
continues from the hovered row; the keyboard band clears on the first
real mouse move so hover and keyboard selection never show two bands at
once. The row's `transition-colors` fade was removed so the highlight
snaps (no comet-tail). Measured on a 100-row scrub: worst frame **333 ms
→ 33 ms**, long frames (>50 ms) **12 → 0**, avg **41 → 60 fps**.
- **Inbox ↔ task-list parity**: keyboard navigation works on every inbox
tab (archive/read keys stay scoped to the archivable tab); group headers
and parent tasks collapse/expand with the arrow keys in both lists; the
task list gains the same j/k / arrows / Enter selection model as the
inbox; hover selection bands match; the `g` then `i` go-to-inbox chord
works app-wide.
- **List nesting & alignment**: the workspace group-header chevron lines
up exactly with the task chevrons below it (both lists); the
parent→child connector line drops from under the parent's **status
icon** rather than its chevron and breaks with a 14px gap around a
nested row's own chevron; and inbox rows line up with the task list
(read rows no longer reserve a mark-read column).
- **Inbox archive affordance**: moved from a bare `x` left of the status
icon to an `Archive` icon + label button on the right (before the
timestamp), revealed on row hover; the left slot now carries only the
unread dot; swipe-to-archive is unchanged.
- **Sidebar**: when no agent has a live run, the AGENTS section shows 3
recent agents (was 5) plus "See all agents"; the working-agents view is
unchanged.
- **Routine detail page**: the layout is bounded to the main scroll area
so the header (Run now + automation toggle) and the sub-nav stay fixed
and only the section content scrolls (was a page-level scroll competing
with a `sticky` sub-nav). The sub-nav items adopt the primary nav's
rhythm — row padding/height, inset rounded pill, type scale, and 16px
icons — and its background matches the main nav.
- **Task detail**: dropped the redundant `🔵` prefix the breadcrumb added
for live/in-progress tasks; the status glyph already conveys that state.
## Verification
- `pnpm check:token-gates` → 3/3 CLEAN
- `pnpm typecheck` → green (all packages)
- `cd ui && npx vitest run` → 2255/2255 (assertions updated in lockstep
where behavior changed)
- `pnpm --filter @paperclipai/ui build` → exit 0
- Storybook visual regression: run locally throughout (the
baseline-manifest archive is still unpublished, so CI cannot run this
suite — pre-existing condition from #9134). Every visible delta was
reviewed against a live instance and, where it was intentional (nesting
alignment, routine sub-nav restyle, unread-row shift), the affected
snapshots were re-baselined locally.
- Manual/measured: inbox + task lists (grouped and nested, light +
dark), hover-scrub frame timing, keyboard navigation, and the routine
detail scroll/alignment were exercised on a running instance.
## Risks
- Behavior-and-alignment changes concentrated in `IssueRow` /
`IssuesList` / `Inbox` (the shared task-row surfaces). The riskiest area
— the hover/keyboard-selection model — is covered by unit tests (updated
in lockstep) and was measured and driven live.
- The routine detail scroll change restructures that page's layout
container; verified the page's own scroll stays fixed while only the
section content scrolls.
- The visual suite cannot yet run in CI (unpublished baseline archive —
pre-existing); snapshot coverage is local-only until that lands.
## Model Used
- Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — agentic
coding with tool use (file editing, test execution, Playwright
measurement/screenshot verification); extended thinking enabled.
## 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
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Agents index (`ui/src/pages/Agents.tsx`) is the main roster
view, with both a list layout and an org-tree layout
> - On mobile viewports the roster could render rows with no visible
agent names because shrink-resistant trailing controls competed with the
fixed-width title cell
> - A roster where names are unreadable is unusable on phones, and the
org-tree view had a related narrow-width overflow risk for deeply
indented names
> - This pull request lets the list title flex below `xl`, hides
nonessential row controls on mobile, keeps Join/Leave reachable, and
truncates org-tree names safely
> - The result is a readable agents index on mobile while preserving
desktop meta-column alignment and truncation
## Linked Issues or Issue Description
No existing public GitHub issue; describing the bug per the bug report
template:
**What happened?**
On a mobile-width viewport, the agents index showed rows where agent
names could become unreadable or disappear because trailing controls
consumed the available row width. In the org view, deeply indented names
could overflow the row.
**Expected behavior**
Agent names remain visible on every viewport, Join/Leave stays
reachable, and desktop rows continue to align and truncate as before.
**Steps to reproduce**
Open Paperclip in a browser at a narrow viewport, navigate to the Agents
index, and observe rows with long names or left-membership controls.
**Paperclip version or commit**
Reproducible on `master` prior to this fix.
**Deployment mode**
Local dev instance. The bug is viewport-width dependent, not
deployment-mode dependent.
## What Changed
- `EntityRow` now lets callers control title text, subtitle text, and
the meta spacer classes while keeping the default truncation behavior
unchanged.
- Agents list rows now use `flex-1 xl:flex-none xl:w-56`, so names get
mobile width while desktop meta columns keep their aligned fixed title
column.
- Long list-view names/subtitles wrap below `xl` but return to
truncation at `xl` and above.
- Join/Leave stays visible on mobile; run/status/star row controls stay
hidden on mobile to avoid squeezing names.
- Org-tree rows use `min-w-0 truncate`, so deep indentation shortens
names with an ellipsis instead of overflowing.
- Regression tests cover mobile name visibility, left-membership
dimming, responsive title/meta behavior, and mobile Join/Leave
reachability.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/EntityRow.test.tsx src/pages/Agents.test.tsx` — 2 files,
18 tests passed.
- `pnpm check:token-gates` — all gates clean.
- `git diff --cached | rg -n "(AKIA|ASIA|SECRET|TOKEN|PASSWORD|PRIVATE
KEY|BEGIN RSA|BEGIN
OPENSSH|api[_-]?key|bearer|paperclip_api_key|DATABASE_URL|postgres://|sk-[A-Za-z0-9]|xox[baprs]-)"`
— no matches before push.
## Risks
- Low risk: UI-only change scoped to the agents index. The main behavior
change is that nonessential row controls remain hidden on mobile while
Join/Leave remains available there and all controls remain available on
larger screens/detail pages.
## Model Used
- OpenAI GPT-5 via Codex (`codex_local` adapter), agentic code editing
and local/GitHub verification.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Skills Manager gives operators a reusable skill layer, but
iteration still required manual edits, ad hoc prompts, and indirect run
inspection.
> - Skill authors need a focused workflow for editing skill files,
saving representative test inputs, and running those inputs through an
agent without exposing harness tasks as normal company work.
> - The backend therefore needs durable test inputs, reusable run
templates, hidden harness issues, scoped run execution, retention
metadata, and read-containment rules around hidden work.
> - The frontend needs a three-pane Studio that keeps skill files, saved
inputs/templates, and run output/history visible together while
preserving the existing design system and token rules.
> - This pull request ships that Skill Studio surface end to end:
database migrations, shared contracts, server APIs/services, hidden
harness execution behavior, UI routes/components, and focused tests.
> - The benefit is faster and safer skill iteration, with inspectable
outputs and fewer ways for internal harness work to leak into normal
task lists, costs, or adjacent read APIs.
## Linked Issues or Issue Description
No public GitHub issue exists for this feature. Feature request summary:
- Problem: Skill authors need to edit and test company skills in one
place instead of switching between the skill detail page, task creation,
run output, and manual prompt history.
- Proposed solution: Add a Skill Studio workbench with saved inputs,
reusable templates, hidden sandboxed test runs, live run status, output
inspection, run history, rerun/delete controls, and frontmatter-aware
editing.
- Expected users: Paperclip operators and agent-company maintainers who
create, fork, import, and tune skills.
- Related public PRs: Supersedes #9205, which was replaced so the public
PR branch name follows contributor policy.
- Duplicate search: searched public GitHub issues and PRs for "Skill
Studio"; no other active public issue or PR directly covers this
feature.
## What Changed
- Added database migrations for Skill Studio test inputs, test runs,
test run retention, and reusable run templates.
- Added shared Skill Studio types, validators, route helpers,
frontmatter utilities, and status handling.
- Added server services and routes for saved inputs, test runs,
templates, reruns, terminal-run deletion, hidden harness issue
execution, and run-detail hydration.
- Strengthened hidden-issue read containment across issue-adjacent
routes and cost rollups used by skill test harness work.
- Added the Skill Studio UI with skill file editing, frontmatter
editing, saved inputs, templates, run creation/cancel/rerun/delete
flows, output rendering, history, route support, and responsive pane
behavior.
- Added focused backend, shared, and UI tests for the new APIs, routing
logic, editor/run behavior, hidden-issue containment, and migration
safety.
- Rebased onto current `master`, removed the generated lockfile diff
from the PR, and verified no workflow files are changed.
## Verification
- [x] `pnpm --filter @paperclipai/db check:migrations`
- [x] `pnpm check:token-gates`
- [x] `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/company-skills-routes.test.ts
server/src/__tests__/company-skill-test-runs-service.test.ts
ui/src/lib/skill-studio.test.ts ui/src/pages/SkillStudio.test.tsx` — 5
files, 132 tests passed
- [x] Greptile review on the latest PR head
- [x] GitHub PR checks on the latest PR head
## Risks
- Medium risk because this is a broad feature touching database schema,
server orchestration, issue visibility, and a large UI surface.
- Hidden harness issue containment is security-sensitive; this PR
includes regression coverage for adjacent read paths and cost rollups.
- The new migrations are additive and use idempotent guards where
applicable, but deployed databases that previously tested draft
migration numbers should still be checked carefully.
- The UI depends on a new resizable panels package in `ui/package.json`;
the lockfile is intentionally left to repository automation.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent with shell, git, and GitHub CLI tool
use. Earlier feature commits include assistance from other Paperclip
coding agents; this PR preparation, rebase, cleanup commit, and PR body
were completed by OpenAI Codex in a Paperclip worktree.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators rely on the issue detail thread to understand whether a
task is blocked, live, or waiting on another task.
> - A blocked issue can have a healthy blocker chain where downstream
work is actively running and the parent will resume automatically.
> - Showing that case with the same amber blocked notice as a stalled or
attention-needed blocker makes the state look more severe than it is.
> - The UI already receives blocker-attention state, blocker summaries,
and company live-run ids, so this can be clarified without a new API
shape.
> - This pull request adds a blue "Waiting on live work" notice for
covered blocker chains while preserving the existing amber notice for
the other blocked states.
> - The benefit is that operators can distinguish healthy queued work
from blocked work that needs intervention.
## Linked Issues or Issue Description
Refs #3820
Refs #8271
Related PR: #3877
Supersedes #9295
## What Changed
- Added a blue `IssueBlockedNotice` variant when
`blockerAttention.state` is `covered` and the blocker chain has live
work.
- Rendered blocker-chain progress as done, running, and queued steps,
including a "Now running" row for live terminal blockers.
- Preserved the existing amber blocked notice for stalled,
attention-needed, ordinary blocked, and successful-run handoff states.
- Plumbed the existing `liveIssueIds`, `blockedBy`, and
`blockerAttention` data from issue detail into the chat-thread blocked
notice.
- Added regression coverage around the covered live-work state, the
no-confirmed-live fallback, numeric step ordering, and amber fallback
states.
- Hardened a low-trust server route test cleanup helper so CI deletes
heartbeat run events before deleting heartbeat runs.
## Verification
- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm exec vitest run ui/src/components/IssueBlockedNotice.test.tsx`
- GitHub PR workflow is green on head
`52ab6d9076ce233c183bf7133fa666e8597b6765`.
- Greptile check is green on head
`52ab6d9076ce233c183bf7133fa666e8597b6765` with zero unresolved review
threads.
## Risks
Low runtime risk: the product change is frontend-only and uses data
already returned to the issue detail page. The main risk is visual
regression in the blocked notice; the change keeps non-covered states on
the existing amber path and adds focused regression coverage. The
server-side change is test-only cleanup for an existing CI shard
failure.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex using GPT-5, tool-use enabled in a repository workspace.
The runtime did not expose a more specific model build id or
context-window size.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The work timeline helps operators understand what agents did, when
they did it, and who kicked off each run
> - Timeline statistics should describe the window the operator is
viewing, not hidden work outside the viewport
> - Kickoff chips should point to the run that was actually closest to
the triggering edge, not stale later runs on the same issue
> - This pull request scopes timeline summary stats to the visible
window and deduplicates kickoff attribution to the nearest matching run
> - The benefit is that operators get a more accurate, less misleading
timeline while scanning agent work
## Linked Issues or Issue Description
No matching public GitHub issue was found. Public duplicate search found
no open PR for "work timeline visible window kickoff".
### What happened?
Work timeline summary stats could count hidden spans outside the
selected visible window, and kickoff attribution could appear on a stale
matching run instead of the closest run associated with the edge.
### Expected behavior
Visible timeline stats should reflect only the current visible range,
and each kickoff edge should attach to the nearest matching run.
### Steps to reproduce
1. View a work timeline with agent spans that begin before or end after
the selected visible window.
2. Compare the summary stats against only the spans visible in the
viewport.
3. View repeated runs for the same actor and issue that share a kickoff
edge.
4. Check which run gets the kickoff chip.
### Paperclip version or commit
Current `origin/master` before this PR.
### Deployment mode
Board UI.
## What Changed
- Filters timeline summary stats to the visible time window.
- Passes visible-window metadata into the work timeline chart and page
state.
- Assigns each kickoff edge to only the closest matching run, with
deterministic tie-breaking.
- Adds focused regression coverage for visible stats and stale kickoff
attribution.
## Verification
- `pnpm exec vitest run
ui/src/components/timeline/WorkTimelineChart.test.tsx
ui/src/lib/timeline/layout.test.ts ui/src/pages/Timeline.test.tsx` -- 38
tests passed.
- `pnpm check:token-gates` -- all gates clean.
- `git diff --check origin/master...HEAD` -- passed.
## Visual Evidence
Public review artifacts for the user-visible timeline behaviours. The
SVGs use sanitized run labels and no internal Paperclip issue
identifiers.


Artifact gist:
https://gist.github.com/cryppadotta/dc2645d54c4bc56ffc0323224b3ea4bf
## Risks
- Low risk. The change affects timeline rendering and summary
calculations only; the main risk is a subtle difference in which run
gets a kickoff chip when multiple runs are very close together.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent based on GPT-5, tool-enabled shell workflow.
Exact hosted model variant and context window were not exposed by the
runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Adapter packages are the bridge between the control plane and local
agent harnesses such as Claude Code, Codex, and Gemini CLI.
> - ACP support was concentrated in a separate `acpx_local` adapter,
which made ACP feel like a separate agent choice instead of an execution
capability of the harness adapters.
> - Claude, Codex, and Gemini now have ACP-capable harnesses, so the
native adapter should own ACP selection, fallback, config, transcript
parsing, and environment diagnostics.
> - The standalone ACPX adapter still needs a compatibility path for
existing rows, but it should not be offered as an active adapter for new
agents.
> - This pull request moves the shared ACP runtime into
`@paperclipai/acpx-engine`, wires Claude/Codex/Gemini local adapters to
prefer ACP when prerequisites are available, and retires `acpx_local` to
a tombstone.
> - The benefit is one adapter per harness, richer ACP transcripts by
default where possible, and a migration path for existing Claude/Codex
ACPX agents.
## Linked Issues or Issue Description
Closes#5932 — the broken default `acpx_local` Claude path is replaced
by native `claude_local` ACP support, existing Claude/Codex ACPX rows
migrate to native adapters, and new agents no longer choose the
standalone ACPX adapter.
Refs #4893 — original merged ACPX local adapter runtime that this PR
replaces with native per-harness ACP engines.
Refs #6590 — prior ACPX-Claude seamlessness work folded into the new
native Claude ACP path.
Refs #197 — related open generic ACP/Kiro adapter work; this PR does not
close it because Kiro/custom generic ACP remains a separate adapter
decision.
Refs #7018 — related Kimi-specific `acpx_local` shell failure; this PR
retires the built-in standalone adapter but does not add a native Kimi
adapter.
Refs #8864 — related ACPX prompt/API guidance PR; this PR moves runtime
guidance into the shared/native ACP engine path instead of the old
standalone adapter.
Refs #8881 — related `acpx_local` POSIX shell failure from the old
`acpx` pin; this PR updates ACP dependencies but does not claim
custom/OMP ACP support as a first-class native adapter.
Refs #8964 — related open `acpx_local` stderr cleanup PR; this PR makes
the old runtime path obsolete for new agents but keeps it as a
non-closing reference.
Problem description:
- The standalone `acpx_local` adapter duplicates Claude/Codex agent
choices that already have first-class local adapters.
- ACP should be an execution engine capability of each harness adapter
when the underlying harness supports ACP.
- Existing `acpx_local` agents should either migrate to native harness
adapters or fail with an explicit retirement message instead of silently
falling back to the process adapter.
## What Changed
- Added `@paperclipai/acpx-engine` as the shared ACP execution,
session-codec, CLI formatter, and UI parser package.
- Wired `claude_local`, `codex_local`, and `gemini_local` to auto-select
ACP by default when prerequisites pass, with `engine=cli` opt-out and
`engine=acp` strict mode.
- Added ACP config schema/UI fields, environment checks, session-codec
preservation, transcript parsing, and adapter capability metadata for
the native adapters.
- Retired `acpx_local` to a server tombstone, removed its
UI/package/runtime image surface, and added a migration for existing
Claude/Codex ACPX agents.
- Updated package manifests, lockfile, release tooling, docs, Kubernetes
sandbox defaults, and tests.
## Verification
- `corepack pnpm --filter @paperclipai/acpx-engine typecheck`
- `corepack pnpm --filter @paperclipai/adapter-claude-local typecheck`
- `corepack pnpm --filter @paperclipai/adapter-codex-local typecheck`
- `corepack pnpm --filter @paperclipai/adapter-gemini-local typecheck`
- `corepack pnpm --filter @paperclipai/acpx-engine exec vitest run`
- `corepack pnpm --filter @paperclipai/adapter-claude-local exec vitest
run src/server/acp.test.ts src/server/execute.acp-fallback.test.ts
src/ui/build-config.test.ts`
- `corepack pnpm --filter @paperclipai/adapter-codex-local exec vitest
run src/server/acp.test.ts src/ui/build-config.test.ts`
- `corepack pnpm --filter @paperclipai/adapter-gemini-local exec vitest
run src/server/acp.test.ts src/ui/build-config.test.ts
src/ui/parse-stdout.test.ts`
- `corepack pnpm --filter @paperclipai/plugin-sdk ensure-build-deps &&
corepack pnpm --filter @paperclipai/server exec tsc --noEmit`
- `corepack pnpm --filter @paperclipai/server exec vitest run
src/__tests__/adapter-routes.test.ts
src/__tests__/adapter-session-codecs.test.ts
src/__tests__/adapter-models.test.ts`
- `corepack pnpm --filter @paperclipai/ui typecheck`
- `corepack pnpm --filter @paperclipai/ui exec vitest run
src/adapters/metadata.test.ts
src/adapters/adapter-display-registry.test.ts
src/components/AgentConfigForm.test.ts
src/components/AgentConfigForm.render.test.tsx
src/components/transcript/RunTranscriptView.test.tsx`
- `node --test scripts/bootstrap-npm-package.test.mjs
scripts/release-package-map.test.mjs
scripts/verify-release-registry-state.test.mjs`
Note: the server typecheck script calls `pnpm` internally; this dev
shell exposes pnpm through Corepack only, so I ran the two script steps
manually with `corepack pnpm`.
## Risks
- Migration changes existing `acpx_local` Claude/Codex agents to native
adapter types and clears old ACPX task sessions/runtime state.
- Custom ACP commands remain on the retired tombstone and will need a
separate future adapter/plugin path.
- ACP auto-selection depends on local Node and ACP server command
prerequisites; remote and unsupported environments fall back to CLI
unless `engine=acp` is explicit.
- `@paperclipai/acpx-engine` is a new public package and needs npm
trusted-publishing bootstrap before release automation can publish it.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex coding agent. Exact hosted model build and
context-window size are not exposed in this runtime. Tool use included
shell execution, repository editing, GitHub CLI operations, and local
test/typecheck 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Its UI is governed by a design system (`DESIGN.md` + the token layer
in `ui/src/index.css`, merged in #9134) whose first principle is "one
way to say each thing" — one Card, one Badge, one nav row
> - After the token extraction landed, ~35 files still hand-rolled card
containers, ~45 files hand-rolled pill spans, the sidebar agents section
duplicated the nav-row chrome, and the inbox and tasks lists rendered
the same task rows two subtly different ways
> - Each divergence is a place where a future design change (radius,
hover language, status vocabulary) silently misses surfaces, defeating
the "edit tokens + run checks" model the design system exists for
> - This pull request converges those surfaces onto the shared
primitives, codifies the radius scale as the modern multiplicative
shadcn ladder, and unifies the row/hover/tree-guide language across the
inbox and tasks lists — every visible delta was human-reviewed
screen-by-screen against a live instance across nine feedback rounds
> - The benefit is that the app's look is now steerable from single
knobs (one `--radius` anchor, one Card, one Badge, one row component),
and the visual regression suite covers the result (514 snapshots
including a new AgentDetail page story)
## Linked Issues or Issue Description
No public GitHub issue exists for this work; describing per the feature
template:
- **Problem**: after the design-token foundation (#9134),
component-level drift remained — hand-rolled cards/pills, duplicated
sidebar row chrome, and two different renderings of task rows (inbox vs
tasks list) meant design changes had to be applied per-surface and
frequently missed spots (e.g. status glyphs rendered 16px in the inbox
but 20px in the tasks list because a slot override silently beat the
component default).
- **Proposed behavior**: all card-shaped containers render via `Card`,
all label pills via `Badge`, sidebar rows via `SidebarNavItem`, and both
task-list surfaces via one `IssueRow` configuration; the radius scale is
a single multiplicative ladder anchored at `--radius: 0.5rem`.
- **Alternatives considered**: converting interactive
`<button>`/`<Link>` cards to `Card` divs (rejected — breaks semantics;
documented inline with `design-allow` comments instead); keeping the
legacy additive radius ladder (rejected in favor of the standard shadcn
multiplicative mapping).
## What Changed
- `Card` adoption across ~35 files (settings pages, auth/board flows,
dashboards, list containers, KPI tiles); non-adoptable sites
(interactive cards, `<li>` rows, class-string props, chart tooltip)
carry documented `design-allow(card-pattern)` comments
- `Card` gains an `interactive` prop — one quiet hover affordance for
clickable cards (cursor, border darken, shadow lift, focus ring),
applied to skills tiles, artifact cards, and the company selector; cards
carry no resting shadow
- `Badge` adoption for 113 hand-rolled pill spans across ~45 files;
`PropertyChip` wraps `Badge` internally; `StatusBadge`, external-object
chips, and match chips stay bespoke by documented decision (WCAG-tuned
status mechanics)
- Radius ladder becomes the multiplicative shadcn mapping
(`sm/md/lg/xl/2xl/3xl/4xl = 0.6/0.8/1.0/1.4/1.8/2.2/2.6 × --radius`,
anchor `0.5rem`); every card surface unifies on `rounded-lg`; the
orphaned 8px literal token is deleted
- Sidebar: agent rows render via `SidebarNavItem` (new additive props:
`iconNode`, `active`, `trailing`, `liveAccessory`); live dots use
`--status-agent-running`; one row rhythm and inset rounded pill
highlight; right-aligned trailing badges; every labeled section is
collapsible
- Inbox + tasks lists unified: md status glyphs, `accent/50` rounded row
hovers, vertical tree guides under parent rows (opaque underlay so
dark-mode translucent borders don't stack), no horizontal dividers under
expanded parents; the swipe-to-archive reveal layer shows only
mid-swipe; board toggle uses the `SquareKanban` glyph
- Kanban: every column carries a status-hued tint; lanes default
expanded (including empty); compact mode collapses empty lanes to
labeled rails (fixes a clipped, label-less empty-column state)
- Storybook: new AgentDetail page story (realistic fixtures, light+dark)
joins the visual suite; suite captures with `reducedMotion: 'reduce'`
and the ux-lab reasoning ticker honors `prefers-reduced-motion`; a stale
lexical alias in `storybook/main.ts` is fixed (build was broken since
the lexical 0.46 bump)
- Keyboard navigation, from live review of the unified lists: inbox
navigation keys work on every tab (archive keys stay scoped to the
archivable tab); keyboard-driven scrolling no longer hands the selection
to whatever row lands under the stationary cursor (hover selects only
after real pointer movement); the tasks list view gains the same j/k /
arrows / Enter selection model as the inbox; and the `g` then `i`
go-to-inbox chord works app-wide instead of only on the issue detail
page
- Token gates restored to 3/3 CLEAN (tokenized a post-#9134 regression
in the recovery card); decisions recorded in
`doc/design/DECISION-SHEET.md` and `doc/design/COMPONENT-INVENTORY.md`
(investigation verdicts: FileTree vs WorkspaceFileBrowser and the four
entity pickers stay separate — evidence included)
## Verification
- `pnpm check:token-gates` → 3/3 CLEAN
- `pnpm typecheck` → green (all packages)
- `cd ui && npx vitest run` → 2106/2106 (assertions updated in lockstep
where they documented superseded decisions; new tests for the global
go-to-inbox chord)
- `pnpm --filter @paperclipai/ui build` → exit 0
- `pnpm build-storybook` → succeeds (also fixes the lexical-alias break
on master)
- Visual regression: 514-snapshot Playwright suite green against the
updated baseline (zero diffs from the keyboard-navigation round — those
changes are purely behavioral). Note: baselines live outside git per the
suite design and the baseline-manifest archive is not yet published, so
CI cannot run this suite — it was run locally throughout; every visible
delta was reviewed screen-by-screen in a live instance across nine
review rounds. Review evidence (before/after triplets) intentionally
kept out of the repo for size; available on request.
- Manual: exercised dashboard, tasks (list + board), inbox, agents,
skills, costs, settings, and artifact surfaces in light and dark themes
## Risks
- Wide but shallow visual surface: most changes are class-string
substitutions with behavior preserved (props, handlers, roles, test
ids). The riskiest areas — dnd-kit card refs (React 19 ref-as-prop),
inbox swipe-to-archive, and sidebar overlays — are covered by existing
unit tests (all green) and were manually exercised.
- Intentional visual deltas (rounded cards, tinted kanban columns, md
status glyphs, unified hovers) are design decisions recorded in
`doc/design/DECISION-SHEET.md`; each maps to a re-baselined snapshot set
locally.
- The visual suite cannot yet run in CI (unpublished baseline archive —
pre-existing condition from #9134); until that lands, snapshot coverage
is local-only.
## Model Used
- Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code — agentic
coding with tool use (file editing, test execution, Playwright
screenshot verification); extended thinking enabled.
## 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
- [ ] All Paperclip CI gates are green (will confirm once CI runs)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending first review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Adds company skill fork precheck metadata, fork result/reassignment contracts, selected-agent reassignment during fork creation, and targeted server/shared test coverage.
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The work timeline helps operators understand when agent and user
activity actually happened across a project.
> - The timeline view needs clearer interaction context so activity is
easier to inspect and reason about.
> - The existing story coverage did not fully exercise the denser
activity states needed to review this UI safely.
> - This pull request expands the work timeline data shape, service
behavior, UI rendering, tests, and Storybook story so the activity
timeline is easier to verify.
> - The benefit is a more inspectable timeline for project activity,
backed by targeted server and UI coverage.
## Linked Issues or Issue Description
No public GitHub issue found, so this PR describes the feature inline
following the feature request template.
**Subsystem affected**
Cross-cutting: `server/`, `packages/shared`, and `ui/`.
**Problem or motivation**
Project operators need a clearer timeline view that shows when work
activity happened, how much agent time is represented inside the
selected window, and enough realistic activity states for safe visual
review. Sparse mock data and unbounded summary calculations make it
harder to trust the timeline when inspecting historical or capped
windows.
**Proposed solution**
Enrich the work timeline activity data returned by the service, render
clearer top-level timeline summary stats, clamp duration calculations to
the returned window, prorate token totals for partially visible spans,
and add Storybook/test coverage with realistic timeline activity data.
**Alternatives considered**
Keeping the existing sparse timeline story was considered, but it would
leave dense activity layouts and selected-window summary behavior
under-reviewed. Counting full span usage for partially visible spans was
also considered, but it makes historical windows report activity outside
the displayed range.
**Roadmap alignment**
Searched `ROADMAP.md` for timeline/activity references and found no
conflicting planned core work.
**Additional context**
This PR does not include migrations and does not commit generated design
screenshots or images.
## What Changed
- Extended shared work timeline activity types and server timeline
service behavior.
- Updated the timeline page and work timeline chart for richer activity
rendering.
- Clamped timeline runtime summary calculations to the returned window
and prorated summary token usage for clipped spans.
- Added and updated targeted server/UI tests for timeline activity
behavior.
- Added Storybook timeline mock coverage and Storybook preview setup
needed by the story.
## Verification
- `git rebase origin/master` completed cleanly after fetching
`paperclipai/paperclip:master`.
- `git diff --check origin/master...HEAD`
- `pnpm exec vitest run
server/src/__tests__/work-timeline-service.test.ts
ui/src/components/timeline/WorkTimelineChart.test.tsx
ui/src/pages/Timeline.test.tsx` — latest run: 3 files passed, 28 tests
passed.
- Greptile review completed at 5/5 with no unresolved Greptile threads
after fixes.
- GitHub checks completed green on the latest head SHA; Storybook visual
regression was skipped by the workflow.
- `pnpm check:token-gates` currently fails locally on existing
`origin/master` violations in `ui/src/components/ActivityCharts.tsx` and
`ui/src/components/IssueRecoveryActionCard.tsx`; this PR does not modify
those files.
## Risks
Low to moderate risk. The change affects the work timeline service
response shape and timeline UI rendering, so regressions would likely
show up as missing/incorrect timeline activity display. Targeted service
and UI tests cover the changed behavior. No migrations are included.
## Model Used
OpenAI Codex running GPT-5 as a tool-enabled coding agent with local
shell and GitHub CLI access. Exact runtime model ID/context-window size
was not exposed by the environment.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board sidebar is a high-frequency navigation surface for
companies, projects, and work queues.
> - Starred projects render as child rows under Projects in the expanded
sidebar.
> - The collapsed rail should align every nav icon in the same rail
column.
> - The starred-project child indent was still applied in the collapsed
rail, which pushed the project glyph out of alignment.
> - This pull request keeps the expanded hierarchy indent while removing
it only for the collapsed rail.
> - The benefit is a cleaner collapsed sidebar without changing expanded
sidebar hierarchy.
## Linked Issues or Issue Description
No public GitHub issue exists.
## What happened?
In the collapsed sidebar rail, starred project rows kept the expanded
child indentation. That pushed the project glyph out of alignment with
the rest of the collapsed sidebar icons.
## Expected behavior
Collapsed starred project icons should align with the other sidebar rail
icons while the expanded sidebar should keep the child-row indentation
under Projects.
## Steps to reproduce
1. Open Paperclip with at least one starred project.
2. Collapse the sidebar into rail mode.
3. Compare the starred project glyph position with the other collapsed
sidebar glyphs.
## Paperclip version or commit
Reproduced on the PR base before this branch; fixed on commit
e57d14343d with CI cleanup on commit
12aafe8959.
## Deployment mode
Local dev (`pnpm dev`).
## What Changed
- Applies the starred-project left padding only when the sidebar is not
in rail mode.
- Adds a regression test covering expanded and collapsed starred-project
rendering.
- Makes the heartbeat worktree suppression test cleanup tolerate late
heartbeat run events before deleting heartbeat runs.
## Verification
- Passed: `pnpm exec vitest run
ui/src/components/SidebarStarredProjects.test.tsx`
- Passed: `pnpm exec vitest run
server/src/__tests__/heartbeat-worktree-suppression.test.ts`
- Passed: PR #9215 latest-head GitHub checks on commit
`12aafe8959351826038baf0c1e401fb44913c67c`
- Passed: Greptile 5/5 with no inline comments or unresolved review
threads on commit `12aafe8959351826038baf0c1e401fb44913c67c`
- Known existing baseline failure: `pnpm check:token-gates` reports
violations in `ui/src/components/ActivityCharts.tsx` and
`ui/src/components/IssueRecoveryActionCard.tsx`, which this PR does not
touch.
## Risks
Low risk. The UI change adjusts one conditional class on starred project
links and preserves the expanded sidebar layout. The server test change
is cleanup-only and does not alter production behavior.
## Model Used
OpenAI GPT-5 Codex coding agent with local command execution and
repository editing tools.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run in heartbeat runs orchestrated by the server; run
lifecycle, retry scheduling, and the dashboard's run-activity metrics
are the subsystems involved
> - A spike in "failed" tasks traced to three causes: server restarts
killing in-flight runs and mislabeling them as failures, deterministic
workspace-validation loops when a worktree's branch diverged, and
provider quota/usage-limit errors being classified as generic transient
failures (putting agents into error state and polluting metrics)
> - Killed-then-recovered runs and quota waits are not product failures,
so both the runtime behavior and the reporting needed to distinguish
them
> - This pull request drains runs gracefully on shutdown with idempotent
restart retries, self-heals workspace branch mismatches, adds a
quota-aware failure class with reset-time retry, separates recovered
restart kills from true failures on the dashboard, and documents restart
hygiene for operators
> - The benefit is fewer spurious failures, automatic recovery instead
of manual repair, and dashboard metrics that reflect real failure rates
## Linked Issues or Issue Description
No public GitHub issue exists; describing the bug inline per the
bug-report template:
**What happened?**
In-flight heartbeat runs are marked `failed` when the server restarts,
even though a retry later succeeds. Worktrees whose checked-out branch
diverges from the issue branch fail workspace validation on every
subsequent run with no recovery path. Provider quota/usage-limit
responses are treated as generic transient upstream errors, putting
agents into an error state and retrying before the quota window resets.
The dashboard counts all of these as true failures, inflating failure
metrics.
**Expected behavior**
Graceful shutdown should interrupt (not fail) running runs and chain
exactly one recovery retry. Workspace validation should repair
recoverable branch mismatches automatically. Quota errors should get
their own error class with the retry scheduled at the provider reset
time and the agent left idle. The dashboard should report recovered
restart kills separately from true failures.
**Steps to reproduce**
1. Start a heartbeat run, then restart the server (SIGTERM) while it is
in flight — the run lands as `failed` with a process-loss error code
even when its retry succeeds
2. Check out an issue whose worktree branch has diverged (e.g. after a
force-moved branch) — every subsequent run fails
`workspace_validation_failed` deterministically
3. Drive an agent into a provider usage-limit window — the run fails as
a generic transient upstream error and the agent enters an error state
instead of idling until the reset time
**Paperclip version or commit**
master (base c07e650cd)
**Deployment mode**
Self-hosted dev plane (Linux, node server + embedded Postgres)
## What Changed
- Graceful shutdown (SIGTERM hook) now marks in-flight runs
`interrupted` instead of `failed` and enqueues an idempotent
process-loss retry (pre-insert existence check on `retryOfRunId`
prevents duplicates; bursts chain exactly one retry per interrupted run)
- Run-liveness classification routes `interrupted` to `needs_followup`
rather than `failed`
- Workspace validation self-heals branch mismatch / missing-branch
states instead of failing deterministically on every run
- New `provider_quota` error class: session/usage-limit responses
schedule the retry at the provider reset time and leave the agent idle
(not errored); fixes a case where a quota-terminated run with subtype
`success` was misclassified as failed; HTTP 529 remains transient
- Dashboard run-activity query separates recovered restart kills from
true failures via a recursive CTE over `retry_of_run_id` (ancestors of a
succeeded retry count as recovered), adds a per-day failed-by-error-code
breakdown, and binds the window start as a timestamptz string
- Activity charts UI: amber "Recovered" segment with legend and per-day
error-code tooltip; success-rate chart counts recovered runs as
successes
- New ops runbook: `docs/deploy/dev-plane-restart-hygiene.md`
## Verification
- Greptile follow-up fixes on `992705edd`: `pnpm exec vitest run
server/src/__tests__/server-startup-feedback-export.test.ts
server/src/__tests__/heartbeat-workspace-branch-containment.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts` (209
tests); `pnpm --filter @paperclipai/server typecheck`; `git diff
--check`
- Post-rebase CI fixes: `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts`; `pnpm exec
vitest run server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/workspace-runtime.test.ts
server/src/__tests__/heartbeat-workspace-branch-containment.test.ts
server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts` (227 tests);
`pnpm --filter @paperclipai/server typecheck`
- `npm test` server suites covering the changes:
`heartbeat-process-recovery`, `heartbeat-stop-metadata`,
`heartbeat-retry-scheduling` (95 tests), quota parse +
execute/retry-scheduling suites (100 tests), workspace self-heal suites
(200 tests), dashboard run-activity tests (3 tests) — all green,
typecheck exit 0
- Dashboard CTE cross-checked against a real development database: two
restart-burst days moved from 17 to 8 and 19 to 6 true failures once
recovered kills were separated, matching manual retry-chain inspection
- Screenshot verification of the real ActivityCharts component
(recovered segment + tooltip) during QA
## Risks
- Behavioral shift: runs killed by a restart no longer surface as
`failed`; anyone consuming raw run statuses will see `interrupted` (new
status value) — dashboards/queries in this repo were updated accordingly
- Retry chaining on repeated restarts is bounded (one chained retry per
interruption) but a pathological restart loop still delays work rather
than failing it; the runbook covers operator hygiene for that case
- Dashboard query adds a recursive CTE; cost is bounded by the
day-window row count and was verified against production-sized data
- No schema migrations; low migration risk
## Model Used
- Claude (Anthropic) — claude-fable-5 via Claude Code / Paperclip agent
harness, extended thinking with tool use; implementation commits also
produced with Codex CLI (GPT-5 class) agents under the same harness
## 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)
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Adds the task-page recovery affordance for workspace branch divergence: reconcile-forward when ancestry is safe, and a break-glass override flow that requires explicit confirmation and a reason.
Includes client wiring for the existing reconcile-branch endpoint, issue-detail refresh after successful reconciliation, runtime-management gating for break-glass, and tests for action visibility, payloads, permission gating, and workspace-target selection.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board sidebar is the primary navigation surface for operators
scanning companies, projects, tasks, agents, and related control-plane
tools.
> - Goals still has a route and product surface, but keeping the
top-level sidebar link always visible makes it part of the default
navigation whether or not that surface is ready for every operator.
> - Instance experimental settings already provide a controlled place to
expose optional UI surfaces while they are being evaluated.
> - This pull request adds a dedicated experimental setting for
restoring the Goals sidebar link.
> - The benefit is a quieter default sidebar with an explicit escape
hatch for operators who still need the Goals entry point.
## Linked Issues or Issue Description
No public GitHub issue exists for this internal task, so the feature
request is described inline.
**Subsystem affected**
Cross-cutting: `ui/`, `server/`, and `packages/shared`.
**Problem or motivation**
The Goals route remains available, but the top-level Goals sidebar entry
makes that surface part of the default operator navigation. While the
goals surface is still being evaluated, operators need a quieter default
sidebar without losing an escape hatch for teams that still rely on the
link.
**Proposed solution**
Add a boolean instance experimental setting, `enableGoalsSidebarLink`,
default it to `false`, and render the Goals sidebar link only when the
setting is enabled. Expose the toggle in Instance Experimental Settings
so operators can restore the link without changing routes or rebuilding
the app.
**Alternatives considered**
- Remove the Goals route entirely: rejected because this task only asks
to hide the sidebar entry point and preserve access for teams evaluating
goals.
- Keep the sidebar link always visible: rejected because it does not
provide the requested quieter default navigation.
- Hard-code a local UI flag: rejected because instance experimental
settings already provide the expected operator-controlled pattern.
**Roadmap alignment**
Checked `ROADMAP.md`; no overlapping goals/sidebar/experimental roadmap
entry was found.
**Additional context**
The `/goals` route is preserved. This PR only gates the sidebar
navigation item.
## What Changed
- Added `enableGoalsSidebarLink` to the shared instance experimental
settings type and validator, defaulting to `false`.
- Normalized the new setting in the server instance settings service.
- Hid the Goals sidebar nav item unless the new setting is enabled.
- Added a Goals Sidebar Link toggle to the Instance Experimental
Settings page.
- Updated shared, server, sidebar, and settings page tests for the new
setting.
## Verification
- `pnpm exec vitest run packages/shared/src/validators/instance.test.ts
server/src/__tests__/instance-settings-service.test.ts
server/src/__tests__/instance-settings-routes.test.ts
ui/src/components/Sidebar.test.tsx
ui/src/pages/InstanceExperimentalSettings.test.tsx`
- `git diff --check origin/master...HEAD`
- `git merge-tree --write-tree HEAD origin/master`
- Searched for duplicate/related PRs by title and
`enableGoalsSidebarLink`; none found.
- Checked `ROADMAP.md` for overlapping goals/sidebar/experimental
entries; none found.
## Risks
Low risk. The main behavior shift is that operators who depended on the
sidebar Goals link need to enable the new experimental toggle. The
`/goals` route itself is not removed.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5-based Paperclip CodexCoder session with repository
tool access and command execution. Exact API model identifier and
context window were not exposed by the Paperclip harness.
## 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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Its UI is the operator's daily surface: task lists, boards, budgets,
agent status — all built on shadcn components and Tailwind
> - Visual values (colors, spacing, type sizes, radii) were hardcoded at
~1,600 call sites: the same "small gray label" was 9/10/11px depending
on the file, charts disagreed with chips about status colors, two
toggle-switch implementations coexisted in two greens, and there was no
visual regression coverage
> - This made the UI drift-prone and made any restyle a
hundreds-of-files project, which discourages design iteration
> - This pull request extracts visual values into a single token layer
in `ui/src/index.css`, adds a Storybook visual regression suite backed
by external immutable baseline archives, and then applies a deliberate
retune reviewed change-by-change on screenshot diffs
> - The benefit is that Paperclip's look becomes a config surface:
retheming is a token edit reviewed as a snapshot diff, drift is blocked
by a token gate, and future UI PRs can prove exactly what changed
visually without committing hundreds of PNGs
## Linked Issues or Issue Description
No existing public issue covers this work (searched "design tokens",
"visual regression", "design system" across issues and PRs). Related in
spirit: Refs #8982 (theming a hardcoded panel — a one-off instance of
the same problem class this PR addresses systematically).
**Problem (feature-request form):** UI visual values are hardcoded per
call site with no source of truth and no regression coverage;
consistency depends on reviewer memory, and restyling requires mass file
edits.
**Proposed solution (this PR):** a single token layer + enforcement gate
+ externally stored visual snapshot suite, then an intentional restyle
on top of that foundation.
## What Changed
- **Token extraction (zero visual change, machine-verified during
development):** committed codemods (`scripts/codemod-*.mjs`) moved
~1,600 hardcoded color/type/spacing/radius/shadow/misc values into named
tokens in a non-inline `:root` block of `ui/src/index.css`.
- **Visual regression suite:** `pnpm test:storybook-visual` covers 255
stories × light/dark = 510 Playwright screenshots at `maxDiffPixels: 0`,
plus new primitive-coverage stories and deterministic-render fixes.
- **External visual baselines:** committed PNG snapshots were removed.
`tests/storybook-visual/baseline-manifest.json` pins an immutable
archive URL/hash/size/count, and `scripts/storybook-visual-baseline.mjs`
handles `download`, `verify`, `pack`, and trusted maintainer `upload`
flows.
- **Opt-in visual CI artifacts:** added a `Storybook Visual` workflow
that runs on manual dispatch or PRs labeled `storybook-visual`,
downloads/verifies the baseline, runs Playwright, and uploads Playwright
report/test-result artifacts for review. Normal PR runs do not mutate
baseline objects.
- **Token gate:** `pnpm check:token-gates` — zero hex literals, zero
arbitrary bracket values, zero raw font-sizes in `ui/src/components/**`
and `ui/src/pages/**`, with a documented inline allowlist for legitimate
opt-outs.
- **Theme retune (intentional, snapshot-reviewed):** new base theme
values; radius ladder derived from a single `--radius` knob; micro-type
cluster collapsed to a named ladder (`--text-nano/micro/compact` +
Tailwind `text-xs`/`text-sm`); letter-spacing collapsed to named steps.
- **One status-color vocabulary:** charts, quota/budget bar fills,
RUNNING/live chips, and liveness indicators all use the canonical
`--status-*` hues. Light-mode legibility fixes for red alert surfaces
that used dark-tuned text classes.
- **One switch:** `ToggleSwitch` restyled to the registry capsule form,
second hand-rolled implementation removed, and all call sites unified.
- **Docs:** `DESIGN.md` is the design contract; `doc/design/` holds
audit reports, decision logs, and updated guidance for external baseline
review/update workflows.
- Dead code removed (`agentStatusBadge` duplicate map), byte-identical
contrast constants consolidated, semantic renames
(`--project-seed`/`--project-none`, `--liveness-blue`).
## Verification
- `pnpm check:token-gates` — 3/3 gates CLEAN during the design-system
run
- `pnpm typecheck` && `pnpm --filter @paperclipai/ui build` — green
during the design-system run
- `node --test scripts/__tests__/storybook-visual-baseline.test.mjs` —
pass after external-baseline rework
- `pnpm exec tsc --noEmit --pretty false --module NodeNext
--moduleResolution NodeNext --target ES2022 --types
node,@playwright/test tests/storybook-visual/playwright.config.ts
tests/storybook-visual/storybook-visual.spec.ts` — pass after
external-baseline rework
- `git diff --check origin/pr/9134..HEAD` — pass after external-baseline
rework
- `find tests/storybook-visual -type f -name '*.png' -print | wc -l` —
`0`
- `node scripts/storybook-visual-baseline.mjs verify` — intentionally
fails closed until the first trusted maintainer publishes the baseline
archive and updates `baseline-manifest.json`
## Risks
- **Large but shallow:** the PR still touches many UI files due to
mechanical token extraction and retune work, but committed PNG snapshot
churn has been removed from the branch.
- **Baseline publication required before the visual suite can pass in
clean clones:** the manifest currently has placeholder archive metadata.
A trusted maintainer must publish the first immutable archive, then
update `baseline-manifest.json`.
- **Rendering platform variance:** the external baseline should be
captured in the documented Linux/Chromium environment. Future CI runs
verify against the pinned archive and fail closed on checksum/count
mismatch.
- **Visual CI is opt-in while stabilizing:** add the `storybook-visual`
label or dispatch the workflow manually to produce downloadable
Playwright report/test-result artifacts.
- **Scheduled follow-ups, deliberately out of scope:** Tailwind palette
classes map to semantic tokens in a dedicated pass; card/pill component
consolidation; ESLint ratchet. Tracked in
`doc/design/DECISION-SHEET.md`.
## Model Used
Claude Fable 5 (Anthropic, `claude-fable-5`, Mythos-class tier) with
extended thinking, running in Claude Code with tool use; mechanical
phases delegated to Claude Sonnet subagents. Follow-up external-baseline
rework assisted by OpenAI Codex (`gpt-5` coding agent with repository,
terminal, and GitHub tool use). All bulk rewrites executed via
deterministic, idempotent scripts committed in `scripts/`; intentional
visual changes were human-reviewed on screenshot contact sheets.
## 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 targeted local verification and documented the
intentional baseline-publication failure above
- [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
- [ ] All Paperclip CI gates are green *(pending new CI run after this
rework)*
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
*(pending review)*
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code) and
OpenAI Codex
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Dotta <bippadotta@protonmail.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue and routine editor surfaces share document annotation
behavior for commentable text.
> - The annotation layer currently observes document selections inside
its container and converts those selections into pending comment
anchors.
> - Routine editor fields can live inside that same annotated document
container while still needing normal native text selection behavior.
> - When a user selects text inside an editable routine field, the
annotation layer should leave that selection alone instead of preparing
a comment.
> - This pull request teaches the annotation layer to ignore selections
touching editable controls and covers that case with focused component
tests.
> - The benefit is that routine editing remains editable text UX, while
document annotation selection continues to work for normal read-only
document text.
## Linked Issues or Issue Description
No public GitHub issue exists for this bug. Inline bug report follows.
### Pre-submission checklist
- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on current `master` for this PR branch.
- [x] I have confirmed the error originates in Paperclip itself, not in
an agent adapter, API provider, or local configuration.
### What happened?
Selecting text inside an editable routine field could be interpreted as
a document annotation selection. That meant the annotation layer could
prepare a pending comment anchor or show annotation affordances while
the user was just selecting text to edit routine content.
### Expected behavior
Editable controls should keep native text selection behavior. Selecting
text inside `input`, `textarea`, `select`, or contenteditable routine
editor regions should not build a document annotation anchor or show the
document annotation toolbar.
### Steps to reproduce
1. Render document annotation behavior around a document/editor
container that also contains an editable routine text region.
2. Select text inside the editable region.
3. Observe that the document annotation layer treats the selection as
commentable text instead of ignoring it.
### Paperclip version or commit
Reproduced and fixed against `master` at `5356b7d73`, with PR head
`4f53f83f1`.
### Deployment mode
Local dev / component test environment.
### Installation method
Built from source.
### Agent adapter(s) involved
Not adapter-specific; core UI behavior.
### Database mode
Not database-related.
### Access context
Board / human operator UI behavior.
### Relevant logs or output
No runtime logs. Regression coverage is in
`ui/src/components/DocumentAnnotationLayer.test.tsx`.
### Relevant config
Not applicable.
### Additional context
Root cause: `DocumentAnnotationLayer` filtered selections by container
membership, but did not exclude editable controls or all valid
contenteditable hosts before building a pending annotation anchor.
### Privacy checklist
- [x] I have reviewed all pasted output for PII and redacted where
necessary.
## What Changed
- Added an editable-selection guard in `DocumentAnnotationLayer` that
ignores selections touching `input`, `textarea`, `select`, or
contenteditable elements.
- Covered both `contenteditable="true"` and bare/empty `contenteditable`
hosts so routine editor selections do not build annotation anchors.
- Added focused regression tests proving editable selections do not call
the annotation anchor helpers or show the annotation toolbar.
## Verification
- `pnpm vitest run ui/src/components/DocumentAnnotationLayer.test.tsx`
- `git diff --check`
- GitHub PR checks are green for head `4f53f83f1`, including policy,
review, typecheck/release registry, build, general tests, serialized
suites, e2e, canary dry run, security checks, and Greptile.
## Risks
Low risk. The change only narrows annotation capture when a selection
touches an editable element inside the annotation container. Normal
read-only document annotation selections still use the existing
anchor-building path.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent based on GPT-5, with shell, Git, Vitest, and
GitHub connector/CLI tool use. The exact hosted runtime model identifier
is not exposed in this session.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The issue chat thread renders comments from agents, users, and the
system itself
> - When Paperclip's internal recovery system posts a comment (e.g.,
stranded issue recovery), it creates a comment with `author_type:
"system"`, no agent ID, and no user ID
> - The UI function `authorNameForComment` falls back to `"You"` when
both author IDs are null
> - This PR returns `"Paperclip"` instead for system notices
> - The benefit: system comments are clearly attributed to Paperclip,
not the logged-in viewer
## What Changed
- `ui/src/lib/issue-chat-messages.ts`: In `authorNameForComment`, the
null-userId fallback now checks `options?.isSystemNotice` and returns
`"Paperclip"` instead of `"You"` for system-authored comments. One-line
change.
## Verification
- 82/82 existing tests pass (issue-chat-messages 20,
IssueChatThreadSystemNotice 11, IssueChatThread 51)
- Manual repro: view any issue with a system-authored comment (stranded
issue recovery moves issue to blocked). Before: author "You". After:
author "Paperclip".
## Risks
Low risk. One-line change. Only affects system-authored comments with no
user/agent ID. The `isSystemNotice` flag is already computed upstream
(`comment.authorType === "system"`) and passed into this function.
## Model Used
zai/glm-5.1 (200K context). Investigation and code tracing assisted by
the model.
## Checklist
- [x] Thinking path included
- [x] Model specified
- [x] Checked ROADMAP.md — bug fix, not a feature
- [x] Tests pass locally (82/82)
- [x] Risks documented
- [x] Will address all reviewer comments
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Environment settings let board users configure sandbox execution
targets for agent runs
> - Sandbox custom-image setup is company-scoped because the backend
resolves provider secrets and access through company context
> - The browser UI already knows the selected company, but the
custom-image setup calls were not passing it to routes whose OpenAPI
contract includes `companyId`
> - In multi-company authenticated deployments, the backend cannot
safely infer company context and returns a `companyId query parameter is
required` error
> - This pull request passes the selected company id through the
custom-image overview, setup, rollback, and disable UI paths
> - The benefit is that custom-image setup works consistently in
multi-company instances while preserving backend company-boundary checks
## Linked Issues or Issue Description
- No public issue is filed for this regression.
- Related prior work: #8911
- Bug summary: after the browser SSH terminal custom-image setup flow
shipped, opening custom-image setup in an authenticated multi-company
instance could show `companyId query parameter is required for
environment customImage setup` instead of starting the setup session.
- Expected behavior: the environment settings UI should send the
selected company context to company-scoped custom-image endpoints.
- Reproduction shape: run Paperclip in authenticated/private mode with
more than one company, open a sandbox environment's edit dialog, and use
the custom-image setup controls.
## What Changed
- Added company-id query construction for custom-image overview, setup,
rollback, and disable calls in the UI environment API wrapper.
- Passed the selected company id into the custom-image panel used by the
environment edit dialog.
- Updated the environment page tests so the regression fails if company
context is dropped again.
## Verification
- `pnpm exec vitest run ui/src/pages/CompanyEnvironments.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `git diff --check`
- Also applied the same patch to a local dev checkout serving port 3100
and confirmed `/api/health` still returns `ok` after the dev watcher
reload.
## Risks
- Low risk: this only adds the selected company id to UI calls for
endpoints that already declare or require company context.
- If the selected company id is stale or invalid, the existing
server-side company access checks still reject the request.
## Model Used
- OpenAI Codex CLI using GPT-5, with tool-enabled repository inspection,
editing, shell command execution, and local test execution. Context
window size is not exposed in this environment.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Bumps
[@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot)
from 1.2.4 to 1.3.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/radix-ui/primitives/blob/main/packages/react/slot/CHANGELOG.md">@radix-ui/react-slot's
changelog</a>.</em></p>
<blockquote>
<h2>1.3.0</h2>
<h3>Added generic type arguments for <code>SlotProps</code> and
<code>createSlot</code></h3>
<p><code>SlotProps</code> and <code>createSlot</code> now accept generic
type arguments to specify the type of element a slot should render, as
well as its props.</p>
<pre lang="tsx"><code>const Slot = createSlot<HTMLButtonElement,
MyCustomButtonProps>('Slot');
</code></pre>
<h2>1.2.5</h2>
<ul>
<li>Fixed infinite re-render loop in React 19 caused by
<code>Slot</code> creating a new ref callback on every render</li>
<li>Added support for nested <code>Slottable</code> via a render prop,
so a slotted element can be wrapped while still merging Slot props and
refs onto it</li>
<li>Added repository.directory to all package.json files</li>
<li>Improved error messages for invalid slot children</li>
<li>Updated dependencies:
<code>@radix-ui/react-compose-refs@1.1.3</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slot">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for <code>@radix-ui/react-slot</code> since your current
version.</p>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The work timeline UI visualizes agent and human activity across
kickoff chips and the Gantt chart.
> - The timeline actor chips were falling back to generic initials or
blank circular avatars instead of using the richer identity data already
available elsewhere in the app.
> - Agent rows should match the sidebar identity treatment, and human
entries should use the user's configured avatar image when one exists.
> - This pull request teaches the timeline avatar renderer to prefer
configured agent icons and human avatar URLs, while preserving initials
as a fallback.
> - The benefit is a more recognizable work timeline that matches the
rest of the Paperclip UI.
## Linked Issues or Issue Description
No public GitHub issue exists for this UI bug. The underlying issue:
work timeline actor avatars did not consistently use the available actor
identity assets. Agents appeared as generic white-circle initials
instead of their configured sidebar icons, and human kickoff entries
appeared as initials even when the user had an avatar image.
Related prior timeline work: #8875 and #8880. No open duplicate PR was
found for this avatar correction.
## What Changed
- Updated the work timeline actor avatar renderer to show configured
agent icons for agent rows and chips.
- Updated human kickoff avatar rendering to prefer the user avatar image
and fall back to initials only when no image is available.
- Added regression coverage for agent sidebar-style icons and human
avatar images in `WorkTimelineChart`.
## Verification
- `pnpm exec vitest run
ui/src/components/timeline/WorkTimelineChart.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `git diff --check origin/master...HEAD`
## Risks
Low risk. The change is scoped to work timeline avatar presentation and
preserves the existing initials fallback when configured icons or avatar
images are unavailable.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5-class coding agent in local tool-use mode with
shell, git, test execution, and GitHub connector access.
## 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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board inbox is a high-frequency operator surface, especially on
mobile where row gestures are the primary way to dismiss handled work.
> - The existing swipe archive control shared one pending-state gate
across the inbox, so one archive request could make other inbox archive
controls feel stuck.
> - The touch handler also kept click suppression and archive timers
coupled too loosely, which made cancelled or partial iOS touch sequences
behave unpredictably.
> - This pull request keeps archive state per issue, makes swipe/cancel
cleanup explicit, and removes archived issues from every relevant inbox
query cache before navigation/refetch.
> - The benefit is that iOS users can archive from inbox/detail views
without the inbox getting into a weird pending or stale-cache state.
## Linked Issues or Issue Description
Bug report:
- Summary: On iOS/mobile, inbox swipe/archive interactions can leave the
inbox feeling stuck or stale after archiving a task.
- Expected: Archiving one inbox item should remove that item
optimistically, keep other archive controls usable, and recover cleanly
from partial or cancelled touch gestures.
- Actual: A pending archive globally disabled other archive controls,
touch cancellation could share the touch-end path, and issue-detail
archive navigation could happen before related inbox query caches were
cleaned up.
- Root cause: Archive pending state and inbox cache updates were handled
locally in multiple places instead of through reusable per-company cache
helpers, and the swipe component did not independently track live
offset, archive timers, and click-suppression timers.
## What Changed
- Split `SwipeToArchive` archive timeout handling from temporary click
suppression and added explicit touch-cancel/reset cleanup.
- Track swipe offset in a ref so commit threshold checks use the latest
touch position rather than potentially stale React state.
- Added shared inbox archive cache helpers for cancelling, snapshotting,
optimistic removal, rollback, and invalidation across inbox query
variants.
- Updated inbox archive mutations to disable only the row being archived
instead of every row while any archive request is pending.
- Updated issue-detail archive-from-inbox to remove the archived issue
from cached inbox variants before navigating back.
- Added focused tests for partial drags, cancelled touches, unmount
cleanup, per-row pending archive behavior, and detail-page cache removal
before navigation.
## Verification
- `pnpm exec vitest run ui/src/components/SwipeToArchive.test.tsx
ui/src/pages/Inbox.test.tsx ui/src/pages/IssueDetail.test.tsx`
- 3 test files passed
- 53 tests passed
## Risks
Low-to-medium risk. This changes mobile archive gesture state and
optimistic inbox cache handling, so the main risk is cache invalidation
missing an inbox query variant. The helper uses prefix-based React Query
APIs for the known inbox variants and still invalidates those variants
plus sidebar badges on settle.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 based coding agent, with repository tool use and
local command 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
Related PR search performed with `gh search prs "inbox archive iOS
swipe" --repo paperclipai/paperclip` and `gh search prs "SwipeToArchive"
--repo paperclipai/paperclip`; existing related work includes #1857 and
#1860, but no duplicate of this regression fix was found.
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents execute on isolated workspaces (git worktrees), each pinned
to a specific branch and commit at checkout time
> - When an agent's live checkout diverges from the recorded workspace
branch — either through a branch rename, a stale worktree, or a
concurrent git operation — Paperclip detects the mismatch and surfaces a
recovery card to the operator
> - But the existing recovery card showed a generic error with no
diagnostic context: it didn't display *which* branch was expected vs.
which was checked out, the commit SHAs involved, or whether the branches
share ancestry
> - Without that information operators cannot diagnose the root cause,
and the only recovery path was fully manual re-issue
> - This pull request extends `IssueRecoveryActionCard` for
`workspace_validation` / `git_worktree_branch_incoherence` recovery
kinds to render a divergence-diagnosis panel (expected branch, live
branch, short SHAs, ancestry-verdict badge, plain-language reason) and
adds a confirm-gated "Re-issue on isolated workspace" action that
creates a new task with `executionWorkspacePreference:
isolated_workspace` so the re-issued run cannot trip the same
branch-mismatch gate
> - The benefit is that operators can immediately see *why* a workspace
was declined and recover with a single click instead of having to
manually reconstruct the task
## Linked Issues or Issue Description
Refs #4757 (heartbeat re-wake doesn't reconcile working-tree HEAD
against ticket's expected branch — this PR surfaces the resulting
divergence to the operator and provides a one-click isolated re-issue
path)
Refs #8460 (workspace_validation_failed local-only project workspaces —
this PR extends the recovery card UI for this case)
**Subsystem affected:** ui/ — React + Vite board UI
**Problem or motivation:** When Paperclip records a workspace branch for
an agent run and the live checkout disagrees (diverged HEAD, renamed
branch, stale worktree), the issue recovery card surfaces a generic
`workspace_validation` error. The operator sees "run declined" but has
no visibility into the expected vs. actual branch, the relevant commit
SHAs, or whether the branches even share ancestry. There is no one-click
path to re-issue the task on a clean isolated workspace — the operator
must manually reconstruct the task from scratch.
**Proposed solution:** Extend `IssueRecoveryActionCard` to:
1. Render a divergence-diagnosis panel from the `recoveryEvidence`
field: expected branch, live branch, short SHAs for both, an
ancestry-verdict badge (`forward-only` / `diverged` / `ancestry
unknown`), and the server's `plainLanguageReason`.
2. Add Action 3 "Re-issue on isolated workspace" — a confirm-gated
button that calls `issuesApi.create` with `executionWorkspacePreference:
isolated_workspace` and `workspaceStrategy.baseRef` set to the live
branch (SHA fallback when detached). The current workspace is never
mutated.
3. Wire `onReissueIsolated` / `reissuePending` through `IssueChatThread`
→ `IssueDetail` so the operator sees an immediate success toast and is
navigated to the new task.
**Alternatives considered:** Showing divergence details only in a
tooltip (rejected — too easy to miss). Providing a "force-reset the
workspace" action (rejected — destructive, no audit trail, doesn't fix
stale-branch root cause). Isolated re-issue via isolated workspace was
the clearest safe path.
## What Changed
- `IssueRecoveryActionCard.tsx` — Added `DiagnosisPanel` sub-component
rendered for `workspace_validation` / `git_worktree_branch_incoherence`
recovery kinds: displays expected vs. live branch, short SHAs,
ancestry-verdict badge, and plain-language reason. Added Action 3
confirm-popover with `onReissueIsolated` callback and `reissuePending`
loading state. Kept existing Action 1 and Action 2 unchanged.
- `IssueChatThread.tsx` — Threaded `onReissueIsolated` and
`reissuePending` props down to `IssueRecoveryActionCard`.
- `IssueDetail.tsx` — Implemented `handleReissueIsolated`: calls
`issuesApi.create` with `executionWorkspacePreference:
isolated_workspace` + `workspaceStrategy.baseRef` derived from live
branch / SHA; shows a success toast and navigates to the new task on
completion.
- `IssueRecoveryActionCard.test.tsx` — Added 19 unit tests covering
diagnosis-panel rendering, verdict label rendering, base-ref derivation
(branch-first then detached-HEAD SHA fallback), and action gating.
## Verification
```bash
# Unit tests — 19/19 pass
pnpm vitest run ui/src/components/IssueRecoveryActionCard.test.tsx
# Typecheck — 0 new errors
pnpm typecheck
```
Manual browser validation deferred to QA — see Risks.
## Risks
- **Re-issue creates a new task** — the original task remains unchanged.
This is intentional (safe default), but operators should be aware both
tasks exist after re-issue.
- **Base-ref derivation falls back to the live HEAD SHA when detached.**
SHA-based worktrees are valid for isolated re-issue but may surprise
operators expecting a branch name.
- **Browser-level end-to-end validation not included here.** Toast,
navigation, and full create-flow are covered by integration QA in a
follow-up pass.
- **Low overall risk** — no new endpoints, no data mutations on existing
records, no PII or telemetry changes. Composes the existing
`issuesApi.create` endpoint; all new behavior is additive.
## Model Used
Claude Sonnet 4.6 (`claude-sonnet-4-6`) — Anthropic. 200k context
window, tool use, code execution. Extended thinking not used.
## 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
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run in git worktrees tied to a workspace branch; when the
actual branch diverges from the expected one (e.g. a parent feature
branch was renamed), Paperclip currently has no structured field to
report *why* the branch is incoherent or whether it can be
auto-reconciled
> - The workspace-incoherence fingerprint already captures SHA
mismatches, but there is no evidence field distinguishing "actual branch
is a descendant of expected" (safe to fast-forward) from "branches have
diverged" (needs human review) or "SHAs are unavailable" (unknown)
> - Operators and future recovery flows need a typed verdict to make
decisions without re-running git commands themselves
> - This pull request adds `ancestryVerdict` and `plainLanguageReason`
evidence fields computed via `git merge-base --is-ancestor`, and
scaffolds the off-by-default `enableWorkspaceBranchReconcileForward`
instance setting with no runtime behavior yet
> - The benefit is that future recovery logic can branch on a typed
verdict rather than parsing prose, while the fingerprint v1 payload
stays stable
## Linked Issues or Issue Description
No public GitHub issue pre-exists for this diagnostic addition.
**Problem or motivation**
When Paperclip detects that an agent's actual workspace branch differs
from the recorded expected branch, the current fingerprint carries only
raw SHAs. There is no typed field indicating whether the actual branch
is a descendant of the expected one (safe reconcile path) vs. a true
divergence (requires human intervention) vs. an indeterminate state
(missing SHAs or git errors). Downstream recovery logic cannot branch
safely without re-running git.
**Proposed solution**
Add `ancestryVerdict` and `plainLanguageReason` to the workspace
incoherence evidence type; compute via `git merge-base --is-ancestor`;
scaffold a feature-flag for future forward-reconcile behavior
(`enableWorkspaceBranchReconcileForward`, off by default, not yet read
by any runtime path).
**Alternatives considered**
Encoding the verdict in the existing fingerprint string was rejected
because the fingerprint is a stable identity hash, not a mutable
evidence bag. Changing it would break monitors keyed on the string.
**Roadmap alignment**
Supports future workspace auto-reconcile work; ROADMAP.md has no
conflicting entry for this diagnostic layer.
## What Changed
- `packages/shared/src/types/heartbeat.ts` adds `ancestryVerdict` and
`plainLanguageReason` fields to `WorkspaceIncoherenceEvidence`
- `packages/shared/src/types/instance.ts` adds
`enableWorkspaceBranchReconcileForward` boolean (off by default)
- `packages/shared/src/validators/instance.ts` exports the new flag from
the settings validator
- `server/src/services/workspace-runtime.ts` computes `ancestryVerdict`
via `git merge-base --is-ancestor`; falls back to `unknown` on missing
SHAs or command errors; excludes verdict fields from fingerprint v1
computation
- `server/src/services/instance-settings.ts` wires the new setting
through to the settings service
- Tests updated in `workspace-runtime.test.ts`,
`instance-settings-service.test.ts`, `instance-settings-routes.test.ts`,
and `instance.test.ts` (104 tests total)
## Verification
```bash
pnpm exec vitest run \
server/src/__tests__/workspace-runtime.test.ts \
server/src/__tests__/instance-settings-service.test.ts \
server/src/__tests__/instance-settings-routes.test.ts \
packages/shared/src/validators/instance.test.ts
# 104 tests pass
pnpm --filter @paperclipai/shared typecheck
pnpm --filter @paperclipai/server typecheck
# both exit 0
```
Manual: trigger a workspace incoherence event and confirm the evidence
object carries `ancestryVerdict` and `plainLanguageReason`; confirm the
fingerprint string stays `workspace_incoherence:v1:sha256:...`.
## Risks
**Low risk.** Purely additive. Fingerprint v1 payload is unchanged. The
new flag has no runtime effect in this PR. `git merge-base
--is-ancestor` exits non-zero for both "not an ancestor" and "command
error"; both are handled and collapsed to typed values with a prose
reason.
## Model Used
Provider: Anthropic, model: Claude Sonnet 4.6 (`claude-sonnet-4-6`),
200k context, tool use enabled.
## 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
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## Problem
On the Agents page, there are two small icon buttons for switching
between list view and org chart view. But these buttons had:
- No title attribute, so hovering show nothing
- No aria-label, so screen readers just say "button"
- No aria-pressed, so no way to know which view is active
- No role="group" on the container
I was confused the first time I see these buttons because one is a list
icon and the other is a git-branch icon, and without hovering or
clicking I had no idea what they do. Specially the git-branch icon - is
it for branches? Repos? No, it's for org chart view.
## What I changed
- Added `title` to each button: "List view" and "Org chart view"
- Added `aria-label` matching the title for screen readers
- Added `aria-pressed` to indicate which view is currently selected
- Added `role="group"` with `aria-label="View mode"` on the container
div
## How to test
1. Go to Agents page (must have more than 1 agent for toggle to appear)
2. Hover over the list icon - should see "List view" tooltip
3. Hover over the branch icon - should see "Org chart view" tooltip
4. Use screen reader - should announce "List view, pressed" or "Org
chart view, not pressed"
1 file, 7 lines added.
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI is where operators create issues, inspect issue-thread
decisions, write markdown, and move between issue workflow surfaces.
> - A broad recovered branch mixed these operator workflow polish fixes
with unrelated backend, pipeline, plugin, eval, and work-product
changes.
> - Reviewers need the operator UI workflow fixes in a small PR that can
be understood without pulling in the rest of that recovery branch.
> - This pull request extracts only the issue workflow UI slice:
interaction cards, markdown editing fallback behavior, create-issue work
mode shortcuts, file-viewer URL handling, and issue navigation scroll
behavior.
> - The benefit is a lower-risk review path for operator workflow fixes
while keeping unrelated execution-workspace and server work out of this
PR.
## Linked Issues or Issue Description
- Refs #8866, the closed broad source PR that this focused slice was
extracted from.
- Related: #8228 is an open UI polish PR, but its file list does not
overlap this branch.
- Related: #4090 is older merged operator workflow polish context.
No public GitHub issue exists for this exact extracted slice. The
problem is that several small operator issue-workflow fixes were bundled
inside a broad recovery branch, making them hard to review and ship
independently.
## What Changed
- Removed interaction continuation wake-policy labels from issue-thread
interaction card headers and updated the card test/story copy
accordingly.
- Added a markdown editor error boundary so rich editor render crashes
fall back to a raw textarea instead of breaking the compose surface.
- Made the create-issue work-mode shortcut accept ctrl-period and iOS
hardware-keyboard command-period-as-Escape behavior without dismissing
the dialog.
- Fixed file-viewer URL navigation to compare against the live browser
search string when router state is stale.
- Restored scroll reset when navigating from an issue detail route back
to the issue index, while preserving browser-history restoration
behavior.
## Screenshots
Before removing the continuation wake-policy badge from interaction card
headers:

After removing the wake-policy badge:

## Verification
- `pnpm exec vitest run
ui/src/components/IssueThreadInteractionCard.test.tsx
ui/src/components/MarkdownEditor.test.tsx
ui/src/components/NewIssueDialog.test.tsx
ui/src/context/FileViewerContext.test.ts
ui/src/lib/navigation-scroll.test.ts` — 5 files, 102 tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `git diff --check` — passed.
## Risks
Low risk. The PR is limited to UI workflow components/helpers and their
tests. Main behavioral risks are keyboard shortcut edge cases across
browsers and fallback editor rendering, both covered by focused tests.
## Model Used
OpenAI Codex, GPT-5-based coding agent (`gpt-5` family; exact served
variant and context window are not exposed in this runtime), with
terminal, Git, GitHub, and test execution tools.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents for work.
> - The relevant subsystem is the agent heartbeat policy surface: the
Paperclip skill, default onboarding AGENTS.md, new-agent runtime
defaults, and promptfoo eval coverage for agent behavior.
> - A broad recovery PR collected several unrelated local-mainline
changes, which made review too large and mixed policy/eval updates with
server execution and UI work.
> - This PR extracts only the heartbeat policy and prompt-eval slice so
reviewers can assess the behavior contract independently.
> - The eval additions cover scoped wake handling, idle no-op behavior,
dependency-blocked comment triage, final disposition, budget hard stops,
and Phase 5 memory/control-surface policy expectations.
> - The benefit is a narrower review surface plus deterministic
follow-up guidance for server/shared tests that should back these
prompt-level checks.
## Linked Issues or Issue Description
Refs #8866
No public issue was filed for this split. This is a focused extraction
from the closed broad recovery PR so heartbeat policy and eval coverage
can be reviewed separately from execution behavior, work-product feature
work, plugin hardening, pipeline health, and unrelated UI polish.
## What Changed
- Added promptfoo release-gate cases for scoped wake payload handling,
idle exits, dependency-blocked comment triage, final disposition, and
budget hard-stop behavior.
- Added Phase 5 memory/control-surface prompt eval cases for provider
binding precedence, provenance/audit fields, hook cost/trust handling,
and auditable board command surfaces.
- Documented how these prompt evals map to deterministic server/shared
follow-up coverage.
- Updated agent policy guidance so operator-facing engineering outputs
such as PRs, branches, commits, previews, and runtime services get
matching work products.
- Defaulted new agent runtime config to skip timer heartbeats when there
is no actionable work, with focused test coverage.
## Verification
- `cd evals/promptfoo && npx promptfoo@latest validate -c
promptfooconfig.yaml` passes.
- `/srv/paperclip/home/paperclipai/paperclip/node_modules/.bin/vitest
run ui/src/lib/new-agent-runtime-config.test.ts` passes in an isolated
worktree after `pnpm install --ignore-scripts --frozen-lockfile` created
workspace links.
- A live promptfoo eval was not run because `OPENROUTER_API_KEY`,
`OPENAI_API_KEY`, and `ANTHROPIC_API_KEY` were unset in the workspace.
## Risks
Low-to-medium risk. The runtime default reduces timer-driven empty
heartbeats for newly created agents, so the main behavioral risk is
missing an edge case where timer wakes were expected despite no
actionable work. The promptfoo additions are deterministic assertion
coverage and documentation-only until a live eval is run with provider
credentials.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5-based Codex coding agent in the Paperclip local Codex
adapter environment; exact hosted model ID and context window were not
exposed to the agent runtime. Tool use included shell, git, promptfoo
validation, Vitest, and the GitHub connector/CLI.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI is the main daily navigation surface for agents,
projects, and their related resources.
> - Operators need a lightweight way to keep frequently used agents and
projects close without changing company-wide ordering or ownership.
> - Resource memberships already model per-user relationships to
projects and agents, so they are the right place to store user-specific
starred state.
> - This pull request extends that membership contract with a starred
timestamp and exposes star controls in list/detail views.
> - The sidebar then uses those starred memberships to show compact,
user-specific shortcuts.
> - The benefit is faster navigation without introducing a separate
favorites system or leaking preferences across users.
## Linked Issues or Issue Description
No public GitHub issue exists.
Feature request:
## Problem or motivation
Users cannot pin frequently used agents or projects into the main
sidebar. Returning to important resources requires scanning full
project/agent lists or navigating through detail pages, which adds
friction to repeated daily workflows.
## Proposed solution
Store a per-user `starred_at` timestamp on agent and project
memberships, expose API actions to set or clear that state, add star
toggle controls to list/detail pages, and render starred projects and
agents as compact sidebar shortcuts.
## Alternatives considered
A separate favorites table would work, but it would duplicate membership
scoping and require another resource relationship model. Keeping starred
state on memberships preserves existing company/user boundaries and
avoids a second source of truth.
## Roadmap alignment
Checked `ROADMAP.md`; no overlapping planned core work for starred
resource/sidebar navigation was found.
## Additional context
The affected subsystems are `packages/db`, `packages/shared`, `server/`,
and `ui/`. The migration is idempotent with `IF NOT EXISTS` guards so
environments that saw an earlier local migration name can still apply
the final ordered migration safely.
## What Changed
- Added idempotent migration `0133_resource_membership_stars` for
`starred_at` columns and lookup indexes on agent/project memberships.
- Extended shared resource membership types and validators with starred
metadata and actions.
- Updated server resource membership services/routes to read and mutate
starred resource state.
- Added reusable star toggle UI and resource membership hook support for
starred state.
- Added starred projects and agents sidebar rendering, plus star
controls on list and detail pages.
- Added focused shared, server, and UI coverage for starred membership
behavior and sidebar rendering.
## Verification
- Rebased and force-with-lease pushed current PR head
`a086fc965391c9e50a51b5b83b5b44a797b2a6f4` onto current
`paperclipai/paperclip:master`; `gh pr view` reports `MERGEABLE` with no
merge conflicts. GitHub checks are green for this fresh head.
- `pnpm exec vitest run packages/shared/src/resource-memberships.test.ts
server/src/__tests__/resource-memberships-routes.test.ts
server/src/__tests__/workspace-runtime.test.ts
ui/src/components/Sidebar.test.tsx
ui/src/components/SidebarAgents.test.tsx
ui/src/components/SidebarStarredProjects.test.tsx
ui/src/components/StarToggle.test.tsx
ui/src/pages/InstanceExperimentalSettings.test.tsx` passed after the
rebase: 8 files, 143 tests.
- Greptile re-review is 5/5; the remaining screenshot thread was
resolved as non-blocking because this task explicitly requested no
screenshots/images in the PR.
- `pnpm exec vitest run
ui/src/components/SidebarStarredProjects.test.tsx` passed after the
mobile pending-spinner fix.
- `pnpm exec vitest run packages/shared/src/resource-memberships.test.ts
server/src/__tests__/resource-memberships-routes.test.ts
ui/src/components/Sidebar.test.tsx
ui/src/components/SidebarAgents.test.tsx
ui/src/components/SidebarStarredProjects.test.tsx
ui/src/components/StarToggle.test.tsx
ui/src/pages/InstanceExperimentalSettings.test.tsx` passed: 7 files, 68
tests.
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter @paperclipai/ui typecheck` passed
db/shared/server, then failed in pre-existing UI code outside this PR:
`src/pages/CompanyEnvironments.tsx` missing `@xterm/*` type declarations
and `previous` possibly null.
- Checked that the PR diff does not include `pnpm-lock.yaml` or
`.github/workflows` changes.
- Checked `ROADMAP.md` and found no overlapping planned core work for
starred resource/sidebar navigation.
- Searched existing GitHub PRs for duplicate starred-resource/sidebar
work and found none.
## Risks
- Migration touches membership tables. The SQL uses `IF NOT EXISTS` for
columns and indexes so environments that saw an earlier local migration
name can still apply this safely.
- Sidebar ordering and visibility changes could affect users who rely on
the previous flat sidebar layout.
- Starred state is per-user membership metadata; code paths must
continue preserving company/user scoping around memberships.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex, tool-enabled coding agent with shell/GitHub access.
Context window not disclosed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The browser UI depends on the Inter font family for its intended
visual baseline
> - The app previously referenced remote Google Fonts stylesheets at
runtime
> - That meant self-hosted, offline, or privacy-sensitive deployments
could lose the intended typography or depend on an external request
> - This pull request bundles the required Inter variable font files
with the UI and serves them from the app
> - A normal UI test now covers the static assets and CSS wiring without
adding a package script or build step
> - The benefit is a more reliable, self-contained UI that does not rely
on third-party webfont hosting
## Linked Issues or Issue Description
No public GitHub issue was found for this exact gap.
**Subsystem affected**
ui/ — React + Vite board UI
**Problem or motivation**
Paperclip's UI should ship the webfont assets it references so
production and self-hosted deployments render consistently without
reaching out to Google Fonts at runtime.
**Proposed solution**
Bundle the Inter variable font files under the UI public assets, load
them with local `@font-face` declarations, document the bundled assets,
and cover the source assets/CSS wiring with a normal UI Vitest test.
**Alternatives considered**
Keeping the remote stylesheet dependency is simpler, but leaves
deployments dependent on external font hosting. Using system fonts only
would avoid the asset footprint, but changes the intended UI typography.
**Roadmap alignment**
This is a focused UI reliability/polish fix, not a roadmap-level core
feature.
**Additional context**
Searched public GitHub issues and PRs for `webfonts
repo:paperclipai/paperclip`; no duplicates or closely related open items
were found.
## What Changed
- Added bundled Inter variable font assets and their notice under
`ui/public/fonts/`.
- Replaced remote Google Fonts imports with local `@font-face`
declarations using relative public-asset URLs that remain subpath-safe
from built CSS.
- Documented the local font asset expectation in development and UI spec
docs.
- Removed the follow-up font asset checker scripts and package/build
wiring after review feedback clarified they are not required for
building the UI.
- Added `ui/src/lib/ui-font-assets.test.ts` to verify the shipped WOFF2
files, notice text, and CSS font references through the normal UI test
suite.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/ui-font-assets.test.ts --config vitest.config.ts`
- Passed.
- Confirms the bundled font files exist, are WOFF2 files, have notice
coverage, and are referenced by `ui/src/index.css`.
- `pnpm --filter @paperclipai/ui build`
- Passed.
- Confirmed the UI still builds after removing the checker from
`ui/package.json`.
- Confirmed `ui/dist/fonts/` contains `InterVariable.woff2`,
`InterVariable-Italic.woff2`, and `NOTICE.md` after the build.
- The UI build emitted existing warnings about `::highlight(...)`, a
dynamic/static import overlap for `MarkdownEditor.tsx`, unresolved
relative public font URLs left for runtime resolution, and large chunks,
but completed successfully.
## Risks
Low risk. This adds static font assets and swaps the font source from a
remote stylesheet to same-origin files. The main tradeoff is a larger
repository/UI asset footprint from the bundled `.woff2` files.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5-based coding agent, tool-enabled terminal/GitHub
workflow with reasoning support.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI is where operators inspect company activity, agent
work, and issue progress.
> - Existing board views show individual issue and run details, but they
do not give operators a compact time-based picture of work across
agents.
> - A work timeline helps operators scan when agents worked, how
handoffs happened, and where overlapping work occurred.
> - This pull request adds a company-scoped Work Timeline page backed by
the existing API surface and renders the timeline as a custom SVG
Gantt-style view.
> - The benefit is faster operator understanding of multi-agent
execution without opening each issue thread individually.
## Linked Issues or Issue Description
No public GitHub issue exists for this change.
Subsystem affected:
- ui/ — React + Vite board UI
Problem or motivation:
- Operators can inspect individual issues and runs, but there is no
compact time-based view of company work across agents.
- This makes it harder to scan overlaps, handoffs, retries, and activity
windows without opening many issue threads.
Proposed solution:
- Add a company-scoped Work Timeline page in the board UI.
- Render agent and system run spans as a custom SVG Gantt-style chart
with packed overlap lanes.
- Show issue color identity, kickoff attribution, hover-revealed
delegation connectors, retry styling, zoom controls, a sticky actor
gutter, and a minimap brush.
- Keep human activity lightweight by showing human kickoff chips without
plotting standalone human event rows.
Alternatives considered:
- Add the same information to existing issue-list or run-list views.
That would preserve simpler UI, but it would not show temporal overlap
or handoff paths clearly.
- Build this as a plugin-only surface. That keeps core smaller, but the
board already has the company-scoped route, navigation, and API client
patterns needed for this operator workflow.
Roadmap alignment:
- `ROADMAP.md` does not list an existing duplicate work-timeline
milestone. This supports the broader operator visibility direction
around artifacts, enforced outcomes, and higher-autonomy execution.
Additional context:
- Storybook includes `Pages/Work Timeline` stories for hour/day zoom and
a human-activity sample so reviewers can inspect the component without a
live backend.
## What Changed
- Added the Work Timeline page, route, sidebar entry, API client, query
key, and company-prefixed route helper coverage.
- Added a pure timeline layout transform for row packing, issue colors,
kickoff attribution, connector calculation, tick selection, and duration
formatting.
- Added the custom SVG timeline chart with sticky actor gutter,
hover-revealed connectors, zoom controls, minimap brushing,
visible-range feedback, and issue navigation.
- Added Storybook coverage plus sample fixtures for the work timeline.
- Added and corrected focused UI tests covering layout, chart behavior,
routing, sidebar behavior, and collapsed-sidebar expectations.
- Addressed Greptile feedback for kickoff fallback ordering, minimap
range math, document drag listener cleanup, and stable default `now`
handling.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/timeline/layout.test.ts
src/components/timeline/WorkTimelineChart.test.tsx
src/pages/Timeline.test.tsx src/lib/company-routes.test.ts
src/components/Sidebar.test.tsx
src/components/RequestCollapsedSidebar.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `git merge-tree $(git merge-base HEAD origin/master) HEAD
origin/master | rg -n "<<<<<<<|changed in both|CONFLICT"` returned no
conflicts.
- Attempted Storybook screenshot capture with Playwright; Storybook ran
locally, but Chromium could not launch in this container because native
browser libraries such as `libatk-1.0.so.0` are unavailable and `npx
playwright install-deps chromium` requires interactive sudo.
## Risks
- Medium UI risk: this adds a substantial visual surface with custom SVG
interaction logic, so browser-level review is still useful for
responsive behavior and usability.
- Low backend risk: this PR only adds a UI client/page around the
existing timeline API contract and does not change database schema or
server routes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex coding agent, with repository file access, shell
command execution, GitHub connector access, and focused test execution.
Exact context-window details are not exposed in this runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - When you swap an agent's adapter (e.g. from one LLM provider to
another), the server merges the incoming PATCH body with stored config —
keys listed in \`ADAPTER_AGNOSTIC_KEYS\` are preserved regardless of
which adapter is active
> - That constant was defined independently in two places:
\`server/src/agents.ts\` (used by the adapter-swap route) and
\`ui/src/lib/agent-config-patch.ts\` (used by the UI patch builder)
> - PR #8975 fixed the bug where \`paperclipSkillSync.desiredSkills\`
was dropped on adapter swap by adding it to the server-side constant,
but the UI-side copy was not updated in the same PR — creating ongoing
drift risk
> - This pull request hoists \`ADAPTER_AGNOSTIC_KEYS\` into
\`packages/shared\` so both consumers import the same constant
> - The benefit is a single source of truth: any future key addition is
made in one place and both the server route and the UI patch builder
pick it up automatically, with a drift guard to catch any accidental
re-duplication
## Linked Issues or Issue Description
Refs #8975 — follow-up deduplication: #8975 fixed the runtime bug but
left the constant duplicated across server and UI. This PR closes that
gap.
## What Changed
- Added \`ADAPTER_AGNOSTIC_KEYS\` constant and \`AdapterAgnosticKey\`
type to \`packages/shared/src/adapter-agnostic-keys.ts\`
- Updated \`server/src/agents.ts\` to import the shared constant,
removing the local copy
- Updated \`ui/src/lib/agent-config-patch.ts\` to import the shared
constant, removing the local copy
- Added \`packages/shared/src/adapter-agnostic-keys.test.ts\`: drift
guard asserting the expected key set and both consumer import sites
## Verification
\`\`\`bash
pnpm exec vitest run packages/shared/src/adapter-agnostic-keys.test.ts
ui/src/lib/agent-config-patch.test.ts
server/src/__tests__/agent-instructions-routes.test.ts
pnpm --filter @paperclipai/shared typecheck
pnpm --filter @paperclipai/server typecheck
pnpm --filter @paperclipai/ui typecheck
\`\`\`
All 15 tests pass across the three files; all three packages typecheck
clean.
## Risks
Low risk — behavior-preserving refactor. The key set is unchanged; only
the import source changes. The drift guard will fail loudly if someone
accidentally re-introduces a local copy or modifies one without updating
the other.
> For core feature work, check [\`ROADMAP.md\`](ROADMAP.md) first and
discuss it in \`#dev\` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See \`CONTRIBUTING.md\`.
## Model Used
- Provider: Anthropic
- Model: Claude Sonnet 4.6 (\`claude-sonnet-4-6\`)
- Context: standard context window, tool use enabled
- Reasoning: standard mode (no extended thinking)
## 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)
- [ ] 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
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent runs often need provider credentials, API tokens, and other
environment-bound secrets.
> - Company-level secrets work for shared credentials, but they do not
model values that should differ by human operator.
> - Without a user-scoped model, a run can dispatch without knowing
whether the responsible human has supplied the needed value.
> - Paperclip also needs run attribution to make those user-scoped
runtime checks deterministic and auditable.
> - This pull request adds user-specific secret definitions, per-user
values, environment bindings, responsible-user attribution, and runtime
resolution gates.
> - The benefit is that teams can define the secret once, let each user
provide their own value, and block runs before dispatch when required
user secrets or active definitions are unavailable.
## Linked Issues or Issue Description
Refs #224
Refs #6057
This PR implements user-specific secret support as a core
secret-management capability rather than a one-off adapter setting. It
is related to existing public work on company secrets UI and runtime
secret refs, but is distinct because the value is owned by the
responsible user and resolved at run dispatch time.
Related PR search before opening found existing secrets work such as
#1550, #8256, #8614, #8634, and #8647; none of those add the full
user-secret definition/value/runtime gate covered here.
## What Changed
- Added user-secret definitions and per-user "My secrets" values,
keeping stored values out of access metadata.
- Added `user_secret_ref` environment bindings and UI affordances to
pick them alongside existing secret refs.
- Added responsible-user runtime resolution so user-secret refs resolve
against the human responsible for the run.
- Added pre-dispatch missing-secret gates so runs fail before adapter
dispatch when required user values are absent or definitions are
inactive.
- Added low-trust allowlist hardening for user-secret runtime access.
- Added issue, routine, run, and agent API key responsible-user
attribution and fail-closed dispatch behavior when attribution cannot be
resolved.
- Added denial-copy mapping so responsible-user authorization failures
surface as actionable run outcomes instead of opaque setup failures.
- Added OpenAPI documentation for the user-secret routes.
- Rebases cleanly on current `master`; migrations were renumbered
incrementally as `0128_user_specific_secrets`,
`0129_agent_api_key_responsible_user`, and
`0130_run_responsible_user_invariant` after upstream `0126`/`0127`
migrations.
- Removed previously committed local design screenshots so the PR
contains code/docs/tests only.
## Verification
- PASS: PR head `2527febd106bcf3ca264ca0da7fca491084192d6` is based on
`paperclipai/paperclip:master`.
- PASS: `git diff --check`
- PASS: `git diff --name-only public/master...HEAD | rg
'^(pnpm-lock\\.yaml|\\.github/workflows/|screenshots/)' || true`
produced no files.
- PASS: migration journal audit confirmed unique indexes through `130`
with tail entries `0126_issue_comment_derived_attribution`,
`0127_environment_custom_images_instance_scoped`,
`0128_user_specific_secrets`, `0129_agent_api_key_responsible_user`, and
`0130_run_responsible_user_invariant`.
- PASS: `pnpm --filter @paperclipai/ui typecheck`
- PASS: `pnpm --filter @paperclipai/server typecheck`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-responsible-user-invariant.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-active-run-output-watchdog.test.ts
src/__tests__/heartbeat-stale-queue-invalidation.test.ts
src/__tests__/heartbeat-workspace-finalize-branch.test.ts
src/__tests__/issue-monitor-scheduler.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-comment-wake-batching.test.ts
src/__tests__/heartbeat-retry-scheduling.test.ts
src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts
src/__tests__/heartbeat-plugin-environment.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/low-trust-red-team-routes.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/secrets-service.test.ts` (55 tests)
- PASS: `pnpm vitest run server/src/__tests__/secrets-routes.test.ts
server/src/__tests__/secrets-service.test.ts` (89 tests after final
Greptile cleanup fixes)
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-issue-liveness-escalation.test.ts` (17 tests
after the final rebase CI fix)
- PASS: focused server Vitest batches covering heartbeat recovery,
project env, plugin env, routines, low-trust, pipelines, monitors,
watchdog, and stale queue paths.
- PASS: GitHub checks are green on
`2527febd106bcf3ca264ca0da7fca491084192d6`, including Typecheck +
Release Registry, Build, General tests, serialized server suites, e2e,
Canary Dry Run, verify, security checks, and Greptile Review.
- PASS: Greptile Review completed successfully on
`2527febd106bcf3ca264ca0da7fca491084192d6` with Confidence Score 5/5,
and GraphQL review-thread audit returned zero unresolved non-outdated
threads.
## Risks
- Runtime behavior now depends on a run having a correct responsible
user; missing or incorrect responsibility assignment can block runs
before adapter dispatch.
- `user_secret_ref` bindings intentionally expose metadata without
values, but UI/API callers may need to handle the new binding kind
explicitly.
- External secret providers and IAM policies are not automatically
provisioned by this PR; operators still need to configure provider-side
access for non-local vaults.
- The PR is broad across db/shared/server/UI/runtime paths, so release
validation should include both API and UI secret workflows before merge.
- The migration renumbering is intentionally incremental after upstream
migrations; the branch migrations use guarded
column/table/index/constraint creation so users who tested the older
draft numbering should not hit duplicate DDL for the existing objects.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5-based coding agent (`gpt-5`), Codex local adapter
with shell/tool use and code execution. Context window and internal
reasoning mode are not exposed by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Each agent runs on an adapter (`claude_local`, `codex_local`, …) and
can be assigned company skills that are synced into its runtime
> - An agent's desired-skill selection is persisted inside its single
`adapterConfig` JSON blob under `paperclipSkillSync`, even though the
selection is a company-level, adapter-agnostic choice
> - When a user changes an agent's adapter type, both the server PATCH
handler and the UI patch builder rebuild `adapterConfig` and carry over
only a hardcoded allow-list of adapter-agnostic keys (`env`, `cwd`,
instructions bundle, …)
> - `paperclipSkillSync` was missing from both allow-lists, so switching
adapters (e.g. claude_local → codex_local) silently wiped every assigned
skill
> - This pull request adds `paperclipSkillSync` to the adapter-agnostic
preservation list on both layers and covers it with regression tests
> - The benefit is that switching an agent's adapter no longer destroys
its skill configuration — skills are preserved exactly like
env/cwd/instructions already are
## Linked Issues or Issue Description
Fixes#8974
## What Changed
- **Server (authoritative fix)** — `server/src/routes/agents.ts`: added
`"paperclipSkillSync"` to the `ADAPTER_AGNOSTIC_KEYS` list in the
`changingAdapterType` branch of `PATCH /agents/:id`. On an adapter-type
change the handler now restores the skill-sync selection from the
existing persisted config when the incoming config omits it — the same
mechanism already used for `env`, `cwd`, and the instructions bundle.
This protects every API/CLI client, not just the UI.
- **UI (defense in depth)** — `ui/src/lib/agent-config-patch.ts`: added
`"paperclipSkillSync"` to the client-side `ADAPTER_AGNOSTIC_KEYS` in
`buildAgentUpdatePatch`, so the optimistic patch the client builds on an
adapter switch stops stripping the key before it reaches the server.
- **Tests** — added regression tests on both layers:
- `server/src/__tests__/agent-instructions-routes.test.ts`: `PATCH`ing
`adapterType` (claude_local → codex_local) with `replaceAdapterConfig:
true` keeps `adapterConfig.paperclipSkillSync`.
- `ui/src/lib/agent-config-patch.test.ts`: `buildAgentUpdatePatch`
preserves `paperclipSkillSync` when the overlay changes the adapter
type.
## Verification
```
# server (run from repo root)
cd server && ../node_modules/.bin/vitest run \
src/__tests__/agent-instructions-routes.test.ts \
src/__tests__/agent-skills-routes.test.ts \
src/__tests__/agent-adapter-validation-routes.test.ts \
src/__tests__/agent-permissions-routes.test.ts
# 83 passed
../node_modules/.bin/tsc --noEmit -p tsconfig.json # clean
# ui
cd ui && ./node_modules/.bin/vitest run src/lib/agent-config-patch.test.ts # 7 passed
pnpm --filter @paperclipai/ui typecheck # clean
```
Both new tests fail without the corresponding source change (verified
red → green).
Manual: create an agent on `claude_local`, assign skills, switch it to
`codex_local`, and confirm `GET /api/agents/:id/skills` still returns
the desired skills.
## Risks
Low risk.
- The change only *adds* one key to an existing preservation allow-list;
it does not alter how any other key is handled. Behavior for agents
without a `paperclipSkillSync` block is unchanged (the key is simply
absent and nothing is copied).
- `paperclipSkillSync` is adapter-agnostic (company skill keys, not
adapter-specific), so carrying it across an adapter switch is always
safe — a target adapter that does not support skill sync just ignores
it, and switching back restores the selection.
- Same-adapter config edits already merged and preserved the key; this
only closes the adapter-type-change gap, matching the existing
env/cwd/instructions behavior.
- Follow-up (not in this PR to keep it minimal): the server and client
`ADAPTER_AGNOSTIC_KEYS` lists are maintained separately and already
diverge (`instructionsFilePath` is client-only); a shared constant could
prevent future drift.
## Model Used
Claude Opus 4.8 (`claude-opus-4-8`, 1M-token context), extended thinking
enabled, with tool use (file edit, shell, GitHub CLI) via Claude Code. A
read-only sub-agent was used to trace the root cause across the server
and UI layers.
## 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 (bug fix, not a feature)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (none found)
- [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
(`fix/preserve-skills-on-adapter-type-switch`) 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 (N/A —
internal config-preservation fix, no user-facing docs or API contract
change)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI on this PR)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The agent detail page is where operators inspect heartbeat runs and
watch live execution output.
> - Heartbeat runs can publish progress events while longer operations
are happening.
> - The live log viewer already appended streamed log and structured run
events, but it ignored progress events for the same run.
> - That made useful progress text invisible in the run log until
another event type arrived or the operator inspected other surfaces.
> - This pull request renders run progress events as system log lines in
the live agent run viewer.
> - The benefit is clearer live feedback during long-running heartbeat
operations without changing the backend event contract.
## Linked Issues or Issue Description
No public GitHub issue exists, so this PR describes the issue inline
following the bug report template.
### What happened
The agent detail run log subscribed to company live events and handled
`heartbeat.run.log` plus structured `heartbeat.run.event` payloads, but
it ignored `heartbeat.run.progress` events for the active run.
### Expected behavior
When a heartbeat run emits a progress message, the active run log should
show that message immediately as operator-visible system output.
### Steps to reproduce
1. Open an agent detail page for a live heartbeat run.
2. Trigger a run operation that emits `heartbeat.run.progress` events
with a `message` and optional `phase`.
3. Watch the live log viewer.
Before this change, the progress event was ignored by the log viewer.
After this change, it appears as a system log line, prefixed by
`[phase]` when a phase is present.
### Paperclip version / deployment mode
Current `master`; local development and normal board UI deployments.
### Related work search
Searched public GitHub issues and PRs in `paperclipai/paperclip` for
`heartbeat.run.progress AgentDetail` and `run progress log viewer`; no
duplicate issue or PR was found.
## What Changed
- Added live handling for `heartbeat.run.progress` events in
`AgentDetail`'s run `LogViewer`.
- Render progress messages as `system` log lines for the matching run.
- Include the optional progress phase in the displayed line as `[phase]
message`.
- Prefer the event's `updatedAt` timestamp when provided, falling back
to the live event timestamp.
- Added a replay key for progress log lines so WebSocket reconnect
replay does not duplicate the same rendered progress line.
- Added focused formatter/key tests covering phased progress, unphased
progress, empty messages, and replay-key output.
### Visual output example
The rendered log text is covered by the new formatter test:
```text
[workspace] Syncing issue history
Preparing workspace
```
No layout or styling changes are included; this PR only makes existing
log-line UI receive one more live event type.
## Verification
- `pnpm install --frozen-lockfile` — completed; emitted non-fatal
bin-link warnings for the unbuilt plugin SDK dev CLI.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/AgentDetail.progress.test.ts
src/context/LiveUpdatesProvider.test.ts` — passed, 2 files / 26 tests.
- `git diff --check` — passed.
- Local sensitive-content scan over the PR diff using patterns for API
keys, tokens, secrets, passwords, auth headers, private keys,
localhost/private paths, internal ticket ids, agent links, and tailnet
markers — no findings.
## Risks
Low risk. This is a UI-only live-event handling change for an existing
event type. The replay guard is intentionally scoped to progress lines
and uses the rendered timestamp, stream, and chunk as the key, so
repeated progress events with distinct timestamps or messages still
appear.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent with repository tool use, shell
execution, GitHub CLI access, and local test execution. Context window
size was not exposed in this runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Environment sandboxes already support custom image creation and
refresh through a temporary SSH setup session.
> - The existing workflow makes operators copy an SSH command into an
external terminal before they can install packages or make image
changes.
> - That extra context switch is slower, easier to get wrong, and less
integrated with the setup session Paperclip already tracks.
> - This pull request adds an embedded browser SSH terminal for custom
image setup, so operators can start working in the target sandbox
directly from the environment configuration flow.
> - The implementation uses short-lived websocket attachment tokens,
session-lifetime SSH host-key pinning, and server-managed terminal
cleanup so the feature fits the existing setup-session boundary.
> - The benefit is a smoother custom image creation and refresh
experience without asking users to leave Paperclip for routine sandbox
setup work.
## Linked Issues or Issue Description
No public GitHub issue exists.
### Subsystem affected
Cross-cutting: `server/` custom image setup APIs and websocket handling,
`ui/` environment configuration UI, and shared custom image contracts.
### Problem or motivation
Custom image creation and refresh require an operator to open a separate
SSH client, paste the command shown by Paperclip, perform setup work,
then return to the browser to finish the image flow. This is functional
but awkward for a setup process that already starts and tracks a
temporary sandbox session.
### Proposed solution
Embed an SSH terminal in the custom image setup UI. When a setup session
exposes an SSH payload, Paperclip should open a browser terminal backed
by a server-side websocket session, let the operator run setup commands
in-place, and then close the terminal when setup is finished, cancelled,
expired, or disconnected.
### Alternatives considered
- Keep the existing copy/paste SSH command workflow. This remains a
fallback, but it does not streamline the common path.
- Put SSH credentials directly into websocket URLs. This was avoided so
terminal authentication can happen in an explicit first websocket auth
frame rather than in logged URLs.
- Trust the SSH host blindly for every reconnect. This PR instead pins
the observed host-key fingerprint for the setup-session lifetime.
### Roadmap alignment
This fits the roadmap theme of making agent workspaces usable in more
remote and sandboxed environments while preserving Paperclip's
control-plane model.
### Additional context
Public GitHub search did not find a duplicate issue or PR for `custom
image terminal ssh` in `paperclipai/paperclip`.
## What Changed
- Added server-side terminal session tracking for custom image setup
sessions, including connect-token issuance, websocket attachment,
expiry, resize, input, and shutdown handling.
- Added an embedded browser terminal to the custom image creation and
refresh flow when a setup session provides SSH connection details.
- Moved terminal token authentication out of the websocket URL and into
the first websocket JSON auth frame.
- Added SSH host-key SHA-256 pinning for each terminal session and
documented the provider convention for username-embedded SSH
credentials.
- Updated the custom image environment API and UI so the setup terminal
can open, reconnect, show status, authenticate, resize, and remain
active for the setup-session lifetime once attached.
- Kept custom image setup routes company-scoped and closed active
terminal sessions on setup finish/cancel.
- Added focused unit/integration/UI coverage for token expiry,
setup-session expiry, websocket close paths, host-key pinning, and
terminal session lifecycle behavior.
- Removed the generated lockfile delta from the PR; CI owns temporary
lockfile regeneration for manifest-changing PRs.
## Verification
- `pnpm exec vitest run
server/src/__tests__/server-startup-feedback-export.test.ts
server/src/__tests__/environment-custom-image-terminal-ws.test.ts
server/src/services/environment-custom-image-terminal-sessions.test.ts
server/src/__tests__/environment-custom-image-routes.test.ts
packages/shared/src/environment-custom-images.test.ts
ui/src/pages/CompanyEnvironments.test.tsx`
- 6 test files passed
- 58 tests passed
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server build`
- `pnpm --filter @paperclipai/ui build`
- `pnpm run typecheck:build-gaps`
- `git diff --check`
- Local sensitive-content scan over the PR diff using patterns for API
keys, private keys, private hostnames, local paths, token fields, and
credential-like strings.
- Findings were limited to removed URL-token code and synthetic test
placeholders such as `ssh-token-secret` and
`terminal-token-terminal-token-123456`.
- No real credentials, private hostnames, local filesystem paths, or
instance-local links were found.
- Remote PR checks were green after the implementation commit, including
Build, Typecheck + Release Registry, General tests, serialized server
suites, e2e, verify, Socket, Snyk, Superagent, and Greptile 5/5.
- Post-merge PR hardening on July 3, 2026: merged `origin/master` at
`47448721e` into the branch, resolved the `CompanyEnvironments.tsx`
import conflict, reran focused tests, server/UI typechecks, server/UI
builds, `pnpm run typecheck:build-gaps`, and `git diff --check`, scanned
the final diff for sensitive content, pushed `4b43558cc`, and confirmed
all remote checks plus Greptile 5/5 were green.
- PR metadata correction on July 3, 2026: changed the title/body framing
from bug-fix language to feature-request language. No source files
changed for this metadata-only update.
## Risks
- Moderate surface area because this adds websocket routing,
setup-session runtime state, package dependencies, and a new custom
image UI path.
- New websocket attachments still require valid short-lived tokens;
established terminal sessions remain bounded by setup-session expiry,
explicit finish/cancel, client close, or server shutdown.
- The terminal-session store is in-memory, so active terminal websocket
tokens and host-key pins do not survive server restarts.
- SSH host-key verification uses session-lifetime TOFU pinning because
the current provider payload does not expose a trusted host-key
fingerprint.
- The external SSH command remains important as a fallback if a browser,
proxy, or network environment cannot sustain the websocket terminal.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent with shell/tool execution. Context
window size was not exposed in this runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue details sidebar is a high-traffic operator surface for
scanning task state, ownership, policy, relationships, and external
references.
> - The previous properties pane mixed too much behavior in one large
component and several rows could overflow or become hard to scan with
long labels and relation lists.
> - The UI needed a narrower, more reusable structure so compact
property rows, relation pills, external URL rows, and picker triggers
behave consistently.
> - This pull request splits the properties pane into focused helper
modules and tightens the pane's layout, truncation, popover, and
overflow behavior.
> - The benefit is a denser, more stable properties pane that stays
usable when issues have long titles, many relationships, external URLs,
or execution policy IDs.
## Linked Issues or Issue Description
No public issue exists for this work. I searched GitHub issues and PRs
for `IssueProperties properties pane`; there was no duplicate issue and
the matching PRs were unrelated test stabilization or other work.
### Problem or motivation
The board issue details properties pane is a frequent operator workflow
surface, but it was implemented as one large component and several rows
could become hard to scan with long labels, many relationships, external
URLs, or execution policy IDs.
### Proposed solution
Split the properties pane into focused modules, tighten compact row
layout and truncation behavior, add bounded previews with explicit
expansion controls for long relation and URL lists, and make execution
policy ID generation work on insecure origins as well as normal browser
origins.
### Alternatives considered
A smaller patch inside the existing monolithic component would fix
individual overflow symptoms, but it would keep related primitives,
picker behavior, relation controls, and external URL rendering tangled
in one file. The split keeps the behavior easier to test and review
without changing public APIs.
### Roadmap alignment
This is an incremental UI quality improvement to the existing board task
details surface. I checked `ROADMAP.md` and did not find overlapping
planned core feature work.
## What Changed
- Split the issue properties pane into focused modules for helpers,
primitives, relation controls, property pickers, and external object
rows.
- Cleaned up row spacing, truncation, picker trigger alignment, scroll
behavior, status color coverage, and long-value titles.
- Added bounded previews and expand/collapse controls for blocking,
sub-task, related-task, and external URL lists.
- Fixed execution policy ID generation so insecure origins fall back to
a stable base URL instead of throwing.
- Updated Storybook issue-management stories and expanded unit coverage
for the new properties pane behavior.
## Verification
- `git diff --check origin/master..HEAD`
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/IssueProperties.test.tsx` — 38 tests passed
## Risks
Low to moderate risk. The changes are UI-only, but they touch a
frequently used task details surface. The main risk is a subtle layout
regression in an untested viewport or issue shape; the branch adds
targeted coverage for long relation and external URL lists.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex coding agent with repository tool use, shell
execution, GitHub CLI access, and local test execution. Context window
size was not exposed in this runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source control plane people use to manage AI
agents for work.
> - Environment variables and secrets sit in the configuration surfaces
that let agents, projects, routines, and company environments run with
the right runtime inputs.
> - The previous editor was a single legacy component with cramped row
behavior, weak secret conversion affordances, and duplicated handling
across several call sites.
> - Operators need a clearer editor that handles text values, secret
references, draft rows, and sensitive-value warnings consistently
wherever environment variables are configured.
> - This pull request replaces the legacy editor with a reusable
environment variables editor and migrates the existing configuration
surfaces to it.
> - The benefit is a more reliable editing workflow with targeted test
coverage around row state, dotenv parsing, secret selection, and
affected page integrations.
## Linked Issues or Issue Description
No public GitHub issue exists, so this PR describes the issue inline
following the feature request template.
### Subsystem affected
ui/ — React + Vite board UI
### Problem or motivation
Environment variables are edited in several Paperclip configuration
surfaces, including agent config, project properties, stage secrets,
routine sections, company environments, and company settings. The legacy
editor made common operator work difficult: rows could feel cramped,
secret conversion was inconsistent, draft rows and imported dotenv data
were easy to mishandle, and sensitive-value warnings did not have a
consistent place in the workflow.
### Proposed solution
Introduce a reusable environment variables editor component that
consistently supports text values, secret references, draft rows, dotenv
import parsing, sensitive-value hints, secret picking, secret creation,
and conversion to stored secrets. Migrate the existing
environment-variable call sites to the shared editor so behavior and
tests live in one component family.
### Alternatives considered
Keeping the existing `EnvVarEditor` and patching individual call sites
would preserve duplication and leave each surface responsible for its
own row and secret handling. This PR instead centralizes the behavior so
future fixes cover all migrated surfaces.
### Roadmap alignment
Checked `ROADMAP.md`; this does not duplicate a named roadmap item. It
supports the existing local-first and deployment-oriented product
direction by improving the UI where operators configure runtime
environment values.
### Additional context
The PR includes targeted tests for the editor model, dotenv parsing,
sensitive-value detection, component behavior, affected page
integrations, and Greptile review regressions around external saves and
bulk import immutability.
## What Changed
- Replaced the legacy `EnvVarEditor` with a reusable
`environment-variables-editor` component family.
- Added editor model helpers for draft rows, dotenv parsing,
sensitive-value detection, secret picking, secret creation, and
conversion to secret references.
- Migrated agent config, project properties, stage secrets, routine
editable sections, company environments, company settings, design guide
examples, and Storybook stories to the new editor.
- Added targeted tests for the editor model, parsing, sensitive-value
detection, component behavior, and affected company environment/settings
integrations.
- Fixed the company settings test harness to use the repo’s
`flushSync`-based React test helper pattern under the current React
build.
- Addressed Greptile feedback by flushing pending editor drafts before
enclosing form submits or external save-button clicks, cloning
bulk-import rows before mutation, and deferring the overflow
store-as-secret popover open path.
## Verification
- `pnpm exec vitest run
ui/src/components/environment-variables-editor/EnvironmentVariablesEditor.test.tsx
ui/src/pages/CompanyEnvironments.test.tsx
ui/src/components/AgentConfigForm.render.test.tsx` — passed, 3 files /
40 tests.
- Earlier focused Vitest coverage for model, dotenv parsing, sensitive
detection, company environments, and company settings passed, 6 files /
78 tests.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- GitHub PR checks on head `0aa49c6f8afed1f62d9e26da07fc466fbf299850` —
passed; CI checks green, security review neutral, Greptile check
success.
- Greptile review — 5/5 confidence on head
`0aa49c6f8afed1f62d9e26da07fc466fbf299850`; 0 unresolved review threads.
## Risks
- Medium UI risk: several environment-variable entry surfaces now share
the new editor, so regressions could affect multiple configuration
workflows at once.
- Secret conversion, draft-row behavior, external save flushing, and
bulk import behavior are covered by targeted tests, but reviewer
attention should still focus on manual editing flows, focus retention,
and save/cancel affordances.
- No database or API contract changes.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with tool use and local command
execution; medium reasoning mode. Exact context-window metadata is not
exposed in this runtime.
## 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
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - A core part of that experience is watching active agent runs without
dropping into raw logs first
> - Local and sandbox-backed adapters already record useful run output,
progress, and tool activity
> - But active issue threads could sit visually stale while the agent
was syncing workspaces, tailing sandbox output, or emitting incremental
tool-call updates
> - Operators need timely, human-readable progress while preserving the
raw transcript underneath
> - This pull request streams sandbox run-log progress into runtime
status, keeps visible issue threads refreshed, and folds repeated ACPX
tool updates into stable transcript cards
> - The benefit is that long-running agent work becomes easier to
supervise without changing the task/comment control-plane model
## Linked Issues or Issue Description
No public GitHub issue exists for this exact change.
Problem/motivation:
- During long-running sandboxed agent work, the issue UI can appear idle
even though the agent is actively syncing, running tools, or producing
incremental output.
- Operators need realtime feedback at the issue-thread layer, not only
after opening raw logs or waiting for the final heartbeat result.
- Related public context: #1808 previously added live-run status dots to
Projects; #4362 touches heartbeat wakeup behavior but is not a duplicate
of this runtime/UI feedback change.
## What Changed
- Added sandbox run-log streaming support and defaulted sandbox-capable
local adapters into the richer live-feedback path.
- Surfaced environment/sandbox sync progress through heartbeat runtime
status with bounded, redacted snippets.
- Added live issue-thread cache patching so visible active runs update
as progress events arrive.
- Folded repeated ACPX `tool_call` updates into one transcript card
instead of stacking duplicate cards.
- Updated adapter docs and added focused regression coverage for sandbox
log streaming, runtime status, ACPX parsing, live updates, transcript
rendering, and issue chat messages.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run ui/src/context/LiveUpdatesProvider.test.ts`
- `pnpm exec vitest run
server/src/services/heartbeat-run-runtime-status.test.ts
server/src/__tests__/heartbeat-runtime-state.test.ts
ui/src/context/LiveUpdatesProvider.test.ts`
- `pnpm exec vitest run
packages/adapter-utils/src/execution-target-sandbox.test.ts
packages/adapter-utils/src/sandbox-managed-runtime.test.ts
server/src/services/heartbeat-run-runtime-status.test.ts
server/src/__tests__/agent-live-run-routes.test.ts
server/src/__tests__/heartbeat-runtime-state.test.ts
packages/adapters/acpx-local/src/ui/parse-stdout.test.ts
ui/src/context/LiveUpdatesProvider.test.ts
ui/src/components/transcript/RunTranscriptView.test.tsx
ui/src/lib/issue-chat-messages.test.ts
ui/src/components/IssueChatThread.test.tsx`
- GitHub PR workflow on head `8397953e7b41ccd42e5d9457ee7e4dfb996e4ec5`:
`verify`, build, typecheck/release-registry, e2e, general shards,
serialized server shards, and canary dry run passed.
- Greptile Review on head `8397953e7b41ccd42e5d9457ee7e4dfb996e4ec5`:
Confidence Score 5/5, no unresolved review threads.
## Risks
- Live issue-thread cache patching could miss an edge case for a route
shape not covered by tests.
- Surfacing active-run snippets needs continued care around redaction;
this PR keeps snippets bounded and adds redaction-focused coverage.
- More frequent active-run UI refreshes could expose performance issues
on very large issue threads, though updates are scoped to visible
run/query caches.
## Model Used
OpenAI GPT-5 via Codex, operating as a tool-enabled coding agent with
shell, git, and repository-editing capabilities. Context window size is
not exposed in this runtime.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Adapter names are part of the board-facing agent setup and
management experience.
> - The product now treats adapters as harnesses, while execution
environments are modeled separately.
> - Several built-in adapter labels still carried legacy local wording
from the older harness-by-environment model.
> - That wording makes the UI noisier and implies a distinction users no
longer need to reason about.
> - This pull request normalizes adapter display labels while keeping
persisted adapter type identifiers unchanged.
> - The benefit is clearer adapter selection and management copy without
a database migration.
## Linked Issues or Issue Description
No public GitHub issue was found for this exact cleanup.
Related public PRs:
- Supersedes #8910, an earlier branch for the same cleanup that did not
include the later docs/gateway/Cursor alignment.
- Refs #8819, which is related display-registry work for external
multi-segment adapter labels, but not a duplicate of this built-in label
cleanup.
Feature request details:
- Subsystem affected: Cross-cutting (`ui/`, `packages/adapters`, and
docs).
- Problem or motivation: user-facing adapter names include legacy local
qualifiers even though adapters map to harnesses and environments are
first-class elsewhere.
- Proposed solution: remove the legacy local wording from built-in
display labels, keep machine-readable adapter type ids unchanged, and
keep gateway disambiguation where it is useful.
- Alternatives considered: changing persisted adapter type ids was ruled
out because it would create migration and compatibility risk; one-off UI
replacements were ruled out because the display registry is already the
correct central label boundary.
- Roadmap alignment: this is small adapter UX polish, not a new
roadmap-level core feature.
## What Changed
- Updated the adapter display registry so known adapter labels are final
and no built-in local adapter renders a legacy local suffix.
- Preserved clean derived labels for unknown plugin local types while
keeping gateway disambiguation for unknown gateway types.
- Updated `AdapterManager` to prefer registry labels when the server
reports raw adapter type ids for built-ins.
- Removed legacy local wording from built-in adapter metadata labels in
UI and adapter packages.
- Aligned Cursor adapter metadata with the central display registry
label.
- Updated adapter docs and Storybook fixtures to match the new display
names.
- Added focused registry coverage for built-in labels and unknown plugin
suffix behavior.
## Verification
- `pnpm check:tokens`
- `git diff --check origin/master...fix/adapter-display-labels`
- Patch-addition scan for added secrets, private paths, and internal
links: no matches.
- GitHub duplicate search for open adapter-label/local-suffix issues and
PRs; #8910 was identified as the older superseded public PR.
- `pnpm exec vitest run
ui/src/adapters/adapter-display-registry.test.ts`
- `pnpm --filter @paperclipai/ui typecheck`
- Stale-label scan found no remaining user-facing display-label
suffixes; remaining local wording is operational/test terminology such
as adapter ids, docs about running locally, and test descriptions.
## Risks
Low risk. The change is display-label and documentation focused, and
adapter type ids remain unchanged. The main risk is ambiguous gateway
naming, mitigated by keeping explicit gateway labels where variants need
disambiguation.
## Model Used
OpenAI GPT-5 via Codex, tool-enabled coding agent in a local repository
workspace. Context window size is not exposed by this environment.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The app shell exposes user-visible product surfaces through routes
and the left navigation
> - The Work Timeline frontend landed on `master` in related PR #8880
before the product surface was ready for general users
> - The backend aggregation endpoint and shared DTOs can remain
available for continued iteration without exposing the page in the main
app
> - This pull request removes the visible `/timeline` route, sidebar
entry, page implementation, chart component, and Storybook fixtures
> - The benefit is that users no longer see an unfinished Work Timeline
page on `master`, while future work can continue from a preserved
branch/workspace
## Linked Issues or Issue Description
No public GitHub issue exists for this rollback request.
Bug-style description:
- **What happened:** A Work Timeline page was exposed in the app
navigation before the surface was ready for users.
- **Expected behavior:** Unready product work should not be visible from
the default app shell on `master`.
- **Steps to reproduce:** Open the app on current `master`; the sidebar
includes a `Timeline` item that routes to `/timeline`.
- **Paperclip version/commit:**
`60f7fb422394c94618b8eef27ae032702004f544`.
- **Deployment mode:** Local app / standard Paperclip app shell.
- **Related public PR:** #8880.
## What Changed
- Removed the `/timeline` route and `Timeline` page import from the app
route table.
- Removed the `Timeline` sidebar item and unused `GanttChartSquare` icon
import.
- Deleted the frontend Work Timeline API wrapper, chart component,
layout helper, page, Storybook story, and sample fixtures.
- Removed the now-unused `queryKeys.workTimeline` entry.
- Left the backend timeline endpoint and shared DTOs intact so the data
contract can continue to be developed off the preserved work.
## Verification
- `pnpm --filter @paperclipai/ui typecheck`
- Searched the frontend for stale `workTimeline`, `WorkTimeline`,
`/timeline` route/sidebar, and `GanttChartSquare` references after
deletion.
## Risks
- Low runtime risk: this removes an app route and navigation entry for
an unfinished surface.
- Deep links to `/timeline` will now fall through to the app's existing
not-found behavior.
- The backend endpoint remains available; if the intent was to remove
the API too, that should be handled in a separate, explicit PR.
## Model Used
OpenAI Codex coding agent, GPT-5-based model, with shell/tool use for
repository inspection, code editing, git, and GitHub CLI operations.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators need to *see* how work actually flowed across their agents
over time — who was invoked, what they worked on, and how work was
delegated between them
> - The dashboard shows point-in-time state but nothing reconstructs the
temporal, cross-actor picture of heartbeat runs and delegations
> - A read-only company work-timeline endpoint was landed first (server
aggregation over runs/issues/activity); it had no frontend
> - This pull request adds the Gantt-style **Work Timeline** page that
renders that endpoint, plus the small additive server contract change it
needs (shared DTOs + a task title on each span)
> - The benefit is a single dense view — actor rows, concurrency lanes,
delegation connectors, zoom and a mini-map — that makes agent activity
legible without an N+1 fetch storm from the client
## Linked Issues or Issue Description
No public GitHub issue. Problem, in-PR:
- **Gap:** the company work-timeline aggregation endpoint has no UI.
There is no way to visually inspect how heartbeat runs unfolded over
time or how work was delegated between agents.
- **Solution:** a dashboard-adjacent Gantt-style page at
`/:companyPrefix/timeline`, linked from the sidebar's "Work" section,
rendering runs as bars on per-actor rows with delegation connectors,
kickoff chips, zoom, a lens filter, and a mini-map.
- Built with React + custom inline SVG (no chart dependency; consistent
with the existing Tailwind/Radix stack).
## What Changed
- **Frontend Gantt page** (`ui/src/pages/Timeline.tsx`,
`ui/src/components/timeline/WorkTimelineChart.tsx`): actor rows
(agents/system only — humans never get a row), overlapping runs packed
into concurrency sub-lanes, bars = heartbeat runs with a left colour tab
for issue identity, truncated task title + timing/status on hover,
click-through to the task.
- **Human activity markers & human rows** for kickoff/delegation
involving people, without giving humans their own run lane.
- **Kickoff avatar chips** at each bar's leading edge; straight
agent→agent delegation connectors (dashed for
retries/changes-requested); in-progress runs extend to a dashed "now"
line and fade out.
- **Zoom** (hour/day/week, auto-fit), full-window **mini-map** with a
draggable brush, **lens filter** (Everyone / per-user, server-side), and
colour **by task / by status**.
- **Pure layout/transform module** (`ui/src/lib/timeline/layout.ts`) —
packing, kickoff derivation, connector resolution, scales — unit-tested
in isolation.
- **Server contract (additive):** moved the `WorkTimeline*` DTOs into
`@paperclipai/shared` so the aggregation service and the UI consume one
contract; added `issueTitle` to each span so the tooltip shows the task
title with no N+1 client fetch.
- Sidebar link, query keys, API client (`ui/src/api/workTimeline.ts`),
and a Storybook story with fixtures.
## Verification
- `pnpm --filter @paperclipai/shared build` ✅
- `pnpm --filter @paperclipai/server typecheck` ✅ · `pnpm --filter
@paperclipai/ui typecheck` ✅
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/timeline/layout.test.ts
src/components/timeline/WorkTimelineChart.test.tsx` ✅ (15/15)
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/work-timeline-service.test.ts` ✅ (5/5) — the DTO move +
`issueTitle` are additive; existing service tests use `objectContaining`
and still pass.
- Rendered `WorkTimelineChart` headless against a real slice of company
activity via a Storybook story; manual browser QA of the live page
passed on the feature branch.
## Risks
- **Low risk.** The change is UI-only plus an additive server DTO
refactor (types relocated to `@paperclipai/shared`, one new optional
field). No schema/migration changes, no change to endpoint behaviour
beyond the extra `issueTitle` field. The page is behind its own route
and does not alter existing views.
## Model Used
- Claude, Opus 4.8 (`claude-opus-4-8`), via Claude Code with extended
thinking and tool use.
## 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 (only the merged endpoint PR #8875 is related; no duplicates)
- [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 and contains no internal
ticket id
- [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
- [ ] All Paperclip CI gates are green
- [ ] 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 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The inbox is where operators quickly scan which issues are active,
blocked, or waiting for attention
> - A blocked parent can still have active descendant work, but the
inbox previously depended on only loaded rows to infer that state
> - That made collapsed or partially loaded issue trees look more stuck
than they really were
> - This pull request carries live descendant summary data through the
issue list API and inbox UI
> - The benefit is a more accurate blocked-inbox signal, so operators
can distinguish truly stalled work from blocked parents that still have
live child activity
## Linked Issues or Issue Description
No public GitHub issue was found for this exact inbox descendant-status
polish.
Feature request fields:
**Subsystem affected**
Cross-cutting: `server/`, `packages/shared`, plugin/MCP API surfaces,
and `ui/` inbox rendering.
**Problem or motivation**
Inbox rows need to show when blocked or collapsed parents still have
live descendant work, even when the live child row is not loaded in the
current client tree. Without a server-provided descendant summary, a
parent can look stalled even though active work continues below it.
**Proposed solution**
Expose an optional live descendant count on issue list results, request
it from inbox views, and use it to render covered blocked status and
live-below indicators. Keep the field opt-in so other issue list callers
keep their existing payload shape and query cost.
**Alternatives considered**
Relying only on client-loaded subtree state was ruled out because it
misses collapsed or unloaded descendants. Always returning the count was
also avoided because most list callers do not need this extra summary.
**Roadmap alignment**
This is scoped operator-visibility polish for the existing inbox. It
does not duplicate a named `ROADMAP.md` milestone.
**Additional context**
The recursive summary query is guarded against parent cycles, and the UI
still falls back to loaded subtree live counts when server summary data
is absent or stale.
## What Changed
- Added optional `includeLiveDescendantSummary` support to issue list
contracts, SDK surfaces, MCP tools, routes, services, and tests.
- Added `liveDescendantCount` to issue list results when requested.
- Updated inbox and blocked-inbox queries to request live descendant
summaries.
- Updated inbox row status rendering so blocked parents with live
descendants show covered blocker treatment without duplicating the
live-below chip.
- Hardened live descendant summary traversal against parent cycles and
preserved the loaded-subtree fallback path for blocked inbox rows.
- Added focused tests for the API parameter, service behavior, helper
logic, cycle handling, and inbox UI query/rendering behavior.
## Verification
- `pnpm exec vitest run
server/src/__tests__/issue-list-assignee-filter-routes.test.ts
ui/src/lib/inbox-live-descendants.test.ts
ui/src/components/IssueColumns.test.tsx
ui/src/components/BlockedInboxView.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- Rebased cleanly onto current upstream `master` before pushing.
- Confirmed the branch diff does not include `pnpm-lock.yaml` or
`.github/workflows/*` changes.
## Risks
Low to moderate risk. The new descendant count is opt-in on list
requests, but it adds query work when the inbox asks for it. The
recursive traversal now tracks visited ancestors to avoid cycle
failures. The UI uses the server count as a supplement to existing
loaded-tree state, so stale or absent counts fall back to the prior
behavior.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent, tool-enabled with local shell and git
access. Reasoning mode and context window are managed by the
Paperclip/Codex runtime.
## 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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Environments are now managed as instance-level runtime resources
rather than per-company rows
> - The custom environment image setup tables were introduced with their
own `company_id` columns and route query parameters
> - That split made one saved environment image state depend on an extra
company context even though the environment itself is the durable owner
> - It also made saved-environment probes harder because applying the
active custom image template could require a company context when no
secret-backed config needed one
> - This pull request scopes custom image templates and setup sessions
directly to the saved environment
> - The benefit is that reusable environment images follow the same
instance-scoped model as environments while secret resolution still uses
company context only when secrets require it
## Linked Issues or Issue Description
No matching public GitHub issue was found.
Bug report:
### What happened?
saved environment custom-image routes and persistence required a
`companyId` even though environments are instance-scoped, and saved
sandbox probes did not opt into active custom-image template application
unless a company context was present.
### Expected behavior
custom-image templates and setup sessions should be owned by the saved
environment, and saved sandbox probes should apply the active template
while still requiring a company context only for secret-backed runtime
config.
### Steps to reproduce
1. Configure an instance-scoped sandbox environment with custom-image
setup support.
2. Start or inspect a custom-image session or template for that saved
environment.
3. Probe the saved environment without a custom-image-specific
`companyId` query parameter.
### Paperclip version or commit
current `master` after the environment custom-image template migration.
### Deployment mode
Local dev (pnpm dev) or authenticated local Paperclip instance.
### Installation method
Built from source (pnpm dev / pnpm build).
### Agent adapter(s) involved
Not adapter-specific (core bug).
### Database mode
Embedded PGlite/Postgres dev database.
### Access context
Board human operator.
### Privacy checklist
No logs, secrets, tokens, private URLs, or local machine paths are
included.
Duplicate search performed:
- `gh search prs "environment custom image companyId
repo:paperclipai/paperclip" --state open --limit 20`
- `gh search prs "custom image environment scoped
repo:paperclipai/paperclip" --state open --limit 20`
- `gh search issues "environment custom image
repo:paperclipai/paperclip" --state open --limit 20`
The returned results were unrelated adapter, Docker, auth, or
stale-workspace items.
## What Changed
- Removed redundant `company_id` columns from environment custom-image
templates and setup sessions.
- Added migration `0127_environment_custom_images_instance_scoped` to
collapse duplicate active rows per environment before dropping the old
company-scoped indexes/columns.
- Updated custom-image services, route handlers, shared validators, and
UI API/query keys to use environment-scoped custom-image state.
- Kept runtime secret resolution company-aware only when secret refs or
bindings require a company context.
- Made saved sandbox environment probes opt into active custom-image
template application.
- Updated DB, shared, server, and UI tests for the new
environment-scoped contract.
## Verification
- `pnpm --filter @paperclipai/db run check:migrations`
- `pnpm exec vitest run
packages/db/src/environment-custom-images-schema.test.ts
packages/shared/src/environment-custom-images.test.ts
server/src/__tests__/environment-custom-image-routes.test.ts
server/src/__tests__/environment-custom-images-service.test.ts
server/src/__tests__/environment-routes.test.ts
ui/src/pages/CompanyEnvironments.test.tsx`
- `pnpm -r typecheck`
- `pnpm test:run` before rebasing onto latest `master`; after the rebase
only the migration number changed, and the migration check plus focused
suite, typecheck, and build were rerun.
- `pnpm build`
## Risks
- Migration safety: the migration supersedes duplicate active templates
per environment and fails duplicate active setup sessions before adding
environment-only unique indexes. Operators with duplicate historical
active rows should review which active template is kept.
- Behavior shift: plugin custom-image setup calls now receive
`companyId: "instance"` when no secret binding determines a concrete
company context.
- Secret-backed configs still require an explicit or uniquely inferable
company context; environments with secret bindings spread across
multiple companies continue to fail fast.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex via the `codex_local` adapter, GPT-5-based coding model
with tool-enabled repository inspection, editing, testing, git, and
GitHub CLI access. Exact context-window metadata was not exposed by the
runtime.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Task/issue threads render each comment as a chat bubble; the author
determines whether it shows as a left-aligned agent bubble (name + icon)
or a right-aligned "Board" bubble
> - Comments posted by an agent from a local execution environment are
written with a non-human author id (`local-board`/system), so they were
mis-rendered as blue "Board" bubbles instead of being attributed to the
authoring agent
> - This misattribution is confusing (it looks like the human board said
something an agent actually said) and it can drive false
wake/reconciliation behavior on the affected threads
> - This pull request adds server-side attribution derivation (lossless
run-id join first, then an explicit run-log post marker), persists the
derived agent so the read path stops re-scanning run logs, and stops the
client from labeling agent-derived comments "Board"
> - The benefit is agent comments render as the correct agent, genuine
human board comments are never reattributed, and reads get cheaper after
a one-time persistence
## Linked Issues or Issue Description
<!-- No public GitHub issue — describing the problem in-PR (bug report
shape). -->
**What happened?**
In a task/issue comment thread, comments authored by an agent from a
local execution environment are stored with a non-human author id
(`local-board`/system). The UI renders these as right-aligned blue
"Board" bubbles, implying a human board member authored them. The
mislabeling is also a wake/reconciliation hazard: an agent comment that
reads as "Board" can look like human board input.
**Expected behavior**
Such comments should render as the authoring agent (left-aligned bubble
with agent name + icon). Genuine human/board comments must continue to
render as "Board" and must never be reattributed to an agent.
**Steps to reproduce**
1. Have an agent post a comment on an issue from a local execution
environment (author id `local-board`).
2. Open the issue comment thread in the UI.
3. Observe the agent's comment rendered as a right-aligned blue "Board"
bubble instead of the authoring agent.
**Root cause**
The read path did not resolve the authoring agent for these comments,
and the client fell back to a "Board" label for the `local-board`
author.
## What Changed
- **Server derivation (`server/src/services/issues.ts`):**
- Resolve the authoring agent from the comment's run id first
(`createdByRunId`/`derivedCreatedByRunId` → `heartbeatRuns.agentId`) —
lossless when present.
- Second tier `run_log_comment_post`: read the run log lazily (only for
still-unresolved comments) to match the explicit `comment id:` post
marker.
- **Guard:** never reattribute a comment whose author maps to a genuine
user profile. Only the non-human sentinel (`local-board`, which is
itself a `user` row) and authors absent from the `user` table are
eligible.
- Pure timing-overlap tiers are intentionally **not** used (Option A) —
an agent comment and a human board comment posted during the same run
are indistinguishable rows, so any timing guess risks mislabeling a real
human comment.
- **Persistence
(`packages/db/src/migrations/0126_issue_comment_derived_attribution.sql`,
`packages/db/src/schema/issue_comments.ts`):** add stored `derived_*`
attribution columns and write the resolved agent back with a single bulk
`UPDATE ... FROM (VALUES ...)`, so reads stop recomputing from run logs.
Migration is additive (new nullable columns) with a batched, idempotent
backfill of the lossless run-id tier over historical rows.
- **Types (`packages/shared/src/types/issue.ts`):** expose the persisted
attribution fields and the `IssueCommentDerivedAuthorSource` union.
- **Client (`ui/src/lib/issue-chat-messages.test.ts`):** the message
builder already prefers a resolved agent id (`authorAgentId ??
runAgentId ?? derivedAuthorAgentId`), so once the server persists the
derived agent the bubble renders as the agent automatically — no client
code change needed. Adds a regression guard confirming a genuine board
comment with no derived agent is still rendered as "Board".
- **Tests:** derivation + message-building tests, including assertions
that genuine board/user comments are **not** reattributed.
## Verification
- `cd server && npx vitest run issues-service` — 94 tests pass: run-id
resolution, no-attribution on timing overlap alone (Option A), multi-run
ambiguity, same-agent multi-run, and the genuine-user guard. Exercises
the real persistence path (bulk UPDATE) against the test DB.
- `cd ui && npx vitest run issue-chat-messages` — 27 tests pass; client
no longer labels agent-derived comments "Board", and a genuine board
comment with no derived agent is not re-labeled.
- `cd server && npm run typecheck` — passes (exit 0).
- Manual: on a thread containing old agent-authored comments, the blue
"Board" bubbles render as the authoring agent; a genuine board comment
on the same thread still renders as "Board".
## Risks
- **Mis-reattributing a genuine board comment made during an agent run**
→ mitigated by the human-profile guard (only `local-board`/system
authors are eligible) and by dropping pure timing tiers (Option A): only
the lossless run-id join and the explicit run-log post marker attribute
history.
- **Backfill volume / run-log reads** → the migration backfill is
batched (5000 rows/loop) and results are persisted so reads stop
recomputing; the read-path persistence is a single bulk UPDATE rather
than per-comment round-trips. Migration adds only nullable columns (no
destructive change).
- The persistence/backfill has **not** been run against any production
database as part of opening this PR.
## Model Used
Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, via Claude Code
with tool use.
## 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 — related open
PRs (#6006 narrow attribution run scan, #4729 attribution roll-up, #7014
reaped-run attribution) address different attribution paths; none fix
the `local-board` "Board" bubble rendering this PR targets. Supersedes
#8832 (same change; branch renamed to drop an internal ticket id per
CONTRIBUTING → Branch Naming)
- [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 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
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
Two interactive UI controls were missing WAI-ARIA attributes, so
screen-reader users couldn't perceive their state. The IssuesList
view-mode toggle already had `title` tooltips but no
`aria-label`/`aria-pressed` and its container had no `role="group"`; the
GoalTree expand/collapse chevron announced only "button". Approach:
attributes only, no logic/render changes, reusing the pattern already
shipped on the Agents page toggle. Per review feedback, GoalTree uses a
**stable** `aria-label` (`` `${goal.title} subtree` ``) with
`aria-expanded` for state, rather than a dynamic label that
double-announces state.
## Issue
_No existing tracking issue — described inline per CONTRIBUTING.md →
"Link Issues or Describe Them In-PR"._
**What happened**
Two components expose buttons with no accessible name or state, making
them unusable via screen reader: (1) the `IssuesList` view-mode toggle
doesn't convey which view is active; (2) the `GoalTree` expand/collapse
chevrons have no name and no expanded/collapsed state.
**Expected behavior**
Both controls announce their purpose and current state to assistive
technology.
**Steps to reproduce**
Enable VoiceOver, open the Issues page and Tab to the view-mode toggle,
then open the Goals page with nested goals and Tab to a tree chevron —
each control announces only "button", with no name and no
pressed/expanded state.
## What Changed
**`ui/src/components/IssuesList.tsx`** — `role="group"` +
`aria-label="View mode"` on the container; `aria-label` ("List
view"/"Board view") and `aria-pressed` on each button.
**`ui/src/components/GoalTree.tsx`** — stable `aria-label` (``
`${goal.title} subtree` ``) and `aria-expanded` on the chevron button.
2 files, ARIA attributes only, no behavioral change.
## Verification
1. Issues page → toggle announces "List view, pressed" / "Board view,
not pressed", grouped as "View mode".
2. Goals page with nested goals → each chevron announces "<goal title>
subtree" with expanded/collapsed state.
3. Manual VoiceOver pass; no visual/behavioral change for sighted users.
## Risks
Minimal — additive HTML attributes with no impact on logic, rendering,
or state. Worst case is a suboptimal announcement string, trivially
adjusted.
## Model Used
Original change human-authored by @bluzername. Two follow-up commits
(stable `aria-label` refinement; removal of a stray tooling file)
applied via maintainer edit; the refinement was drafted with Claude Opus
4.8.
## Checklist
- [x] I searched the GitHub PR list (open + recently closed) for
similar/duplicate PRs before opening — none found.
---------
Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
## Thinking Path
> - Paperclip orchestrates ai-agents for zero-human companies
> - But humans want to watch the agents and oversee their work
> - Human users interact with the dashboard UI to monitor costs,
schedules, and agent behavior
> - The UI has toggle button groups where you click one option and other
become unselected (like date range presets, day-of-week selectors)
> - Visually the active button look different (filled vs outlined), but
screen reader users hear no difference - all buttons sound same
> - The `aria-pressed` attribute is what screen readers need to announce
which toggle is active and which is not
> - I searched entire UI codebase for `aria-pressed` and found zero
usage anywhere
> - This pull request adds `aria-pressed={isActive}` to two toggle
button groups: date range presets in Costs page and day-of-week selector
in ScheduleEditor
> - Now screen reader users can tell which button is currently selected
without relying on visual styling only
## Problem
The app has button groups that work like toggles - you click one button
to select it and the others become unselected. Visually this work fine
because the active button change to a different variant (filled vs
outlined). But for screen reader users, ALL buttons sound exactly the
same - just "button, 7 days", "button, 30 days", etc with no way to tell
which one is currently active.
I searched the entire UI codebase for `aria-pressed` and found zero
results. This attribute is what screen readers need to announce "7 days,
pressed" vs "30 days, not pressed" for toggle button groups.
## What I changed
Added `aria-pressed={isActive}` to two toggle button groups:
1. **Costs.tsx** - Date range preset buttons (7d, 30d, 90d, Custom).
Screen reader now announce which date range is selected.
2. **ScheduleEditor.tsx** - Day of week selector (Mon, Tue, Wed...).
Screen reader now announce which day is selected for weekly schedule.
## How to test
1. Go to Costs page, use VoiceOver (Cmd+F5 on Mac)
2. Tab through the date preset buttons
3. Active button should announce "pressed", others "not pressed"
4. Same for ScheduleEditor - create/edit trigger with weekly preset, tab
through day buttons
2 files, 2 lines added.
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Issue-thread interactions are how agents ask board users for typed
decisions and structured answers inside an issue thread
> - Confirmation interactions already become stale when a later
board/user comment supersedes the pending decision
> - Question interactions had the same workflow risk, because a
board/user could answer in a comment while the old question card stayed
pending
> - This pull request extends the supersede-by-comment lifecycle to
ask-user-question interactions and makes that status visible in the UI
> - The benefit is agents get a clear continuation signal and users do
not see stale question forms after the discussion has moved on
## Linked Issues or Issue Description
No exact public GitHub issue was found.
Bug report:
**Pre-submission checklist**
- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on the latest released version of Paperclip, or can reproduce
on `master`.
- [x] I have confirmed the error originates in Paperclip itself, not in
an agent adapter, API provider, or local configuration.
**What happened?**
Pending `ask_user_questions` interactions could remain open after a
later board/user comment changed or answered the request in-thread. That
left a stale form visible and kept the interaction in a pending state
even though the discussion had moved on.
**Expected behavior**
Question interactions should follow the same default
supersede-on-comment behavior as confirmation interactions, with an
explicit expired result that points to the superseding comment.
**Steps to reproduce**
1. Create an `ask_user_questions` interaction on an issue.
2. Add a board/user comment created at or after that interaction.
3. Observe that before this change, the question interaction stayed
pending instead of expiring as superseded by the comment.
**Paperclip version or commit**
Current `master` before this PR.
**Deployment mode**
Self-hosted server or local dev. The bug is in shared issue-thread
interaction lifecycle handling.
**Installation method**
Built from source.
**Agent adapter(s) involved**
Not adapter-specific. This is a core issue-thread interaction bug.
**Database mode**
Applies to the normal Paperclip database-backed interaction lifecycle.
**Access context**
Board user comments supersede agent-created questions.
**Relevant logs or output**
No crash output. The stale pending interaction was visible in the issue
thread state.
**Relevant config (if applicable)**
None.
**Additional context**
Confirmation-style interactions already supported this stale-by-comment
behavior. This PR brings question interactions into the same lifecycle
model.
**Privacy checklist**
- [x] I have reviewed all pasted output for PII and included no private
instance links, local ticket ids, secrets, logs, or screenshots.
## What Changed
- Added `supersedeOnUserComment` support to `ask_user_questions`
payloads, defaulting it to `true` during interaction creation.
- Expire pending question interactions when a later board/user comment
supersedes them, including a result with `expirationReason:
"superseded_by_comment"` and the superseding `commentId`.
- Updated interaction summaries and cards so expired question requests
show a clear amber state with a jump link to the comment and correct
singular/plural copy.
- Updated agent onboarding guidance to describe the new default and how
to opt out.
- Added shared, server, and UI test coverage for the new lifecycle
behavior.
## Verification
- `pnpm exec vitest run
packages/shared/src/issue-thread-interactions.test.ts
server/src/__tests__/issue-thread-interaction-routes.test.ts
server/src/__tests__/issue-thread-interactions-service.test.ts
ui/src/components/IssueThreadInteractionCard.test.tsx
ui/src/lib/issue-thread-interactions.test.ts --reporter=dot` passed: 5
files, 73 tests.
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck && pnpm --filter @paperclipai/ui
typecheck` passed.
- `pnpm exec vitest run
server/src/__tests__/issue-thread-interactions-service.test.ts
--reporter=dot && pnpm --filter @paperclipai/server typecheck` passed
after the final type-safety cleanup.
- Confirmed the branch is rebased on current `origin/master`.
- Confirmed the diff does not touch `pnpm-lock.yaml`,
`.github/workflows`, or database migrations.
## Risks
- Low-to-medium risk: `ask_user_questions` now defaults to expiring
after later board/user comments. Existing callers that need questions to
stay open through discussion can set `supersedeOnUserComment: false`.
- Expired question interactions store an empty `answers` array, so
downstream consumers should treat the explicit `expirationReason` as the
meaningful outcome.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5-based coding agent in Paperclip CodexCoder runtime,
with terminal and repository tool use. Exact context window is not
exposed in this runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent runs are assembled by the heartbeat service from agent config,
project workspaces, environment config, secret bindings, skills, and
runtime session state.
> - The heartbeat service intentionally reuses adapter sessions,
execution workspaces, and sandbox leases when that preserves useful
state.
> - Reuse becomes incorrect when the effective next-run config changes
after a saved session, workspace, or lease was created.
> - Stale reuse can make a later run appear pinned to old agent,
environment, secret, instruction, or workspace settings.
> - This pull request records non-sensitive fingerprints for the
effective session, workspace, and lease config at run boundaries.
> - When those fingerprints drift, Paperclip refreshes persisted runtime
config or starts fresh execution instead of reusing stale state.
> - The benefit is predictable next-run config freshness without storing
raw secret values, full env maps, provider credentials, or private path
details.
## Linked Issues or Issue Description
- Refs #8058
- Related PRs checked during dedup search: #4968, #4155, #84, #8480.
These cover nearby workspace/session routing or model-config freshness
areas, but do not duplicate this effective run config fingerprinting
path.
## What Changed
- Added effective run config fingerprinting for session, workspace, and
lease reuse decisions, with canonicalization that ignores generated
runtime noise and redacts sensitive values.
- Updated heartbeat reuse logic to compare stored and next-run
fingerprints, reset stale saved sessions, refresh persisted workspace
config snapshots, replace stale reused workspaces when required, and
avoid stale sandbox lease reuse.
- Included plain environment value drift via value hashes, without
storing the raw env values.
- Root-bound instruction content hashing so legacy direct absolute
instruction paths are represented but not read for config fingerprints.
- Batched secret/version metadata lookups for environment lease
fingerprinting.
- Added workspace operation/run result freshness metadata so operators
can inspect non-sensitive decision categories.
- Surfaced config freshness labels and next-run copy in the UI and docs.
- Added focused coverage for fingerprint redaction, session reset
decisions, workspace refresh/replace behavior, environment lease drift,
and persisted workspace restoration.
## Verification
- `git diff --check`
- Sensitive-data scan before push:
- `git diff --unified=0 origin/master...HEAD | rg -n --pcre2
"(AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9]{20,}|-----BEGIN
(RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----|AKIA[0-9A-Z]{16})"`
- `git diff --unified=0 origin/master...HEAD | rg -n --pcre2
"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"`
- `pnpm exec vitest run
server/src/__tests__/effective-run-config-fingerprints.test.ts
server/src/__tests__/heartbeat-workspace-session.test.ts
server/src/__tests__/environment-runtime.test.ts`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm -r typecheck`
- `pnpm --filter @paperclipai/db clean`
- `pnpm test:run`
- `pnpm build`
- UI screenshots from Cutter:
-
https://artifacts.cutter.sh/8797/run-2f4827c-2026-06-30T18-57-25/preview/change-01.png
-
https://artifacts.cutter.sh/8797/run-2f4827c-2026-06-30T18-57-25/preview/change-02.png
-
https://artifacts.cutter.sh/8797/run-2f4827c-2026-06-30T18-57-25/preview/change-03.png
## Risks
- Medium: overly broad fingerprints could start fresh sessions,
workspaces, or sandbox leases more often than necessary.
- Medium: missing a config category would allow stale reuse to persist
for that category.
- Medium: legacy direct absolute instruction paths are no longer
content-hashed unless they are paired with an absolute managed
instructions root.
- Low data risk: fingerprint metadata stores hashes and category names,
not raw secrets, raw env values, provider credentials, or private path
details.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex CLI / Codex coding agent, tool-enabled with
shell, Git, GitHub CLI, local test execution, and code editing. The
exact deployed model variant and context window are not exposed by this
environment.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Cody <cody@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - A growing part of that work runs in sandboxed environments rather
than on the operator's local machine.
> - Today sandbox providers can start fresh workspaces and run probes,
but they do not have a shared contract for capturing and reusing
prepared sandbox state.
> - Operators need a way to set up tools, credentials, and project
dependencies once, then reuse that prepared image for later agent runs.
> - This pull request adds reusable sandbox custom images across the
provider contract, server runtime, and board UI.
> - It also keeps probes and sandbox copy flows aligned with
pre-authenticated/custom-image environments.
> - The benefit is faster, more reliable sandbox runs without repeatedly
rebuilding the same environment setup.
## Linked Issues or Issue Description
No public GitHub issue was found for this change. Inline feature request
follows.
### Problem or motivation
Sandboxed agents need reusable prepared runtime state so repeated runs
do not require manual setup every time. Operators often need system
packages, CLIs, SDKs, dependency caches, credentials, and project
tooling available before an agent can work productively.
### Proposed solution
Add a provider-level custom-image capability, server-side setup/capture
lifecycle, Daytona/fake provider support, and board UI controls for
creating, testing, selecting, and deleting custom images.
### Alternatives considered
Leaving this as provider-specific setup outside Paperclip would keep the
control plane blind to image state and would not give agents consistent
environment metadata. Re-running setup commands for every lease is
simpler, but slower and less reliable for interactive or credentialed
setup.
### Roadmap alignment
Checked `ROADMAP.md`; this aligns with the Cloud / Sandbox agents
roadmap area and does not duplicate any related public issue or PR found
by search.
Additional context:
- Subsystem affected: cross-cutting (`packages/db`, `packages/shared`,
`packages/plugins`, `server`, `ui`).
- Duplicate search: searched GitHub for `sandbox custom image` and
`sandbox template environment`; no related public issues or PRs were
found.
## What Changed
- Added custom-image shared types, validators, constants, API paths, and
database schema/migration.
- Added server services/routes for custom-image templates and setup
sessions, including runtime cleanup and provider metadata handling.
- Extended plugin/sandbox provider capabilities for interactive setup,
template capture, and template deletion.
- Implemented custom-image support in the fake sandbox provider and
Daytona provider.
- Updated environment runtime/config handling so active custom images
flow into leases, probes, and agent execution.
- Added board UI controls and API client support for custom-image setup,
capture, selection, status, and error states.
- Hardened sandbox copy/probe behavior for insecure clipboard contexts
and pre-authenticated sandbox images.
- Added targeted coverage across shared validators, DB schema, server
routes/services, provider plugins, adapter probes, and UI flows.
## Verification
- `pnpm install --frozen-lockfile --ignore-scripts`
- `pnpm vitest run
packages/adapters/claude-local/src/server/test.probe.test.ts
packages/adapters/claude-local/src/server/test.ts
packages/adapters/codex-local/src/server/test.remote.test.ts
packages/adapters/codex-local/src/server/test.ts`
- `pnpm --filter @paperclipai/adapter-claude-local typecheck`
- `pnpm --filter @paperclipai/adapter-codex-local typecheck`
- `pnpm vitest run
packages/db/src/environment-custom-images-schema.test.ts
packages/shared/src/environment-custom-images.test.ts
packages/shared/src/validators/plugin.test.ts
server/src/__tests__/environment-custom-images-service.test.ts
server/src/__tests__/workspace-runtime.test.ts
packages/plugins/sandbox-providers/daytona/src/plugin.test.ts
ui/src/pages/CompanyEnvironments.test.tsx
ui/src/pages/CompanySettings.test.tsx`
- `pnpm -r typecheck`
- `pnpm build`
- `rm -rf packages/db/dist && pnpm test:run`
- Public-safety scan of the final diff found no internal Paperclip issue
links, private instance URLs, or real secret patterns.
## Risks
- Adds a database migration and new environment runtime tables, so
migration ordering and rollback need care.
- Provider implementations may differ in how reliably they can
capture/delete images; unsupported providers surface capability-gated UI
states.
- Custom-image state can contain operator-prepared tooling and
credentials inside the provider image, so providers must enforce their
own access controls and cleanup semantics.
- Broad surface area across shared contracts, server runtime, plugins,
adapters, and UI means CI and Greptile review should be watched closely.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 (`gpt-5`) via Codex CLI with tool use and code execution.
Assisted with branch cleanup, conflict resolution, local verification,
and PR preparation. Earlier branch implementation work was assisted by
Paperclip-managed Claude/Codex agents.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - CI runs a set of required PR checks; the Canary Dry Run job is the
slowest (~4m)
> - Its dominant cost is `release.sh` Step 2 `pnpm build` (~133s), whose
critical path is the `ui` package
> - In `ui`, `tsc -b` runs ~46s *before* `vite build` (~20s), but
`ui/tsconfig.json` sets `noEmit: true`, so `tsc -b` is a pure type-check
that emits nothing — vite produces all artifacts
> - The PR workflow already runs a parallel Typecheck job
(`typecheck:build-gaps`) that auto-type-checks any workspace package
whose build script omits `tsc`
> - This pull request drops `tsc -b` from the `ui` build so the
type-check moves off the build/release critical path onto the slack-rich
Typecheck job
> - The benefit is ~46s shaved off the slowest PR check with no loss of
type coverage
## What Changed
- `ui/package.json`: `build` script changed from `tsc -b && vite build`
to `vite build`.
- Type coverage is preserved automatically:
`scripts/run-typecheck-build-gaps.mjs` already type-checks any workspace
whose `build` script omits `tsc` and defines a `typecheck` script. With
this change, `@paperclipai/ui` (whose `typecheck` is `tsc -b`) is now
picked up by the parallel Typecheck job. No script edit was required —
the gap-detection is generic.
## Verification
- `pnpm --filter @paperclipai/ui build` produces `ui/dist` via vite
alone (no `tsc` invocation).
- `pnpm typecheck:build-gaps` now type-checks `@paperclipai/ui` and
exits 0.
- A type error in `ui` still fails the parallel Typecheck job, so
type-error coverage is unchanged.
## Risks
- Low. The type-check is relocated, not removed. This is build-tooling
only — no migration, no runtime behavior change. If
`scripts/run-typecheck-build-gaps.mjs` ever stopped covering `ui`, a
type error could slip past the build job — but its gap-detection is
generic (any package with a tsc-less build plus a `typecheck` script),
and `ui` qualifies.
## Model Used
Claude (Anthropic), `claude-opus-4` family, extended-thinking mode with
tool use, operating as an automated CI-health agent.
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue detail surface includes an Issue Properties pane that
shows live metadata for a selected issue.
> - Watchdog configuration can be changed from that pane, but cached
issue detail data also feeds the pane key and rerender behavior.
> - If the cache does not reflect watchdog changes immediately, the
properties pane can keep stale watchdog state until a later refetch
completes.
> - This pull request updates the watchdog save/delete paths to write
the returned watchdog state into cached issue detail data.
> - It also includes watchdog fields in the issue properties panel key
so watchdog configuration changes invalidate the memoized panel state.
> - The benefit is a properties pane that updates consistently after
operators set or remove a watchdog.
## Linked Issues or Issue Description
Refs #8789
This PR fixes stale watchdog state in the Issue Properties pane after
watchdog save/delete mutations.
## What Changed
- Write the saved watchdog summary into cached issue detail data after a
successful watchdog upsert using the exact
`queryKeys.issues.detail(issue.id)` key.
- Clear cached issue detail watchdog data after a successful watchdog
delete using the exact `queryKeys.issues.detail(issue.id)` key.
- Include stable watchdog fields in `buildIssuePropertiesPanelKey` so
watchdog configuration changes produce a new panel key.
- Add focused tests for watchdog cache updates and panel-key
invalidation, including save and delete cache assertions.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/IssueProperties.test.tsx
src/lib/issue-properties-panel-key.test.ts`
- Result: 2 files passed, 40 tests passed.
## Risks
Low risk. The change is scoped to UI cache handling for issue detail
watchdog state and panel-key generation. The main risk is an unexpected
query-cache shape, covered by focused tests around the existing issue
detail query key.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent running GPT-5-class model with repository tool
use and local command execution. The exact backend model identifier and
context window are not exposed in this runtime.
## 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
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Issue detail pages and run output cards both render user-facing
attachment previews
> - Media attachments were handled through separate gallery entry
points, so attachments from issue comments and issue output did not
share one consistent viewing path
> - A shared gallery path makes image browsing more predictable across
the issue surface
> - This pull request unifies the issue media attachment gallery
behavior across attachments, output cards, and issue detail rendering
> - The benefit is a more consistent image preview experience with
focused regression coverage around the affected UI paths
## Linked Issues or Issue Description
Refs #8788
This PR fixes inconsistent issue media preview behavior across
attachment and output surfaces.
## What Changed
- Unified issue media attachment gallery behavior across issue
attachments, issue output, output primary cards, and issue detail
rendering.
- Added image gallery modal coverage and regression tests for
attachments, output image handling, keyboard navigation, and issue
detail interactions.
- Preserved the primary video output Open action while still allowing
gallery browsing when a gallery handler is available.
- Updated issue attachment and issue output helper logic to preserve
shared image gallery metadata.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/ImageGalleryModal.test.tsx
src/components/issue-output/IssueOutputSection.test.tsx
src/components/IssueAttachmentsSection.test.tsx
src/pages/IssueDetail.test.tsx`
- Result: 4 files passed, 47 tests passed.
## Risks
Low to medium risk. The change is UI-scoped but touches shared issue
attachment/output rendering paths, so regressions would most likely
appear as image/video preview ordering, missing gallery entries, or
incorrect non-image attachment behavior.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent session with shell and git/GitHub CLI
tool use. Exact serving revision and context window were not exposed by
the runtime.
## 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Pipeline automations let operators standardize repeated issue and
workflow actions.
> - Pipeline-created issues currently need a way to derive useful titles
from routine variables.
> - Without a configurable title template, automated pipeline output is
harder to scan and distinguish.
> - This pull request adds a title-template field through shared
contracts, server persistence, API routes, and the pipeline settings UI.
> - The benefit is clearer issue titles for pipeline-created work while
preserving the existing pipeline behavior when no template is
configured.
## Linked Issues or Issue Description
Refs #8790
This PR adds configurable generated-issue title templates for pipeline
automations.
## What Changed
- Added `issueTitleTemplate` to the shared pipeline automation contract
and field constants.
- Persisted and returned the title template through pipeline service and
route code.
- Applied title-template rendering when pipeline automations create
issue work.
- Added pipeline settings UI controls for editing the title template and
reusing routine variables.
- Moved title-token cursor restoration out of the React state updater
and into a layout effect.
- Added server and UI coverage for storing, returning, and rendering
pipeline title templates.
## Verification
- `NODE_ENV=test pnpm run preflight:workspace-links && NODE_ENV=test
pnpm exec vitest run server/src/__tests__/pipelines-service.test.ts
server/src/__tests__/pipelines-routes.test.ts
ui/src/pages/PipelineSettings.test.ts`
- Result before review follow-up: 3 files passed, 58 tests passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/PipelineSettings.test.ts`
- Result after review follow-up: 1 file passed, 7 tests passed.
- Branch was merged with current `paperclipai:master` at `f019f54bb3`
before opening this PR.
- Searched existing PRs for the same head branch and for pipeline
title-template duplicates; no matching existing PR was found.
- Note: GitHub could not open a PR directly from `cryppadotta/paperclip`
because that repository is not a fork of `paperclipai/paperclip`. The
same updated branch SHA was pushed to `paperclipai/paperclip` so this PR
can compare normally against `master`.
## Risks
Low to moderate risk. The change touches pipeline automation persistence
and generated issue creation, so regressions would most likely appear as
missing or incorrectly rendered generated issue titles. Existing
behavior should remain unchanged when `issueTitleTemplate` is unset.
## Model Used
OpenAI Codex, GPT-5-based coding agent, tool-enabled execution in a
Paperclip heartbeat, with repository inspection, Git, GitHub CLI, and
local 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
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The ⌘K command palette is the primary fast-navigation surface across
tasks and projects
> - When searching, projects were ranked below tasks, so typing a
project name (e.g. "Paperclip") surfaced tasks first and buried matching
projects — often only one project was visible even when several matched
> - Users expect name-matching projects to appear at the top, like other
quick-switchers
> - This pull request promotes matching projects above tasks in the
palette (with a sensible cap) and ensures the project name is always
visible in the row
> - The benefit is faster, more predictable project navigation from ⌘K
## Linked Issues or Issue Description
No public GitHub issue exists for this, so the underlying problem is
described inline below following the [feature request
template](.github/ISSUE_TEMPLATE/feature_request.yml).
**Subsystem affected**
UI — the ⌘K command palette (`ui/src/components/CommandPalette.tsx`).
**Problem or motivation**
When typing in the ⌘K palette, projects were ranked *below* tasks. A
query matching several projects (e.g. "paperclip") surfaced mostly tasks
above the fold and pushed matching projects to the bottom of the list,
so often only one — or zero — matching projects were visible. Users
expect name-matching projects to appear at the top, the way other
quick-switchers behave.
**Proposed solution**
Compute matching projects from the typed query client-side (exact >
prefix > substring > description > fuzzy-subsequence ranking), promote
them into a dedicated "Projects" group above the Tasks group, and cap
both groups so neither crowds the other out. Keep the project name
always visible in each row.
**Alternatives considered**
A dedicated "Projects" search mode/scope toggle — rejected as heavier
and more UI for what is really a ranking problem. A backend
search-ranking change — unnecessary since the project list is already
loaded client-side.
**Roadmap alignment**
Small, self-contained UX improvement to an existing surface; not
roadmap-level core feature work.
## What Changed
- Added a `scoreProjectMatch` + `matchedProjects` memo in
`CommandPalette` that ranks already-loaded projects against the typed
query (exact > prefix > substring > description > fuzzy subsequence,
capped at 5).
- Promoted the matched "Projects" group above the Tasks group, and
reduced the task cap from 10 → 6 when projects are promoted so neither
group crowds the other out. Empty-query preview order is unchanged.
- Fixed the project-name span so the name stays visible in the typeahead
row (the name was being starved to 0px width by a flexible description
span).
## Verification
- `cd ui && npx vitest run CommandPalette` — added test seeds
react-query project data and asserts matching projects are promoted to
the top of the palette, non-matching projects are excluded, and
selecting a promoted project navigates correctly (6/6 passing).
- `pnpm --filter @paperclipai/ui typecheck` — passes.
- Manual (screenshots below): open ⌘K, type "paperclip"; matching
projects appear at the top with names visible.
**Before** — typing "paperclip" surfaces Tasks first; matching projects
are buried below the fold:

**After** — matching projects are promoted into a "Projects" group above
Tasks, names visible:

## Risks
Low risk. UI-only change scoped to the command palette; no backend or
data changes. Worst case is palette ordering for project-name queries.
Ranking is pure and unit-tested; the prefix-score length penalty is
clamped so the prefix > substring > description > fuzzy invariant holds
even for pathologically long project names.
## Model Used
Claude — claude-opus-4-8 (Opus 4.8), extended thinking, tool use (Claude
Code agent).
## Checklist
- [x] Thinking path included
- [x] Model used specified
- [x] Checked ROADMAP.md; small self-contained UX change
- [x] Searched GitHub for duplicate/related PRs
- [x] Described the issue in-PR (no public issue exists)
- [x] No internal/instance-local references
- [x] Branch name descriptive, no internal ticket id
- [x] Tests run locally and pass
- [x] Added/updated tests
- [x] Screenshots included for the visual change
- [ ] Docs updated (n/a — no user-facing docs for this)
- [x] Risks documented
- [ ] All CI gates green (verify after push)
- [ ] Greptile 5/5 (drive after push)
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue/task chat subsystem renders board and agent comments as
markdown inside chat bubbles.
> - Human board comments use a saturated blue bubble so the message can
be distinguished from agent output.
> - The shared `.paperclip-markdown` rule forced `var(--foreground)` for
body text and related markdown elements.
> - On the blue bubble, that foreground color is too dark, so
paragraphs, lists, links, and emphasis lose contrast.
> - This pull request adds an explicit on-accent markdown treatment for
blue/current-user message bubbles.
> - The benefit is readable markdown without changing neutral markdown
surfaces elsewhere in the app.
## Linked Issues or Issue Description
Fixes#8688
## What Changed
- Added a `paperclip-markdown-on-accent` class for markdown rendered on
accent-colored message bubbles.
- Updated current-user issue chat messages to use the accent-safe
markdown class.
- Added CSS overrides for body text, list markers, headings, emphasis,
links, blockquotes, and inline code on accent backgrounds.
- Added a regression test proving current-user blue-bubble markdown
receives the accent-safe class.
## Before Screenshot:
<img width="816" height="618" alt="Screenshot 2026-06-27 at 14 22 59"
src="https://github.com/user-attachments/assets/2a982326-9ff3-40ce-9955-ceb081d8a2e0"
/>
## After Screenshot:
<img width="821" height="624" alt="Screenshot 2026-06-27 at 15 05 17"
src="https://github.com/user-attachments/assets/4722a0db-9f6a-4775-a7eb-47328c2f71e0"
/>
## Verification
- `CI=true corepack pnpm exec vitest run
ui/src/components/IssueChatThread.test.tsx`
- `CI=true corepack pnpm --filter @paperclipai/ui typecheck`
- GitHub Actions PR workflow passed on latest head
`f645cca3fc9675071c201471c0df938880c0c351`, including Build, Typecheck +
Release Registry, e2e, general test shards, serialized server shards,
Canary Dry Run, and aggregate verify.
- Greptile Review passed with Confidence Score 5/5 on latest head
`f645cca3fc9675071c201471c0df938880c0c351`.
## Risks
Low risk. The new color treatment is opt-in and only applied to
current-user issue chat markdown on accent bubbles. Neutral comment,
document, artifact, and skill markdown surfaces keep the existing
`.paperclip-markdown` colors.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex in Codex desktop, with repository tool use and local
command 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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The experimental server info debug view helps local operators
inspect what code a running dev instance is actually serving
> - The view was moved into the account-menu drawer, which only mounts
while that drawer is open
> - That made stale health-query data easier to see after restarts, and
the server was also caching the running commit at process boot
> - A clean commit label alone is incomplete when the checkout has
uncommitted local changes
> - This pull request keeps the drawer health data fresh, refreshes git
metadata on demand, and adds a path-free checkout-state summary
> - The benefit is that the debug view reports restart time, running
commit, and dirty-checkout state without exposing local paths, secrets,
logs, or environment details
## Linked Issues or Issue Description
Fixes: #8752
## What Changed
- `SidebarServerInfo.tsx`: refetch the health query whenever the drawer
opens and poll every 2s while the dev server is active.
- `server-info.ts`: keep `processStartedAt` stable while refreshing git
HEAD through a short TTL cache instead of freezing commit metadata at
module boot.
- Shared health contract/OpenAPI: add `serverInfo.git.localChanges` with
only staged, unstaged, and untracked counts plus safe unavailable
fallbacks.
- `SidebarServerInfo.tsx`: add a `Checkout state` row that renders
clean/dirty/unavailable copy without file paths.
- Tests: cover stale drawer refresh, interval polling, TTL commit
refresh, health response shape, checkout-state count parsing, and
path-free UI rendering.
## Verification
- `npx vitest run server/src/__tests__/server-info.test.ts
server/src/__tests__/health.test.ts
ui/src/components/SidebarServerInfo.test.tsx` -> 3 files / 19 tests
passing.
- `pnpm install --frozen-lockfile --ignore-scripts` -> refreshed stale
workspace links without lockfile/source churn.
- `pnpm --filter @paperclipai/shared --filter @paperclipai/server
--filter @paperclipai/ui typecheck` -> passing.
- `pnpm --filter @paperclipai/ui typecheck` -> passing after the
Greptile test-coverage fix.
- `pnpm check:tokens` -> no forbidden tokens found.
- Local diff scans for obvious secrets, credentials, private URLs, local
paths, and PII patterns -> no matches.
- GitHub PR checks on head `56defd446` -> all green, including `verify`,
canary dry run, e2e, security scans, and Greptile Review.
- Greptile latest summary -> Confidence Score 5/5, 0 new comments; the
prior P2 polling-coverage thread is resolved.
## Risks
Low risk. The UI remains behind the experimental
`enableServerInfoDebugView` flag. The extra git status call is throttled
by the existing server-info TTL and reports only counts, not paths or
file names. If git status is unavailable, the commit row still works and
the checkout-state row shows clear fallback copy.
## Model Used
Claude Opus (claude-opus-4-8), extended thinking, with tool use / code
execution assisted the original stale-metadata fix. OpenAI GPT-5 via
Codex local, with tool use and code execution, added the checkout-state
follow-up, Greptile fix-up, and PR verification.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip runs agents through adapters, including built-in local and
gateway-style adapters.
> - Hermes gateway users need to connect Paperclip to an already-running
Hermes API server.
> - The gateway setup flow was missing clear non-local adapter
configuration fields and accepted fewer URL shapes than operators
naturally paste from Hermes.
> - It also surfaced sparse diagnostics when the gateway was unreachable
or when Paperclip generated onboarding prompts for gateway agents.
> - This pull request tightens Hermes gateway configuration, URL
normalization, diagnostics, and onboarding defaults.
> - The benefit is that Hermes gateway setup is easier to complete and
easier to debug without affecting unrelated adapters.
## Linked Issues or Issue Description
No public issue found for this exact follow-up. Related prior/in-flight
Hermes work:
- Refs #2363
- Refs #4359
- Refs #6473
Problem statement:
- **Type:** Adapter follow-up / setup reliability
- **Adapter:** `hermes_gateway`
- **Motivation:** Operators configure `hermes_gateway` against a running
Hermes API server, but the UI and onboarding flow did not expose enough
gateway-specific configuration or diagnostics.
- **Expected behavior:** Paperclip should render the gateway fields,
normalize common Hermes dashboard/API URL inputs, preserve sensible
gateway onboarding defaults, and report reachability failures with
actionable detail.
- **Deployment mode:** Built-in adapter package in the Paperclip
monorepo.
## What Changed
- Added UI config fields for non-local Hermes gateway settings,
including tests for rendering and field behavior.
- Accepted Hermes dashboard URLs by normalizing them to gateway API URLs
for execution.
- Improved gateway reachability and run URL diagnostics.
- Updated Hermes gateway onboarding text/default behavior so join
prompts preserve gateway configuration.
- Added focused server, UI, and adapter tests for the gateway
configuration and onboarding paths.
## Verification
- `pnpm install --frozen-lockfile --prefer-offline`
- `pnpm --filter @paperclipai/hermes-paperclip-adapter exec vitest run
src/gateway/server/execute.test.ts` — 20 passed
- `pnpm exec vitest run
server/src/__tests__/invite-accept-gateway-defaults.test.ts
server/src/__tests__/invite-onboarding-text.test.ts
ui/src/adapters/hermes-gateway/config-fields.test.tsx
ui/src/components/AgentConfigForm.render.test.tsx
ui/src/lib/agent-onboarding-prompt.test.ts` — 23 passed across 5 files
- Confirmed the PR diff excludes `pnpm-lock.yaml` and
`.github/workflows`.
## Risks
Low to moderate risk. The changes are scoped to Hermes gateway
configuration/onboarding and generic non-local adapter field rendering.
The main risk is rejecting an unusual Hermes URL shape that should be
accepted; the normalization tests cover dashboard and API URL variants
added here.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5-based coding agent in a tool-enabled local CLI
environment, with shell/GitHub/Paperclip API access. Exact runtime model
identifier was not exposed in this session.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operator secrets and provider vaults let a company connect external
secret stores without copying every value manually.
> - The existing AWS Secrets Manager import dialog already supports
importing remote secrets from any eligible vault.
> - From the Provider vaults tab, operators can see a specific vault
row, but there was no row-level import action that preserved that vault
context.
> - That meant importing from a specific vault required opening the
generic import dialog and reselecting the intended provider config.
> - This pull request adds a row-level refresh/import action that opens
the import dialog with the selected vault preselected.
> - The benefit is a tighter, less error-prone flow when a company has
multiple AWS provider vaults configured.
## Linked Issues or Issue Description
No public issue exists for this small UI follow-up.
Problem description:
- Users with multiple AWS Secrets Manager provider vaults need to
refresh/import secrets from the vault they are currently looking at.
- The existing import dialog defaulted to the configured default vault
or first eligible vault, even when the operator initiated the flow from
a specific provider-vault row.
- This creates extra selection work and makes it easier to import from
the wrong vault.
Related public PRs: Refs #5429, #8586.
## What Changed
- Added a Provider vaults row action for AWS Secrets Manager configs to
refresh/import existing remote secrets.
- Threaded an optional initial provider config id into
`ImportFromVaultDialog` so the selected vault is preselected when
eligible.
- Reset the initial vault selection when the dialog closes or routes to
vault management.
- Added render coverage proving the row action opens the import dialog
and previews against the selected vault id.
## Verification
- `pnpm exec vitest run ui/src/pages/Secrets.render.test.tsx` passed: 1
file, 9 tests.
- `pnpm --filter @paperclipai/ui typecheck` passed.
## Risks
Low risk. This is UI-only behavior scoped to AWS Secrets Manager
provider-vault rows and the existing import dialog. The main risk is
that future provider types may need their own row-level import labels or
eligibility rules instead of sharing this AWS-specific action.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex via Paperclip/Codex local agent, with repository file
access, shell command execution, GitHub CLI, and TypeScript/Vitest
verification. Exact context-window metadata was not surfaced in this
runtime.
## 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
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Agents page is where operators scan the current agent roster,
runtime status, model, adapter, and last heartbeat.
> - Environment selection now affects where agents execute, but the
roster did not expose each agent's effective execution environment.
> - Operators need that context when multiple environments are
configured, especially when sandbox-backed environments use different
providers.
> - This pull request adds environment/provider metadata to the Agents
page while keeping it hidden when the column would not add useful
information.
> - The benefit is better operational visibility without adding noise
for single-environment or environment-disabled instances.
## Linked Issues or Issue Description
No matching public GitHub issue was found after searching for related
Agents environment-column issues and PRs.
## Problem or motivation
Operators use the Agents page to scan active agents, but when multiple
execution environments are configured there was no row-level indication
of which environment each agent uses. That makes it harder to
distinguish host-local agents from sandbox-backed agents and to see the
sandbox provider at a glance.
## Proposed solution
Show each agent's effective environment in the Agents page metadata when
environments are enabled and multiple environments are configured.
Resolve the value from the agent override, instance default, or local
fallback, and include sandbox provider display names when available.
## Alternatives considered
Always showing the column would add noise for single-environment
instances. Adding filters and grouping was also considered, but this PR
keeps the scope to read-only metadata until the product has more usage
data.
## Roadmap alignment
This is a small UI visibility improvement related to the roadmap's
cloud/sandbox agents direction, without adding a new core workflow or
adapter capability.
## Additional context
The column is hidden when the experimental environments setting is
disabled or when only one environment is configured.
## What Changed
- Added environment metadata loading to the Agents page, including
environment list, capabilities, and instance settings.
- Resolved each agent's effective environment from the agent override,
instance default, or local fallback.
- Rendered environment name and sandbox provider detail in list and org
views when environments are enabled and multiple environments are
configured.
- Hid the environment column when environments are disabled or only one
environment is configured.
- Added focused UI tests for display, fallback, loading, hidden-column,
disabled-feature, and no filter/grouping behavior.
- Addressed Greptile review feedback by preserving custom local
environment names, reserving the column while environment data loads,
and separating the capabilities query key.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/Agents.test.tsx` — 11 tests passed
- `pnpm --filter @paperclipai/ui build`
- Local PR-diff scan for secrets/private references before push
- GitHub PR workflow passed on head `bb770acc3`
- Commitperclip review check passed
- Greptile Review passed with confidence score 5/5 and no inline review
comments
## Risks
Low risk. The change is limited to the Agents page, query keys, and
tests. The main behavioral risk is extra metadata query traffic on the
Agents page when environments are enabled; those queries are skipped
when the experimental environments flag is disabled.
## Model Used
OpenAI Codex, GPT-5 coding agent with shell/tool use and code execution
in the local repository.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The dev/server UI exposes a `/api/health` endpoint and a lower-left
account drawer, but nothing surfaces *which* build the running instance
is on or when it last restarted
> - When iterating on a local dev instance it is hard to tell whether
the server you're looking at has actually restarted onto your latest
commit, or how stale the running process is
> - Developers need a lightweight, opt-in way to confirm the running
instance's identity without digging through logs or shelling into the
host
> - This pull request adds an experimental "Server Info Debug View"
setting that surfaces the running instance's last-restart time and
current commit as read-only rows in the account drawer
> - The benefit is a quick, in-UI sanity check of what the live server
is actually running, behind an experimental flag so it ships zero cost
to users who don't opt in
## Linked Issues or Issue Description
No public GitHub issue exists. Describing the underlying request inline
following the feature request template:
**Problem or motivation:**
When working against a local Paperclip dev instance there is no in-UI
way to confirm what the running server is — its current commit or when
it last restarted. You have to check logs or the host shell to know
whether the process picked up your latest build.
**Proposed solution:**
An opt-in experimental setting ("Server Info Debug View") that, once
enabled, renders a small read-only "Server" section at the bottom of the
lower-left account drawer showing **Last restarted** (the server process
start time) and **Running commit** (the current git HEAD short SHA +
subject).
**Alternatives considered:**
A separate top-right pill/overlay (like the work-life-balance plugin).
The account drawer was chosen to reuse existing menu-row styling and
avoid adding new always-present chrome.
**Roadmap alignment:**
Small, self-contained developer-experience aid gated behind an
experimental flag; does not overlap planned core roadmap work.
## What Changed
- Added `server/src/server-info.ts`: captures a `serverInfo` snapshot
once at boot — process start time and current git commit (SHA +
subject). Git is read via `execFileSync` with SHA validation and a
timeout.
- `/api/health` exposes the `serverInfo` snapshot, but only on
full-details health responses (board/agent in authenticated mode, or
local-trusted dev).
- Gated the UI surface behind a new `enableServerInfoDebugView`
experimental setting, wired through the shared instance type, validator,
settings normalizer, and OpenAPI schema.
- UI: added `SidebarServerInfo` rendering the read-only rows in the
account drawer (`BreadcrumbBar` / `SidebarAccountMenu`), plus the
experimental settings toggle and a typed `health` API client.
- Moved `ServerGitInfo` / `ServerInfoSnapshot` into
`@paperclipai/shared` so the server and UI share one definition instead
of duplicating it.
- Added unit tests for the server-info snapshot, health route exposure,
validator/normalizer, settings routes, the experimental settings page,
and the sidebar component.
## Verification
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/health.test.ts src/__tests__/server-info.test.ts
src/__tests__/instance-settings-service.test.ts
src/__tests__/instance-settings-routes.test.ts` — 32 passed
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/SidebarServerInfo.test.tsx
src/pages/InstanceExperimentalSettings.test.tsx` — 9 passed
- `tsc --noEmit` on both `@paperclipai/server` and `@paperclipai/ui` —
clean
- Manual: enable **Settings → Experimental → Server Info Debug View**,
refresh the UI, open the lower-left account drawer — a "Server" section
shows Last restarted and Running commit.
## Risks
- Low risk. The UI surface is fully opt-in via an experimental flag and
defaults off.
- The `serverInfo` field on `/api/health` is access-controlled to
full-details responses only (board/agent in authenticated mode, or
local-trusted dev) — never anonymous authenticated callers — so the git
SHA is not broadly exposed.
- The only new server work is a one-time git read at boot, guarded with
SHA validation and a timeout; failures degrade gracefully (the git block
reports `available: false` rather than throwing).
## Model Used
Claude — `claude-opus-4` (Anthropic), extended thinking with tool use,
via Claude Code.
## 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 and contains no
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
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent adapters are the boundary between the control plane and the
runtimes that actually do work.
> - Hermes support needs to be available as first-class local and
gateway adapters while still preserving the adapter-manager override
path for external packages.
> - The adapter work touches runtime execution, UI adapter metadata,
onboarding prompts, scoped credentials, release packaging, and smoke
coverage, so the handoff needs concrete verification rather than only
unit tests.
> - This pull request adds built-in Hermes local and Hermes gateway
support, keeps external adapter overrides compatible, and
documents/tests the gateway flow end to end.
> - The benefit is that operators can hire Hermes-backed agents without
a manual plugin install, while self-hosted installs can still
override/shadow the built-ins through Adapter manager packages.
## Linked Issues or Issue Description
No public GitHub issue exists for this exact Hermes built-in adapter,
gateway onboarding, and release-source work.
Problem description:
- Hermes local and gateway adapters need a public, reviewable source
path in the monorepo so package artifacts and built-in adapter behavior
match the application source.
- Operators need built-in `hermes_local` and `hermes_gateway` adapter
choices without losing the ability to install external Hermes packages
as overrides.
- Gateway onboarding needs secure defaults for API server URLs, API
keys, and generated agent setup text.
- Hermes-originated task bridge credentials need narrower API-key scope
configuration.
- Related public PRs found during duplicate search include #3027, #2363,
#7544, #7950, #8095, and #8543.
## What Changed
- Added the unified Hermes adapter package with local and gateway
server/UI/CLI exports, config schemas, transcript parsing, model
detection, and package metadata.
- Registered `hermes_local` and `hermes_gateway` as built-in adapters
across shared constants, server registries, CLI packaging, and UI
adapter registries.
- Kept the external adapter override path compatible so installed Hermes
packages can shadow built-ins and restore the built-in parser when
disabled.
- Added Hermes gateway onboarding docs, board-operator docs, Docker
smoke assets, and shell smoke harnesses for join/e2e validation.
- Added scoped task-bridge API-key support, authorization checks,
issue-origin handling, and tests for Hermes-created Paperclip tasks.
- Hardened gateway transport and redaction behavior for API keys,
headers, session data, and smoke diagnostics.
- Updated release packaging/bootstrap checks for the Hermes packages
while leaving `pnpm-lock.yaml` out of the PR per repository policy.
## Verification
Targeted local verification recorded before PR handoff:
- `pnpm --filter @paperclipai/hermes-paperclip-adapter exec vitest run
src/gateway/server/execute.test.ts` — 14/14 passed.
- `pnpm test:hermes-gateway-smoke` — 6/6 passed.
- Hermes package typecheck/build checks passed.
- Focused server/UI adapter tests passed — 31/31.
- Release helper Node tests passed — 18/18.
- `git diff --check origin/master..HEAD` passed.
Fresh Docker E2E smoke evidence:
- Ran `pnpm smoke:hermes-gateway-e2e` on 2026-06-26 with a fresh state
directory and fresh Docker container against a live Paperclip dev
server.
- Hermes direct execution reached `completed`.
- Hermes stop/cancel path reached `cancelled`.
- Hermes gateway created a Paperclip task, Paperclip ran the Hermes
agent, and the task reached `done` with the expected marker response.
- Temporary board auth keys, token files, smoke state, and Docker
containers were cleaned up after the run.
PR checks on head `b5eae40ce`:
- GitHub Actions passed: `policy`, `review`, `Typecheck + Release
Registry`, all general test shards, all serialized server shards,
`Build`, `Canary Dry Run`, `e2e`, and aggregate `verify`.
- External checks passed: Snyk and Socket Project Report.
- External Socket Pull Request Alerts remained pending after the
first-party CI matrix completed.
## Risks
- Medium risk: this spans adapter registration, package publishing,
gateway execution, onboarding docs, API-key scoping, and UI adapter
metadata.
- Migration risk is low: the scope-config migration adds a nullable
column and does not rewrite existing keys.
- Gateway execution depends on operator-provided Hermes API
configuration; the smoke covers the Docker gateway path but real
deployments may differ by network/auth setup.
- Direct Greptile review on the latest expanded diff is file-count
limited, although the commitperclip review gate passed.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent, tool use enabled in a local repository
workspace. Context window size is not exposed in this environment.
## 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] Commitperclip review gate is green; direct Greptile review is
file-count limited on the latest expanded diff
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agent runs stream their transcripts through per-adapter stdout
parsers into the chat/run transcript UI
(`ui/src/adapters/transcript.ts`)
> - The Cursor CLI (local) streams assistant text as many small `text`
events (often a token or word each), and the parser emitted one
assistant entry per event and trimmed each
> - As a result the chat rendered one bubble per token ("every line a
new token") and dropped inter-token whitespace, making Cursor runs hard
to read
> - The render layer already coalesces consecutive `delta` entries
(`appendTranscriptEntry`), but the Cursor parser never tagged streamed
text as a delta
> - This pull request tags streamed `text` as a delta (without trimming)
so the existing render-time coalescer merges them into one assistant
block, while a `tool_call`/`tool_result` between deltas still breaks the
run
> - The benefit is readable Cursor transcripts with correct spacing and
preserved tool boundaries, with no change to the canonical event stream
(raw view unaffected)
## Linked Issues or Issue Description
No existing public issue — describing the bug inline (per
`.github/ISSUE_TEMPLATE/bug_report.yml`):
**What happened**
In the chat/run transcript, Cursor (local) assistant messages render as
one bubble per token/word, and inter-token spaces are dropped, making
the transcript unreadable. Root cause:
`packages/adapters/cursor-local/src/ui/parse-stdout.ts` (`type: "text"`
branch) emitted `{ kind: "assistant" }` per streamed `text` event
without `delta: true` and trimmed each, so the render-time coalescer
(`ui/src/adapters/transcript.ts`) never merged them and whitespace was
lost.
**Expected behavior**
Streamed assistant text should render as a single contiguous prose
block, with tool calls preserved as boundaries between blocks.
**Steps to reproduce**
1. Run a Cursor (local) agent that streams a multi-word assistant
message.
2. Open the run transcript in the chat UI.
3. Observe each streamed token/word rendered as its own bubble, with
inter-token spaces missing.
**Paperclip version**
Reproduced on current `master` (cutover base `e68188c43`).
**Deployment mode**
Self-hosted, `cursor_local` adapter.
## What Changed
- `packages/adapters/cursor-local/src/ui/parse-stdout.ts`: tag streamed
`text` events as `{ kind: "assistant", delta: true }` and stop trimming,
so the existing `appendTranscriptEntry` coalescer merges consecutive
deltas into one block.
- `ui/src/adapters/cursor-coalescing.test.ts` (new): dual-shape golden
fixtures (Cursor local + cloud) exercising the full render-time
projection via `buildTranscript`.
## Verification
- `pnpm --filter @paperclipai/ui exec vitest run
src/adapters/cursor-coalescing.test.ts src/adapters/transcript.test.ts`
→ **10/10 pass**.
- `pnpm --filter @paperclipai/ui --filter
@paperclipai/adapter-cursor-local typecheck` → **green**.
- The golden fixtures assert the run `text → tool_call → tool_result →
text → consolidated final` renders as exactly **two prose blocks with
the tool between them**, **no duplication** of the consolidated final,
and **inter-token whitespace preserved** across coalesced deltas.
## Risks
- **Low risk.** Pure classification at parse time; the canonical event
stream and the raw view are unchanged — only the "nice" render-time
projection changes. The coalescing logic (`appendTranscriptEntry`) is
pre-existing and already covered by tests. No schema, migration, or
behavioral change outside transcript rendering.
## Model Used
- **Claude Opus 4.8** (Anthropic), extended/high reasoning mode, driven
via the Cursor agent with tool use + code execution. Diagnosis and
fixtures grounded in the repo's actual parser/render code.
## 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 (none found)
- [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 references)
- [x] My branch name describes the change
(`fix/cursor-transcript-coalescing`) 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
- [ ] I have updated relevant documentation to reflect my changes (N/A —
no documented behavior changes)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending CI run)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(pending review)
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Sebastian Heyneman <sebastian@joinnova.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Reviewed by CTO for PAP-12039. Client-side ordering change only, with focused helper coverage; CI, security scans, and Greptile are green.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Reviewed by CTO for PAP-12039. Scope is limited to external object URL/link label fallback and focused UI regression coverage; CI and Greptile are green.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The pipeline subsystem models repeatable work as items moving
through stages, with agent automation, review gates, blockers, drift
notices, and linked work.
> - Operators need this to be usable as one coherent workflow surface,
not just as backend primitives or disconnected route experiments.
> - The branch now carries the pipeline data model, service/routes,
CLI/tutorial path, aggregation feeds, operator UI, stage automation
controls, liveness/retry handling, and follow-up polish that make the
primitive reviewable end to end.
> - This pull request is the single review target for that pipeline
workflow primitive stack.
> - The benefit is that reviewers can evaluate the full operator
experience and server contract together against `master`.
## Linked Issues or Issue Description
No public GitHub issue exists for this work. The underlying feature
request is described inline.
### Problem or motivation
Paperclip needs a first-class way to model multi-stage agent/company
workflows where upstream items can spawn downstream work, request
review, carry fields across pipelines, surface drift, retry automation,
and show operators where work is blocked or active. Without a unified
pipeline primitive, these workflows spread across ad hoc issues,
routines, and comments, making the state hard to inspect or operate.
### Proposed solution
Add the pipeline workflow primitive stack: database schema and
migrations, shared validators/types, server services and REST routes,
aggregation and liveness helpers, CLI/tutorial smoke support, and the
React operator UI for pipeline lists, boards, item detail,
review/learnings views, settings, stage automation, secrets, carry-over
fields, and retry/recovery flows.
### Alternatives considered
- Keep workflows as loosely linked issues and routines: rejected because
operators need a single board/detail/settings surface for repeated
workflow patterns.
- Ship backend primitives first and defer UI: rejected for this branch
because the operator experience is the main way to validate the
primitive.
- Add a narrower one-off content workflow: rejected because the same
primitives are useful across future company processes.
## What Changed
- Added and evolved pipeline schema, migrations, shared contracts,
server services, REST routes, route tests, and CLI/tutorial smoke
support.
- Added pipeline aggregation, health/liveness, drift acknowledgment,
blocker/carry-over, automation retry, stage automation environment, and
permission recovery behavior.
- Added the operator UI for pipeline index/board/item
detail/settings/review/learnings flows, including stage secrets,
automation controls, markdown/item descriptions, linked issue assets,
liveness banners, and source automation metadata.
- Refactored issue document frame rendering through the shared
`DocumentFrameHeader` component to keep document controls consistent
with the pipeline document surfaces.
- Kept this PR as the single base-branch review target for the current
pipeline branch.
## Verification
Current branch refresh:
- `pnpm vitest run server/src/__tests__/pipelines-service.test.ts` — 31
passed
- `pnpm vitest run server/src/__tests__/pipelines-routes.test.ts` — 19
passed
- `pnpm --filter ./server typecheck` — passed
- `pnpm --filter ./ui typecheck` — passed
- Verified Pipelines remains gated by `enablePipelines === true`:
sidebar item is hidden unless the flag is enabled, direct pipeline
routes redirect to `/dashboard` when disabled, and the Experimental
settings UI still has no Pipelines toggle.
- GitHub status checks on `df071c710646de625131064c3fb6588b5e97964a` —
all complete with no failing conclusions, including Actions, Socket,
Superagent/Security, and Greptile Review
- Greptile summary on `df071c710646de625131064c3fb6588b5e97964a` —
Confidence Score 5/5
- GitHub review-thread sweep — 0 unresolved Greptile threads
Previously recorded during branch development:
- Server pipeline service/route and aggregation tests
- Shared validator tests
- UI pipeline page/settings/item-detail/learnings/liveness tests
- Pipeline tutorial smoke path
## Risks
- High review surface: this is a large feature branch spanning database,
shared contracts, server behavior, CLI/docs, and UI.
- Migration ordering and schema compatibility need reviewer attention
because this branch has been kept current across multiple `master`
syncs.
- GitHub still reports merge state `BLOCKED` because the PR is awaiting
normal human review/branch-protection completion; all current status
checks are green.
- Branch-name checklist exception: this PR uses the pre-existing
requested branch name, which predates the current public-branch naming
rule. The PR title/body avoid internal issue references.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent based on GPT-5, with repository tool use,
shell execution, git/GitHub CLI operations, and local verification
commands. Earlier commits in this branch were assisted by Paperclip
agents and other AI coding agents as recorded in commit authorship.
## 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)
- [ ] 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The onboarding and company switcher UI are the first places users
create or select an organization
> - Some of that surface still used older team/workspace wording even
though the product model is company-centric
> - Mixed wording makes the setup path feel inconsistent and can make
users wonder whether they are creating a team, workspace, or company
> - This pull request updates the affected UI copy to consistently say
company
> - The benefit is a clearer first-run and navigation experience without
changing behavior
## Linked Issues or Issue Description
No public issue exists for this small UI polish change.
### Problem or motivation
The company creation and switcher surfaces used mixed
team/workspace/company wording for the same concept, which makes the
setup path feel inconsistent.
### Proposed solution
Update the visible copy, accessibility label, inline comment, e2e
expectations, and matching test expectations to use company-centric
language consistently.
### Alternatives considered
Leave the existing wording alone, but that preserves inconsistent
terminology in a high-traffic setup path.
### Roadmap alignment
This is focused UI polish and does not overlap with a roadmap-level core
feature.
### Additional context
The create action keeps its trailing ellipsis because it opens the
onboarding wizard rather than completing immediately.
## What Changed
- Updated front door and onboarding wizard labels from team-oriented
copy to company-oriented copy.
- Updated the sidebar company menu from workspace/team wording to
company wording, including the trigger accessibility label and empty
fallback text.
- Kept the sidebar create action ellipsis for the dialog/wizard
affordance.
- Updated component and Playwright test expectations for the new copy.
## Verification
- `pnpm exec vitest run ui/src/components/SidebarCompanyMenu.test.tsx`
- Attempted `npx playwright test --config tests/e2e/playwright.config.ts
tests/e2e/onboarding.spec.ts tests/e2e/nux-phase4-screenshots.spec.ts
tests/e2e/planning-mode-visual-verification.spec.ts
tests/e2e/conference-room-typing-intro.spec.ts`; local browser launch is
blocked by missing host Chromium dependencies (`libatk1.0-0t64`,
`libatspi2.0-0t64`, `libxcomposite1`, `libxdamage1`, `libxfixes3`,
`libxrandr2`, `libgbm1`, `libasound2t64`).
- Screenshots intentionally omitted because this is a copy-only change
and no design screenshots are needed for review.
## Risks
Low risk. This is copy-only UI polish plus matching test updates; no
data model, API, migration, workflow, lockfile, or behavior changes are
included.
## Model Used
OpenAI GPT-5 Codex (`gpt-5`) via the Paperclip Codex agent, with
tool-assisted repository inspection, GitHub CLI usage, and local command
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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Scheduled routines can prompt agents with variables that are filled
in at dispatch time.
> - Existing routine variable handling supported plain text-like values,
but date inputs need a structured contract so routines can pass
consistent date values.
> - Operators also need date variables to be easy to configure and
override from the routine UI.
> - This pull request adds a date variable type across shared
validation, server dispatch, and UI editing/run dialogs.
> - The benefit is that routine authors can define date inputs once and
agents receive validated ISO-style date values when routines run.
## Linked Issues or Issue Description
Refs #219
Feature request:
- Problem/motivation: Scheduled routines need first-class, typed date
variables so operators can configure dates without relying on free-form
text conventions.
- Proposed solution: Add an `x-date` routine variable type with shared
parsing/validation, server dispatch support, and UI date-picker controls
in routine variable editors and run dialogs.
- Alternatives considered: Continue treating dates as plain text, but
that leaves validation and formatting to individual operators and
agents.
- Roadmap alignment: This is a focused improvement to the completed
Scheduled Routines milestone and does not duplicate an active roadmap
item.
Related PR search:
- Searched existing PRs/issues for `routine date picker`, `date
variables`, and `scheduled routine date variable`; no direct duplicate
PR was found.
## What Changed
- Added the shared `x-date` routine variable contract, parsing,
defaults, and validation coverage.
- Extended routine dispatch to validate and pass date variable values.
- Added date input controls to the routine variable editor and routine
run variables dialog.
- Added focused tests for shared validation, server dispatch, and the UI
date controls.
## Verification
- `git diff --check public/master...HEAD`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
packages/shared/src/routine-variables.test.ts
packages/shared/src/validators/routine.test.ts
server/src/__tests__/routines-service.test.ts
ui/src/components/RoutineRunVariablesDialog.test.tsx
ui/src/components/RoutineVariablesEditor.test.tsx`
- 5 test files passed
- 68 tests passed
## Risks
Low to medium risk. This adds a new routine variable type across
shared/server/UI paths, so the main risk is compatibility with existing
routine variable payloads. The change keeps existing variable types
intact and adds targeted validation tests for the new date behavior.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent based on GPT-5, with terminal, git, GitHub
CLI, and local test execution capabilities.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The web UI (`ui/`) renders inside a single persistent shell
(`Layout`) whose `#main-content` element is the scroll container for
every route
> - Because that scroll container survives route changes, browser
back/forward (a history `POP`) lands on the previous page with the
scroll offset of the page you just left
> - Concretely: scroll deep into an issue detail, hit browser back, and
the inbox renders scrolled to the issue-detail offset instead of where
you left off — making it hard to find your place
> - The app already reset scroll for forward (`PUSH`) navigation but
deliberately did nothing on `POP`, so nothing restored the prior
position
> - This pull request records each history entry's scroll offset as the
user scrolls and restores it on `POP`, while keeping the existing
reset-to-top behavior for `PUSH`/`REPLACE`
> - The benefit is that back/forward returns you to the exact place you
were, so the inbox (and any scrolled page) keeps your reading position
## Linked Issues or Issue Description
No public GitHub issue — describing the bug inline following the bug
report template.
**What happened**
From the inbox, open an issue, scroll down within the issue detail, then
press the browser Back button. The inbox is restored at the wrong scroll
position — it shows the Y offset from the issue-detail page rather than
the position you had in the inbox.
**Expected behavior**
Browser back/forward returns the page to the scroll position it had when
you left it.
**Steps to reproduce**
1. Open the inbox and scroll to a known position partway down the list.
2. Click into an issue.
3. Scroll down within the issue detail.
4. Press the browser Back button to return to the inbox.
5. Observe the inbox is scrolled to the issue-detail offset instead of
where you left off.
**Deployment mode**
Web UI (`ui/`), any deployment — client-side scroll behavior only.
Root cause: `#main-content` is a single scroll container that stays
mounted across route changes. Scroll was only reset on forward (`PUSH`)
navigation and left untouched on `POP`, so the stale offset from the
outgoing page persisted onto the page being returned to.
## What Changed
- Added `NavigationScrollMemory` (`ui/src/lib/navigation-scroll.ts`): a
per-history-key map of `#main-content` scroll offsets, clamped to `>=
0`.
- Added `applyMainContentScrollTop` helper to restore a saved offset
onto the main content element (null-safe).
- In `Layout` (`ui/src/components/Layout.tsx`): continuously record the
active history entry's scroll offset on scroll, and on `POP` navigation
restore the remembered offset (re-applying on the next animation frame
so a late-laying-out cached page doesn't clamp the offset to a shorter
interim height). Forward `PUSH`/`REPLACE` keeps the existing
reset-to-top behavior.
- Added unit tests covering the remember/recall logic and the restore
helper.
## Verification
- `ui` unit tests pass, including the new `navigation-scroll` cases
(remember/recall per key, clamping, and DOM restore).
- TypeScript clean on the changed files.
- Manual: inbox → open issue → scroll down → browser Back returns the
inbox to its previous scroll position; forward navigation still resets
to top.
## Risks
Low risk. Scoped to client-side scroll restoration in the web UI; no
API, schema, or migration changes. The only behavioral change is that
`POP` navigation now restores a saved offset instead of leaving the
container untouched; `PUSH`/`REPLACE` behavior is unchanged. Memory is
per-session and bounded by visited history keys.
## Model Used
Claude (Anthropic), Opus-class model via Paperclip's `claude_local`
adapter, with extended thinking and tool use.
## 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
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI needs to render issue details, comments, properties,
and rich external object labels clearly during normal operator
workflows.
> - Three small UI regressions made those workflows harder to scan: rich
object URLs could show weak labels, interrupting comments could briefly
flash in the wrong state, and watchdog labels could overflow the
properties panel.
> - These are related quality fixes in the same UI surface, with focused
regression coverage for each behavior.
> - This pull request groups the fixes so they go through normal review
instead of bypassing CI.
> - The benefit is a quieter, more predictable issue detail experience
for board operators.
## Linked Issues or Issue Description
No public GitHub issue was found for these regressions after searching
related PRs and issues.
### Pre-submission checklist
- Existing open and closed GitHub issues and PRs were searched for
duplicates.
- The fixes are based on current `master` behavior.
- The regressions originate in Paperclip board UI code, not an adapter,
API provider, or local configuration.
### What happened?
In the board UI, three issue-detail regressions made normal review
workflows harder to scan:
- URL-rich external objects could fall back to a weak generic label
instead of showing a useful URL label.
- An interrupting issue comment could briefly flash through the wrong
state while issue run data refreshed.
- Long watchdog property instructions could truncate or overflow instead
of wrapping inside the properties panel.
### Expected behavior
- URL-rich external objects should surface a clear URL label.
- Interrupting comments should stay visually stable while live run state
refreshes.
- Long watchdog property values should wrap within the available
properties panel width.
### Steps to reproduce
1. Open an issue detail view that includes URL-rich external object
metadata, an interrupting run/comment state, or long watchdog
instructions.
2. Observe the rendered issue detail thread and properties panel.
3. Compare the rendered label, comment state, and watchdog row wrapping
against the expected stable/readable behavior above.
### Paperclip version or commit
Current `master`, fixed by this PR branch at
`1c763001c9a16fa6cf3faad6f58a83c4b202c928`.
### Deployment mode
Local dev (`pnpm dev`) / board UI.
### Installation method
Built from source (`pnpm dev` / `pnpm build`).
### Agent adapter(s) involved
Not adapter-specific (core board UI bug).
### Database mode
Not database-related.
### Access context
Board operator UI.
### Relevant logs or output
Not applicable.
### Relevant config
Not applicable.
### Additional context
The PR includes focused regression tests for all three behaviors.
Browser-visible before/after evidence for the watchdog wrapping change
was posted in
https://github.com/paperclipai/paperclip/pull/8613#issuecomment-4794863305.
### Privacy checklist
- All pasted output was reviewed for sensitive data; this PR body
contains no private config, logs, or internal instance links.
## What Changed
- Prefer direct URLs as rich object labels when rendering external
object metadata.
- Keep interrupting issue comments from flashing through the wrong
thread state while issue chat data is refreshing.
- Allow watchdog property labels to wrap cleanly inside the issue
properties panel.
- Added focused regression coverage for the external object helper,
issue detail interrupt behavior, and watchdog property wrapping.
## Verification
- `pnpm exec vitest run ui/src/lib/external-objects.test.ts
ui/src/lib/issue-chat-messages.test.ts
ui/src/components/IssueProperties.test.tsx
ui/src/pages/IssueDetail.test.tsx`
## Risks
Low risk. The changes are scoped to UI rendering/state handling and add
regression coverage for the touched behavior. No database, API,
workflow, lockfile, or migration changes are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex using GPT-5 (`gpt-5`) with repository shell/tool access.
The changes were prepared by an AI coding agent with local command
execution for git inspection and focused Vitest verification.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The execution workspace subsystem powers project and workspace views
by listing runtime state, branch metadata, issue links, and status
summaries.
> - The existing workspace views relied on broad list data that can grow
expensive as a company accumulates many workspaces and linked issues.
> - That makes the Workspaces page and project workspace cards slower
than necessary because the UI does not always need the full workspace
detail payload up front.
> - This pull request adds a bounded overview contract for workspace
listings and moves the relevant UI surfaces to that cheaper path.
> - The benefit is faster workspace loading while preserving detail
fetches for pages that actually need full workspace data.
## Linked Issues or Issue Description
No public GitHub issue exists for this change. Inline bug report follows
the repository bug template.
### What happened?
Workspace index-style screens can load too much execution workspace
detail before the user asks for it. Several UI surfaces used fuller
workspace data paths for summary displays, which can make workspace
loading slower as workspace history grows.
### Expected behavior
Overview screens should request a bounded summary payload, while detail
screens should keep using the full workspace detail endpoint.
### Steps to reproduce
1. Run Paperclip from source with enough execution workspace history to
make workspace lists non-trivial.
2. Open the Workspaces page or a project workspace summary card.
3. Observe that summary UI needs only bounded workspace metadata but can
depend on broader workspace payloads.
### Paperclip version or commit
`master` at the time this branch was prepared.
### Deployment mode
Local dev (`pnpm dev`)
## What Changed
- Added shared types, validators, and path constants for bounded
execution workspace overviews.
- Added server service and route support for overview queries with
bounded linked issue/runtime metadata.
- Updated workspace overview UI API calls, query keys, breadcrumbs,
quicklooks, close dialogs, project summaries, and detail links to
consume the cheaper overview shape where appropriate.
- Added regression coverage for the new server route/service behavior
and the UI overview consumers.
- Registered the new workspace overview route in the generated OpenAPI
spec.
- Kept overview totals aligned with the project join and preserved
project slug links in workspace headers.
## Verification
- `pnpm exec vitest run
server/src/__tests__/execution-workspaces-service.test.ts
server/src/__tests__/execution-workspaces-routes.test.ts
ui/src/api/execution-workspaces.test.ts
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/pages/Workspaces.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck && pnpm --filter @paperclipai/ui
typecheck`
- `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts
server/src/__tests__/execution-workspaces-routes.test.ts
server/src/__tests__/execution-workspaces-service.test.ts`
- `pnpm test:run:serialized -- --shard-index 1 --shard-count 4`
## Risks
Low to medium risk. The change introduces a new overview contract across
shared/server/ui layers, so the main risk is a mismatch between summary
and detail payload expectations. The added route/service/UI tests cover
the intended split, and full detail pages continue using the detail
path.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5-based coding agent with repository tool use and
local command execution. Exact served model identifier and context
window were not exposed in this session.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent configuration includes adapter model settings and optional
cheap model profiles used by runtime lanes.
> - The agent configuration screen already exposes a Test action for
adapter environment checks.
> - When a cheap model profile is configured, that Test action only
exercised the primary model configuration.
> - That left users able to save a cheap model profile that had not been
validated by the same configuration test flow.
> - This pull request makes the Test action probe both the primary model
and the configured cheap model.
> - The benefit is earlier feedback when the cheap model is unavailable
or misconfigured.
## Linked Issues or Issue Description
No exact public issue found.
Problem description:
- What happened: the agent configuration Test action validated the
primary adapter model but did not validate an enabled cheap model
profile.
- Expected behavior: when a cheap model is configured and enabled, the
Test action should also test that cheap model using the same environment
selection.
- Steps to reproduce: configure an agent with a primary model and
enabled cheap model profile, then click Test in the agent configuration
UI.
- Paperclip version/commit: current `master` at PR creation.
- Deployment mode: local development UI behavior.
Related public context found during GitHub search:
- #4881 added cheap model profiles for local adapters.
- #6534 is related cheap-primary-model preservation work.
- #6956 tracks broader agent configuration settings exposure.
GitHub searches performed:
- `cheap model config test`
- `AgentConfigForm cheap model`
- `modelProfiles cheap`
- `adapter environment cheap model`
## What Changed
- Updated `AgentConfigForm` so the adapter environment Test action runs
the primary model check first.
- Added a second cheap model check when the cheap profile is enabled and
resolves to a model.
- Built the cheap test payload from the resolved cheap profile config
instead of a model-only override, preserving adapter-default and saved
cheap-profile fields.
- Merged the individual results into a single labeled result so the UI
can show both outcomes together.
- Preserved request/API failures so they still surface through the
existing error UI instead of becoming synthetic adapter checks.
- Added render coverage asserting both primary and cheap test calls,
non-model cheap-profile fields, and request failure handling.
## Verification
- `git diff origin/master...HEAD | rg -n
"(API[_-]?KEY|SECRET|TOKEN|PASSWORD|PRIVATE[_-]?KEY|BEGIN RSA|BEGIN
OPENSSH|Bearer
[A-Za-z0-9._-]+|ghp_[A-Za-z0-9_]+|sk-[A-Za-z0-9]+|AIza[0-9A-Za-z_-]+|OPENAI_API_KEY|ANTHROPIC_API_KEY)"
|| true` produced no matches.
- `git diff --check origin/master...HEAD`
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/AgentConfigForm.render.test.tsx --reporter=dot`
- `pnpm --filter @paperclipai/ui typecheck`
- GitHub PR checks on head `3d9ba15d2` are green, including Build,
Typecheck + Release Registry, General tests, serialized server suites,
e2e, Canary Dry Run, security scans, and policy/review checks.
- Greptile Review passed on head `3d9ba15d2`; all review threads are
resolved.
## Risks
Low risk. The change is limited to the agent configuration UI test
action and its render test. The cheap-model probe now preserves
adapter-default and saved cheap-profile fields, matching the runtime
merge order more closely. The main residual risk is adapter-specific UI
coverage for cheap-profile fields that are stored but not directly
editable in this form.
## Model Used
OpenAI GPT-5 Codex via the Codex local agent environment, with
terminal/tool use for repository inspection, implementation, GitHub CLI
operations, and local verification. Exact context window was not exposed
by the runtime.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI has been graduating experimental conference-room task
experiences into the default issue and onboarding flows
> - Several task UI improvements were still coupled to the Conference
Room Chat experimental flag even though they are useful outside chat
itself
> - That coupling meant disabling chat also reverted unrelated defaults
such as work-mode labels, task status colors, team creation copy, and
the graduated issue thread
> - This pull request keeps chat-specific gating scoped to chat while
making the graduated task UI the default experience
> - The benefit is that operators can use the newer task workflows
without needing to enable the separate chat experiment
## Linked Issues or Issue Description
No public GitHub issue exists for this exact change.
### Problem or motivation
The Conference Room Chat experimental flag was controlling unrelated
task UI defaults, which made non-chat workflows regress when chat was
disabled.
### Proposed solution
Remove that flag from task-thread, work-mode, onboarding, status, and
team-creation presentation paths while leaving chat-specific behavior
separately gated.
### Alternatives considered
Keeping the flag as a broad umbrella until chat graduates would avoid a
behavior change, but it keeps unrelated UI improvements hidden behind
the wrong capability switch.
### Roadmap alignment
Checked `ROADMAP.md`; this is focused graduation/polish for existing UI
surfaces rather than a new roadmap-level core feature.
Related search:
- Searched open PRs and issues for `conference room chat experimental
flag`: no matches.
- Searched open PRs and issues for `graduated issue thread`: no matches.
## What Changed
- Removes Conference Room Chat flag branching from task-thread
rendering, work-mode labels, task status colors, and team creation copy.
- Makes the onboarding completion path create/reuse an onboarding
project, create the first assigned task, and send the user to the
dashboard instead of chat.
- Deletes the legacy onboarding wizard and classic task-thread files now
that the graduated flow is the default.
- Updates focused UI tests and affected E2E specs for the default task
experience.
- Adds a user-visible onboarding error if restored state is missing the
company or agent required for launch.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
ui/src/components/NewIssueDialog.test.tsx
ui/src/components/OnboardingWizardVariant.test.tsx
ui/src/components/RunChatSurface.test.tsx
ui/src/components/SidebarCompanyMenu.test.tsx
ui/src/components/StatusBadge.test.tsx ui/src/lib/agent-order.test.ts
ui/src/lib/onboarding-launch.test.ts ui/src/lib/work-mode-meta.test.ts
ui/src/pages/IssueDetail.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `npx playwright test --config tests/e2e/playwright.config.ts --list
tests/e2e/conference-room-typing-intro.spec.ts
tests/e2e/planning-mode-visual-verification.spec.ts`
- Attempted targeted Playwright execution locally, but this host is
missing Chromium system libraries (`libatk1.0-0t64`, `libatspi2.0-0t64`,
`libxcomposite1`, `libxdamage1`, `libxfixes3`, `libxrandr2`, `libgbm1`,
`libasound2t64`). CI runs the specs in the proper Actions environment.
## Risks
- Medium UI behavior risk: this intentionally changes the default
experience for users who have not enabled Conference Room Chat.
- Medium onboarding risk: completion now creates/reuses a project and
creates the first task instead of only navigating.
- Low migration risk: no database schema or migration changes are
included.
- The PR avoids `pnpm-lock.yaml` and `.github/workflows` changes.
## Model Used
OpenAI Codex, GPT-5-class coding model, tool-enabled local repository
workflow with shell, git, and 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Skills can be installed, inspected, filtered, and grouped inside
company settings
> - Skill category metadata already exists in the data model and list
filters, but users could not edit categories after a skill was created
or imported
> - That made category filters and counts drift from the way operators
actually want to organize their skills
> - This pull request adds category editing to the existing skill
settings dialog and sends those edits through the existing
company-scoped skill update API
> - The server mutation now includes category information in the
activity log so settings changes are auditable
> - The benefit is that operators can keep installed skills organized
without reinstalling or recreating them
## Linked Issues or Issue Description
No duplicate or closely related public GitHub issues or PRs were found
for `skill categories settings`.
Feature request fields:
**Subsystem affected**
Cross-cutting: `server/` REST API routes/services and `ui/` React board
settings.
**Problem or motivation**
Company operators can create or import skills with categories, and
Paperclip already exposes category filters and category counts. After
installation, though, operators could not edit a skill's categories from
the skill detail settings screen. That made it difficult to keep skills
grouped correctly as workflows evolved.
**Proposed solution**
Add category editing to the existing skill settings dialog. The category
field accepts comma-separated values, normalizes them into slugs,
deduplicates repeated categories, allows clearing all categories, and
saves categories together with the existing sharing setting through the
company-scoped skill update API.
**Alternatives considered**
One alternative was to keep categories editable only during
create/import flows, but that forces users to recreate or reinstall
skills just to adjust grouping metadata. Another was a separate
categories-only action, but batching settings into one explicit Save
action keeps the dialog predictable.
**Roadmap alignment**
This supports the completed Skills Manager roadmap area by making
installed skills easier to organize and maintain inside company
settings.
**Additional context**
The server already persisted skill categories and supported category
list filters/counts. This PR wires the existing metadata into the
settings editing path and adds focused route, service, and UI tests.
## What Changed
- Added category editing to the skill detail settings dialog, including
comma-separated input, normalized deduplication, reset, dirty-state
handling, and save feedback.
- Updated the skill settings mutation path to save categories and
sharing scope together, then refresh detail/list cache entries.
- Included updated categories in `company.skill_updated` activity
details.
- Added server route/service coverage for category updates,
normalization, filtering, counts, clearing, and activity logging.
- Added UI coverage for saving category edits, clearing categories,
reordered no-op category sets, saving sharing changes together, and
preserving draft input after a failed save.
- Updated the Storybook skill detail harness for the renamed settings
callback props.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/company-skills-routes.test.ts
server/src/__tests__/company-skills-service.test.ts
ui/src/pages/CompanySkills.test.tsx`
- Latest-head GitHub checks are green for typecheck, build, e2e, general
tests, serialized server suites, policy, commitperclip review, Socket
Security, Snyk status, and Canary Dry Run.
- Greptile Review succeeded on the latest head with zero unresolved
review threads.
## Risks
Low risk. The change uses the existing company skill update API and
category normalization path. The main behavioral change is that the
settings dialog now batches sharing and category edits behind an
explicit Save button instead of saving sharing immediately on select
change.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex coding agent based on GPT-5, with tool-enabled repository
inspection, shell execution, git, and GitHub CLI access.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents and board users operate inside a company-scoped control plane
where permissions decide which mutating actions they can perform
> - Company skills are part of the reusable agent-company setup surface,
but skill mutation had been coupled to broader agent-creation authority
> - That coupling meant importing or managing skills required a
permission that also implies hiring power, which is broader than the
operation needs
> - Paperclip already has a grant-based permission vocabulary, so skill
mutation should be authorized through a dedicated `skills:create`
capability while preserving existing default behavior for trusted agents
> - This pull request adds the skill creation permission contract,
enforces it on company skill mutations, exposes it in agent permission
management, and documents the changed CLI/API expectations
> - The benefit is a narrower, auditable permission path for skill
import/create/update/delete flows without forcing agents to receive
broader agent-creation authority
## Linked Issues or Issue Description
No public issue is linked.
Problem: company skill mutation APIs were effectively tied to broader
agent creation authority. This PR splits skill mutation authorization
onto the public `skills:create` permission while keeping existing
default skill creation behavior for agents unless explicitly disabled.
Related public PR found during duplicate search: #5330. That PR uses an
older `canManageSkills` shape; this PR implements the `skills:create`
grant path instead.
## What Changed
- Added `skills:create` to shared permission constants and agent
permission types/validators as `canCreateSkills`.
- Backfilled default human/member role grants for `skills:create`.
- Updated company skill mutation routes to require board/user or agent
access to `skills:create`, while preserving legacy/default agent
behavior through `canCreateSkills` unless explicitly disabled.
- Updated agent permission update handling, UI permission controls,
duplicate-agent payloads, plugin SDK fixtures, and agent detail API
surfaces for `canCreateSkills`.
- Added regression coverage for skill route authorization, permission
schema/default behavior, invite grants, omitted permission updates, and
duplicate-agent payloads.
- Updated CLI and Paperclip skill documentation for the new skill
creation permission.
## Verification
- `pnpm exec vitest run
server/src/__tests__/agent-permissions-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/company-skills-routes.test.ts
server/src/__tests__/invite-join-grants.test.ts
ui/src/lib/duplicate-agent-payload.test.ts` — 5 files, 90 tests passed.
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck && pnpm --filter @paperclipai/ui
typecheck` — passed.
- `pnpm test:run ...changed files...` was attempted first, but the
stable wrapper rejects explicit file arguments; direct Vitest was used
for the same targeted files.
## Risks
- Moderate authorization risk: this changes the gate for company skill
mutations, so the tests cover board grant checks, agent explicit grant
checks, legacy default allowance, and explicit denial.
- Migration/backfill risk is low: the migration only grants
`skills:create` to existing human roles that already need broad
management capability.
- UI/API compatibility risk is low: `canCreateSkills` remains default-on
for full agent permissions, and the update validator preserves omitted
values so unrelated permission edits do not re-enable disabled skill
creation.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex, GPT-5 coding agent with terminal/tool use enabled.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue thread is where operators watch active agent work and
decide whether a run is healthy.
> - Backend runtime-progress plumbing can expose a concise current
status, but users only benefit if the issue UI renders it in context.
> - The UI should show that current status as part of the active run
surface without turning it into durable transcript content.
> - This pull request adds the issue-thread rendering and message
formatting needed for sandbox runtime status.
> - The benefit is that active sandbox setup phases are visible from the
normal issue detail view.
## Linked Issues or Issue Description
Refs #248
No exact public GitHub issue was found for this UI rendering work. The
underlying problem is that active sandboxed runs can have backend
progress state without any concise issue-thread display, leaving users
to infer whether setup is still moving. This PR adds the UI layer on top
of the runtime-status API branch.
GitHub search performed for related or duplicate work: `sandbox runtime
status`, `sandbox restore index`, and `runtime progress`. No direct
duplicate PR was found.
## What Changed
- Included current runtime status in the heartbeat API client shape.
- Rendered active run status text in the issue chat thread when
available.
- Updated issue-chat message formatting helpers for runtime status
display.
- Added focused component and formatting tests for the new active-run
status behavior.
## Verification
- Local PII scan before push: high-confidence secret patterns, internal
issue links, local user paths, and private URL patterns checked across
all three split diffs; no real secrets or internal links found.
- `git diff --check feat/sandbox-runtime-status..feat/sandbox-status-ui`
- `pnpm exec vitest run ui/src/components/IssueChatThread.test.tsx
ui/src/lib/issue-chat-messages.test.ts` — 2 files, 91 tests passed.
- `pnpm run typecheck` passed on `feat/sandbox-status-ui`.
- `pnpm run build` passed on `feat/sandbox-status-ui`; Vite reported
existing CSS `::highlight` and chunk-size warnings.
- Visual evidence (desktop + mobile) is attached to the tracking issue
rather than committed to the repo.
## Risks
- This PR is stacked on the runtime-status API branch and depends on
that response/event shape.
- The status line is intentionally concise; long or sensitive status
content should continue to be bounded/redacted by the backend.
- The visual change is limited to active issue-thread runs with current
runtime status data.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex coding agent, with shell/tool execution in a
local worktree. Exact context-window metadata is not exposed by the
runtime.
## 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
---------
Co-authored-by: Paperclip CTO <cto@paperclip.local>
Co-authored-by: Paperclip CTO <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Sandboxed agent runs can spend meaningful time preparing a remote
workspace before the agent transcript shows useful output.
> - Operators need short, current progress text for those setup phases,
but that text should not become durable run history.
> - The existing live-run websocket path already carries run updates to
the UI, so the backend can reuse that channel instead of adding polling.
> - This pull request adds an ephemeral runtime-progress contract, a
process-local status store, and heartbeat integration for
sandbox-managed runs.
> - The benefit is a clearer active-run experience without database
migrations or persistent progress rows.
## Linked Issues or Issue Description
Refs #248
No exact public GitHub issue was found for this status-message plumbing.
The underlying problem is that active sandboxed runs currently have
setup phases, such as workspace sync and restore, where the operator
cannot see concise current progress through the live run state. This PR
addresses that gap for the backend/runtime layer while keeping progress
messages ephemeral.
GitHub search performed for related or duplicate work: `sandbox runtime
status`, `sandbox restore index`, and `runtime progress`. No direct
duplicate PR was found.
## What Changed
- Added shared runtime-progress types and the `heartbeat.run.progress`
live event type.
- Added a process-local heartbeat run runtime-status store with TTL,
bounded/redacted messages, and terminal cleanup.
- Threaded runtime progress callbacks through heartbeat execution and
active/live run serialization.
- Emitted sandbox-managed runtime phase updates for sync, adapter
startup, restore/export, and finalization paths.
- Added backend and adapter-utils tests for ephemeral status behavior,
terminal cleanup, live serialization, and sandbox progress callbacks.
## Verification
- `pnpm install --frozen-lockfile`
- Local PII scan before push: high-confidence secret patterns, internal
issue links, local user paths, and private URL patterns checked across
all three split diffs; no real secrets or internal links found. The only
secret-like text is an intentional fake test fixture (`sk-test-secret`).
- `git diff --check origin/master..feat/sandbox-runtime-status`
- `pnpm exec vitest run
server/src/services/heartbeat-run-runtime-status.test.ts
server/src/__tests__/heartbeat-runtime-state.test.ts
server/src/__tests__/agent-live-run-routes.test.ts
packages/adapter-utils/src/sandbox-managed-runtime.test.ts` — 4 files,
23 tests passed.
- `pnpm run typecheck` passed on both top stacks that include this
branch: `feat/sandbox-status-ui` and `fix/sandbox-restore-index-sync`.
- `pnpm run build` passed on both top stacks that include this branch;
Vite reported existing CSS `::highlight` and chunk-size warnings.
- `pnpm run test:run` was attempted on `fix/sandbox-restore-index-sync`;
it failed in two unrelated broad-suite tests. One depends on this host's
Git default branch behavior, and one depends on local Claude
model-discovery environment. The changed focused suites above pass.
## Risks
- Runtime progress is process-local by design, so status disappears
after TTL, terminal cleanup, or server restart.
- Clients that do not consume `heartbeat.run.progress` simply keep
existing behavior.
- Message redaction is intentionally generic; overly specific phase
details should stay out of runtime-progress payloads.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 via Codex coding agent, with shell/tool execution in a
local worktree. Exact context-window metadata is not exposed by the
runtime.
## 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
---------
Co-authored-by: Paperclip CTO <cto@paperclip.local>
Co-authored-by: Paperclip CTO <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work, and task status is one of the most-scanned signals across its
whole UI.
> - The Tasks UI shows status through small coloured ring icons and
chips spread across the list, kanban, task detail, the properties
flyout, inline `@`-mentions and the breadcrumb.
> - Those ring glyphs lean heavily on colour to distinguish states,
which is hard to read for colour-blind users, and the status hues were
hard-coded in component classes rather than a single source.
> - We want color-blind-safe, distinct *shapes* per status plus a single
`--status-*` colour-token system the chips and icons share.
> - This pull request adds a unified `StatusGlyph` (one shape per
status) and the `--status-*` colour-token system, and adopts them across
every task-status surface so the new glyphs + colours render by default.
> - The benefit is a more accessible, consistent status language with
one source of truth for status hues.
## Linked Issues or Issue Description
This is a **feature** (no public issue filed). Following the feature
issue template:
- **Problem / motivation:** Task status is communicated mostly by colour
(ring fills/borders), which is hard to distinguish for colour-blind
users, and the status hues are duplicated across component classes with
no single source of truth. Several community PRs have nibbled at parts
of this (see related PRs below).
- **Proposed solution:** A single `StatusGlyph` component with a
distinct *shape* per status (not just colour), backed by a `--status-*`
CSS-variable colour system (base hues + AA-tuned icon hues +
`.status-chip` / `.status-fill` color-mix helpers), adopted across all
task-status surfaces.
- **Alternatives considered:** Recolouring the existing rings in place
(rejected — still colour-only, no shape differentiation).
- **Roadmap alignment:** Additive UI only; no overlap with planned core
work.
Related community PRs (partial / different approaches to the same area —
not duplicates):
- Refs #3806 — Show issue ref and status icon in breadcrumbs and
properties
- Refs #1760 — Improve design of cancelled task status icon
- Refs #1856 — a11y title/aria-label on status and priority icons
## What Changed
- **Colour token system:** `--status-agent-*` / `--status-task-*` base
hues, AA-tuned `--status-task-icon-*` hues (light + dark), and
`.status-chip` / `.status-fill` color-mix helpers in `index.css`;
matching status→CSS-var maps in `status-colors.ts`.
- **`StatusGlyph`** — one `viewBox="0 0 24 24"` glyph per status with
distinct, color-blind-safe shapes (dashed ring, open ring, half-fill,
ring+dot, disc+check, ring+bar, ring+slash, and `in_queue` = the blocked
shape recoloured blue). Sizes `sm`/`md`/`lg`.
- **Adoption (renders by default)** across `StatusIcon`, `StatusBadge`
(agent + issue chips), `MarkdownBody` inline mentions, `IssueRow`,
`IssuesList`, `IssueProperties`, `BreadcrumbBar` + `BreadcrumbContext`,
and the task-detail header/breadcrumb.
- **Tests** for `StatusGlyph`, `StatusIcon`, `StatusBadge`, `IssueRow`,
`IssuesList`, `MarkdownBody` lock the rendered behaviour.
Scope notes: no experimental flag and no Theme Editor surfaces. The
generic `StatusBadge` (runs/goals/approvals) is unchanged.
Project-status recolour is deferred (no in-scope consumer).
## Verification
- `pnpm --filter @paperclipai/shared build` — green (tsc).
- `pnpm --filter @paperclipai/ui build` — green (tsc + vite).
- Targeted unit tests green: `StatusGlyph`, `StatusIcon`, `StatusBadge`,
`IssueRow`, `IssuesList`, `MarkdownBody`, plus consumer suites that
render these (`IssueProperties`, `IssueDetail`, `Search`,
`IssueChatThread`, `IssueFiltersPopover`, `InterruptHandoffViews`) — all
passing.
- Remaining: interactive light + dark visual confirmation across list /
kanban / detail / properties / inline mentions / breadcrumb. The glyph
shapes + AA-tuned hues were previously QA'd on the originating feature
branch.
## Risks
Low risk. Additive UI: a new component + CSS tokens, adopted at existing
status call sites. The generic `StatusBadge` and all non-status UI are
untouched; no schema/migration changes. Behaviour is exercised by unit +
consumer test suites.
## Model Used
Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, with tool use /
code execution (file edits, local builds + vitest).
## 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 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
- [ ] 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
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The new issue dialog lets operators create follow-up tasks and
optionally reuse an existing execution workspace
> - Reusing a workspace depends on a searchable selector that can
include workspace names, branches, and local paths
> - The selector previously treated all matched text equally, so hidden
path text could outrank the visible workspace label and unrelated fuzzy
letter matches could leak into results
> - The reusable workspace popover also needed to stay inside the modal
so scrolling and layering behave like the rest of the dialog
> - This pull request improves the shared searchable select scoring and
applies it to reusable execution workspace choices
> - The benefit is a more predictable workspace reuse flow when an
operator searches by branch, task name, or workspace label
## Linked Issues or Issue Description
No public GitHub issue found for this selector bug.
### Pre-submission checklist
- [x] I have searched existing open and closed issues and this is not a
duplicate.
- [x] I am on the latest released version of Paperclip (or can reproduce
on `master`).
- [x] I have confirmed the error originates in Paperclip itself — not in
my agent adapter, API provider, or local configuration.
### What happened?
Workspace searches could rank hidden path matches ahead of direct
visible label matches, and broad fuzzy matching could match letters
spread across unrelated workspace metadata.
### Expected behavior
Direct label/name matches should sort ahead of weaker hidden metadata
matches, and fuzzy matching should stay constrained enough to avoid
unrelated workspace results.
### Steps to reproduce
1. Open the new issue dialog.
2. Choose reuse existing execution workspace.
3. Search for a term that appears in one workspace label and only in
another workspace path.
4. Observe that the path-only match can rank ahead of the direct visible
label match.
### Paperclip version or commit
Current `master` before this change.
### Deployment mode
Local dev (`pnpm dev`).
### Installation method
Built from source (`pnpm dev` / `pnpm build`).
### Agent adapter(s) involved
- [x] Not adapter-specific (core bug)
### Database mode
Not database-related.
### Access context
Board (human operator).
### Node.js version
Not version-specific.
### Operating system
Not OS-specific.
### Relevant logs or output
No logs; this is client-side selector behavior.
### Relevant config (if applicable)
None.
### Additional context
This PR also keeps the reusable workspace selector popover inside the
modal and contains command-list scroll events to keep the dialog
interaction stable.
### Privacy checklist
- [x] I have reviewed all pasted output for PII (usernames, file paths,
API keys, tokens, company names) and redacted where necessary.
## What Changed
- Added fuzzy scoring helpers for searchable text fields, including
field weights for visible labels versus secondary search metadata.
- Updated `SearchableSelect` to sort filtered results by score while
preserving original order for ties and custom filters.
- Updated reusable execution workspace matching to prefer visible
labels, then descriptions, then hidden search text.
- Kept the reusable workspace selector popover inside the new issue
modal and contained wheel/touch scrolling in the command list.
- Added unit/component coverage for selector ranking, reusable workspace
matching, modal popover containment, and scroll containment classes.
## Verification
- `pnpm exec vitest run ui/src/lib/searchable-select.ts
ui/src/lib/reusable-execution-workspaces.test.ts
ui/src/components/SearchableSelect.test.tsx
ui/src/components/NewIssueDialog.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
## Risks
Low risk. The change is scoped to client-side searchable selector
ranking and modal popover behavior. The main behavior shift is that
searches are intentionally less permissive for unrelated fuzzy letter
spreads, which should reduce noisy results but could hide a result
someone previously reached through very loose matching.
## Model Used
OpenAI Codex, GPT-5-based coding agent with repository file access,
shell/tool execution, and medium reasoning effort. Exact hosted model
build and context window were not surfaced in this environment.
## 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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents rely on local skills, provider-backed secrets, and workspace
file previews during normal execution.
> - Local skill imports need bounded reference-file inventory so direct
skill discovery stays accurate without accidentally walking too much of
the filesystem.
> - Secrets provider setup needs actionable AWS discovery errors so
operators can recover from IAM/config problems without losing manual
form input.
> - The issue detail file viewer should reopen cleanly after the first
close so users can keep inspecting files during task review.
> - This pull request collects the small fixes and regression tests for
those related operator workflows.
> - The benefit is more predictable local skill imports, clearer secrets
setup failure states, and a less brittle file preview interaction.
## Linked Issues or Issue Description
- No public GitHub issue was found for this extracted local work.
- Related prior sync context: #8536.
- Problem: local skill reference discovery, AWS provider-vault discovery
errors, and issue file preview reopening each had narrow workflow
regressions that made operator recovery harder.
- Expected behavior: skill imports inventory reference files within
bounded local skill directories, AWS discovery failures present safe
actionable guidance while preserving manual values, and closing the
first file preview does not prevent opening another preview.
- Reproduction scope: import a local skill with referenced files,
attempt AWS Secrets Manager discovery with insufficient IAM/list
permissions, and open/close/reopen file previews from an issue detail
page.
- Duplicate search: searched GitHub PRs/issues for `skill inventory
secrets file viewer` and `skill inventory secrets AWS file viewer`; no
matching public duplicate was found.
## What Changed
- Bounded direct local skill file inventory discovery and added
regression coverage for reference file imports.
- Preserved and surfaced safe, actionable AWS Secrets Manager
discovery/import errors in server responses and the secrets UI.
- Kept AWS provider-vault manual form values intact when discovery fails
or returns no candidates.
- Fixed issue file viewer state so closing the first preview still
allows later file previews to open.
- Updated the secrets render test harness to avoid the missing
`React.act` export in the current React package set.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/secrets-routes.test.ts
server/src/__tests__/secrets-service.test.ts
ui/src/context/FileViewerContext.test.ts
ui/src/pages/Secrets.render.test.tsx`
- Result: 5 test files passed, 116 tests passed.
- Install note: the isolated worktree needed `NODE_ENV=development pnpm
install --frozen-lockfile --prod=false --force` before local
verification because it initially had no dev dependencies installed.
## Risks
- Low-to-medium risk: this touches skill import inventory,
secrets-provider error handling, and file-viewer UI state, but each
change is covered by focused regression tests.
- No migrations.
- No dependency or lockfile changes.
- CI is rerunning on the latest head after review fixes; Greptile is 5/5
with no unresolved Greptile threads.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex coding agent, GPT-5-family model as provided in the
Paperclip run environment, with repository tool use and local command
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
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI, plugin examples, and plugin SDK all share the same
React dependency graph.
> - Dependabot PR #8471 raised `react` and `@types/react`, but left
`react-dom` and `@types/react-dom` on older 19.x ranges.
> - That let dependency resolution install incompatible React runtime
versions, which makes React refuse to run and breaks UI-oriented test
suites.
> - Current PR policy keeps `pnpm-lock.yaml` owned by CI for
non-dependabot authors, so this PR updates manifests and lets CI
regenerate the lockfile artifact.
> - This pull request replaces #8471 with a complete React toolchain
alignment across the package manifests it touched.
> - The benefit is a single React 19.2.7 runtime graph that keeps
dependency consumers from deduping onto a stale `react-dom` patch.
## Linked Issues or Issue Description
Refs #8471
This PR replaces #8471, which updated `react` and `@types/react` but
left `react-dom` and `@types/react-dom` behind. The resulting dependency
graph can install mismatched `react` and `react-dom` versions, producing
React's incompatible-version runtime error in UI tests.
## What Changed
- Aligned `react-dom` package ranges to `^19.2.7` anywhere the React
dependency set is present.
- Aligned `@types/react-dom` package ranges to the 19.2 line.
- Kept the `react@^19.2.7` and `@types/react@^19.2.17` bumps from #8471.
- Added root pnpm overrides for `react` and `react-dom` so peer-only
consumers resolve to the same 19.2.7 runtime instead of a stale patch.
- Left `pnpm-lock.yaml` out of the PR, per the PR workflow policy; CI
regenerates and shares the lockfile artifact when manifests change.
## Verification
- `cd ui && NODE_ENV=test npx vitest run` passed locally before handoff:
243 files / 1739 tests.
- `NODE_ENV=development pnpm install --lockfile-only --ignore-scripts
--no-frozen-lockfile` passed locally on the rebased branch.
- Confirmed regenerated `pnpm-lock.yaml` resolves `react` and
`react-dom` to 19.2.7 and contains no `react@19.2.4` /
`react-dom@19.2.4` entries.
- GitHub Actions are green on this PR, including `policy`, `Typecheck +
Release Registry`, all general test shards, `Build`, `e2e`, and
`verify`.
- Greptile completed at 5/5 with zero unresolved threads.
## Risks
Low risk. This is a dependency-version alignment only, but React
dependency changes can expose package-manager resolution issues. The
root overrides intentionally constrain the React runtime pair to the
same patch version to avoid that class of failure.
## Model Used
OpenAI Codex based on GPT-5, using command-line tool execution and
GitHub CLI operations in a Paperclip-managed workspace.
## 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
Co-authored-by: Paperclip <noreply@paperclip.ing>