## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A native run saves the agent's files after its turn (copyback). One controller owns copyback and records an owner receipt on the run > - #19721 made retained saved-work recovery stop spending attempts when a restart killed that owner. It records the receipt as `workspaceOwnerRecovery.state = "operator_required"`, and a waiting Retry reports `workspace_owner_stop_required` > - #19717 added one task control, **Confirm the previous server stopped**, for the direct finalization hold. That control did not cover the retained hold, so the task told the board to confirm but offered nothing to click > - This pull request shows the same control for the retained hold, with the recorded receipt token > - The benefit is that every copyback-owner hold has exactly one visible action, and the waiting Retry then continues by itself ## Linked Issues or Issue Description Refs #19721 (retained hold, server side), #19717 (the confirmation control). **What happened?** The retained recovery action named the stopped copyback owner, and the task's Retry said "Retry is waiting for a board member to confirm that the previous server stopped copying files". No control called the confirmation route for this state. **Steps to reproduce** 1. Retained saved-work recovery owns copyback for a finished run. 2. A restart kills the controller while it owns copyback, on another host or without automatic stop proof. 3. Open the task. **Expected behavior** The task shows one **Confirm the previous server stopped** action to an owner or administrator. It sends the recorded receipt token to `resume-workspace-finalization`. **Actual behavior** The task shows only the retained file-recovery controls. The token is in `evidence.workspaceOwnerRecovery.owner`, which the #19717 control did not read. ## What Changed - `WorkspaceExportRecovery`: when a retained export action has `workspaceOwnerRecovery.state = "operator_required"` and no recorded confirmation, render the existing confirmation control with that receipt's token, in place of the retained file-recovery controls. After the confirmation (state `resuming`), the ordinary retained controls return. - The confirmation control now takes the owner token as a prop, so the direct hold (`evidence.owner`) and the retained hold (`evidence.workspaceOwnerRecovery.owner`) share it. - Docs: one sentence in `doc/native-workspace-finalization-recovery.md`. - There are no server changes. The route already dispatches the retained path (#19721). ## Verification - Red then green: the new test "offers the same action for retained saved-work recovery held by a stopped copyback owner" fails against master's component and passes with this change. It checks that the request body carries the retained receipt token, that the host name is not shown, and that the retained controls return after `resuming`. - `cd ui && npx vitest run src/components/WorkspaceExportRecovery` (35 passed). - `pnpm check:token-gates` clean. UI typecheck clean. ## Risks - Low. The change is UI only. The server rechecks the receipt, the hold state and `runtime:confirm_owner_stop`. - While the hold is active, the "Recover saved files" control is hidden. It cannot release the receipt, so hiding it removes a futile action. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edits, 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <[email protected]>
@paperclipai/ui
Published static assets for the Paperclip board UI.
What gets published
The npm package contains the production build under dist/. It does not ship the UI source tree or workspace-only dependencies.
Storybook
Storybook config, stories, and fixtures live under ui/storybook/.
pnpm --filter @paperclipai/ui storybook
pnpm --filter @paperclipai/ui build-storybook
Typical use
Install the package, then serve or copy the built files from node_modules/@paperclipai/ui/dist.
Editor dependency identity
Keep the root and workspace overrides for @codemirror/state,
@codemirror/view, and @lezer/common aligned. CodeMirror requires shared
extension identity, while Lezer parsers and syntax highlighters require shared
NodeProp IDs. Multiple Lezer copies can crash code-block highlighting with
tags is not iterable. src/lib/codemirror-single-instance.test.ts checks the
installed dependency graph and highlights sample code through the editor's real
language dependencies. GitHub Actions owns regeneration of pnpm-lock.yaml.