Commit Graph
771 Commits
Author SHA1 Message Date
DottaandPaperclip 7f72ea7534 fix(links): retain PR status across page reloads (#20654)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task messages contain GitHub pull request links.
> - A hover loads PR status into a shared memory cache.
> - A page reload clears that memory and returns each link to an unknown
state.
> - The browser already saves selected reads for each user and company.
> - This pull request adds learned link previews to that cache.
> - Reloads show the saved status. Only user interest can refresh stale
previews.

## Linked Issues or Issue Description

**What happened?**

After a user loaded PR status, a page reload removed the learned state.
The browser persistence rules excluded link-preview queries. Related
implementation: #20593. Searches of open and closed PRs found no
duplicate reload-cache fix.

**Expected behavior**

Keep learned PR status across reloads for the same account and company.
Do not load every PR on page render. Reuse fresh results and refresh
stale results on hover or keyboard focus.

**Steps to reproduce**

1. Open a task with repeated GitHub PR links.
2. Hover one link and wait for its status.
3. Reload the page.
4. Before this fix, both links return to unknown until another hover.

**Paperclip version or commit**

Reproduced against master source at
`30403781b9418355c78dc0ed978366af77036e76`.

## What Changed

- Add link-preview queries to the existing user and company persistence
rules.
- Keep at most 100 previews across accessible companies. Use the
existing 24-hour retention, build boundary, and sign-out cleanup.
- Keep the original read time and freshness when restoring previews.
Other query types keep their existing restore behavior.
- Add regressions for saved status, negative results, bounds, age,
account changes, membership loss, and sign-out.
- Add a production-component Storybook example and browser reload
checks. Update the cache documentation.
- Use one query-key definition for the component, persistence rules, and
cache tests.

The browser read-cache module owns persistence and account cleanup. This
change extends that owner. The link service and provider API contracts
stay the same.

## Verification

- Red: the new reload and retention tests failed because a fresh client
had no saved PR data.
- Green: 102 focused UI tests passed across the cache, sign-out,
account-query, and query-client suites.
- UI typecheck passed.
- Storybook catalog generation and the static Storybook build passed.
- All 21 browser checks passed with retries disabled. They prove status
and negative-cache restoration after real reloads, no eager requests, no
fresh-hover refetch, and one stale-interest refresh shared by duplicate
links. Existing light/dark and narrow PR states, keyboard access, exact
URLs, and card positioning also pass.
- The first browser attempt could not launch because this session lacked
Chromium. Chromium was installed before the complete passing run; that
first attempt supplies no test verdict.
- `pnpm verify --explain` returned exit 2. The browser test path
requires broad CI coverage. No full local suite is claimed.
- The supplied staging page was not accessible through the web tool. No
deployed acceptance is claimed.
- CI on the first head failed in a Slack long-publication timeout and a
font-loading-frame browser check. Both files are outside this change.
The two publication cases passed when run locally in isolation. The font
case also passed locally against the built UI. [Final-head
CI](https://github.com/paperclipai/paperclip/actions/runs/42909433167)
passes on `e19d5b8bb1a910b1cbb9bb9a62ab87cdf684737f`. Typecheck, build,
tests, E2E, and Rust checks pass. There are 58 successful check runs,
three expected skips, and one neutral Paperclip Review check. The legacy
Snyk status passes.

Both Greptile and the supplemental Paperclip review report 5/5 on the
final head. No actionable findings or unresolved review threads remain.
The Paperclip Review check itself has a neutral conclusion.

## Risks

- PR display data now remains in browser local storage until expiry,
build change, or sign-out. Each query requires the matching user and an
accessible company.
- A stale saved status can remain visible for up to 24 hours. Hover or
keyboard focus refreshes it after the provider freshness window.
- Storage restrictions retain the existing memory-only fallback. No
schema migration is required.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools, code
execution, and browser automation. The runtime does not expose the exact
serving model ID or context-window size. No subagents were 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
- [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>
2026-11-10 17:43:06 -06:00
DottaandClaude Opus 5.5 2e6850e745 perf(task-chat): open tasks from a preloaded cache and cold tasks in one request (#20592)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - People open tasks from task lists, the Inbox and the sidebar many
times each hour
> - A task's chat shows its first frame only after about seven separate
reads finish
> - #20355 added one batched `chat-bootstrap` read that returns all of
these reads. #20450 used it only for the sidebar's Recents
> - The hosted database is CPU-bound, so the UI must open tasks faster
and must also send fewer requests
> - This pull request uses the bootstrap for task lists, for open intent
on any task link, for cold opens and for a hard load of a task URL. It
shares one coordination module between all of these paths
> - The benefit is that a task opens from cache more often, a cold open
sends one chat request instead of about seven, and the first content
paint comes sooner

## Linked Issues or Issue Description

Refs #20355 (server read that this pull request consumes). Refs #20450
(Recents preload, now on the shared path).

**Is your feature request related to a problem?**
A click on a task in a task list waited for about seven separate chat
reads. The default task list (Coordinator) had no preload at all. A
press or a resting hover on a task link sent the same seven reads, and
the page then sent a second set. A hard load of a task URL started its
chat reads only after the JavaScript bundle and the company list loaded.
Sidebar project task links had no open-intent prefetch.

**Describe the solution you'd like**
Use the single `chat-bootstrap` read everywhere a task is about to open.
Let the page's gating reads share a bootstrap that is in flight instead
of sending their own requests. Keep the core issue read separate so the
header does not wait for the larger read.

**Describe alternatives you've considered**
Preload 30 rows per list in three requests (the first prototype). Each
task costs the server about fifty queries, and a resting hover warms any
other row before the click, so this change preloads 10 rows in one
request.

## What Changed

- `ui/src/lib/issue-chat-bootstrap.ts`: one module decides when to send
a bootstrap and lets page reads share it (`readFromIssueChatBootstrap`).
Seeding uses the existing `issueChatBootstrapCache` (replace-style reads
are refreshed when newer, merge-style reads are filled only when empty,
other companies are ignored). It skips tasks that are loading, loaded in
the last 30 seconds, or already fresh in cache, under the id or the
identifier. It never aborts a request in flight, so a click that
unmounts the list still uses the answer. Each request is bound to the
account that sent it; a request sent before the first session read is
bound to the first account the session reports, and a sign-out revokes
it. A page read shares a bootstrap only while its key holds no data (a
refresh after a write always reads the server), and waits at most 5
seconds for it. Every ref of a task (id and identifier) is tracked at
request start. Entries expire after 30 seconds.
- Task lists preload the chat of their top 10 rows in one request, after
the list's own reads settle and the browser is idle
(`usePreloadTaskChats`, `when-page-settles.ts`): the Coordinator task
list (`TaskList` with `preloadCompanyId`), the streamlined and legacy
issues lists and the streamlined Inbox. Recents now uses the same path,
so a task in both places loads once. A list that stays open preloads
each task at most once in five minutes: live events (#20563's lanes)
keep a preloaded task's cache current or mark it stale, and a stale key
is read from the server, never from an older bootstrap. All seeded keys
are per-task keys on the fast live lane, not the ambient lane.
- Open intent (press, click, resting hover or focus) on a task with no
chat history in cache warms it with one bootstrap request instead of six
separate reads (`prefetchIssueDetailForNavigation`). A task seen before
still refreshes its cached reads one by one.
- A cold open starts one bootstrap. The gating reads (recent comments,
activity, runs, input facts, interactions, attachments, live runs,
active run) wait for it instead of sending their own requests. The core
issue read stays independent.
- A hard load of a task URL starts `GET /api/issues/:ref/chat-bootstrap`
from the inline preload script, in parallel with the JavaScript bundle.
The page adopts that answer.
- Sidebar project task links (Coordinator sidebar) get the same hover,
press and focus warming as Recents and `IssueLinkQuicklook`.
- Tests: unit tests for the coordination module, Recents, `TaskList`
preload, sidebar warming and the cold-intent path. A new layout journey
checks that a task the Inbox preloaded reveals once at its latest
message without reading its chat again. Journeys that hold one separate
read now also hold or fail the bootstrap. The Inbox reveal journey held
a read that the chat no longer sends on open (it was flaky on master
too), so it now holds the recent comment window.
- The perf harness gains `PAPERCLIP_ISSUE_PERF_LIST_DWELL_MS` and the
README documents it.

## Verification

Measurement setup: production UI builds of master (`e01fe7f84a` and the
earlier base `8d2bf78323`) and of this branch, served by one local
server with `PAPERCLIP_UI_DEV_MIDDLEWARE=false` and embedded PostgreSQL.
Only `index.html` changed between variants (both asset sets were
present), so the server, database and seeded task were the same. Four
rounds of the issue-detail harness, with master and branch alternated in
each round: 20 pairs per S1 cell, 40 per S2 cell. The host was shared
with other agents (load average 16 to 43), so the spread is large.

Content paint (the task is readable), median paired delta, branch minus
master:

| Scenario | Profile | Pairs | Content paint | Branch faster in | API
requests before content (master / branch) |
|---|---|---:|---:|---:|---:|
| S1 preloaded click (dwell 1500 ms) | unthrottled | 20 | -45 ms | 15/20
| 25 / 18 |
| S1 preloaded click (dwell 1500 ms) | Fast 4G, 4x CPU | 20 | -23 ms |
13/20 | 26 / 18 |
| S1 click, no dwell | unthrottled | 20 | -51 ms | 15/20 | 25 / 18 |
| S1 click, no dwell | Fast 4G, 4x CPU | 20 | -21 ms | 12/20 | 26 / 18 |
| S2 cold deep link | unthrottled | 40 | -61 ms | 28/40 | 54 / 40 |
| S2 cold deep link | Fast 4G, 4x CPU | 40 | -129 ms | 29/40 | 47 / 39 |

Medians (p25 to p75), content paint in ms: S1 dwell unthrottled 295
(283-315) → 248 (217-276). S1 dwell Fast 4G 1133 (1114-1226) → 1093
(853-1176). S1 no dwell unthrottled 295 (277-328) → 260 (233-332). S1 no
dwell Fast 4G 984 (842-1196) → 923 (816-1110). S2 unthrottled 1002
(677-1235) → 896 (619-1280). S2 Fast 4G 4643 (4155-5061) → 4516
(4298-4894).

Header paint is neutral to slightly later in two cells: S1 dwell
unthrottled +39 ms (3/20 faster) and S2 Fast 4G +56 ms (17/40). In those
cells the header now paints in the same frame as the content. In the
first two rounds, the median gap from header to content fell from
105-157 ms to 29-74 ms unthrottled and from 557 ms to 298 ms for S2 on
Fast 4G. Other header cells are -27 to +1 ms.

The local server answers each read in a few milliseconds, so the network
wait that the preload removes is small here. The request count is the
stable result: a cold open sends one chat request instead of about
seven.

List page: the preload request starts after the first task row is in the
DOM (64 ms after it unthrottled, 200-280 ms after it on Fast 4G, page
clock). Time to the first visible row, 30-task list: unthrottled master
1863 ms vs branch 1563 ms (6 pairs, branch faster in 5). Fast 4G master
3626 ms vs branch 3727 ms (14 pairs, branch faster in 6, mixed signs).
The preload starts after the row paints, so it cannot delay it; the Fast
4G difference is noise. One 10-task bootstrap took 78-162 ms on the
server.

Checks run:
- `pnpm exec vitest run` for the bootstrap, cache, prefetch, Recents,
sidebar, quicklook, `TaskList`, issues list, Inbox, issue detail and
Coordinator tests: 18 files, 646 tests pass.
- `pnpm exec tsc -p ui/tsconfig.json --noEmit`: clean, except missing
generated Storybook catalog files that this change does not touch.
- `pnpm check:token-gates`: clean.
- `tests/e2e/task-loading-frame.spec.ts`,
`disconnect-resilience.spec.ts` and `connection-reviews.spec.ts`
(production build): 21 passed, 1 skipped.
- `tests/perf/issue-detail/layout-stability.spec.ts` and
`plan-startup.spec.ts` against the production build: all pass. The Inbox
reveal journey and the new preloaded journey passed 4 of 4 repeats each.
- `pnpm verify` asks for broader CI coverage because an e2e spec
changed.

## Risks

- More server work for list views: one bootstrap of up to 10 tasks per
list visit (about fifty queries per task), skipped for tasks already
loaded in the last 30 seconds. It runs only after the page's own reads
settle and the browser is idle, and only while the tab is visible.
- A resting hover on a never-opened task now sends one bootstrap instead
of about six reads. The server work per task is about the same; the
request count is lower.
- If a bootstrap fails, each page read falls back to its own request.
The fallback is slower than before for that one open (the reads start
when the page mounts, not at the press).
- The header can paint a few tens of milliseconds later in the same
frame as the content when everything is cached.
- An answer that arrives after a sign-out or account change is dropped.
An answer for another company is ignored.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits, Playwright 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 <noreply@anthropic.com>
2026-11-10 14:26:27 -06:00
DottaandClaude Opus 5.5 9b21dcbdda refactor: remove SPEC docs and repoint spec citations (#20565)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Contributors and agents read the docs under `doc/` before they
change code
> - `doc/SPEC.md` and `doc/SPEC-implementation.md` were out of date.
`doc/SPEC-implementation.md` became an append-only log: in 60 days it
got about 1,645 new lines and lost 181, across 140 commits
> - #20557 changed `AGENTS.md` so the code and its tests are the source
of truth, and the specs are only background
> - This pull request removes the two spec docs. It moves only the
sections that live code comments cite into focused docs, and it repoints
all other references
> - The benefit is one less stale document that agents read and append
to

## Linked Issues or Issue Description

Refs #20557 (merged). This PR is the follow-up that removes the docs. It
is based on `master`, which already contains #20557.

**Issue type**
Outdated content. The docs are out of date and conflict with the code.

**Where is the issue?**
`doc/SPEC.md`, `doc/SPEC-implementation.md`, and the code comments and
docs that cite them.

**What's wrong?**
`doc/SPEC.md` and `doc/SPEC-implementation.md` are out of date, but
agents still read them and append a paragraph with each fix.

**Suggested fix**
Delete both files. Move the sections that code cites into focused docs.
Repoint the references.

## What Changed

- Deleted `doc/SPEC.md` and `doc/SPEC-implementation.md`.
- Moved these spec sections, because live code comments cite them:
- §9.3.1 "Shared default-open issue writes" → new section in
`doc/agent-permission-defaults.md`.
- §9.8.1 "Issue-thread interaction resolver contract" →
`doc/execution-semantics.md`, "Issue-thread interaction resolution".
That section already had most of the contract. I added only the missing
parts: the `supersedeOnUserComment` default, the canonical policy list,
the addressee and cap rules, the rule that surfaces show the server's
effective audience, and the cross-issue budget charge. The short policy
summary at the top of the section now points to the list, so the
definitions occur one time only.
- §9.9 "Task Watchdog Authority Contract" → new "Authority contract"
section in `doc/TASK-WATCHDOG.md`. It holds the allowed and disallowed
mutation lists and the deferred capabilities. The atomic recovery batch,
restoration verification, and interaction rules were already in
`doc/execution-semantics.md` §11, so the new section links there and
does not repeat them. I did not move the "Downstream acceptance
criteria" checklist or the sentence about the CTO and Security sign-off.
These were implementation-time notes; the tests now hold that coverage.
- §9.10 "Company Skill Policy Contract" → new
`doc/company-skill-policy.md`. No focused doc had this contract. I did
not move the "Required regression tests" phase checklist.
- Did not move any other part of the specs.
- Repointed code comments that cited spec sections:
- `packages/shared/src/issue-write-denial.ts` (§9.3) →
`doc/agent-permission-defaults.md`
- `server/src/routes/issues.ts` (§9.9) → `doc/TASK-WATCHDOG.md`,
"Authority contract"
- `server/src/routes/issues.ts` (§9.3, §9.8.1 cross-issue budget) →
`doc/agent-permission-defaults.md`
- `ui/src/lib/interaction-audience.ts` and
`ui/src/components/InteractionGovernancePanel.tsx` (§9.8.1) →
`doc/execution-semantics.md`
- `ui/src/lib/skill-policy-denial.ts`, `ui/src/pages/CompanySkills.tsx`,
`ui/src/pages/CompanySkills.production.tsx`,
`ui/src/pages/SkillStudio.tsx` (§9.10) → `doc/company-skill-policy.md`
- `server/src/services/recovery/ownership-invariant-report.ts` (monitor
deadline wait path) → `doc/execution-semantics.md` §8. That section
already lists the one-shot monitor as a valid wait path.
- Resynced the git blob pin for
`packages/shared/src/issue-write-denial.ts` in
`tests/runner-e2e/source-sdk-public-inputs.json`. The comment edit
changed the file's hash, and the Runner support suite checks that pin. I
used `node tests/runner-e2e/source-sdk-public-inputs.mjs <HEAD>
--write`; only that one pin changed.
- Updated `AGENTS.md` §1 so it does not name the deleted files.
- Removed the links to the deleted files from
`doc/plugins/PLUGIN_SPEC.md`.
- Updated the `doc-maintenance` agent skill
(`.agents/skills/doc-maintenance/`) so it no longer audits or stages
`doc/SPEC.md`.
- Removed or replaced references in `maps/README.md`,
`maps/feature-map/README.md`, `maps/storybook-map/README.md`,
`doc/PRODUCT.md`, `doc/CLIPHUB.md`, `doc/DEPLOYMENT-MODES.md`,
`doc/DEVELOPING.md`, `doc/evals.md`, `doc/spec/agent-runs.md`,
`doc/AGENTCOMPANIES_SPEC_INVENTORY.md`,
`doc/connections/CONNECTOR-PLAYBOOK.md`,
`doc/plugins/ideas-from-opencode.md`, `doc/execution-semantics.md`,
`doc/TASK-WATCHDOG.md`, and `.devin/wiki.json`.
- In `doc/execution-semantics.md`, the "admission is independent of
cleanup and accounting" reference now states the rule inline: only a
hard stop, an explicit pause, or an inactive company refuses admission.
- Kept these references as they are: dated plans in `doc/plans/` and
`docs/plans/`, the dated log
`doc/logs/2026-05-24-cli-api-parity-e2e-log.md`, `releases/`, the
transcript fixture `ui/src/fixtures/runTranscriptFixtures.ts` (it uses
the filename only as sample text), and
`docs/specs/external-task-protocol.md` (it refers to the Symphony
project's own `SPEC.md`).

## Verification

- `git grep -n "SPEC-implementation\|SPEC\.md"` (without `releases/`,
and ignoring `PLUGIN_SPEC.md` citations) finds only the kept references
listed above.
- `grep -rn "§9\.\(3\|8\|9\|10\)" ui/src server/src packages/shared/src`
finds no results.
- `node scripts/check-maps.mjs`: "Maps aligned: 35 areas, 192 capability
IDs".
- `pnpm check:token-gates`: "All gates clean".
- `pnpm exec vitest run --config tests/runner-e2e/vitest.config.ts
tests/runner-e2e/source-sdk-public-type.test.ts
tests/runner-e2e/source-sdk-evidence.test.ts`: 45 passed. These failed
in CI before the pin resync.
- `pnpm verify`: exit 2 ("Broader verification required") because
`AGENTS.md` is a global CI input. Required CI supplies the broad
coverage for this revision.
- All code changes are comments only. No runtime behavior changes. The
title uses `refactor:` because comments in source files changed.

## Risks

- Low risk. The change removes docs and edits comments only.
- A reader can lose context from spec sections that I did not move. The
text stays in git history. The code and its tests are the source of
truth.
- External links to the two deleted files will return 404.

> 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 5.5 (`claude-opus-5-5`), in Claude Code with
tool use (shell, file edits). Standard 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 (focused checks above; CI
supplies broad coverage)
- [x] I have added or updated tests where applicable (not applicable:
docs and comments only)
- [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 <noreply@anthropic.com>
2026-11-10 13:07:59 -06:00
Nicky LeachandPaperclip 98ce93dd4e Choose project storage from compatible environments (#20587)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Projects supply repository files to agent runs through core staging
or a storage plugin.
> - A storage plugin's default claim silently attached new projects even
when their agents used a different environment.
> - An environment name or driver type does not establish shared storage
access. The existing binding and task-driver contracts do.
> - This pull request groups creation choices by that contract and
selects storage from the creator's effective environment.
> - The benefit is a visible, validated choice and a clear error before
an incompatible managed workspace operation.

## Linked Issues or Issue Description

Fixes #20459.

Related: #20156 covers a separate provider-failure diagnostic. The PR
search found no other fix for #20459. ROADMAP.md has no duplicate
project-creation change.

## What Changed

- Show **Available in** when configured environments require different
project storage. Each option names the environments that can use the
same storage owner. Hide the selector when all choices are compatible.
- Derive compatibility from repository staging and managed binding
ownership. Local, SSH, and sandbox environments use core staging.
Managed storage requires a registered task driver from the same
installed storage plugin. Separate storage plugins remain incompatible.
Unknown plugin contracts and the fake sandbox are excluded.
- Follow the creating agent's environment, or the person's primary
agent, then the instance default and local fallback. Apply managed-only
and forced Kubernetes policy. The product has no separate user or
company environment-default field today.
- Add a company-authorized discovery endpoint and an optional creation
`environmentId`. Validate the choice again on save. Store the storage
owner without overriding agent run environments. Preserve explicit API
storage choices and existing project ownership.
- Keep the hidden repository-free private-task project independent of
environment availability. Submit the observed storage owner as
`expectedStorageProviderPluginId` so a changed contract fails at save
without setting a workspace policy. Instance task-isolation defaults
stay inherited.
- Reject an incompatible managed workspace before a provider operation.
Name the storage and selected environment in the error. Add regression
tests, API documentation, creation-story fixtures, and minimal
corrections to obsolete map text.

## Verification

- **102 tests passed across 7 files:** project environment integration,
installed storage policy, repository persistence, project environment
routes, project creation telemetry routes, OpenAPI, and
NewProjectDialog. Run with `node node_modules/vitest/vitest.mjs run
server/src/__tests__/project-environments.test.ts
server/src/__tests__/project-workspace-provider.integration.test.ts
server/src/__tests__/project-repositories-persistence.test.ts
server/src/__tests__/project-routes-env.test.ts
server/src/__tests__/project-goal-telemetry-routes.test.ts
server/src/__tests__/openapi-routes.test.ts
ui/src/components/NewProjectDialog.test.tsx --silent`.
- The review fixes cover both archived-default private-task cases and
changed-storage rejection. With both instance isolation flags enabled, a
new core repository project keeps a null policy and resolves to an
isolated Git worktree. The complete affected set passes all 102 tests.
- The CI query-error policy failure was fixed by using the shared query
view. The form and shared query-view suites pass **24 tests**, including
cached choices during a transient refetch failure. `node
scripts/check-query-error-rendering.mjs --enforce --summary` reports
zero findings.
- CI exposed stale SDK source hashes and heartbeat fixtures that paired
plugin storage with Local. Refreshed exactly four shared-source hashes
with the maintained generator (runtime pins unchanged). All **53 SDK
privacy/source-proof tests**, including real built SDK output, pass. All
**7 corrected managed-storage heartbeat scenarios** pass; the fixtures
declare the owning plugin's task environment and substitute transport at
the existing execution seam.
- Shared and UI TypeScript checks pass. The UI production build passes.
`pnpm check:token-gates`, `node scripts/check-maps.mjs`, `node
scripts/check-module-boundaries.mjs`, and diff checks pass.
- Tested UI states: compatible choices, incompatible groups, default
within a group, manual override, create failure, loading, discovery
failure and retry, and an unavailable default. Tests use the production
form and existing design tokens.
- The integration tests use real isolated PostgreSQL on macOS arm64 with
Node 26.12.0. They cover user/agent defaults, storage persistence,
policy restrictions, company and authentication denials, and rejection
before a provider call. Discovery uses saved configuration and makes no
worker or provider requests.
- Local verification limits: `pnpm -r typecheck` and `pnpm build` stop
in unchanged adapter-utils code because the installed ACPX types lack
`inheritProcessEnv`. A direct server check also finds an unchanged AI
SDK type-version mismatch. The existing heartbeat test `inherits the
instance default environment across companies while preserving explicit
agent overrides` times out at line 518 on both this change and
unmodified master with the same dependencies. The full `pnpm test:run`
attempt passed all six chat shards (1,948 tests), then the Git workspace
expiration suite reported 14 failures. I stopped that broad run. Two
sampled failures reproduce on unchanged master with the same
`unknown_worktree_git_files` refusal; the other expiration failures were
not independently compared. A later broad workspace-preparation run also
hit workspace validation failures and was stopped; the seven corrected
fixture scenarios pass independently. These limits do not establish a
full local suite pass. Hosted CI remains required.
- Initial missing-package and loopback-denial failures were resolved by
repairing isolated dependency links, building the SDK, and running
database tests with loopback access. No deployed or live-provider
execution is claimed.

## Risks

- Creation defaults now follow the effective environment. A plugin's
`defaultForProjects` claim alone no longer selects storage. This is the
intended behavior change.
- Compatibility covers repository project storage. It does not guarantee
arbitrary host paths, retained-workspace moves, adapter/image support,
credentials, or worker readiness. Matching provider configs cannot prove
a cross-plugin storage contract.
- A person's primary agent supplies the personal default because there
is no separate personal environment setting. An unavailable configured
default requires a valid explicit choice or a configuration repair.
- No migration runs. Existing projects keep their storage owner. This
change does not detach existing managed data or provide a storage
migration.
- Full local verification has the limits listed above. CI and standard
Greptile review are pending.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, tool use, and code
execution. The exact serving snapshot and context-window size 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>
2026-11-10 17:38:31 +00:00
DottaandClaude Opus 5.5 d82e5efeb9 perf(ui): cut repeated reads while a task is open (#20563)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI keeps an open task current with a shared company
websocket and with polling fallbacks
> - In a busy company, one open task sent 35 to 60 API requests in 17
seconds, and each request waits on the database
> - Most of these requests were repeated reads: task summaries 22 times,
the task run list 11 times, task browsers 6 times, and the 60 KB agent
list 6 times
> - The cause is not the open task. Other agents' run and task events
invalidate company-wide lists on every 300 ms batch, and the task view
polls runs and browsers on fixed intervals even while the websocket is
open
> - This pull request spaces company-wide refreshes, and it lets the
websocket replace run and browser polling while it is connected
> - The benefit is about 79% fewer reads from an open task in a busy
company, with the same polling fallback when the connection is down

## Linked Issues or Issue Description

Refs #17608
Refs #9627

**What happened?**

An open task in a busy company sent the same reads again and again. In
one 17-second visit, the UI requested `issues/summaries` 22 times, the
task live runs 11 times, the task browsers 6 times, and the company
agent list 6 times. The company live runs and the task's child lists
(`parentId`, `createdFromIssueId`, `descendantOf`) were requested 2 to 3
times each.

**Expected behavior**

A busy company must not make an open task refetch company-wide lists on
every event batch. While the live connection is open, the UI must use
events, not fixed fast intervals, for task runs and browsers. The UI
must keep its polling fallback when the connection is closed.

**Steps to reproduce**

1. Open a task in a company where other agents run work.
2. Keep the task open for 17 seconds.
3. Count the API requests in the browser network panel.

**Paperclip version or commit**

Based on master at bd30ef8510.

## What Changed

- `ui/src/lib/query-invalidation-batcher.ts`: Add a minimum spacing
option to the invalidation batcher. A quiet lane flushes after the
normal batch interval. A busy lane flushes at most once per spacing
window. Add `createLaneRoutingBatcher`, which sends each invalidation to
one of two batchers by query key.
- `ui/src/context/LiveUpdatesProvider.tsx`: Route company-wide keys to a
5-second ambient lane. These keys are task lists and summaries, the
agent list, heartbeats, company live runs, sidebar badges, the
dashboard, costs and the company activity feed. All other keys, such as
task detail, comments, task runs and browsers, keep the 300 ms lane.
- `ui/src/lib/live-runs-cache.ts`: While the company connection is open,
a successful active run list or active run polls every 15 seconds
instead of every 1 or 3 seconds. Run lifecycle events already refresh
these queries, and progress events patch them.
- `ui/src/hooks/useTaskBrowsers.ts`: While the company connection is
open, stop browser polling when no browser can change state. Keep the
3-second poll for a starting, running, idle or stopping browser, and
when the connection is closed. The two callers pass the task's company.
Socket open and reconnect already reconcile this active query.
- `ui/src/lib/task-browsers-refresh.ts` and `LiveUpdatesProvider.tsx`:
On `browser.*` activity for a task, refresh that task's browser list. If
a read is in flight, one shared follow-up read runs after it settles, so
an older empty result cannot stop polling. Two views of the same task
share that follow-up. A list that no view observes becomes stale, so a
returning view reads it. When the connection opens or resyncs, each
browser list with a read in flight also gets one follow-up read, because
missed activity is not replayed.
- `doc/DATABASE.md`: Document the ambient lane and the new polling
contract.
- Tests: Add a fan-out test that replays a busy-company event stream for
17 seconds against an open task, plus unit tests for spacing, lane
routing, key classification, the run interval and the browser interval.
Update the recovery test for the connected active-run interval.

## Verification

Request count over a fixed 17-second open window
(`ui/src/context/LiveUpdatesProvider.fanout.test.ts`). The test mounts
the reads that an open, running task keeps active. It replays a busy
company: one run status event every second and one task activity event
every 0.7 seconds, none for the open task. Fake timers make each run
deterministic, so the spread is zero. The baseline used the same test
with master's batcher, run interval and browser interval.

| Read | Before | After |
| --- | ---: | ---: |
| Task summaries (sidebar) | 24 | 5 |
| Company agent list | 17 | 5 |
| Company live runs | 17 | 5 |
| Child list: parent | 24 | 5 |
| Child list: created from | 24 | 5 |
| Child list: descendants | 24 | 5 |
| Task live runs | 18 | 2 |
| Task browsers | 6 | 1 |
| **Total** | **154** | **33** |

The baseline numbers match the staging capture well (summaries 22,
browsers 6).

- `pnpm exec vitest run src/context/LiveUpdatesProvider*.test.*
src/lib/live-runs-cache.test.ts
src/lib/query-invalidation-batcher.test.ts
src/hooks/useTaskBrowsers.test.ts src/lib/task-browsers-refresh.test.ts
src/hooks/useTaskPluginPanels.test.tsx
src/components/task-side-panel/TaskSidePanel.test.tsx
src/pages/IssueDetail.test.tsx` (in `ui/`): all pass.
- `pnpm exec tsc --noEmit -p ui`: no errors in `ui/src`.
- `pnpm check:token-gates`: clean.
- Tests also prove these cases:
- With the connection closed, task runs still poll every second and
browsers every 3 seconds.
- With a quiet company, one event refreshes the summaries and child
lists after one normal batch.
- `browser.started` on the open task refreshes its browsers. The same
event on another task does not.
- A browser that starts during a slow empty read appears after that read
settles (`ui/src/lib/task-browsers-refresh.test.ts`). This test fails
when the refresh joins the older read.
- Two views and repeated events during one read cause one follow-up
read. This test fails without the shared queue.
- A list cached while the task was closed is read again when a view
returns.
- A connection that opens during an older empty browser read causes one
more read, so a browser that started while the socket was closed
appears. This test fails without the follow-up.
- Task detail, comments, task runs, browsers, attention and another
company's keys stay on the fast lane.
  - An unrecognized active result keeps the fallback interval.

This is a local request-count proof. It does not include a timed browser
journey on a deployed instance.

## Risks

- Company-wide lists refresh at most once every 5 seconds from live
events, so they can be up to 5 seconds late. After 5 quiet seconds, an
event refreshes them in about 300 ms. Own mutations invalidate their
queries directly, so they do not wait.
- An active task run refreshes from events. Fields without an event,
such as output silence, can be up to 15 seconds late while the
connection is open. Reconnect still reconciles missed events.
- A browser that appears without activity is not shown until the next
activity or reconnect. Every browser lifecycle step logs activity on the
task, so this case is not expected.
- No server, schema or API change.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code
execution in 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
- [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 5.5 <noreply@anthropic.com>
2026-11-10 11:01:53 -06:00
DottaandClaude Opus 5.5 7329eaf353 perf(ui): open sidebar Recents tasks from cache (#20450)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI sidebar has a Recents list of the tasks a person opened
last
> - The Recents list renders from a local snapshot, but its links had no
warm-up. A click waited for the task-detail code and about nine
first-frame reads
> - Task lists (Inbox, Tasks) already warm a task on hover, press and
click, and #20355 added a batched `chat-bootstrap` read that had no UI
consumer
> - The hosted database is CPU-bound, so a fix must add at most one read
per page load and must not compete with the page's own reads
> - This pull request gives Recents the same open-intent prefetch as
task lists, warms the task-detail code, and preloads the top ten recent
tasks with one `chat-bootstrap` request after the page is idle
> - The benefit is that a click on a recent task shows its chat from
cache, without a wait for the API

## Linked Issues or Issue Description

Refs #20355 (server read this PR consumes). Builds on #20389, #20398 and
#20419.

**Is your feature request related to a problem?**
Opening a task from the sidebar's Recents was slower than opening it
from a task list. The Recents links had no prefetch. The task page
waited for its first-frame reads after the click.

**Describe the solution you'd like**
Recents links use the task lists' open-intent prefetch. When the page is
idle, one batched request loads the first frame of the most recent tasks
into the task page's caches.

**Describe alternatives you've considered**
Per-task prefetch of every recent task. That makes up to ten times nine
requests per page load, which the hosted database cannot afford.

**Additional context**
I searched open PRs and remote branches for "chat-bootstrap" and
"instant task opens". The only related work is the server-side branches
that merged as #20355. There is no other in-flight UI consumer of
`chat-bootstrap`.

## What Changed

- `ui/src/hooks/useIssueNavigationPrefetch.ts`: the open-intent handlers
from `IssueLinkQuicklook` move into one hook (light warm on pass-over;
`history: true` on a resting hover or focus, a mouse or pen press, and a
click). `IssueLinkQuicklook` now uses the hook. Its behavior does not
change.
- `ui/src/components/RecentTaskNavItem.tsx`: one Recents link wrapper
that applies the hook. The classic list (`SidebarRecentTasks`), the
Coordinator task rows and `DesignSidebar` use it. `SidebarNavItem`
accepts the handlers through a new optional `linkHandlers` prop.
- Classic Recents links now open `/issues/<identifier>` (Coordinator
already did). The old UUID link remounted the task page after a redirect
to the identifier route.
- `ui/src/hooks/useRecentTaskOpenWarmup.ts`: when Recents shows a task,
it fires the task-detail code warm-up (`useWarmTaskDetailCode`). After
two frames, a visible tab, no fetching queries and an idle callback, it
sends one `issues/chat-bootstrap?ids=` request for the ten most recent
tasks. It skips tasks whose first frame is cached and fresh. It sends no
request for an empty list or a hidden tab. It sends at most one request
per account per page load. Unmount or an account change aborts the
request.
- `ui/src/lib/issueChatBootstrapCache.ts`: `seedIssueChatBootstrap`
writes each field under the keys the task page reads, for the requested
ref, the id and the identifier: core issue, recent comments, comment
input facts (one batch, through the new `seedIssueIdBatch`), recent
activity, the recent run window (through the new shared
`buildRecentIssueRunWindow`), interactions, attachments, live runs and
active run. An explicit `null` active run is stored. A missing field is
not stored. It does not replace data that is as new or newer, data with
a read in flight, or data with a pending invalidation. Keys whose page
merges observed history (comments, activity, runs, input facts) are
filled only when empty. Items for another company are ignored.
- The seed fills the `?view=core` key, not the full detail key.
`seedIssueDetailCache` is not used: the core payload cannot stand in for
the detail read's related data, and seeding it there would suppress that
read.
- `issuesApi.chatBootstrap` client method.
- `tests/perf/collections/recent-task-open.spec.ts` and its README
section.

## Verification

Local disposable instance (`local_trusted`, production UI build,
embedded PostgreSQL, collection fixture plus 12 comments on each of 8
recent tasks, streamlined UI with the Coordinator sidebar). Journey:
open Projects with 8 Recents, wait for network idle plus 3 s, click a
recent task, measure until its latest comment is on screen. Each run
uses a fresh browser context (empty HTTP cache and storage apart from
Recents). Median of 5 runs, same server and fixture for both arms:

| Arm | Click to first chat frame | Idle page API requests |
| --- | --- | --- |
| `master` (b6f4008f9b), loopback | 227 ms (first run: 249 ms) | 24
(range 21–25) |
| This PR, loopback | 134 ms | 22 (range 21–27) |
| `master`, +150 ms on each API response | 455 ms | 19 |
| This PR, +150 ms on each API response | 135 ms | 20 (+1
`chat-bootstrap`) |

With added latency, the first frame no longer depends on API latency.

- `PAPERCLIP_COLLECTION_PERF_BASE_URL=http://127.0.0.1:<port> pnpm exec
playwright test --config tests/perf/collections/playwright.config.ts
recent-task-open.spec.ts recent-task-summaries.spec.ts`: 5 passed. On
`master` the two assertion cases fail (0 `chat-bootstrap` requests; 10
first-frame reads after the click).
- Reads that still follow a click on a preloaded task: full detail (`GET
/issues/:id`), `work-products`, `documents`, `feedback-votes`,
`tree-control/state`, `tree-holds`, `runner-goal`, `browsers`, and the
`read` mark. The `chat-bootstrap` payload does not cover them. None
gates the chat's first frame (the frame shows at 135 ms while every API
response waits 150 ms).
- Unit tests: `RecentTaskNavItem.test.tsx` (intent levels, href),
`useRecentTaskOpenWarmup.test.tsx` (one request for the top ten, code
warm-up, wait for page reads, hidden tab, fresh entries skipped, once
per account, user isolation, abort on unmount),
`issueChatBootstrapCache.test.ts` (each key in the endpoint shape,
`null` versus missing fields, company isolation, no downgrade of newer,
in-flight or invalidated data, merge keys filled only when empty).
- `ui` Vitest: `src/components src/hooks src/lib src/api` 694 files
passed; `src/pages src/App src/context` 188 files passed. `pnpm
check:token-gates`: clean. `pnpm verify` exits 2 because a `tests/` file
is a global CI input; required CI supplies the broad coverage.

## Risks

- Low to medium. One extra read per page load when Recents has tasks
without a fresh cached first frame. The read starts only after the
page's own queries settle, and the server reads at most two tasks at a
time.
- A live event that arrives while the preload is in flight, for a task
with no cached keys, can leave seeded data up to 30 s old until the next
live event or the next open after the stale time. Inbox hover prefetch
has the same window.
- Classic Recents links now use the identifier route instead of the UUID
route. The task page already redirected UUID links to this route.
- `tests/perf/collections` is a manual suite. CI does not run it.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, in Claude Code with
tool use (shell, file edits, browser automation through Playwright).
Standard 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
- [ ] 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.5 <noreply@anthropic.com>
2026-11-10 08:00:13 -06:00
a5e01634f6 feat(ui): ask self-hosted users to star Paperclip on GitHub after 5 done tasks (#20453)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Many teams run Paperclip self-hosted, so the project grows when
those users tell others about it
> - A GitHub star is the cheapest way for a user to do that, but the app
never asks
> - Asking too early is noise. A user who has finished a few tasks knows
whether Paperclip helps them
> - This pull request adds a one-time, dismissable modal that asks
self-hosted users for a star after their company has 5 done tasks
> - The benefit is more visibility for the project, and no repeated
prompt for the user

## Linked Issues or Issue Description

**Subsystem affected**

Board UI (dialogs shown in the main app shell).

**Problem or motivation**

Self-hosted users have no prompt to star the repository. Users who like
Paperclip often do not think of it. A prompt at the right moment helps
other teams find the project.

**Proposed solution**

Show a small modal once the selected company has 5 tasks with status
"done". The modal shows the Paperclip mark, the headline "You completed
5 tasks!", a short body, a "Star on GitHub" button and a "Not now"
button. It shows only once per browser. Closing it in any way counts as
seen.

**Alternatives considered**

A permanent banner or sidebar link. Both stay on screen after the user
decides, so they cost attention every day. A one-time modal costs
attention once.

**Roadmap alignment**

This is a small UI addition. It does not overlap with planned core work
in ROADMAP.md.

No related public issues or PRs found (searched for "star", "github
star").

## What Changed

- Added `GitHubStarPrompt` (`ui/src/components/GitHubStarPrompt.tsx`).
It reads the health endpoint and shows only on self-hosted instances
(never on Cloud), never during onboarding, and only after 5 done tasks
in the selected company. It waits for any other open dialog to close. It
stores a "seen" flag in `localStorage` when it opens, so a reload does
not show it again.
- Mounted the prompt in `BoardDialogs`.
- Exported the modal-detection helpers (`MODAL_SELECTOR`,
`useModalOpen`) from `AnnouncementWell` so both components use the same
rule.
- Added `PaperclipMark`, the mark alone from the existing lockup
geometry. The lockup and the mark now share one path constant.
- Added the `issues.starPromptDone` query key.
- Added tests for when the prompt opens and when it stays hidden.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/components/GitHubStarPrompt.test.tsx
src/components/AnnouncementWell.test.tsx`
- `pnpm check:token-gates` (all gates clean)
- `pnpm typecheck`
- Manual: on a self-hosted dev instance, mark 5 tasks in one company as
done. The modal opens. "Star on GitHub" opens
github.com/paperclipai/paperclip in a new tab. Reload: the modal does
not open again. To see it again, delete the
`paperclip.github-star-prompt.seen` key from `localStorage`.

## Risks

- Low risk. The change is UI only and additive.
- The prompt runs one small issues query (limit 5) while it is still
eligible. After it is seen, the query is disabled.
- If `localStorage` is not available, the prompt treats itself as
already seen and does not show.

> 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 Opus 5.5 (Anthropic), model id `claude-opus-5-5`, run through
Claude Code with tool use (shell, file edits). Standard context window;
extended thinking at low effort.

## 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: squadbot <squadbot@squadbots-Mac-mini.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:11:29 -08:00
Devin FoleyandClaude Opus 5.5 a9d0946e64 fix(ui): keep the persisted cache's final state when it cancels reads (#20431)
The persisted read cache cancels in-flight reads with revert: false when
it clears account data or evicts a denied task, then writes their final
state itself. A non-silent cancel settled later as a query error, which
overwrote that state with CancelledError and reached QueryCache.onError.
Cancel these reads silently so the written state stays.

Refs #20430

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 18:20:57 -08:00
Devin FoleyandClaude Opus 5.5 5570debc5b fix(ui): stop reporting cancelled queries to Sentry (#20430)
React Query's CancelledError keeps the default Error name, so the
name-based abort check never matched it. A query cancelled with
revert: false (for example by the persisted cache when an issue read
is denied or the account changes) reached the query cache onError as
an "unknown" error and was sent to Sentry. Recognize it by class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 17:43:34 -08:00
DottaandClaude Opus 5.5 50df9a19b9 perf(inbox): read only the execution workspaces Mine rows reference (#20388)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Mine inbox is the first page a board user opens to see the tasks
that need them
> - The page loaded the company's full execution-workspace summary list
and kept task rows hidden until that list arrived
> - On a company with thousands of workspaces, that read checks each
workspace row against the privacy predicate. It was the slowest request
on the page
> - The inbox needs workspace names only for the workspaces its rows
reference, and only when the view shows workspace data
> - This pull request adds an `ids` filter to the summary read, makes
the inbox request only referenced workspaces, and stops rows from
waiting for workspace data when the view does not show it
> - The benefit is that Mine rows appear when the task data arrives, and
the workspace read becomes a small bounded query

## Linked Issues or Issue Description

**What existing behavior does this improve?**
The Mine inbox (`/:companyPrefix/issues?view=mine`) shows its task rows
late on companies with many execution workspaces.

**Subsystem affected**
Board UI inbox and the execution-workspace summary API.

**Current behavior**
The page calls `GET
/api/companies/:id/execution-workspaces?summary=true` with no filter. On
a large company this returned 8,758 rows (2.5 MB) and took 12.3 s on the
server. The reveal gate included this read, so rows appeared at 13.4 s
although `inbox/mine` arrived at 1.6 s.

**Proposed behavior**
The inbox requests summaries only for the workspaces its rows reference.
Rows appear when the Mine task data arrives, unless the view shows
workspace data.

**Reason and benefit**
The workspace read becomes a small bounded query, and it leaves the
critical path for the default Mine view.

**Breaking changes**
None. The `ids` parameter is optional and additive.

## What Changed

- `packages/shared`: add `executionWorkspaceSummaryIdsQuerySchema`
(comma-separated UUIDs, 1–100 distinct values) and
`EXECUTION_WORKSPACE_SUMMARY_IDS_MAX`.
- `server`: `GET /companies/:companyId/execution-workspaces` accepts
`ids`. Bad input returns 422. The service adds
`inArray(execution_workspaces.id, ids)`. The company scope and the actor
read condition still apply, so privacy does not change.
- `ui/src/api/execution-workspaces.ts`: `listSummaries` accepts `ids`.
New `listSummariesByIds` reads ids in chunks of 100 and concatenates the
results.
- `ui/src/pages/Inbox.tsx`: the workspace query is keyed on the sorted
distinct `executionWorkspaceId`s of the rows the inbox holds (Mine,
touched, context and remote search issues). It uses `keepPreviousData`
and runs only when isolated workspaces are on and at least one id
exists.
- Reveal gate: rows wait for the workspace read only when workspace data
changes what the rows show. This is workspace grouping, a visible
Workspace column, or an active search (workspace names are searchable).
Otherwise rows reveal without it.
- Tests: route ids pass-through and 422 cases (non-uuid, empty,
separators only, more than 100); service test for the id filter with
company scope and the read condition; UI api chunking test; Inbox tests
for "rows reveal while the workspace read is pending when no workspace
column or grouping is active", rows waiting when the Workspace column is
visible, and "shows the resolved isolated workspace name" with the
id-filtered call.

Follow-ups (not changed here, they still read the unfiltered list):
- `ui/src/pages/LegacyInbox.tsx:772`
- `ui/src/components/IssuesList.tsx:985`
- `ui/src/components/LegacyIssuesList.tsx:888`
- `ui/src/components/NewIssueDialog.tsx:570` (filtered by project and
reuse, but not by ids)

## Verification

Focused checks on this revision:
- `cd server && npx vitest run
src/__tests__/execution-workspaces-routes.test.ts
src/__tests__/execution-workspaces-service.test.ts -t "summar|ids"`: 14
passed (embedded Postgres ran the service test).
- `cd ui && npx vitest run src/api/execution-workspaces.test.ts
src/pages/Inbox.test.tsx`: all passed.
- `pnpm --filter @paperclipai/server --filter @paperclipai/ui --filter
@paperclipai/shared typecheck`: passed.
- `pnpm check:token-gates`: clean.
- `pnpm verify` selected 2,481 Vitest files (the shared validator change
reaches most suites). I stopped it at a 25-minute limit on a heavily
loaded shared host. Before the limit, three server tests outside this
change failed: `agent-commentary-skills` (skill mount),
`agent-config-read-access` (24.8 s timeout) and
`agent-directory-working-copies`. Required CI supplies the full
coverage.
- Review fix `fc3f82dae0`: when workspace data is visible, the reveal
gate also waits while `keepPreviousData` supplies the previous id set's
summaries. A new test switches Mine → Recent to a new workspace and
checks the Recent row stays hidden until its workspace loads. It fails
without the change.
- Follow-up commits `bf34f84823` and `8fc743b5ca` add a 100-row Mine
test with a counted workspace id scan. It fails with the old `= []`
defaults and passes with the stable `NO_ISSUES` default.

Performance (local, synthetic fixture):
- Instance: isolated worktree instance, `authenticated` mode, signed in
as a non-admin company member so the real workspace privacy predicate
runs. Production UI bundle (`PAPERCLIP_UI_DEV_MIDDLEWARE=false`).
Headless Chromium 1440×900, a new browser context per run (cold HTTP
cache and cold query cache). macOS, Apple Silicon, with other local
sessions running.
- Fixture: 8,750 execution workspaces (each with a source task and a
privacy-source key), 38,750 tasks, 150 Mine tasks; the Mine snapshot
returns 100 rows that reference 100 workspaces.
- Finish event: the first `[data-slot="task-row"]` is in the DOM.

| Measurement | Before (`02bfe71a6b`) | After |
| --- | --- | --- |
| Cold time to rows, median of 7 | 1,209 ms (1,046–1,312) | 702 ms
(567–1,209) |
| Workspace summary read, server wait (time to first byte), median of 7
| 496 ms unfiltered, 2.0 MB | 18 ms with 100 ids, 22 KB |

The difference of medians is 507 ms. This is close to the unfiltered
read's wait on this fixture (about 550–590 ms in the browser runs). The
runs were sequential batches, not alternating pairs.

Limits: this fixture does not reproduce the 12.3 s server time seen on
the development instance. Here the unfiltered read cost 0.5–1.7 s and
grew with the company's task count. The browser gain on the original
instance should be close to that instance's unfiltered read time, but I
did not measure it there.

## Risks

- Low risk. The `ids` filter only narrows the existing query. It does
not change the read condition or the company scope.
- A row whose workspace is not readable by the actor shows no workspace
name, as before.
- When the Workspace column or grouping is visible, rows still wait for
the workspace read. That read is now small.
- When the id set changes, the query refetches all chunks. Before a view
first reveals, rows wait for the new summaries if workspace data is
visible. After that, the previous names stay visible while a refresh
loads.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edit, headless browser). Extended reasoning on.

## 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 <noreply@anthropic.com>
2026-11-09 18:02:55 -06:00
DottaandClaude Opus 5.5 8004ad069e perf(ui): skip socket reconcile for reads that have no live events (#20398)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI keeps company data fresh through `LiveUpdatesProvider`,
which listens on a WebSocket and invalidates queries for each event
> - The provider closes the socket while the tab is hidden, and events
are not replayed, so it invalidates every active query when the socket
opens and when the tab returns
> - Some of those queries have no live events at all: instance settings,
plugin UI contributions, and the person's workspace and sidebar
preferences
> - Reconciling them adds requests to every tab return and to the first
socket open, on a server that is already saturated
> - This pull request excludes those reads from the reconcile and gives
them a 5-minute stale time
> - The benefit is four fewer requests on each tab return and on the
first socket open, with no change to company data freshness

## Linked Issues or Issue Description

**What existing behavior does this improve?**
The burst of requests when a board UI tab regains focus, and the second
wave right after page load.

**Subsystem affected**
Board UI live updates (`ui/src/context/LiveUpdatesProvider.tsx`) and
query defaults (`ui/src/main.tsx`, new `ui/src/lib/live-reconcile.ts`).

**Current behavior**
On a local instance, returning to a hidden Mine tab sends 27 API
requests. Those include `instance/settings/experimental`,
`workspace-preferences/me`, `plugins/ui-contributions` and
`sidebar-preferences/me`. Live events never touch these. The same four
re-fire when the socket first opens after load.

**Proposed behavior**
The socket reconcile skips those four reads. Their stale time is 5
minutes, so a window-focus refetch runs at most every 5 minutes. Their
own mutations still invalidate them at once.

**Reason and benefit**
Each tab return costs four fewer requests. On staging, about 25 requests
re-fire together on focus, and every one delays the visible rows.

## What Changed

- New `ui/src/lib/live-reconcile.ts`: `LIVE_EVENT_FREE_QUERY_KEYS`
(instance settings, general and experimental settings, plugin UI
contributions, workspace preferences, sidebar preferences),
`isLiveReconciledQuery`, and `applyLiveEventFreeQueryDefaults`.
- `LiveUpdatesProvider` passes `isLiveReconciledQuery` as the predicate
on its invalidate-all paths: tab return, disconnected polling, socket
open, `live.resync`, and the transcript recovery that runs after a
resync. The first healthy open still also skips session and health.
- `main.tsx` applies the 5-minute stale time to those keys with
`setQueryDefaults`. No observer sets its own stale time for them.
- Not changed, and why:
- `auth/get-session` and `health` keep focus refetch and reconnect
handling for sign-out and reconnect detection.
- `cli-auth/me` (board access) keeps the reconcile because it detects
revoked access.
- The company list keeps the reconcile because live events invalidate
it.
- Company data (agents, chats, projects, approvals, labels, join
requests, live runs and others) keeps the reconcile. The socket is
closed while hidden, so the reconcile is how missed events arrive.
Keeping the socket open while hidden is a larger change and is not in
this pull request.
- Tests: new `live-reconcile.test.ts` covers the scope and the
stale-time defaults. The hidden-tab test in
`LiveUpdatesProvider.hook.test.tsx` expects the predicate.

## Verification

- `ui`: `npx vitest run src/lib/live-reconcile.test.ts
src/context/LiveUpdatesProvider src/lib/query-client.test.ts
src/lib/persisted-query-cache.test.ts` passes (283 tests).
- `pnpm verify` asks for broader CI coverage because `main.tsx` is
outside the resolved test graphs. Required CI provides it.
- Manual, on a local instance with the Mine page: hide the tab for 45 s,
then show it, and count `/api/` resource entries. `master` sends 27
requests and this branch sends 23. The four reads above are absent;
every company read is still present.

## Risks

- The reconcile was also how other tabs picked up changes to these
reads. A change made in another tab or browser now reaches this tab on
its next focus after 5 minutes, or on reload; before, it was the next
focus after 30 s. This covers an administrator changing an instance or
experimental setting or installing a plugin, and the same person
changing their workspace or sidebar preferences in another tab (for
example the coordinator or classic shell, or sidebar order). Changes
made in the same tab still apply at once.
- Low risk otherwise. Only the reconcile predicate and the stale time
change.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edits, browser). 1M-token context window. Extended
thinking was off.

## 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 <noreply@anthropic.com>
2026-11-09 17:42:54 -06:00
DottaandClaude Opus 5.5 b184e40108 perf(ui): serve every project list from one includeArchived read (#20395)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI reads the company project list in many places: the
sidebar, starred projects, the command palette, the new-task dialog, and
archive-aware pages such as the Mine inbox
> - Active-only consumers read `GET /projects`, and archive-aware ones
read `GET /projects?includeArchived=true`, under two React Query keys
> - A Mine load therefore fetched the same large payload twice (580–590
KB each on our dev instance)
> - The two responses differ only by archived rows; the server's active
list is exactly the rows where `archivedAt` is null
> - This pull request makes every consumer share the
`includeArchived=true` query, and active-only consumers filter it with
`select`
> - The benefit is one project read per page instead of two

## Linked Issues or Issue Description

**What existing behavior does this improve?**
Duplicate project list reads on page load.

**Subsystem affected**
Board UI project queries (`ui/src/lib/company-projects-query.ts` and its
consumers).

**Current behavior**
A cold Mine load sends both `/companies/:id/projects` (sidebar) and
`/companies/:id/projects?includeArchived=true` (inbox).

**Proposed behavior**
One `includeArchived=true` read serves both. Active-only consumers apply
`selectActiveProjects`.

**Reason and benefit**
The project list is one of the largest shell payloads. Reading it once
saves a large response on every page that has both kinds of consumer.

## What Changed

- New `companyProjectsQuery(companyId, { includeArchived? })` in
`ui/src/lib/company-projects-query.ts`. It always uses the
`includeArchived: true` key and fetch. For active-only use it adds a
stable, module-level `select` that keeps rows without `archivedAt`.
- 22 files that read the active list now spread
`companyProjectsQuery(...)`, and so do the plugin SDK project picker and
16 archive-aware pages that wrote the shared key and fetcher inline.
Their `enabled` and other options are unchanged. `OnboardingWizard`'s
one imperative read is unchanged. `pages/Inbox.tsx` already reads the
same key and fetcher, so it shares the entry without an edit.
- Invalidation: project mutations and live events invalidate
`queryKeys.projects.all(companyId)`, which matches the shared key. The
onboarding wizard invalidated the old active-only key; it now
invalidates `projects.all`.
- Tests: new `company-projects-query.test.tsx` (one request for both
consumers, correct filtering, and one refetch after `projects.all`
invalidation). Three existing tests (`CommandPalette`, `CompanyExport`,
`GitHubBotManagement`) now seed or expect the shared key.
- New browser check
`tests/perf/collections/shared-project-list.spec.ts`: after the Mine
inbox read completes, the page has sent no active-only project request.

## Verification

- `pnpm verify` passes.
- `ui`: the consumer suites (`CoordinatorSidebar`, `SidebarProjects`,
`SidebarStarredProjects`, `CommandPalette`, `NewIssueDialog`,
`AgentWorkPanels`, `CoordinatorNeedsYou`, `IssueDetail`,
`ProjectDetail`, `Projects`, `CompanyExport`, `ChatExecutionDefaults`,
`GatewaysList`, `Search`, `IssueProperties`, `persisted-query-cache`)
pass.
- Browser check:
`PAPERCLIP_COLLECTION_PERF_BASE_URL=http://127.0.0.1:<port> pnpm exec
playwright test --config tests/perf/collections/playwright.config.ts
shared-project-list.spec.ts`. It passes on this branch and fails on
`master`.

## Risks

- A page that needs only active projects now downloads archived rows
too. For most companies that is a small difference; the duplicate read
it removes is a full copy.
- The server does not order either list, and consumers sort for display,
so filtering on the client keeps behavior.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edits, browser). 1M-token context window. Extended
thinking was off.

## 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 <noreply@anthropic.com>
2026-11-09 17:42:47 -06:00
Dotta 98f49af70a feat: filter agents by model, status, harness, and connection (#20317)
Add shared filters to both All Agents layouts and views. Keep chart ancestors and bulk selections when filters hide agents. Use the same metadata for filter choices and table cells.

Add tests and update the filter journey and design guidance.
2026-11-09 14:21:38 -06:00
DottaandPaperclip c67909cb0f fix: keep tool approvals valid through OAuth refresh and delayed review (#20340)
## Thinking Path

> - Paperclip lets people manage agent work and approve app actions.
> - An approval must bind the action that the person reviewed.
> - OAuth access tokens can expire while that person is away.
> - The old target snapshot treated each token refresh as a new
authorization. It also imposed a one-hour review deadline.
> - This pull request separates token renewal from authorization
identity and keeps the human decision durable.
> - Current access, the reviewed arguments, meaningful target changes,
task closure, and execution receipts still control dispatch.
> - People can approve later without repeating a decision after routine
connection maintenance.

## Linked Issues or Issue Description

**What happened?**

A pending app action returned `approved_tool_target_changed` after its
OAuth tokens refreshed. The tool and connection settings were unchanged.
The same card also expired after one hour. A sign-in problem could
discard an accepted decision before provider dispatch.

**Expected behavior**

The original card remains reviewable after routine OAuth refresh and a
server restart. A later approval runs the exact reviewed action once. A
connection repair preserves the decision while Paperclip verifies the
same authorization. Meaningful changes require a new review.

**Steps to reproduce**

1. Connect an OAuth MCP app and configure an action to require approval.
2. Request the action, then refresh its access and refresh tokens before
approving.
3. Approve the original card. Before this change, it returns HTTP 409
even when the authorization is unchanged.
4. Wait more than one hour. Before this change, the card expires without
a task-specific deadline.

**Paperclip version or commit**

Reproduced from master at `76254ead2229`. The final branch is rebased
onto current master.

## What Changed

- Add versioned approval target comparison. New snapshots bind
credential identity epochs, consent, permissions, ownership, connection
configuration, tool contracts, and credential references. Authenticated
OAuth refresh preserves identity. Manual credential replacement does
not.
- Remove the implicit one-hour deadline from ordinary app action
reviews. Keep explicit deadlines and terminal-task expiry. Synchronize
the nullable interaction deadline in shared contracts and UI.
- Keep accepted, undispatched actions when sign-in is required. Show a
saved-approval repair state. Resume on committed connection changes
after target and access revalidation, without an idle retry loop.
- Preserve the existing execution claim, result receipt, and
uncertain-outcome no-replay rules.
- Explain meaningful target changes without inviting the person to retry
the stale card.
- Add a production task-thread Storybook example and document
compatibility and recovery behavior.

## Verification

- Red: production-refresher invalid-grant handling and formal-approval
withdrawal during a sign-in wait also reproduced as failing regressions
before their fixes.
- Red: before implementation, the local OAuth HTTP fixture failed for
both public and confidential clients with `approved_tool_target_changed`
after token refresh.
- Green: the extended fixture passes for public and confidential clients
with personal and shared grants. It covers actual local HTTP token
exchanges, multiple refreshes, a rebuilt gateway, an ended originating
run, approval after 25 hours, duplicate approval, preserved sharing
arguments, sign-in waiting, and event-driven repair. The gateways use
the production OAuth refresher; an invalid refresh preserves the
accepted decision before provider dispatch. Omitted-scope responses
cover equivalent scope-reporting changes. The provider is an isolated
local fixture, not a deployed Notebook service.
- Negative controls refuse changed consent, scopes, tool definition,
destination, and manual credential replacement before provider
execution.
- Existing gateway service, connected-tool acceptance, secret owner,
secret service, unsigned-review queue, and connection-health suites
passed. Explicit-deadline fixtures still prove expiry behavior,
including deadlines on accepted sign-in waits. A withdrawn formal
approval cancels a waiting action before connection recovery.
- Approval card, task-chat, error-copy and target-comparison tests
passed. Server and UI typechecks passed. Token gates and Storybook
catalog checks passed.
- Inspected the new saved-approval repair card in local Storybook using
the production task thread. It shows a reconnect link and no second
approval button or false running state.
- `pnpm verify --explain` returned exit 2 and requested broad CI
coverage. [Full
CI](https://github.com/paperclipai/paperclip/actions/runs/42677855507)
passed on `eb4f1c7caa9ee6e802c314ffc5eadec475a5de0c`, including build,
typecheck/release registry, general and serialized tests, and all eight
browser shards.
- Greptile reviewed the same final head at 5/5. Both findings from the
first review are fixed and their threads resolved; no open follow-ups
remain.

## Risks

- No database migration is needed. The new comparison reuses the vault
identity epoch. Only authenticated OAuth refresh code can preserve that
epoch; HTTP credential-write schemas cannot select that behavior.
- Earlier signed requests keep their original exact-version checks and
saved deadlines. Already-expired cards are not revived. A historical
card whose credentials already changed needs a fresh review.
- A new consent, account, permission set, destination, or manually
replaced credential must not inherit the old authorization. Reconnection
continues automatically only when revalidation proves the same saved
authorization.
- The interaction enrichment now permits a null deadline. Existing
string deadlines remain valid. The web client and shared validator
change together.
- This PR does not deploy to staging or perform a live Notebook
publication.

## Model Used

OpenAI Codex, GPT-6 family, with repository tools, code execution, and
browser inspection. The runtime does not expose the exact model ID,
context-window limit, or reasoning-mode label; those values are not
asserted.

## 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>
2026-11-09 13:46:28 -06:00
DottaandClaude Opus 5.5 189fefb991 perf(task-chat): warm the whole first frame before a task opens (#20286)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - People open tasks from the issues list and the inbox to continue a
conversation with an agent
> - The compact task chat shows its first frame only after about ten
reads settle. These include recent comments, activity, recent runs,
interactions, attachments and live runs
> - Navigation prefetch warmed only the issue and its recent comments.
The other reads started on the click, and the slowest one decided when
the chat appeared. On staging that took 1.1–2.0 s
> - This pull request warms the whole first-frame set when an open is
likely, under the exact keys the page reads
> - The benefit is that a task the person is about to open can render
from cache. A pointer that only passes over rows does not add load

## Linked Issues or Issue Description

**Problem or motivation**
On a busy instance, the composer and the latest messages appear 1.1–2.0
s after a click on a task row. The page waits for about ten reads.
Before the click, only two of them are warmed.

**Proposed solution**
Warm the rest of the first-frame reads when an open is likely: a mouse
or pen press, a click, keyboard Enter, or a hover or focus that rests on
the link for 120 ms. The page and the prefetch use the same fetchers and
query keys.

**Alternatives considered**
- Warm everything on every hover. This sends about 7 requests for each
row that a pointer crosses, so it was rejected.
- Warm only on press. The time from press to click is short, so most of
the wait would remain.
- A single bootstrap endpoint. This is planned separately, because it
changes the API contract.

**Roadmap alignment**
This is part of the task-loading performance plan in #20276. It follows
#20277 and #20284.

## What Changed

- New `ui/src/lib/issue-chat-history-queries.ts`:
- The recent activity and recent run window fetchers moved here from
`IssueDetail.tsx`, together with their query keys. The logic is
unchanged.
- `prefetchIssueChatHistory` warms activity, the run window,
interactions, attachments and live runs. It warms the active run only
for running work, as the page does.
- Live state uses a 5 s stale time. The other reads use the existing 30
s navigation stale time.
- The run-window prefetch uses the same conversation mode as the page.
Thus a reconnect in an agent conversation reads only the watched runs by
id.
- `prefetchIssueCommentInputFacts` warms the input facts of the recent
comments after those comments arrive.
- `ui/src/hooks/useIssueIdBatches.ts`: new `prefetchIssueIdBatches`
fills the same batch keys that the hook reads. The hook and the prefetch
share one batching function.
- `prefetchIssueDetailForNavigation` accepts `history: true` and then
also warms that set, plus the comment input facts.
- `IssueLinkQuicklook`:
- A hover still warms the light set at once. The quicklook trigger does
not start a history prefetch.
- A hover that rests on the link for 120 ms warms the full set. Leaving
the link before 120 ms cancels it.
- Keyboard focus uses the same 120 ms dwell as a hover. Blur cancels it.
  - A primary mouse or pen press and a click warm the full set at once.
- A touch can start a scroll, so it warms only the light set. The full
set waits for the click.
- The keyboard Enter handlers in `IssuesList`, `LegacyIssuesList`,
`Inbox` and `LegacyInbox` pass `history: true`.
- `IssueDetail.tsx` uses the shared fetchers and keys.

## Verification

- `cd ui && npx vitest run src/lib/issueDetailCache.test.ts
src/components/IssueLinkQuicklook.test.tsx
src/hooks/useIssueIdBatches.test.tsx` passes 29 tests. These are the new
ones:
- A `history` prefetch fills every key that the compact chat waits on,
with the same run-window shape that the page builds.
- A `history` prefetch fills the comment input-fact batch keys that the
chat reads.
- In an agent conversation, a reconnect reads the watched runs by id,
not every run.
- Prefetched id batches render with no loading state on the first
render.
  - The active run is read only for work that is running.
  - A light prefetch makes no history requests.
- A pointer that passes over a link in less than 120 ms warms only the
light set, with the quicklook on and off.
  - A hover that rests on a link warms the full set.
  - A mouse press warms the full set at once.
  - A touch warms only the light set until the click.
  - Keyboard focus warms the full set only after it rests on the link.
- The tests for the review fixes (quicklook hover, input facts,
conversation mode, touch and focus) fail without the fix.
- The `IssueDetail`, `Inbox`, `IssuesList` and `LegacyInbox` suites
pass: 4 files, 412 tests.
- `pnpm typecheck` in `ui` shows no new errors. The only errors are
existing ones from an unbuilt `plugin-sdk` package in this worktree.
- The staging harness from #20276 will measure the gain after deploy,
for a hover, a click, and a revisit.

## Risks

- More requests when an open is likely: up to 7 extra reads for each
task that is pressed with a mouse or pen, or hovered or focused for 120
ms. These are the same reads that the page would send on open, so a
completed open costs nothing extra. A hover that does not lead to an
open costs those reads.
- Freshness: a prefetched result can stay fresh for up to 30 s (5 s for
live state). Websocket invalidation still refreshes these keys, as on a
revisit today.
- No API or schema change.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used in Claude Code
with tool use (shell, browser automation, file 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
- [ ] 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.5 <noreply@anthropic.com>
2026-11-09 10:26:42 -06:00
DottaandClaude Opus 5.5 2a2d3f6e23 fix(ui): stop refetching the @task mention pool on every task event (#19931)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI keeps its caches fresh from live events in
`LiveUpdatesProvider`
> - Each task activity event invalidates the whole `["issues",
companyId]` query prefix
> - That prefix also holds the `@task` mention pool: the 100 most
recently updated tasks, about 315 KB per response
> - A chat turn emits many comment, status and run events, so an open
task page downloads that list again for almost every event
> - This pull request keeps the pool fresh from push events without a
refetch per event: a closed server flag marks real membership or title
changes, progress on pooled tasks reorders the cache in place, and other
progress refetches at most once per 30 seconds
> - The benefit is less bandwidth and CPU for users with an open task or
chat, and smaller eval traces

## Linked Issues or Issue Description

**What happened**
While a task chat page is open, the browser repeats `GET
/api/companies/:id/issues?limit=100&sortField=updated&sortDir=desc`
(about 315 KB each). One 10-minute chat-eval attempt recorded about 194
of these requests. The trigger is `invalidateActivityQueries` in
`ui/src/context/LiveUpdatesProvider.tsx`. For every `entityType:
"issue"` activity event, it invalidates
`queryKeys.issues.list(companyId)`. That prefix matches
`queryKeys.issues.mentionPool(companyId)`, which `IssueDetail` (and
`NewIssueDialog`) keep active for the `@task` picker. Invalidation
refetches active queries and ignores `staleTime`.

**Expected behavior**
Comments, run progress and status changes on any task do not download
the full mention pool again. The pool still updates when a task that the
picker can show is created, deleted or renamed.

**Steps to reproduce**
1. Run `pnpm dev` and create a company with about 140 tasks.
2. Open a task page, for example `/REF/issues/REF-1`.
3. Every 4 seconds for 15 rounds, post comments on the open task, on a
task in the top 100, and on a task outside the top 100, and change the
open task's priority (60 events).
4. Count requests to `issues?limit=100&sortField=updated`. Before this
change there are 15 (about 3.3 MB). After this change there are 2 (about
480 KB).

## What Changed

- New shared helper `issueActivityChangesTaskIdentity`
(`packages/shared/src/issue-activity-identity.ts`). It is true for a
created, deleted, hidden, unhidden or renamed task.
- `server/src/realtime/live-events-ws.ts`: task `activity.logged` events
now carry the closed boolean `taskIdentityChanged`. Authenticated
sockets still get no `details`; the flag carries no task content.
- `ui/src/context/LiveUpdatesProvider.tsx` refreshes the mention pool
this way:
- The task identity flag is true (or full details show a create, delete,
hide, unhide or rename): the pool refetches at once.
- Other events on a task already in the pool: the task moves to the
front of the cached list. There is no request. The freshness clock does
not change, and a pending invalidation is never overwritten.
- Other events on a task outside the pool: one trailing refetch at most
every 30 seconds, because the task can now be in the top 100.
  - The picker is closed: the pool is only marked stale.
- `ui/src/lib/query-invalidation-batcher.ts`: new `excludingQueryKeys()`
keyed exclusion predicate. The batcher coalesces it and uses it for
prefix coverage. The broad `["issues", companyId]` invalidation uses it
to skip the pool, so a burst is again one broad refresh per batch.
- Tests: `ui/src/context/LiveUpdatesProvider.mention-pool.test.ts`
(authenticated event shape, hide and unhide, the 30-second window,
reorder safety, a burst through the batcher), new batcher tests,
`packages/shared/src/issue-activity-identity.test.ts`, and socket
assertions in `server/src/__tests__/live-events-toast.test.ts`.

## Verification

- `cd ui && npx vitest run src/context/LiveUpdatesProvider
src/lib/query-invalidation-batcher`: 231 tests pass.
- `cd server && npx vitest run src/__tests__/live-events-toast.test.ts`:
11 tests pass. In authenticated mode the socket keeps `details` off and
sends `taskIdentityChanged` (false for routine updates, true for a
create, rename or hide).
- `cd packages/shared && npx vitest run
src/issue-activity-identity.test.ts`: 14 tests pass.
- The new UI tests fail against the first commit of this PR: the
authenticated burst, hide, unhide, the 30-second window and the batcher
burst.
- `tsc --noEmit` for ui, server and shared: no new errors.
- Browser measurement: a local `pnpm dev` instance (local trusted),
headless Chromium on a task page, 141 tasks, 60 events over about 63
seconds (the reproduction steps above):
  - Before (master UI): **15** mention-pool requests, about 3.3 MB.
  - After: **2** requests, about 480 KB (one per 30-second window).
- Freshness after the fix: one rename, hide, unhide or create each
caused **1** refetch. A priority change caused **0**.
- I did not run a browser session in authenticated mode. The
authenticated event shape is covered by the socket test and the UI unit
tests.

## Risks

- A task that moves into the top 100 can be missing from the `@task`
picker for up to 30 seconds.
- The cached order is approximate between refetches: a moved task goes
to the front, but its `updatedAt` value is not changed.
- Other queries under `["issues", companyId]` (descendants,
created-from, summaries, onboarding first task) still refresh for each
event batch. They are small, but they can get the same treatment later.

## Model Used

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), with tool use and code
execution in 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:55:39 -06:00
DottaandClaude Opus 5.5 7fd98682ca fix(workspaces): give every subtask its own execution workspace (#19860)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents split work into child tasks, and each task runs in an
execution workspace (a checkout, branch, or sandbox folder)
> - Since early 2026, a new child task used its parent's realized
workspace by default: the same checkout, branch and sandbox
> - A parent and its child often run at the same time. Two writers in
one folder lose each other's files, the waking parent is cancelled with
`workspace_busy`, and sandbox lease ownership crosses from the parent
into the child, so retained-file recovery refuses the workspace as
ambiguous
> - The product rule is simple: a subtask gets its own workspace,
independent of the parent's saved files. Paperclip never needs to carry
a parent's unmerged changes into a child
> - This pull request makes that the only behaviour on every creation
path, and moves existing shared children to their own workspace on their
next run, with no user or admin action
> - The benefit is that parents and children never block or overwrite
each other, and recovery ownership stays with one task

## Linked Issues or Issue Description

**What happened?**
A child task inherited its parent's realized execution workspace by
default (`createChild` defaulted to `executionWorkspaceInheritanceMode:
"linkage"`, and `issueService.create` fell back from
`inheritExecutionWorkspaceFromIssueId` to `parentId`). When the parent
woke while the child wrote to the shared workspace, the parent run was
cancelled with `workspace_busy`. In sandbox environments, the lease
ownership chain crossed from the parent into the child, and
retained-file recovery refused the workspace with
`allocation_ownership_ambiguous`.

**Expected behavior**
Every new child task gets its own execution workspace, realized from the
project base exactly as a top-level task in that project. It never uses
the parent's checkout, branch, saved snapshot or unmerged changes. A
parent and its descendant never share a workspace by any route.

**Steps to reproduce**
1. Run a parent task in a project with isolated (git worktree) or
sandbox workspaces, so the parent has a realized execution workspace.
2. From the parent run, create a child task with only `parentId` (or
through the runner `create_task` tool, chat suggestions, decision
effects, plugins, or the New sub-task dialog).
3. See that the child's `executionWorkspaceId` is the parent's, with
`executionWorkspacePreference: "reuse_existing"`.
4. Wake the parent while the child runs in a sandbox environment. The
parent run is cancelled with `workspace_busy`.

**Additional context**
Refs #9233 (the existing `strategy_only` opt-out).
The opt-out `executionWorkspaceInheritanceMode: "strategy_only"` already
existed for accepted-plan decomposition. This change makes that
behaviour (tightened to drop a pinned existing branch) the only
behaviour.

## What Changed

- **One rule for every creation path
(`server/src/services/issues.ts`).** `issueService.create` still uses
the parent (or `inheritExecutionWorkspaceFromIssueId`) as a source for
the project, project workspace and workspace *strategy*, but it never
copies the source's `executionWorkspaceId`, `reuse_existing` preference,
or pinned existing branch. All server paths that create a task go
through `create()`/`createChild()`: issue routes (`POST /issues`, `POST
/issues/:id/children`, the run-bound default parent and run workspace
source), runner `create_task`, chat suggestion acceptance, native status
decisions, accepted-plan decomposition, decision effects, plugins
(`ctx.issues.create`), external agents, watchdog follow-ups, monitor
recovery issues, case handoffs and email channels.
- **Explicit ancestor workspaces are dropped.** If a create names an
`executionWorkspaceId` (or an `existing` workspace selection) that an
ancestor of the new task created or is bound to, `create()` drops it and
keeps only the strategy. Older clients and skills that copied the
parent's id keep working and get an independent workspace.
- **`createChild` no longer has a "linkage" mode.**
`executionWorkspaceInheritanceMode` is still accepted from internal
callers and is ignored.
- **`inheritExecutionWorkspaceFromIssueId` decision.** I kept the field
for API compatibility, but it now copies only the source issue's project
and workspace strategy. I removed its workspace-sharing behaviour for
every pair, not only parent/child: the source task and its follow-up can
run at the same time (the runner, watchdog and status-decision callers
all create the follow-up while the source run is live), so sharing would
again let two tasks write one folder. Explicit sharing between unrelated
tasks stays available through an explicit `existing` workspace
selection.
- **New `server/src/services/task-workspace-lineage.ts`.**
`buildIndependentTaskWorkspaceSettings` (strategy only: mode,
environment, runtime, network egress, shared-workspace concurrency, base
ref, branch template, commands; no `reuse_existing`, no
`operator_branch`, no `existingBranch`), `loadTaskLineageIssueIds` (the
parent chain plus, for a task that depth placement put beside its
requester, the requester's chain via `privacyParentIssueId`) and
`isAncestorExecutionWorkspace` (the workspace's `sourceIssueId` is in
that lineage, or a lineage issue is bound to it). A child in another
project does not take the parent's strategy.
- **Upgrade-safe rebind at admission.**
`executionWorkspaceService.releaseAncestorWorkspaceBindingAtAdmission`
runs at the same admission point as the pending workspace selection
(`heartbeat.ts`). For a child (or a flattened task beside its requester)
that an older release bound to an ancestor's workspace, it clears the
child's binding (keeps the project workspace and strategy, bumps the
binding revision) so the run realizes the child's own workspace. It does
nothing while another run of the child is running, while the child's
earlier run still owns workspace finalization or recovery, for native
restart/lease-owner recovery, for a process-loss retry, or for a Retry
that keeps the current workspace. The parent and its workspace are never
changed.
- **Projects without a project workspace (per-task repository
acquisition).** A parent with no project workspace worked in its own
task files, so its settings (for example `isolated_workspace`) do not
describe the project base. A child of such a parent, and an older child
released from one, takes the project's defaults exactly like a top-level
task, instead of an isolated mode that would resolve to a git worktree
with no git base ("needs a project / project workspace"). When the
child's new workspace is created at admission,
`executionWorkspaceRepositoryService.prepareForAdmission` repeats the
parent's (or requester's) repository requests as the child's own pending
requests (identity, URL, catalog id and requested ref only; no pinned
commit, branch or files). The normal acquisition path then clones a
fresh copy into the child's task files with the child's own access
checks. This runs only for a workspace created by that admission, so
existing tasks never gain repositories.
- **UI.** The New sub-task dialog no longer pre-selects the parent's
workspace. `buildSubIssueDefaults` drops `executionWorkspaceId` and
`reuse_existing`, and derives the parent's independent mode
(`shared_workspace`, `isolated_workspace` or `agent_default`) from its
preference, saved settings or current workspace. The "This sub-task will
no longer use the parent task workspace" warning is removed.
- **Contracts and docs.** Shared validator and plugin SDK comments
describe the new meaning of `inheritExecutionWorkspaceFromIssueId`.
Updated `doc/execution-semantics.md` (new rule in §6 and a wording fix
in the retained-sandbox paragraph), `doc/SPEC-implementation.md` (child
workspace rule, requester keeps *strategy* inheritance, watchdog
follow-ups), `doc/SPEC.md` (task workspace ownership),
`docs/specs/external-task-protocol.md`, `skills/paperclip/SKILL.md`
(step 9 and the workspace bullet),
`skills/paperclip/references/issue-workspaces.md`, the CEO onboarding
`HEARTBEAT.md`, and the plugin SDK README.

### Previous-release story (`rules/upgrade-safe-run-state.mdc`)

- **Migrated on next admission.** A child that the previous release
bound to its parent's (or any ancestor's) execution workspace keeps that
binding until its next normal admission. Then the server clears the
binding and the run realizes the child's own workspace from the project
base. No schema migration is needed. No user or admin action is needed.
- **In-flight writers finish first.** A running child is not
interrupted. Its current run, its native restart recovery (admitted
native input or lease owner), a process-loss retry, and a Retry that
keeps the current workspace all finish in the shared workspace. While a
child's earlier run is still running or its workspace finalization or
recovery is unsettled, the binding stays and a later admission releases
it.
- **No files are dropped.** The release changes only the child's binding
row. The parent keeps its binding, and the shared workspace row, folder,
branch and saved snapshots are not touched. Anything the child left
there stays in the parent's workspace.
- **No new refusal codes.** Saved run identity (native execution input,
leases, checkpoints) is not changed or refused; the rebind only affects
new admissions.

## Verification

- New `server/src/__tests__/subtask-workspace-independence.test.ts`
(embedded Postgres): `create` with `parentId`, `createChild` with no
mode / `"linkage"` / `"strategy_only"`,
`inheritExecutionWorkspaceFromIssueId` pointing at the parent, explicit
parent and grandparent `executionWorkspaceId`, a non-child follow-up,
and unchanged top-level defaults and explicit top-level reuse. 7
creation cases fail on `master` and pass with this change. Admission
cases: an old-release child is rebound while its parent is running and
the parent keeps its workspace; a running child is not rebound (deploy
lands mid-run); an unrelated explicit share is kept; the parent is never
rebound.
- New heartbeat case in
`server/src/__tests__/heartbeat-workspace-busy.test.ts`: an old-release
child bound to its running parent's workspace in a sandbox environment
runs to `succeeded` with no `workspace_busy`, gets a new workspace, and
the parent run and binding stay unchanged. It fails on `master` (the
child waits with `workspace_busy`).
- Updated tests that asserted the old inheritance in
`issues-service.test.ts`, `task-workspace-binding.test.ts`,
`chat-project-tools.test.ts`, `task-workspace-child-trust.test.ts`,
`heartbeat-git-restore-point-resume.test.ts` (a child bound to a closed
parent's expired workspace now starts from the project base and leaves
the parent's restore point alone), and
`ui/src/lib/subIssueDefaults.test.ts`. Remote-workspace reservation,
continuation and writer-wait suites now seed the older-release shared
shape directly (or an unrelated explicit share), so those safety paths
stay covered.
- `npx vitest run` on the changed suites and about 45 related server
suites (heartbeat workspace, recovery admission, issue routes, routines,
plugins), `ui` `subIssueDefaults` and `NewIssueDialog` tests, `pnpm
check:token-gates`, `pnpm -r typecheck`.

- New
`server/src/__tests__/heartbeat-subtask-acquired-repository.test.ts`: a
project with no project workspace, a parent whose task files hold an
acquired repository with an unmerged change, and a child (new, and the
older-release shape bound to the parent's workspace) that runs to
`succeeded` in its own task files with a fresh clone at the project base
and its own ready repository receipt. The parent's checkout keeps its
change. Both cases fail on the previous head with "needs a project /
project workspace".

## Risks

- **Project-level configuration still applies to children exactly as to
top-level tasks.** In a project configured for `shared_workspace`, every
task (top-level or child) uses the project's one folder under the
existing shared-workspace concurrency policy. A branch that the project
itself pins applies to every task in the project. This PR removes only
the parent-to-child carry-over of the realized workspace; it does not
change those project defaults.
- **Standalone follow-ups that an older release bound through
`inheritExecutionWorkspaceFromIssueId`** are migrated only when the
source is in their recorded lineage (parent or privacy parent). Others
keep their explicit share and the existing writer-wait serialization.

- **Behaviour change for agents that relied on a shared checkout.** A
child or follow-up no longer sees the parent's unpushed or uncommitted
changes. Agents must push or merge code they want a subtask to build on.
The skill and onboarding text now say this.
- **Explicit ancestor workspace ids are silently dropped** instead of
rejected, so older clients do not break. The task gets an independent
workspace.
- **First run of a migrated child starts from the project base.** If an
older child had uncommitted work in the parent's workspace, that work
stays in the parent's workspace and is not copied into the child's new
workspace.
- **Admission adds one indexed read** for tasks with a parent and a
bound workspace; the locking transaction and ancestor query run only
when the bound workspace was created by another issue.

## Model Used

- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used through
Claude Code with tool use (file edits, shell, test execution) and
extended 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:34:09 -06:00
DottaandClaude Opus 5.5 824b25654c fix(ui): keep a Retry for retained-file holds whose source cannot be verified (#19840)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - When a native run finishes but its files cannot be saved, the task
stays blocked behind a retained-file recovery hold.
> - The server records that hold as blocked and retryable, and the task
notice tells the user to "Retry to try again".
> - The task page hides its own Retry for these holds and relies on the
retained-file recovery panel instead.
> - That panel only binds the recovery record when a read-only source
inspection returns `verified`.
> - Inspection can return `blocked` for normal reasons: a sandbox that
predates ownership receipts (state saved by an older release), or a
workspace that moved on after its files were recovered.
> - Then neither surface offers an action, and "Check file recovery
status" always answers "File recovery changed. Refresh the task", also
after a reload.
> - This pull request binds the record by its own identity, uses
inspection only to gate the physical file-recovery request, and keeps
the task Retry visible whenever the panel has no control of its own.

## Linked Issues or Issue Description

Refs #18848 (the change that added the retained-file recovery panel and
hid the generic Retry for these holds).

**What happened?**

A blocked task with an active
`native_workspace_sync_out_retry_exhausted` hold showed "The saved files
need recovery before work can continue", "The recorded source is
unavailable or has changed", and "Saving the agent's files failed after
automatic retries. Retry to try again". It showed no Retry and no
recover button. "Check file recovery status" always answered "File
recovery changed. Refresh the task to inspect the current recovery", and
a reload did not change it. The hold was saved by an earlier release:
`systemRecovery` exhausted with `allocation_ownership_ambiguous` /
`service_required`, and `workspaceRecovery` blocked with `retryable:
true`. The source inspection endpoint returned `status: "blocked"` for
it. A second task whose files were already recovered showed the same
"need recovery" and "has changed" text, because its inspection reported
a reference mismatch after recovery.

**Expected behavior**

A retryable hold always shows a working Retry or recover control. A
notice that says "Retry" has a Retry button. Status checks never loop on
"changed, refresh". Recovered files show as recovered. When the source
cannot be verified, the page names the reason and who must inspect it.

**Steps to reproduce**

1. Use an active retained-export hold whose `workspaceRecovery` is
blocked and retryable, and whose `systemRecovery` is exhausted.
2. Make the source inspection (`GET
/api/issues/:id/recovery-actions?inspectSourceRunId=…&inspectSourceActionId=…`)
return `status: "blocked"`.
3. Open the task as a board operator.
4. Before this change, the page shows no Retry and no recover button.
"Check file recovery status" reports "File recovery changed. Refresh the
task".

**Paperclip version or commit**

Observed on build `8812c19`. The regressions fail on master `0f5ce5050e`
before the production edits.

## What Changed

- `WorkspaceExportRecovery`: bind the recovery record by action, run and
schema identity, not by inspection status. Inspection status now gates
only the physical "Recover saved files" request (and the request
re-checks it before POST).
- `WorkspaceExportRecovery`: report through `onRecoveryControlChange`
whether the panel offers its own control. It reports `true` only while
the source is loading or verified and the files are not yet recovered,
or while it shows the previous-server stop confirmation.
- `IssueDetail`: hide the task Retry for retained-export holds only
while the panel reports that it owns the control. Otherwise the existing
task Retry (`POST /issues/:id/retry`) shows. The server renews a bounded
recovery generation and re-verifies the source.
- Show recovered files as recovered even when inspection no longer
matches after recovery.
- Name an unverifiable source in plain words (with the code for other
reasons). State that Paperclip does not apply files it can't verify.
- Recovery text never asks a person to inspect a sandbox or environment,
and never names an owner or administrator as the one who must act. This
covers the retained-file panel, the run source inspector, the copyback
wait, and the native cleanup quarantine message. The execution blocker
and the recovery action read model describe a spent recovery budget from
its current state, so text saved by older releases ("If Retry keeps
failing, inspect the run and its environment") is not shown.
- Task Retry uses the existing `createUuid()` helper, so it works on
plain-HTTP previews without `crypto.randomUUID`. An accepted Retry also
refreshes the inspected file-recovery state. After a failed accepted
request, the unverified-source reason stays visible; it hides only while
recovery runs.
- When the recovery record was replaced, "Check file recovery status"
reloads the task detail instead of telling the user to refresh.
- Tests: component regressions for an old-release hold, a recovered hold
with a mismatched source, verified ownership, and a replaced record. A
page regression proves Retry is shown and calls `retryTask` for
unverified and stale sources. A server safety test proves that Retry for
the old-release hold shape is accepted, opens one new bounded
generation, queues file recovery again, keeps the hold active, and is
idempotent.

## Verification

- RED (master `0f5ce5050e` production files, new tests): 5 component
tests fail. The failure output shows the exact staging text: "Check file
recovery status", "File recovery changed. Refresh the task to inspect
the current recovery.", "The recorded source is unavailable or has
changed." 2 page tests (unverified and stale) fail with `expected [
'Check file recovery status', … ] to include 'Retry'`.
- GREEN: `cd ui && npx vitest run
src/components/WorkspaceExportRecovery.control.test.tsx
src/components/WorkspaceExportRecovery.test.tsx
src/components/RunWorkspaceRecoverySurface.test.tsx
src/components/ExecutionBlockerNotice.test.tsx
src/pages/IssueDetail.test.tsx` passes 320 tests.
- Server: `cd server && npx vitest run src/services/task-retry.test.ts`
passes 81 tests, including the new old-release safety case. This is
safety evidence, not a red: the server path was already correct, and the
defect was in the UI.
- Full UI suite: 11564 pass. 4 tests in `IssuesList.test.tsx` and
`CompanyEnvironments.test.tsx` failed under full-suite load and pass in
isolation (106/106). They are unrelated to this change.
- `pnpm check:token-gates` is clean. `node scripts/run-tsc.mjs -p
ui/tsconfig.json --noEmit` reports no errors in the touched files. The
remaining errors are local artifacts that were not built (plugin SDK UI
entry, generated Storybook catalog).
- Staging was read only (GET requests). The inspect request did not loop
in a fresh tab: two calls in 30 seconds. Most of the tab's request
volume is periodic company issue-list refetches, which this change does
not touch.

## Risks

- Low risk. This is a UI-only behavior change. Server authorization,
source verification, stop receipts, and the bounded recovery policy are
unchanged and remain authoritative.
- The task Retry is now visible for retained-export holds whose source
does not verify. Retry renews one recovery generation. The worker
re-runs the same source-identity checks, so Retry cannot bypass an
ownership refusal. If the refusal is permanent (for example an ambiguous
sandbox allocation), the hold returns to exhausted and the page names
the reason. An automatic fallback for such sources is follow-up work.
- Wording: a parametrized UI test renders every recovery state and
asserts that no administrator, owner, or "inspect the environment" copy
appears. A server test proves that a stored old-release message is
replaced by state-derived text. Both failed before the change.
- Previous-release story: the old-release hold shape (board-owned,
`systemRecovery` revision 5 exhausted with `service_required`,
`workspaceRecovery` blocked and retryable) is covered by component,
page, and server tests. No persisted state shape 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

- Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used through
Claude Code with tool use (repository search, file edits, shell, test
execution, and a read-only browser).

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:24:03 -06:00
DottaandClaude Opus 5.5 464e6e16e0 fix(native): resume copyback after a restart kills its same-host owner; one board action otherwise (#19717)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runs save the agent's workspace files after the provider turn
finishes (copyback)
> - One controller owns copyback at a time. The run records that owner
in `runnerProfileJson.nativeWorkspaceFinalizationOwner` (host, PID,
token)
> - A control-plane restart can kill the controller during copyback. The
dead controller can never confirm its own stop
> - Recovery then waited for a board confirmation. No surface offered
that confirmation, so the run stayed `running` and the task stayed
`in_progress` with no visible action
> - This pull request lets the next controller on the same host prove
the stop by itself. It also gives the board one clear action when that
proof is not possible
> - The benefit is that a restart during copyback resumes by itself, and
a task never waits silently for a person

## Linked Issues or Issue Description

**What happened?**
In a Daytona restart-chaos wave, a kill during workspace copyback left
the run `running` and its task `in_progress`. Recovery reported
`native_workspace_finalization_owner_unverified`. The run was still
`running` when the wave timed out.

**Steps to reproduce**
1. Start a native remote (sandbox) run.
2. Kill the control-plane process (SIGKILL) after the provider result is
accepted, during copyback.
3. Restart the control plane on the same host.

**Expected behavior**
If the previous controller and its copyback children are proven stopped
on this host, recovery resumes copyback without a new agent turn.
Otherwise, the task shows one board action to confirm that the previous
server stopped.

**Actual behavior**
The recorded owner was cleared only by
`resumeNativeWorkspaceAfterOwnerStop`. Same-host recovery also required
a completed-copyback barrier, which an interrupted copyback never has.
The task presentation showed `working`, and the UI had no control for
the confirmation.

Refs #19578 (boot-ID fencing for unstarted runs; same proof style).

## What Changed

- Root cause:
`server/src/services/native-runtime/native-workspace-finalization-ownership.ts`
sent a dead same-host owner to `requireStopVerification` when no
`workspace_finalize` operation had succeeded. That case is exactly a
restart during copyback.
- New `native-workspace-owner-stop-proof.ts`. The owner receipt now
records the controller's process group (`processGroupId`). Copyback
children (tar, Git) are not detached, so they keep that group. A
successor on the same host releases the receipt only when the receipt
names another controller boot, the PID is dead or recycled (process
start time), and the group is empty or has no member outside the
successor's own process tree that started after the old controller. Age
alone never proves a stop.
- A still-live group member returns `local_stop_pending`. The
finalization worker rechecks it on its existing ownership recheck
interval. The board action stays visible meanwhile.
- A successful local proof writes `issue.workspace_owner_stop_verified`
with the proof and marks the recovery `resuming`, like the existing
platform proof.
- A receipt from another host, a receipt without a process group, or a
host without `ps` keeps the fence. The task interruption is now
`waiting_for_user` with `userAction: "confirm_owner_stop"` (new shared
enum value), not `working`.
- UI: the task chat shows one **Confirm the previous server stopped**
action. It asks for stop evidence and calls the existing
`resume-workspace-finalization` API. It is shown only to roles that hold
`runtime:confirm_owner_stop` (company owner or administrator, local
board, instance administrator). The inbox names the same action.
- The copyback advisory lock, the coordinator finalization lease, and
remote save fencing are unchanged.
- Docs: `doc/native-workspace-finalization-recovery.md`.

## Verification

- Red then green on embedded Postgres
(`server/src/services/native-runtime/native-workspace-finalizer.test.ts`):
- same-host controller killed with SIGKILL, its process group empty:
before `owner_unverified`, after `acquired` with the proof logged and no
agent wake
- a non-detached orphan child of the dead controller still runs:
`local_stop_pending`, no copyback; after the child exits, `acquired`
  - a receipt without a process group: still `owner_unverified`
- a foreign-host owner: the task presentation was `working` (red), now
`waiting_for_user` / `confirm_owner_stop`; after the board confirmation
it is `working` again
- The existing orphan test (a detached child) and all other finalizer
tests pass: `npx vitest run
src/services/native-runtime/native-workspace-finalizer.test.ts
src/__tests__/native-workspace-finalization-work.test.ts
src/services/task-interruption-presentation.test.ts` (135 passed).
- UI: `npx vitest run src/components/WorkspaceExportRecovery` (34
passed), including the action call body and the non-administrator case.
- `pnpm check:token-gates` clean. Server and UI typecheck clean for the
changed files.
- Live Daytona restart-chaos wave: results are added in a comment below.

## Risks

- A process-table check that reports a member wrongly as live only keeps
the existing fence. The board action is still available.
- A subreaper that adopts orphans does not change their process group,
so the proof does not depend on the parent PID.
- A child that a future change spawns with `detached: true` would leave
the group. Copyback paths do not do that today. The existing
detached-orphan test still fails closed, because a receipt without a
process group is not released.
- Old receipts have no process group. They keep the board 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 <noreply@anthropic.com>
2026-11-06 23:31:07 -06:00
scotttong f5fb8ada3b Restore chat contacts, agent pins, and scrollable recent navigation (#19685) 2026-11-06 19:17:25 -08:00
DottaandPaperclip 752cb8d3da fix(ui): contain patch previews and highlight diff source (#19478)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Tasks show documents and file attachments in cards and file views.
> - Patch source needs clear added and removed lines in each view.
> - Long patch lines can exceed the width of an inline preview.
> - This pull request keeps the preview inside the card and adds patch
colors.
> - Users can read a compact patch preview and open the full source.

## Linked Issues or Issue Description

**What happened?**
Patch text can exceed the inline document box. Patch files and patch
code blocks show plain text in the document view.

**Expected behavior**
Keep source inside its preview. Show added lines in green and removed
lines in red. Keep headers, source markers, copy controls, line numbers,
and downloads.

**Steps to reproduce**
1. Open a document card with a diff code block that has long unbroken
lines.
2. Open a `.patch` or `.diff` attachment in a task tab.
3. Open the same patch in the workspace file view.

**Paperclip version or commit**
Source checkout based on `e82210386f1e`.

**Deployment mode**
Built from source. The browser checks use Storybook fixtures.

Related work: #19269 adds inline diffs for file edits. This change uses
its line classifier for document and file source views.

## What Changed

- Share the unified diff line classifier with file and document views.
Track hunk lengths so later file headers keep their metadata style.
- Add safe patch source rendering for patch files and `diff` or `patch`
code blocks.
- Clip long lines with an ellipsis in compact cards. Wrap the full
source in file views.
- Add a Preview patch action for small `.patch` and `.diff` attachments,
including generic binary MIME types. Opening a large artifact list does
not fetch patch source. Bound the actual response bytes. Reject remote
preview URLs and redirects. Keep download and retry fallbacks.
- Add patch text colors with sufficient contrast in both themes. Keep
source-line selection visible over patch row colors. Copy the original
patch text without layout line breaks.
- Add five production component stories and browser checks at two widths
in both themes.
- Document the focused Storybook browser command.

## Verification

- Final base qualification at
`a120e0ada3f1eee9746f47a10384b5aa95f79824`: normally integrated the
already-merged #19610 model-fixture repair
(`8621bcf53ee2e4bbefb7d193add42dd544ca06e8`), preserving the reviewed
patch preview changes. Official preflight passes (1.8s); all 129 focused
tests pass again (23.67s), with UI typecheck and token gates passing.
Current revision CI and reviews are pending; prior-head review receipts
below are not current-head approval.

- Conflict qualification at `befc954a6116f7292506200b96623b673f9385b5`:
normally merged current master
`c66b4a55efdc5675b5a5146197078b94de6407da`, resolving the append-only
`ui/src/index.css` conflict by retaining both the patch rules and all
current agent identity rules. No feature behavior was changed during
resolution. Official isolated preflight passes. The seven focused
component/parser suites pass 129 tests; UI typecheck and token gates
pass. Fresh head CI and reviews are pending.
- Qualification setup limits: the first focused command used the
repository root instead of the UI working directory, so setup failed
before any test ran. It was corrected to the UI directory and actual
attachment-test path. The first UI typecheck found the unbuilt local
plugin SDK; building that checkout’s SDK and repeating typecheck passes.
These setup attempts are not test/typecheck passes.

- Focused Vitest run: 139 tests passed in eight files. This includes
source preservation, HTML escaping, large patches, tabs, Markdown
wrapping, line numbers, and transcript diff parsing. A 200-card check
confirms no patch fetch occurs before a preview request. CRLF
regressions cover no-newline markers and added lines that start with
`+++`.
- `pnpm --filter @paperclipai/ui typecheck`: passed after the plugin SDK
build prerequisite.
- `pnpm check:token-gates`: passed.
- Storybook catalog check and 30 map/catalog tests: passed.
- `STORYBOOK_TEST_BASE_URL=http://localhost:6006 pnpm exec playwright
test patch-preview.spec.ts
--config=tests/storybook-visual/playwright.config.ts --project=stories
--workers=2 --retries=0`: 20 passed on Linux Chromium. The checks
include accessibility, theme colors, overflow, opening a document,
wrapping source, exact clipboard contents, and the selected source line.
Screenshots were inspected and saved to the task.
- The first browser runs found contrast failures in diff text and source
gutters. Patch text tokens and patch gutter opacity now fix those
failures. The final run passes without retries.
- `pnpm verify`: exit 2. The new browser spec is a global CI input.
Required CI must supply broad coverage.
- The local full Storybook build was killed with exit 137. This is not a
build pass. External screenshot baselines are not published, so no
baseline comparison was run.
- Reviews found selected-row background precedence, file headers in
unified patches without git separators, eager patch loading across a
collection, and CRLF no-newline markers. Each finding has a focused fix
and regression checks. The Storybook hunk count now matches its source
lines.
- Prior feature commit `b3cb9d6d3c0423315540514b1ad68325a64c1cc2`: [CI
typecheck, build, tests, runner checks, and E2E
passed](https://github.com/paperclipai/paperclip/actions/runs/42247974154).
[Security scan
passed](https://github.com/paperclipai/paperclip/runs/127995160859).
[Greptile is
5/5](https://github.com/paperclipai/paperclip/pull/19478#issuecomment-6688482753),
and [Paperclip Review is
5/5](https://github.com/paperclipai/paperclip/runs/127998788846). All
six inline review threads are resolved.

## Risks

- Compact previews omit the end of long lines. The full document and
downloads keep the source.
- Patch colors apply to unified diff syntax. Other patch formats keep
literal text and do not have full format parsing.
- The shared classifier keeps the existing task chat behavior.
Regression tests cover that consumer.
- No database, API, or access rules change.

## Model Used

- OpenAI Codex, GPT-6, with reasoning, tool use, and code execution. The
runtime did not expose a more specific 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
- [ ] 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>
2026-11-07 00:06:50 +00:00
DottaandClaude Opus 5.5 07a548073a fix(ui): keep inline document links in the text line; plain-text request previews (#19639)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents talk to people in task chat. Their messages render as
markdown.
> - In an onboarding walk, two chat papercuts made agent text hard to
read.
> - First: a document link inside a sentence ("Your [plan is saved],
approved, ...") rendered as a padded card. The card is taller than the
text line, so it overlaps the lines around it.
> - Second: the one-line row above a pending review card showed the
agent's raw markdown summary (backticks, "- ", "**").
> - This pull request renders document links in running text as an
inline chip, and flattens the collapsed preview to plain text.
> - The benefit is that agent messages and pending-request rows read
cleanly.

## Linked Issues or Issue Description

**What happened?**
1. In task chat, an agent wrote: "Yes—we completed the onboarding
interview here in chat. Your [plan is saved](#document-plan), approved,
and handed off to execution task PIX-3." The "plan is saved" link
rendered as a 42px-tall bordered card in a 20px text line. It overlapped
the text above and below and left the text baseline.
2. The collapsed row above a "Review the creative brief (v1)" card read:
"The `creative-brief` document is saved on this task. It covers: -
**Brand voice and audience:** ..." with literal markdown syntax.

**Steps to reproduce**
1. Open a coordinator task chat where an agent message has a
`#document-...` link inside a sentence. Or open the Storybook story
`coordinator-conversation--inline-document-link`.
2. Open a task with a pending request whose summary contains markdown.
Or open
`composer-interaction-above-composer--pending-request-markdown-summary`.

**Expected behavior**
1. The document link sits in the text line like other inline links.
2. The collapsed preview shows the words as plain text.

## What Changed

- `SidebarDocumentLink` gets a `variant` prop. `card` (default) is
unchanged and is still used by the standalone plan preview card.
`inline` is a text-flow chip: `display: inline`, horizontal padding
only, a small muted background, and a 14px icon aligned to the text
bottom. Token classes only.
- `MarkdownBody` uses the `inline` variant for both document-link paths
(coordinator issue document links and `#document-...` hash links). The
label no longer uses `truncate` (nowrap), so long labels wrap with the
sentence.
- New `ui/src/lib/markdown-plain-text.ts` (`markdownToPlainText`): drops
code ticks, emphasis/strikethrough markers, list/heading/quote/task-box
prefixes, fences, rules, link/image targets and inline HTML tags, keeps
snake_case intact, and collapses whitespace. Unit tests included.
- `TaskChatInteractionCard` flattens the collapsed pending-request
summary and the unanswered-question prompt with `markdownToPlainText`.
- Stories: `Coordinator conversation / Inline document link` (+ dark)
with a play check that the link is no taller than the line and starts
inside the paragraph; `Interaction above composer / Pending request
markdown summary` with a play check that the row has no markdown syntax.

## Verification

Red on master (`e482f0f632`), then green with the fix:

```
# Red (master + new tests)
 × renders a document link inside a sentence as inline text, not a padded card
   AssertionError: expected 'inline-flex max-w-full items-center gap-2 rounded-lg border border-border bg-muted/25 px-3 py-2.5 ...' not to match /(^|\s)(inline-flex|flex|block|py-\S+|p-\S+)(\s|$)/
 × shows the collapsed pending-request preview as plain text, not raw markdown
   AssertionError: expected 'Review the creative brief (v1)The `cr…' to contain 'The creative-brief document is saved …'
 Tests  2 failed

# Green (fix)
 Tests  7 passed  (the 2 above + 5 markdownToPlainText unit tests)
```

Browser geometry check (Playwright against Storybook, story
`coordinator-conversation--inline-document-link`, light and dark). It
measures the link box against the paragraph line-height and the text
node before the link:

```
# Before
light: FAIL lineHeight=20 chipHeight=42.0 chipTop=61.7 chipBottom=103.7 textTop=76.7 textBottom=93.7
dark:  FAIL lineHeight=20 chipHeight=42.0 chipTop=61.7 chipBottom=103.7 textTop=76.7 textBottom=93.7
# After
light: PASS lineHeight=20 chipHeight=17.0 chipTop=62.7 chipBottom=79.7 textTop=62.7 textBottom=79.7
dark:  PASS lineHeight=20 chipHeight=17.0 chipTop=62.7 chipBottom=79.7 textTop=62.7 textBottom=79.7
```

The story's own play function also failed on master in the browser:
`AssertionError: expected 42 to be less than or equal to 20`.

Inline document link, before / after (dark, then light):

![chip before
dark](https://raw.githubusercontent.com/paperclipai/paperclip/bbbeaadb3fb5ff2521edfe9434a31389bf8dfd50/inline-chip-preview/chip-before-dark.png)
![chip after
dark](https://raw.githubusercontent.com/paperclipai/paperclip/bbbeaadb3fb5ff2521edfe9434a31389bf8dfd50/inline-chip-preview/chip-after-dark.png)
![chip before
light](https://raw.githubusercontent.com/paperclipai/paperclip/bbbeaadb3fb5ff2521edfe9434a31389bf8dfd50/inline-chip-preview/chip-before-light.png)
![chip after
light](https://raw.githubusercontent.com/paperclipai/paperclip/bbbeaadb3fb5ff2521edfe9434a31389bf8dfd50/inline-chip-preview/chip-after-light.png)

Collapsed pending-request preview, before / after:

![preview before
dark](https://raw.githubusercontent.com/paperclipai/paperclip/bbbeaadb3fb5ff2521edfe9434a31389bf8dfd50/inline-chip-preview/preview-before-dark.png)
![preview after
dark](https://raw.githubusercontent.com/paperclipai/paperclip/bbbeaadb3fb5ff2521edfe9434a31389bf8dfd50/inline-chip-preview/preview-after-dark.png)
![preview before
light](https://raw.githubusercontent.com/paperclipai/paperclip/bbbeaadb3fb5ff2521edfe9434a31389bf8dfd50/inline-chip-preview/preview-before-light.png)
![preview after
light](https://raw.githubusercontent.com/paperclipai/paperclip/bbbeaadb3fb5ff2521edfe9434a31389bf8dfd50/inline-chip-preview/preview-after-light.png)

Neighbouring suites: `npx vitest run src/components/MarkdownBody
src/components/task-chat src/lib/markdown-plain-text.test.ts
src/components/MarkdownDocumentCardStyles.test.ts
src/components/MarkdownExternalLink.test.tsx` → 67 files, 1001 tests
passed.

Also run: `pnpm check:token-gates` (clean), UI `tsc -b` (no errors in
changed files; the only errors were an unbuilt local
`@paperclipai/plugin-sdk`), `node scripts/storybook-catalog.mjs`. `pnpm
verify` exited 2 (story file is a broad input), so broad coverage comes
from required CI.

## Risks

- Low risk. UI-only styling and text change.
- Document links in markdown now look like inline links with a small
icon instead of a card. A link that sits alone in its own paragraph also
renders inline. The standalone plan preview card keeps the card style.
- `markdownToPlainText` is a regex flattener for one-line previews, not
a full markdown parser. An unusual construct can keep a stray marker. It
does not change stored data.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edits, Playwright browser checks).

## 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 <noreply@anthropic.com>
2026-11-06 17:41:07 -06:00
DottaandClaude Opus 5.5 411eecdccd fix(ui): store = as typed, without the highlight exporter's escapes (#19540)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - People write task requests and chat messages in the Markdown editor,
and the stored text becomes the agent's prompt
> - MDXEditor supports `==highlight==` marks, and its exporter escapes
every `=` in prose to protect that syntax
> - The editor therefore stores `a=b` as `a\=b` and `x=>y` as `x\=>y`
> - A command or code snippet in a chat message then reaches the agent
as invalid code
> - This pull request removes the `=` escape when the escape cannot
change how the text parses, as #19350 did for intraword underscores
> - The benefit is that commands, assignments and arrows reach agents
exactly as the person typed them

## Linked Issues or Issue Description

Refs #19350

**What happened?**
A chat evaluation sent a `node -e` command through the chat composer.
The stored comment contained `const fs\=require(...)` and `()\=>`. Every
`=` had a backslash. The agent received invalid JavaScript, and an
exact-text check on the stored message failed.

**Expected behavior**
Text the person types is stored as typed. A `=` that cannot start or end
a highlight needs no escape.

**Steps to reproduce**
1. Type `a=b` or `x => y` in any Markdown editor field, such as the chat
composer.
2. Read the stored value through the API.
3. Before this change it is `a\=b` or `x \=> y`.

## What Changed

- New `ui/src/lib/equals-markdown.ts`: `unescapeEqualsSigns` removes
`\=` escapes from prose only when they cannot change the parse:
- any run of escaped `=` whose length is not exactly two (a highlight
sequence is exactly two `=`);
- a run of exactly two with whitespace or the text edge on both sides
(such a run can neither open nor close a highlight).
- Other two-runs, such as `a\=\=b` or `\=\=text\=\=`, stay escaped.
Escaped backslashes (`\\=`) stay as they are.
- `ui/src/lib/underscore-markdown.ts`: the code-range walk is now the
shared `rewriteMarkdownProse` helper. Code spans, code blocks and raw
HTML stay byte-identical for both rewrites. Underscore behavior does not
change.
- `MarkdownEditor.tsx`: `toStoredMarkdown` applies the new step after
the underscore step, on the exporter's exact text.
- Tests: unit cases for commands, `===`, spaced `==`, quotes and lists,
highlight-capable runs, code spans and blocks, raw HTML and escaped
backslashes. One editor test for a pasted command with a code block.

## Verification

- `npx vitest run src/components/MarkdownEditor.test.tsx src/lib
src/components/task-chat` in `ui/`: 280 files, 3388 tests passed.
- Negative control: with the new step removed from `toStoredMarkdown`,
the new editor test fails.
- Parser check: I parsed the unescaped forms with
`micromark-extension-highlight-mark` 1.2.0, the version MDXEditor uses.
`a=b and c=d`, `x === y`, `a == b == c`, `()=>x` and `x!==y` produce
zero highlight nodes. The controls `==a==`, `a ==b== c` and `say
==this== now` produce one highlight node each.
- `tsc -p ui/tsconfig.json`: no errors in the changed files. The
remaining errors are in files this change does not touch.

## Risks

- Low risk. The step only removes an escape when the highlight tokenizer
cannot use that `=`. Text the parser cannot place is returned unchanged.
- Some `==` in code-like prose (for example `x!==y`) stays escaped. This
is intentional: such a run can open a highlight.

> 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 Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code
execution in 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 17:27:59 -06:00
DottaandPaperclip 6148fbd31f feat(ui): distinguish GitHub resources in inline Markdown links (#19476)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - Task conversations and documents use the shared Markdown renderer.
> - GitHub repository, pull request, and issue links currently use the
same icon.
> - Long pasted URLs make these references hard to scan in a
conversation.
> - This change identifies each resource from its URL and shows a
compact label with a distinct icon.
> - The exact destination and custom link text stay intact. Connected
status data keeps its existing behavior.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Inline GitHub links in rendered Markdown.

**Current behavior**

Repositories, pull requests, and issues all use the GitHub mark. Pasted
resource links show the full URL.

**Proposed behavior**

Pull requests use the pull request icon. Issues use the issue icon.
Repositories and other GitHub pages keep the brand mark. Pasted resource
URLs show `owner/repository#number` or `owner/repository`. Custom labels
keep their text and formatting.

**Reason and benefit**

Readers can identify GitHub resource types and scan references in a
narrow conversation. Full URLs, query strings, and comment anchors stay
available through the link destination. Link rendering makes no remote
metadata requests.

**Breaking changes**

The visible text of a bare GitHub resource URL becomes compact. The
destination does not change. No API, database, or connection changes are
required.

Searches found no duplicate work for GitHub inline resource
presentation. Related work: [same-origin Markdown
routing](https://github.com/paperclipai/paperclip/pull/17864). This
change preserves that route handling.

## What Changed

- Add a pure GitHub URL classifier with exact host checks and resource
labels.
- Move ordinary URL presentation into `MarkdownUrlLink`. Keep internal
references, workspace links, custom resolvers, and connected metadata in
their existing order.
- Add separate icons and accessible resource names. Preserve authored
Markdown titles and formatted labels.
- Add Playground, Narrow, and Streaming Storybook examples that mount
`MarkdownBody`.
- Add parser and renderer tests, eight Chromium browser cases, and the
browser command in the development guide.
- Consider Open Graph loading. Use the existing connected metadata path
for remote status and titles. Add no arbitrary page fetches.

## Verification

- Focused Vitest coverage: 129 passing tests across nine files for
GitHub parsing, Markdown rendering, connected metadata, document
rendering, and workspace-file rendering.
- `pnpm check:token-gates`.
- `pnpm --filter @paperclipai/ui typecheck`.
- `pnpm --filter @paperclipai/ui build`.
- `pnpm build-storybook`.
- Storybook catalog generation/check and 30 catalog/map tests.
- Site-route regression: Stars, Education, and enterprise pages keep
general GitHub branding and their full URL text. Three parser cases
failed before the fix; parser and renderer cases now pass.
- Streaming regression: three resource cases failed before the fix and
pass after it. The fix preserves the generated word span and custom
formatted labels.
- Chromium: `STORYBOOK_TEST_BASE_URL=http://localhost:6106 pnpm exec
playwright test inline-links.spec.ts
--config=tests/storybook-visual/playwright.config.ts --project=stories`.
- Browser scope: eight passing cases for ordinary/streaming modes,
dark/light at desktop and 390px mobile, completed play assertions, exact
comment destinations, safe external attributes, keyboard focus,
wrapping, and no GitHub requests. Screenshots are saved as task
artifacts; no review images are committed.
- The first browser attempt in the resumed environment could not launch
Chromium because its executable was missing. The browser was installed,
and all eight cases then passed.
- The browser cases use synthetic text and one connected metadata
fixture. They do not prove provider authentication or server
persistence. No visual baseline comparison is claimed; the repository
baseline manifest is unpublished.
- Initial typecheck found two unsupported story assertion options and an
unbuilt SDK dependency in the fresh checkout. The options were removed
and the SDK was built before the successful retry.
- `pnpm verify` returned 2 because the new browser spec requires broad
CI coverage. Focused tests and the browser suite supply local evidence.
Required CI supplies broad coverage.

## Risks

- Compact labels hide URL detail from the visible line. The exact href
and default tooltip preserve that detail.
- GitHub site routes can change. Unknown paths retain the general GitHub
treatment. Lookalike hosts and credential-bearing URLs do not get GitHub
resource treatment.
- Connected external-object status and title rendering keeps priority
over URL-derived presentation.

## Model Used

OpenAI Codex, GPT-6 (`gpt-6` family), with reasoning, tool use, browser
checks, and code execution. The exact deployment ID and context window
are not exposed in this run.

## 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>
2026-11-06 15:26:10 -06:00
TonioandPaperclip c650f36007 fix(ui): refine agent profiles, identity editing, and list actions (#19535)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent pages expose identity files, reporting relationships, and
runtime settings.
> - Several controls had inconsistent spacing, weak navigation, or
misleading save feedback.
> - Empty task-row detail slots also reduced the title width before
truncation.
> - This pull request fixes those behaviors and aligns the reviewed UI
details.
> - The result is clearer agent editing and more readable task and agent
lists.

## Linked Issues or Issue Description

**What happened?**
Agent headers and file controls had inconsistent layout. The Identity
footer appeared without edits and did not provide clear save
confirmation. File names could wrap or omit the Markdown suffix. Task
titles truncated while empty detail slots occupied the row.

**Expected behavior**
Keep headers and actions aligned. Reveal the Identity footer only for
changes or save feedback. Use valid Markdown file names and actual file
dates. Let titles use available space.

**Steps to reproduce**
1. Open an agent overview, then its Identity tab.
2. Edit, undo, save, copy, and add an instruction file.
3. Resize the file panel and inspect file names and date labels.
4. Hover an agent row and inspect the Chat action and tooltip.
5. Open a task list with no recovery or external details and inspect
title width.

Related: #18716 is the prior UI bundle. #16934 and #14483 concern role
editing; this change does not add a role picker.

## What Changed

- Refine the character header, inline title and name editing, crown
position, runtime shortcut, and Chat/New task actions.
- Align profile metadata and manager selection; hide empty direct
reports.
- Refine Identity panels, Markdown filename validation, file-row hover,
truncation, Paper Mono typography, and copy feedback.
- Add a viewport-bound save footer with dirty, saving, and saved states.
Keep undo/discard separate from successful saves.
- Return recorded creation or modification dates for instruction files.
Reuse the active file transaction for metadata reads to avoid exhausting
the small connection pool.
- Reveal the agent-row Chat icon and label on hover, with a standard
delayed tooltip.
- Collapse empty task-row details so titles can use the available width.

## Verification

- Browser checks covered the local overview and Identity flows, narrow
file panels, successful file creation/update, undo/discard, header
navigation, row hover, tooltip delayed-open state, and full task titles.
- Focused instruction, save-footer, date, font-asset, and task-row tests
passed during development. After review, 55 file/instruction tests
passed, including cold restoration with recorded and unknown dates;
server typecheck and 18 immediate-save response tests also passed.
- Full `pnpm -r typecheck` and `pnpm build` passed after rebase onto
current master. The complete `pnpm test:run` attempt hit a 15-second
timeout in the existing chat Retry HTTP integration test; that case
passed in isolation (1 test passed). A second full attempt hit timeouts
in two other chat integration cases (GitHub webhook draining and
delivery retry budget); both passed in isolation without increasing
timeouts. All 55 remote checks passed on f31405a730, including the full
test matrix, build, typecheck, and final verification gate.
- `pnpm check:token-gates`, `node scripts/check-maps.mjs`, and `git diff
--check` passed on the submitted revision.

## Risks

- File dates are optional. Managed files require recorded dates because
restored projections have synthetic filesystem timestamps. External
files can use modification time; unknown dates are omitted.
- Save-footer timing depends on animation completion; reduced-motion and
failed-save paths have tests.
- Global Paper Mono typography can change text widths. Browser checks
covered the edited surfaces.
- No schema migration or new execution permissions are introduced.

## Model Used

OpenAI Codex, GPT-6, with repository tools, code execution, and browser
verification. The exact serving model ID and context-window size are 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 references)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run the relevant tests locally and they pass; full-run
timeout limitations and successful isolated reruns are 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
- [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>
2026-11-06 11:14:56 -08:00
Devin FoleyandPaperclip fd4b197f83 fix(ui): hide runner selection during agent setup (#19517)
## Thinking Path

> - Paperclip lets people manage AI agents for work.
> - Agent setup connects a harness, model, account, and computer.
> - The system already selects a qualified runner for a new agent.
> - Setup still asks users to select that internal runner.
> - The runner dropdown can be the only field in Advanced.
> - This PR removes the dropdown and hides an empty Advanced section.
> - Users keep their saved execution settings and see fewer setup controls.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Agent creation, onboarding, built-in agent setup, company import, and agent configuration.

**Subsystem affected**

The UI, setup error messages, tests, and Storybook fixtures.

**Current behavior**

Users can select a runner from a dropdown. Some setup screens place only that dropdown in Advanced. Errors can direct users to that control.

**Proposed behavior**

Remove runner dropdowns from setup and configuration. Use the existing automatic selection for new agents. Retain saved runner settings. Show Advanced only when other fields remain.

**Reason and benefit**

Users can configure agents without an extra internal execution choice. Setup also avoids an empty Advanced section.

**Breaking changes**

Users can no longer select a runner through these UI dropdowns. The API and CLI retain their runner options. Existing agents keep their saved execution settings.

**Additional context**

Related: [Default new agents to the qualified runner for their harness](https://github.com/paperclipai/paperclip/pull/15422). This PR changes the existing setup UI. It adds no harness or runtime default. The master merge retains the custom gateway placement from [#19510](https://github.com/paperclipai/paperclip/pull/19510).

## What Changed

- Remove the shared runner dropdown and its setup/configuration callers.
- Hide Advanced when it has no remaining fields. Keep command and argument settings where supported. Preserve custom gateway setup under Advanced on the Claude and OpenAI API key paths.
- Keep setup failures visible and keep the retry action outside the removed control.
- Update errors and documentation that referred to the removed dropdown.
- Update component tests and Storybook fixtures for the new UI states.
- Make Legacy first-task evals resume an API-configured setup record. Record that path separately from fresh automatic onboarding.

## Verification

- Latest master integration (`187c0f6d6e5`): 276 onboarding/provider-connection tests passed across six files. UI typecheck and build passed. Token gates, query-error rendering checks, and the Storybook catalog check passed. The conflict resolution retains master's bounded connection tests and cancellation while removing the obsolete runner instruction. The first test command used the wrong working directory and failed before collecting tests; the corrected invocation from `ui/` passed.
- Earlier branch verification passed: full workspace build and typecheck, 1,037 focused UI/service tests, 49 heartbeat provider contract tests, eval typecheck, and 30 map/catalog tests.
- The full local `pnpm test:run` run passed all six chat integration shards (1,931 tests). It was stopped for the master merge before the complete suite finished. The [complete CI run](https://github.com/paperclipai/paperclip/actions/runs/42264662642) passed on the previous head `72e1699`.
- Current head `a097c8369d079c839e78661a2206cfa62c5210e0`: [full CI](https://github.com/paperclipai/paperclip/actions/runs/42273168268) passed. PR checks total 55 passed and 3 skipped, with none failed or pending. Greptile reviewed this head at 5/5 with no actionable findings or unresolved review threads.
- Live browser/provider evals have not run. The Legacy eval change has no live qualification claim.

## Risks

- Removing the dropdown changes the setup UI. Existing API and CLI runner choices remain available.
- Conditional Advanced sections must retain valid command and argument fields.
- Legacy first-task evals now prove setup resumption. They do not prove the fresh-agent automatic default.
- No schema change or data migration is included.

## Model Used

OpenAI `gpt-6.1-sol`, through Codex in T3 Code. Reasoning effort: `xhigh`. The model used file editing, shell commands, and test tools. The harness 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>
2026-11-06 10:58:42 -08:00
Devin FoleyandPaperclip e3d8f42e19 fix(ui): recover stalled onboarding connection checks (#19505)
Bound the complete onboarding connection check to two minutes, abort its
requests on timeout, and ignore late results. Keep the saved account
available for retry and resume its matching unfinished setup after refresh.

Verified 242 focused tests, workspace typecheck and build, and the complete
hosted CI check set. Greptile is 5/5 with no unresolved review comments.
Local full-UI timing failure and interrupted CI runner attempts remain
documented in the PR evidence.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-06 10:31:18 -08:00
DottaandClaude Opus 5.5 1485d3de1b fix(ui): hold announcements until the onboarding first task is finished (#19514)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A new user's first session is the onboarding first task. The agent
asks its onboarding questions in that task thread.
> - The app also shows an optional in-app announcement. The live feed
now serves "Introducing Connectors" (`2026-09-14-connectors`).
> - The announcement well only waits while the onboarding wizard dialog
is open. It shows 3 seconds after the user lands on the first task.
> - In the Nov 5 cloud onboarding test (Claude API path), the tester
said: "Don't show the connectors upsell until the second session. It's
blocking the user flyout and potentially the onboarding questions."
> - This pull request holds announcements until the company's onboarding
first task is done or cancelled.
> - The benefit is that a new user sees the onboarding questions and the
account menu without an upsell on top of them.

## Linked Issues or Issue Description

Refs #19511 (places the card in the desktop sidebar, above the account
menu. That PR keeps the card off the account menu after onboarding. This
PR adds the first-session gate that #19511 leaves open.)

**What happened?**
On a new company's onboarding first task, the "Introducing Connectors"
card shows 3 seconds after the page loads. At 1440x900 the card (352x346
px, bottom-left, z-40) covers the sidebar account menu trigger and the
left edge of the task composer.

**Expected behavior**
Optional announcements do not show in the first session, while the
onboarding first task is open.

**Steps to reproduce**
1. Create a new company through onboarding. The wizard creates the
onboarding first task.
2. Open the first task at 1440x900 and wait 3 seconds.
3. See the announcement card on top of the account menu and the
composer.

**Paperclip version or commit**
`master` at 819420a60a.

## What Changed

- `AnnouncementWell` reads the company's onboarding first task
(`originKind=onboarding_first_task`, `limit=1`) with React Query under
`["issues", companyId, "onboarding-first-task"]`. Issue live-update
invalidation of `["issues", companyId]` refreshes it.
- The well stays disabled while that task is not `done` or `cancelled`.
While the read is loading or after it fails, the optional card stays
hidden. A company that never had an onboarding first task is not gated.
- "First session" uses the existing `origin_kind` marker. There is no
schema change.
- New e2e spec `tests/e2e/announcement-first-session.spec.ts` with a
mocked feed that copies the live connectors entry. It checks that the
card is absent while the onboarding first task is open. Then it marks
the task done on the same page and checks that the card appears without
a reload (a positive control for the feed mock and the live refresh
path).
- New unit test `AnnouncementWell.onboarding.test.tsx`. It covers open,
finished, no first task, read failure, and the open-to-done transition
after `["issues", companyId]` is invalidated.
`AnnouncementWell.test.tsx` now gives the well a `QueryClient` with a
finished onboarding state.

## Verification

Red on `master` (819420a60a), before the fix. Vitest:

```
cd ui && npx vitest run src/components/AnnouncementWell.onboarding.test.tsx
     × stays hidden while the onboarding first task is open
     × stays hidden when onboarding state cannot be read
      Tests  2 failed | 1 passed (3)
AssertionError: expected true to be false
```

Red on `master`, Playwright at 1440x900 (an earlier draft of the spec
also had a placement check, recorded here as evidence for the flyout
part of the report):

```
✘ announcement-first-session.spec.ts › a brand-new company with its onboarding task open does not show the upsell
    Error: expect(locator).toHaveCount(expected) failed
    Locator:  getByRole('complementary', { name: 'Paperclip announcements' })
    Expected: 0
    Received: 1
✘ … where the upsell shows, it does not cover the account menu or its flyout
    Error: well {"x":12,"y":542,"width":352,"height":346} covers account trigger {"x":20,"y":856,"width":166,"height":36}
```

Green after the fix, merged with current `master` (f7070d68b5), with the
in-page positive control:

```
✓ announcement-first-session.spec.ts › a brand-new company with its onboarding task open does not show the upsell (10.1s)
  1 passed

cd ui && npx vitest run src/components/AnnouncementWell.onboarding.test.tsx src/components/AnnouncementWell.test.tsx
      Tests  6 passed (6)

cd ui && npx vitest run src/components/AnnouncementWell.onboarding.test.tsx src/components/AnnouncementWell.test.tsx \
  src/components/AnnouncementCard.test.tsx src/hooks/useAnnouncement.test.tsx src/components/SidebarAccountMenu.test.tsx src/components/Layout.test.tsx
 Test Files  6 passed (6)
      Tests  95 passed (95)
```

- `pnpm check:token-gates`: All gates clean.
- UI `tsc --noEmit`: no errors in app code. The only errors are the
git-ignored, generated Storybook catalog files.
- CI note: the first CI run had three jobs fail with "The self-hosted
runner lost communication with the server" (server tests 12/12, Runner
vitest package, e2e shard 5/8). These are infra flakes. I reran them.
- Local e2e note: two runs could not start the server because embedded
PostgreSQL ports were in use by parallel e2e runs on the same machine. A
rerun passed. This is not related to the change.

Before (master): the card covers the account menu and the composer on
the onboarding first task.

![before: onboarding first
task](https://raw.githubusercontent.com/paperclipai/paperclip/c97519868225b0c206cf95fa3fb7150287c88116/n13-before-onboarding-task.png)

After: no card on the onboarding first task. The account menu and the
composer are clear.

![after: onboarding first
task](https://raw.githubusercontent.com/paperclipai/paperclip/c97519868225b0c206cf95fa3fb7150287c88116/n13-after-onboarding-task.png)

## Risks

- Low risk. The change only hides optional content.
- A user who never finishes or cancels the onboarding first task does
not see announcements in that company. This is the intended trade-off
for "not in the first session". Closing the task shows them again.
- The well makes one extra small issues read (`limit=1`) per company. It
is cached for 60 seconds.
- `AnnouncementWell.tsx` also changes in #19511. Whichever PR lands
second resolves a small conflict in the `enabled` condition and the
`<aside>` placement.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits, Playwright, 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 (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 <noreply@anthropic.com>
2026-11-06 12:00:50 -06:00
Nicky LeachandClaude Opus 5.5 21a1e78852 feat(environments): add managed plugin environment rows (#19490)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A managed instance gets its instance-level environments from the
`PAPERCLIP_MANAGED_CONFIG` document
> - That document cannot declare a plugin environment yet, so a managed
instance cannot get a plugin-provided environment without manual setup
> - The full change is split into three pull requests. This first one
adds the plugin environment rows and their write floor, and nothing
calls them yet
> - The second pull request lets the managed config declare plugin
entries and provisions them at boot. The third sets the instance default
once and advertises the new managed-config capability
> - The benefit is a smaller review for each step, and no instance
changes behavior until the last step lands

## Linked Issues or Issue Description

**Subsystem affected**
Server environments (managed-config environment provisioning,
environment routes) and the board UI's adapter Test target.

**Problem or motivation**
A managed instance cannot get a plugin environment from its managed
config. Company settings cannot create one either. So each managed
instance that needs one requires manual setup.

**Proposed solution**
Over three pull requests, let an `environments` entry in the managed
config declare `driver: "plugin"`. Boot then provisions one locked row
for each plugin entry, and makes one of them the instance default once
on new instances. This pull request adds only the row layer: create or
adopt a marked row, archive it, find it, and keep tenants from editing
it.

**Alternatives considered**
Manual setup on each instance, and a new control-plane endpoint. Both
need a step outside the deploy. The managed config already delivers the
sandbox entry.

**Roadmap alignment**
This extends the existing managed-config `environments` section, which
already provisions the managed sandbox. It adds no new product surface.

## What Changed

- New `server/src/services/managed-plugin-environments.ts`, used through
new `environmentService` methods (`ensureManagedPluginEnvironment`,
`archiveManagedPluginEnvironment`, `findManagedPluginEnvironment`):
- The ensure validates the driver config through
`normalizeEnvironmentConfig` and
`validatePluginEnvironmentDriverConfig`, the same path an
operator-created plugin environment takes.
- It marks the row `managedByPaperclip: true` and
`managedPluginEnvironment: "<pluginKey>:<driverKey>"`.
- It adopts an existing unmarked row for the same plugin and driver
instead of adding a second one, so a hand-made row keeps its agents.
- A row that already matches gets no write. Changes write activity log
entries.
- Archive sets an active marked row to `archived`. The next ensure
reactivates it.
- Migration `0437`: unique index
`environments_managed_plugin_environment_idx` on `metadata ->>
'managedPluginEnvironment'`. This is the only schema change of the three
pull requests. Snapshot `0432` is pruned to keep the five newest.
- `server/src/routes/environments.ts`:
`isPlatformProvisionedEnvironment` also covers the new marker (write
floor and redaction). `managedPluginEnvironment` is a reserved marker
key, so a client cannot stamp it on its own row. Nothing provisions
marked rows yet, so the marker-clear escape hatch stays open for them.
The next pull request refuses it while the managed config declares the
row's entry.
- `ui/src/lib/adapter-test-environment.ts`:
`resolveManagedSandboxEnvironmentId` requires `driver === "sandbox"`.
The plugin row also carries `managedByPaperclip`, so the old "not local"
check could take it for the managed sandbox.

## Verification

- `cd server && npx vitest run
src/__tests__/managed-plugin-environments.test.ts` (embedded Postgres):
- The ensure creates the marked row through the plugin validator. A
second ensure writes nothing and logs nothing.
- A hand-made row for the same plugin and driver is adopted. It keeps
its id and its agents.
  - Archive, then ensure, reactivates the row.
- The ensure refuses while the plugin has no running worker, and refuses
a name that another environment holds.
  - The unique index allows one row per marker.
- `npx vitest run src/__tests__/environment-routes.test.ts`: on a
cloud-managed instance, edit, env-var edit, status change and delete of
a marked plugin row are refused, including for instance admins. The
marker alone keeps the floor. A client cannot stamp the marker. The
marker clear is allowed.
- `npx vitest run src/__tests__/environment-service.test.ts`: passes.
- `cd ui && npx vitest run src/lib/adapter-test-environment.test.ts`:
the plugin row is never taken for the managed sandbox. Under
managed-sandbox-only, an agent on the sandbox tests in the sandbox, and
an agent on the plugin environment tests there.
- `pnpm --filter @paperclipai/server typecheck`, `pnpm --filter
@paperclipai/ui typecheck` and `pnpm --filter @paperclipai/db
check:migrations` pass.
- I did not run the full `pnpm test:run`, `pnpm -r typecheck` or `pnpm
build`. CI covers them. `pnpm verify` asks for broader verification
because of the migration.

## Risks

- Migration: one new partial unique index on a small table. No row
carries the marker before this change, so the index cannot fail on
existing data.
- No caller uses the new ensure yet, so no instance changes behavior.
- The UI change affects only lists that hold a non-sandbox row with
`managedByPaperclip`. Today that is only the local row, which the old
check already excluded.

> 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 Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code
with tool use (file edits, shell, 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 <noreply@anthropic.com>
2026-11-06 16:52:43 +00:00
DottaandPaperclip 0b8578055a fix: restore trailing spaces in queued steering messages (#19456)
## Thinking Path

> - Paperclip lets people manage AI agents and give them more
instructions during work.
> - The task composer sends those instructions through the shared
Markdown editor.
> - The editor writes a final text space as the HTML entity `&#x20;`.
> - The composer trims spaces before it submits a message. An entity
survives this step.
> - Queued messages can therefore contain extra text that the person did
not type.
> - This pull request restores final prose spaces at the editor output
boundary.
> - The queue and the agent receive clean message text. Code and escaped
examples keep their source.

## Linked Issues or Issue Description

**What happened?**

A message that ends with a space can contain a final `&#x20;` when it
enters the steering queue. The shared editor exports this entity, and
normal message trimming does not remove it.

**Expected behavior**

The editor must return final prose spaces as spaces. Code samples and
escaped literal entities must keep their exact text.

**Steps to reproduce**

1. Open a task while its agent works.
2. Type a steering message with a space at the end.
3. Submit the message to the queue.
4. Inspect its stored Markdown. Before this fix, it can end with
`&#x20;`.

**Paperclip version or commit**

Reproduced from `70d23612dd` with the installed Markdown serializer. The
serializer writes `Steer the current turn ` as `Steer the current
turn&#x20;` plus a newline.

Searches for related trailing-space and steering-entity PRs found no
duplicate.

## What Changed

- Restore trailing space entities only in the final Markdown text node.
- Apply this step before the editor removes its other transport escapes.
- Keep code, raw HTML, escaped entities, links, internal entities, and
hard breaks unchanged.
- Limit the fix to an entity at the end of the message. Keep entities
before internal newlines or closing emphasis markers. Replacing those
entities with spaces can create hard breaks or remove bold and italic
formatting. Add tests for these limits.
- Add editor regression cases and focused Markdown cases. Keep the fix
in the shared editor; no queue API change is needed.

## Verification

- TDD: the new editor test failed in 3 cases before the fix. It received
literal entities instead of spaces. All 6 cases pass after the fix.
- All 353 focused editor, Markdown, composer, and queue tests pass. This
includes 10 new cases for internal newlines, emphasis, and a final space
after these cases.
- `pnpm check:token-gates` and `git diff --check` pass.
- UI typecheck and UI production build pass.
- Repository typecheck and build could not complete locally because
Cargo is not installed. The full test command was started, then stopped
after confirming its native Runner cases also need Cargo. No complete
local full-suite verdict is claimed. Current-head
[CI](https://github.com/paperclipai/paperclip/actions/runs/42229743296)
passes for `350af775850664226d6630072cb8e08732e2a218`. This includes
typecheck, build, tests, and E2E checks.
-
[Greptile](https://github.com/paperclipai/paperclip/pull/19456#issuecomment-6686109030)
and [Paperclip
review](https://github.com/paperclipai/paperclip/pull/19456#issuecomment-6686063906)
each score the current commit 5/5. No review threads remain open.
- Checked the design-review dependency claim against the current commit.
[ui/package.json already declares the
parser](https://github.com/paperclipai/paperclip/blob/350af775850664226d6630072cb8e08732e2a218/ui/package.json#L71).
The existing underscore helper uses the same parser. Six direct checks
also pass for GFM strikethrough, task lists, URLs, and tables.
- No visual style changed. No database or API contract changed. The
helper comment documents the storage rule.

## Risks

- Low risk. The change only restores encoded spaces at the end of the
document. Internal line endings and spaces inside emphasis are out of
scope. The tests keep these entities unchanged to preserve Markdown
formatting. Parser source positions protect code and HTML. Escape pairs
protect literal entity examples.
- Existing stored messages are not rewritten. They use the corrected
output after an editor change.

## Model Used

- OpenAI Codex, GPT-6. GPT-6.1 Sol (`gpt-6.1-sol`) assisted with the
review follow-up. The runtime does not expose the context-window size.
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>
2026-11-06 09:06:25 -06:00
DottaandPaperclip 4696ff4990 fix: reserve agent In Progress display for active runs (#19453)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task status must tell operators whether an agent is working or
waiting.
> - A task can retain its in-progress lifecycle status after its run
stops.
> - The shared read projection only handled scheduled monitoring waits.
> - The UI also ignored other projected statuses for in-progress tasks.
> - This pull request requires an active task run for the In Progress
display.
> - Operators can see waits without changing execution or recovery
authority.

## Linked Issues or Issue Description

**What happened?**

An agent task still showed In Progress after its run ended with an
access failure and left a pending connection request. Stale execution
locks and saved lifecycle state could show the same false activity.

**Expected behavior**

An agent task shows In Progress only with a task-linked queued or
running run. Otherwise, it shows Blocked for recovery, In Review for a
pending response or review, Monitoring for a scheduled check, or Todo
for remaining work. Human ownership keeps its selected status.

**Steps to reproduce**

1. Create an agent-owned task with an in-progress lifecycle status.
2. End its run and retain a pending human request.
3. Read the task, its summary, and search results. Before this change,
they still show In Progress.

## What Changed

- Derive visible activity from company-scoped, task-linked runs in the
existing shared SQL projection.
- Project inactive tasks to existing waiting or todo states. Preserve
lifecycle status, locks, admission, and recovery.
- Make the shared UI resolver use the projection while preserving
explicit and optimistic status changes.
- Cover stopped runs, stale locks, human waits, company isolation,
monitoring, filters, counts, search, and UI labels.
- Document the presentation rule in the product and execution contracts.

## Verification

- Red before the server fix: 7 new regression cases failed because they
returned in_progress. The 10 existing cases passed.
- Green after the server fix: 20 cases passed in
issue-display-status.test.ts, using embedded Postgres and real task
APIs/search. Seven nearby active-question cases also passed.
- Review regression: dismissed requests, historical questions, and
returned review work each failed before the follow-up fix. All three now
pass using the existing active-interaction rule and pending review
state.
- Red before the UI fix: 4 label cases failed. Five additional safety
cases caught stale projections hiding explicit transitions.
- Green after the UI fix: 43 tests passed across
task-status-presentation, issue-filters, and issue-detail-subissues.
- pnpm -r typecheck passed. All UI token gates and synchronization
checks passed.
- pnpm build passed. Server typecheck and build passed again after the
review fixes.
- Full CI passed on 45c4ababc5, including
typecheck, build, general/serialized tests, Runner tests, and browser
checks. The final rollup has 55 successful checks, one neutral check,
and three configured skips.
- The local pnpm test:run command passed all 1,931 chat integration
tests. Its remaining duplicate full-suite run was stopped once full CI
passed on the same commit. This is not a claim that the complete local
command finished.
- Greptile reviewed the final commit at 5/5. Both prior findings have
regression coverage and all review threads are resolved.
- Live acceptance at commit 45c4ababc5: started a fresh isolated local
instance, created an inactive agent task, and saved a pending human
request. API reads changed from Todo to In Review. An initial readiness
probe ran before startup finished; the doctor then verified the exact
serving revision before acceptance. Chrome showed In Review in the task
header, breadcrumb, and sidebar. Stored lifecycle status remained
in_progress. The instance was stopped after verification.
- The reported stopped state was inspected in the live UI. The fix is
not deployed there. Local API tests reproduce the failure shape without
replaying the real deployment task.

## Risks

- This changes visible status and display-status filter membership for
inactive agent tasks. Stored lifecycle filters keep their existing
meaning.
- The projection uses existing indexes on task runs, recovery actions,
and interactions. It adds no worker, timer, schema change, or execution
action.
- A queued run counts as active admission. Human-owned tasks retain
explicit ownership status.

## Model Used

- OpenAI GPT-6 through Codex, with reasoning, repository tools, and code
execution. A more specific model ID and context-window size are 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>
2026-11-06 07:59:38 -06:00
DottaandClaude Opus 5.5 e9999071f3 fix(ui): store intraword underscores as typed, without the editor's escapes (#19350)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - People write task requests in the Markdown editor, and the stored
text becomes the agent's prompt
> - MDXEditor exports every literal underscore as `\_`, even inside
words such as `E2E_ASK_12` or `snake_case`
> - Agents then see backslashes the person never typed, and sometimes
repeat them in their answers
> - This pull request removes only the intraword escape when the editor
hands text back, as is already done for `\>` and `\<`
> - The benefit is that identifiers, filenames and exact strings reach
agents exactly as typed

## Linked Issues or Issue Description

**What happened?**
In QA staging smoke (launch 3108, task QA-671), a person created a task
through the UI that asked for the exact reply
`E2E_ASK_12_journey_1793913565974_507b01b8`. The stored description, and
the agent's prompt, contained `E2E\_ASK\_12\_journey\_…`. The agent's
`paperclip_finish` summary was unescaped, but its final answer copied
the escaped form. The posted reply therefore did not equal the requested
text, though the rendered Markdown looks the same. Earlier attempts
passed only when the model happened to unescape.

**Expected behavior**
Text the person typed is stored as typed. An underscore inside a word
never needs an escape, because CommonMark never treats it as emphasis.

**Steps to reproduce**
1. Type `snake_case` in any Markdown editor field, such as a new task
description or the chat composer.
2. Read the stored value through the API.
3. Before this change it is `snake\_case`.

**Paperclip version or commit**
master at da3084a05d.

## What Changed

- New `ui/src/lib/underscore-markdown.ts`:
`unescapeIntrawordUnderscores` parses the text with
`mdast-util-from-markdown`, collects the source ranges of `inlineCode`
and `code` nodes, and turns `\_` back into `_` only outside those
ranges. It only changes a run of escaped underscores between two letters
or digits (`snake\_case`, `foo\_\_bar`). Text it can't parse is left
unchanged.
- These stay exactly as the editor wrote them: code spans, and fenced or
indented code blocks at any blockquote or list depth; escapes at a word
boundary (`\_\_init\_\_`, which would otherwise render as strong
emphasis); escaped backslashes (`\\_`).
- `MarkdownEditor.tsx`: `toStoredMarkdown` applies the helper after the
existing `\>` and `\<` handling, which is unchanged.
- `ui/package.json`: `mdast-util-from-markdown` becomes a direct
dependency. It was already installed transitively at 2.0.3. Per the
lockfile policy, `pnpm-lock.yaml` is not committed.
- Tests:
- Unit cases cover lists, blockquotes, list-indented fences (including
Greptile's case), inline code, escaped and unmatched backticks,
`foo\_\_bar`, `__init__` and escaped backslashes.
- Two editor tests: the original intraword test, and a round trip of
prose plus code.
- Negative control: when the code ranges are removed, the code
byte-identity tests fail.

## Verification

- `npx vitest run src/components/task-chat src/components/MarkdownEditor
src/lib` in `ui/`: 277 files, 3314 tests passed.
- `pnpm check:token-gates`: all gates clean. `vite build` passed. No
type errors in the changed files.
- An earlier CI Build failure on this PR was `npm error notarget No
matching version found for @microsoft/teams.api@2.2.0`, in the
bundled-server prefetch. That version is published now, and the failure
is unrelated to this change.
- Not verified live on QA yet. QA only uses this after its stacks are
rolled to a build that contains it.

## Risks

- Low risk. Only escapes that cannot change rendering are removed, and
only outside parsed code.
- Stored values written before this change keep their escapes until the
text is edited again.
- Parsing adds a small cost per editor change, and only when the text
contains `\_`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, browser, code editing). Extended reasoning was 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
- [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 <noreply@anthropic.com>
2026-11-06 06:18:47 -06:00
f8cb4957dc fix(ui): default Tasks to All tasks with a neutral sort header (#19393)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Tasks page helps people inspect work across their company.
> - A new viewer currently starts in Mine and can miss other company
tasks.
> - The task table also shows an active sort header before the viewer
selects a sort.
> - This pull request starts new viewers in All tasks and keeps the
default sort header neutral.
> - People see company work in newest-update order and keep their saved
view choices.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The initial task view and the task table's default sort state.

**Subsystem affected**

ui/ — React board UI.

**Current behavior**

Tasks opens Mine when the viewer has no saved view. The table highlights
Last updated even when the viewer has not selected a column sort.

**Proposed behavior**

Tasks opens All tasks when no saved view exists. The table keeps
newest-updated tasks first without an active header or arrow. A column
selection or an explicit sort URL activates the selected header.

**Reason and benefit**

New viewers can inspect company work immediately. The header
distinguishes default ordering from an explicit sort choice.

**Breaking changes**

The first-visit view changes from Mine to All tasks. Saved views and
explicit view URLs keep priority. Existing Inbox and Needs attention
preferences retain their meaning. The change does not alter task access
or database contracts.

**Additional context**

Refs #19363. This change builds on the grouped task menu from that
merged pull request. It uses the existing menu and design tokens.

## What Changed

- Default the Coordinator and combined task surfaces to All tasks.
- Preserve saved personal views and migrate previous picker choices.
- Keep implicit Last updated descending order separate from active
header state.
- Test default ordering, explicit sorting, reset behavior, view
selection, and preference migration.
- Update the product and implementation docs.

## Verification

- Passed 118 focused tests across task routing, table sorting, views,
and collection preferences.
- Passed `pnpm check:token-gates`, `pnpm -r typecheck`, and `pnpm
build`.
- Full `pnpm test:run` is running. Its result will be updated here.
- Greptile passed the current head with 5/5 and no findings or open
review threads.
- Browser check: All tasks uses the grouped My work and Organization
menu. Last updated has no active highlight by default. Selecting the
header adds the sort URL and active direction. Removing the explicit
sort restores the neutral header.
- Manual check: Open Tasks with no saved task view. Confirm All tasks
opens. Select Mine, leave Tasks, and return. Confirm Mine remains
selected.

## Risks

- The new default shows more accessible company tasks on the first
visit. Saved view choices remain intact.
- Task tables share the sort-state change. Explicit sort links and
column clicks retain active styling. Default row order stays
newest-updated first.
- No schema, API, permission, or execution changes.

## Model Used

OpenAI Codex, GPT-6 family. This session does not expose the exact model
ID or context-window size. Used reasoning, code editing, terminal
commands, and browser 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
- [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: Scott Tong <scott@scottsmbpm5max.lan>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 18:44:59 -08:00
Devin FoleyandClaude Opus 5.5 434d224c3a fix(ui): retry stale chunk reloads while a deploy settles (#19361)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI loads each page as a lazy chunk. After a deploy, an
open tab can ask for a chunk from its old build.
> - #17349 added a recovery: the app reloads the page one time when a
chunk fails to load. A loop guard permits only one reload in five
minutes.
> - Staging error monitoring still reports `Failed to fetch dynamically
imported module` from builds that include #17349. The events come 1–10
seconds after page load, and 1–10 minutes after page load.
> - During a deploy, the old server and the new server can both answer
for some seconds. The reloaded page can then miss a chunk again. Also, a
second deploy can occur inside the five-minute window. In both cases the
guard stops the recovery, and the user sees the error page. One more
reload a few seconds later would recover.
> - #18518 keeps old chunks available after a deploy, but only on
deployments that enable shared UI assets. Other deployments still depend
on the reload recovery.
> - This pull request permits up to three reloads in the guard window.
The reloads wait 0 s, 2 s and 5 s, so that the deploy can settle. After
the last reload, the error page shows and reports as before.
> - The benefit is that a stale tab recovers through a normal deploy
without an error page and without error-monitoring noise. A chunk that
stays broken still shows the error after a small, fixed number of
reloads.

## Linked Issues or Issue Description

Refs #17349
Refs #18518

**What happened?**
A browser tab stayed open during a staging deploy. A lazy page chunk
failed to load with `TypeError: Failed to fetch dynamically imported
module`. The route error boundary showed "This page hit an error" and
reported the error. The release included the one-time reload from
#17349. The reload did not occur because the loop guard had already used
its single reload in the last five minutes.

**Expected behavior**
The app reloads again after a short wait while the deploy settles, and
the page loads. The error page shows only when the chunk stays broken.

**Steps to reproduce**
1. Open a board page on a deployment without shared UI assets.
2. Deploy a new build. While the old and new servers both answer, go to
a page that was not loaded yet.
3. The chunk fails, and the page reloads one time.
4. The reloaded page gets its HTML from one server and a chunk request
goes to the other server. The chunk fails again.
5. The guard stops the recovery, and the route error page shows.

**Paperclip version or commit**
`master` at 5ac05871b5

## What Changed

- `ui/src/lib/chunk-load-recovery.ts`: The recovery permits up to three
reloads in the five-minute guard window. The reloads wait 0 s, 2 s and 5
s (`RELOAD_DELAYS_MS`). After the third reload, the recovery returns
`false`, and the boundary shows and reports the error as before.
- Storage stays compatible with older builds.
`paperclip:chunk-load-reload-at` still holds only the bare start time of
the window, so an older build keeps its one-reload guard during a
deploy. The retry count is in a separate key,
`paperclip:chunk-load-reload-attempts` (`<window start>:<attempts>`).
The count applies only to the matching window. A window that an older
build started counts as one reload.
- A delayed retry keeps its timer. The new
`cancelPendingChunkLoadReload()` stops a retry that has not started.
`RouteErrorBoundary` calls it when the user moves to another route, and
when the boundary unmounts. A working page is therefore not reloaded
later.
- The abandonment timer starts after the delay, so a delayed reload gets
the full 10 s before the boundary reports.
- A reload that does not leave the document (for example, the user
cancels it) is not tried again from the same document. This keeps the
behavior from #17349.
- Tests: unit tests for delayed retries, the retry limit, compatibility
with older builds, a stale retry count, and cancellation. Boundary tests
start from a delayed attempt and cancel it on navigation. The e2e spec
for a lazy CSS asset that stays broken now expects 4 page loads before
the error page.

## Verification

- `cd ui && npx vitest run src/lib/chunk-load-recovery.test.ts
src/components/RouteErrorBoundary.test.tsx
src/components/AppErrorBoundary.test.tsx`: 3 files, 32 tests pass.
- Red before the fix: with the new tests, the first PR head fails 5
tests (older-build key, cancellation, cancel on navigation). The
original `master` code fails the retry tests. With this head, all pass.
- `tsc --noEmit -p ui/tsconfig.json` shows no errors in the changed
files.
- `tests/e2e/chunk-load-recovery.spec.ts` runs in CI.
- Manual check: on a deployment without shared UI assets, open a page,
deploy, and go to a page that was not loaded. The page reloads, and it
loads after the deploy settles. If you move to another page during the
wait, the page does not reload.

## Risks

- Low risk. The change affects only the chunk-load recovery path.
- A chunk that stays broken now causes up to three reloads (about 7 s of
waits in total) before the error page shows. Before, it caused one
reload. The limit stays fixed in each five-minute window, so a reload
loop cannot occur, also when old and new builds alternate.
- A retry that the `vite:preloadError` listener starts is not tied to a
route, so navigation does not cancel it. This matches the behavior
before this change, where that reload occurred at once.
- No server, data, or contract changes.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) by Anthropic, used through Claude
Code with tool use (shell, file edits and GitHub connector). Standard
context window, with 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
- [ ] 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 <noreply@anthropic.com>
2026-11-05 17:21:08 -08:00
4550253dc9 feat(ui): rename Needs you to Inbox and move Mine into Tasks (#19363)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Coordinator gives people navigation for conversations, tasks, and
decisions.
> - The decision entry is called Needs you, while a separate Inbox
remains under More.
> - Coordinator's personal task filter does not retain the full behavior
of the original Mine view.
> - This pull request names the decision entry Inbox and hosts the
original personal work views inside Tasks.
> - People keep their triage tools and saved choices without moving
between two task destinations.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Coordinator navigation and the personal Mine view in Tasks.

**Subsystem affected**

UI navigation, task view routing, task collection preferences, and
browser coverage.

**Current behavior**

Needs you opens the decision feed. More contains another Inbox
destination. The default Coordinator task filter matches task creators
and assignees. It does not use the full personal participation rules,
archive behavior, shortcuts, grouping, or saved columns of the original
Mine view. The organization task table also resets its column choices on
reload.

**Proposed behavior**

Inbox opens the same decision feed with the inbox icon. Tasks defaults
to Mine and remembers a person's selected view per company. Mine uses
the existing personal Inbox implementation and preferences. It retains
read/unread title weights, keyboard navigation, read/unread shortcuts,
archive and undo, mobile swipe-to-archive, grouping, and saved columns.
Old Inbox links open the matching task view. The organization task table
saves its columns and order per person and company.

**Reason and benefit**

People get Chat, Tasks, and Inbox as distinct navigation entries.
Personal work remains a view within Tasks without losing the existing
triage behavior.

**Breaking changes**

Coordinator no longer offers the previous Inbox destination under More.
Old Inbox URLs redirect into Tasks and retain query parameters and
fragments. The Needs you label becomes Inbox; its decision feed, count,
history, and resolution paths stay in place. Classic retains its
separate optional combined-list setting.

**Additional context**

Refs: #19360, the workspace-defaults PR now integrated from master.
Refs: #10620, related Inbox attention ordering. Duplicate searches found
no open PR for this navigation and Mine integration. Roadmap review
found no separate new execution feature in this scope.

## What Changed

- Rename Coordinator's desktop and mobile decision entry and page to
Inbox. Use the inbox icon. Remove the old Inbox item under More. Clarify
that the combined-list experiment applies only to Classic.
- Add one Coordinator task-view router. Host Mine, Unread, Recent,
Blocked, and Everything through the existing Inbox owner. Keep
organization views in the task table and board. Normalize legacy
filtered URLs before mounting a collection so clearing the last filter
preserves the current layout and local filters.
- Reuse the existing Mine API, participation, read state, archive cache,
undo, keyboard, mobile gesture, grouping, and preference paths. Existing
personal column choices carry over.
- Save organization task-table columns and order per person and company.
Keep controls usable if browser storage fails.
- Update navigation tests, routing and persistence regressions,
exact-run retry browser coverage for each workspace variant, product
documentation, and the affected Storybook notes. Keep existing story
IDs.

## Verification

- Preference, routing, navigation, task table, and inherited Inbox
regression tests pass in focused runs. After integrating master and
addressing the review finding, all 187 focused tests, UI typecheck, and
token gates pass.
- Full build and repository typecheck pass after integrating master. The
full UI suite previously passed: 894 files, 11,169 tests passed, and one
skipped. All 14 desktop/mobile triage and exact-run retry browser cases,
including a fresh production UI build, pass after integrating master.
- The full repository test suite was not run locally for this UI-only
change; full PR CI is running.
- Browser checks use a fresh isolated local instance and a deterministic
process agent for real run-attributed unread activity. They exercise
legacy URLs, title weights, saved grouping and columns, read/unread
shortcuts, archive/undo, and a real mobile touch gesture.
- Earlier browser fixtures were corrected: own activity is read, agent
comments need valid run attribution, and undo uses `u` after the archive
refresh completes. The Inbox content heading is scoped separately from
the layout heading.
- The final settings wording correction passes all 63 settings tests.
- The [next CI browser
run](https://github.com/paperclipai/paperclip/actions/runs/42137377960/job/127629100535)
caught an old URL assertion for denied run retries. The test now pins
each workspace variant and covers Coordinator Tasks explicitly. The old
expectation fails deterministically for Coordinator; all 12 retry cases
pass with the corrected destination, preserving exact run identity and
truthful queued, deferred, and denied feedback.
- Token gates, map checks, and Storybook catalog checks pass. All 30
map/catalog script tests pass. Storybook play tests were not run.
- Greptile reviewed [the final
commit](https://github.com/paperclipai/paperclip/commit/5f2f3789b83af259f694f379afb769465688fb72)
at **5/5** with no new actionable findings. Its sort-only link finding
is fixed and resolved: three cases fail before the fix and pass after
it. `sortField` and `sortDir` keep organization links on the
organization table. [Current-head
CI](https://github.com/paperclipai/paperclip/actions/runs/42139668774)
is running.
- [CI exposed a routing
race](https://github.com/paperclipai/paperclip/actions/runs/42135293421/job/127621981587)
when removing the last label from a legacy Everything link. Four
deterministic regressions failed before the fix and now pass:
collections only mount after URL normalization. The existing end-to-end
component cases confirm local filters, board layout, fragments, and
history survive the change.
- The [initial server shard 4
run](https://github.com/paperclipai/paperclip/actions/runs/42131163087/job/127608751620)
failed an unchanged monitor-drain idleness assertion: two active
operations remained instead of zero. The complete scheduling suite
passes locally (54 tests). One isolated CI retry passed, including its
dependent verification gate. The original timing failure's root cause is
not established.

## Risks

- Mine includes the existing tasks, approvals, failed runs, and join
requests, preserving the original personal participation and attention
rules.
- Shared URLs with organization filters or table sort parameters still
open the organization task collection. Task back-links retain their
original fragments.
- Saved preferences are browser-local. Mine keeps its existing storage
keys; the organization table and selected Coordinator view use
person/company scopes.
- No database or server API contract changes are included. Existing
company access, personal participation, and decision permissions retain
their owners.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools, code
execution, and browser automation. The exact serving revision and
context-window size are 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
- [ ] 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: Scott Tong <scott@scottsmbpm5max.lan>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 17:07:24 -08:00
f2593b7905 feat: make Coordinator and Agent Chat standard and speed up navigation (#19360)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - People enter that work through the workspace, agent conversations,
and tasks.
> - Coordinator and Agent Chat still require experimental enrollment.
> - Task link prefetches do not warm the compact queries used by the
default task view. Chat also waits for a request when its navigation
list already holds the conversation.
> - This pull request makes Coordinator and Agent Chat standard and
reuses the reads needed for navigation.
> - People can choose Classic workspace in Experimental settings. Cached
work can appear while fresh data loads.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Workspace selection, Agent Chat availability, and navigation between
Chat, Tasks, and task details.

**Subsystem affected**

Cross-cutting: UI, workspace preferences, instance settings, and the
database migration.

**Current behavior**

People must enable Coordinator and Agent Chat in Experimental settings.
Opening a task can repeat requests because prefetch warms the full
detail and comment queries, while the default view reads compact task
data and a recent comment window. Opening Chat waits for a detail
request even when the current person's conversation list contains it.

**Proposed behavior**

Coordinator is the standard workspace for everyone. Experimental
settings offer Classic workspace, off by default. Old Coordinator
experiment values do not select Classic. Agent Chat is available to
everyone, including instances with an old saved or managed off value.
Task link intent warms the queries used by the mounted view. Chat can
use the current person's company-scoped conversation list during
refresh.

**Reason and benefit**

People can use the standard workspace without setup. Repeat navigation
can show cached content while the server refreshes it.

**Breaking changes**

Existing Coordinator experiment and Agent Chat off values no longer
disable these surfaces. Classic requires an explicit new opt-in. The
workspace API keeps its existing response shape and audit checks. The
migration retains legacy values and adds a separate Classic preference.
Execution permissions, company access, and chat privacy checks still
apply.

**Additional context**

Roadmap check: this graduates existing surfaces and improves their
navigation. It does not add a new agent execution capability. Duplicate
searches found no open Coordinator workspace PR. Related server cache
work, Refs: #17828, addresses compact issue-list invalidation; this PR
addresses client navigation queries.

## What Changed

- Add a default-off Classic workspace preference. Derive the existing
workspace API response from that choice. Keep personal persistence,
account isolation, and audited writes in the workspace preference
service.
- Replace the Coordinator experiment control with Classic workspace. Use
Coordinator while preferences load. Update the existing settings
examples and navigation recipe.
- Retire the Agent Chat experiment control. Ignore old stored and
managed off values. Retain the compatibility key and the existing
execution and access checks.
- Reuse account- and company-scoped chat list data with its original
freshness timestamp.
- Share compact task and recent-comment query options between prefetch
and the task view. Keep full-history prefetch for Classic tasks and
expanded history.
- Add upgrade, replay, cache, account isolation, and browser switching
regressions. Update product, database, development, and roadmap
documentation.

## Verification

- Targeted preference, settings, migration, cache, and UI regression
tests passed during implementation. The cache tests hold refresh
requests pending and check that content still renders. They also check
account isolation and stale refresh behavior.
- A real browser test verified fresh Coordinator defaults, Classic
opt-in persistence, and return to Coordinator. It used an isolated
source checkout with fresh data on macOS.
- Token, migration numbering and safety, map, and Storybook catalog
checks pass after the rebase.
- Repository typecheck and build pass. Typecheck and token gates also
pass after review repairs. The broad local run passed all six chat
integration shards (1,931 tests). It then found the host-CLI-dependent
recovery fixture described below. Its remaining duplicate work was
stopped after the full current-head CI matrix passed; this is not a
claim that the broad local command completed green.
- Review repairs passed 419 UI/migration fixture tests, 19 Coordinator
navigation tests, and all 214 tests in the five affected server suites.
The 24 Chat/workspace browser tests pass, including standard Coordinator
switching and explicit Classic persistence.
- The broader browser repair run passed 85 tests. It retained two failed
assertions and skipped five later serial cases. The affected files then
passed all 11 tests after the fixes: reopen the desktop panel after
reload and open More for Audit and Connectors. This includes the five
serial cases that had not run.
- The broad local suite exposed a recovery fixture that executed the
host Codex CLI during a simulated remote probe. On a host with Codex
0.154.0, model fallback broke its fixed-model assertion. The fake
sandbox now reports Codex 0.160.0. All 20 selected recovery/provider
compatibility cases pass after the repair.
- A CLI company import/export suite timed out during server startup in
the second CI run. The port fallback is intentional in that fixture. No
root cause was established. That suite passes on the final commit in CI.
This records recovery, not a claimed product fix for the earlier
timeout.
- The first CI run exposed remaining assumptions about the retired flags
and Classic UI. The repair commit updates those fixtures and navigation
steps. It also restores the missing enabled Cases destination in
Coordinator. Earlier local recovery attempts used the wrong fixture
notification and advanced fake time during live execution; the corrected
tests use the subscribed recovery queue and wall time during execution.
- Greptile reviewed commit `98972b030d4505d838427a2fca6b196b8cad6d47` at
**5/5** with no new comments. The original finding is resolved.
- Current-head CI is green: **56 successful checks**, with three checks
skipped by workflow filters. This includes typecheck, build, the
complete Vitest matrix, all eight browser shards, Runner verification,
release checks, and security checks. [Final CI
run](https://github.com/paperclipai/paperclip/actions/runs/42132651414).
- Typecheck caught missing `pid` and `startedAt` fields in the simulated
command result. The fixture now returns the complete process contract,
and the server test-layer typecheck passes.
- Storybook catalog alignment was checked. Storybook play tests were not
run. Hosted latency and cold bundle loading were not measured.

## Risks

- The new default changes the workspace for people who previously left
the Coordinator experiment off. They can opt into Classic in
Experimental settings.
- The additive migration must run before the new server code. It is safe
to replay and preserves explicit Classic choices.
- Cached content can be stale during background refresh. Existing query
invalidation and source timestamps still control freshness.
- These changes reduce avoidable loading waits. They do not establish a
measured improvement in hosted request latency or initial bundle
download time.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, repository tools, code
execution, and browser automation. The exact serving revision and
context-window size are 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: Scott Tong <scott@scottsmbpm5max.lan>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 16:28:25 -08:00
DottaandClaude Opus 5.5 bf10bc695a feat(task): show the agent's context window in task properties (#19273)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through a harness (Codex app-server, or Claude through
ACPX), and the runner turns harness events into Paperclip events
> - Both harnesses report how full the model's context window is. Codex
sends `modelContextWindow` with its token usage. Claude sends `used` and
`size` in ACP `usage_update`
> - Paperclip dropped both values. The task view could not show how
close an agent is to compaction, and a compaction left no visible mark
in the task chat
> - This pull request carries the context-window occupancy through the
runner, stores it with the existing usage events, and shows it as a
"Context" row in the task Properties panel
> - It also shows a "Context compacted · 230k → 38k tokens" divider in
the task chat when the agent compacts its context
> - The benefit is that operators can see when a long task is near its
context limit and when the agent lost detail to a compaction

## Linked Issues or Issue Description

Refs #19252 (same pattern: a canonical harness event turned into
task-chat UI). Refs #19267 (status notices; it turns Claude's compaction
banner into a notice, and this PR reads that notice too).

**What existing behavior does this improve?**
The task view shows no context-window information, and a compaction is
invisible in the task chat.

**Subsystem affected**
Runner usage normalization (Rust and TypeScript transport), the PRP
usage schema, a read-only issue API, the task Properties panel and the
task-chat transcript.

**Current behavior**
Codex `modelContextWindow` and ACP `used`/`size` are dropped in the
runner. `context.compacted` always has null sizes, Claude compactions
emit no `context.compacted` event at all, and the live runner turn hides
every marker except "interrupted".

**Proposed behavior**
`usage.reported` carries an optional `context {usedTokens,
windowTokens}`. The task Properties panel shows a Context row ("10% of
258k") with a hover breakdown (tokens used, input and cached input,
output, cost, compactions). The transcript shows a compaction divider,
with sizes from the usage before and after the compaction.

**Reason and benefit**
Operators can see when a long task is close to its context limit, and
they can see where the agent's earlier context was summarized.

**Breaking changes**
None. The new schema field is optional, and the new endpoint is
read-only.

## What Changed

- PRP `usage.schema.json`: add an optional `context {usedTokens,
windowTokens}` object. Regenerate the schema bundle and the protocol
manifest.
- Rust `provider_events.rs`: add `codex_context_usage` (last request
`totalTokens` and `modelContextWindow`) and `acpx_context_usage` (ACP
`used`/`size`). ACPX `usage_update` now sets `context`.
- Rust `provider_backend.rs`: Codex `thread/tokenUsage/updated` sets
`context` on live (non-historical) usage.
- Rust `provider_events.rs`: emit `context.compacted` for the completed
`context_compaction` provider notice that #19267 produces from Claude's
compaction banner. Only this path marks a Claude compaction, so the
server counts each compaction once.
- `runnerd-codex-transport.ts`: `rehydrateRunnerdUsageNotification`
keeps a valid `context` on `tokenUsage`, so it reaches the stored
`item.completed` usage item.
- Server: new `GET /api/issues/:id/context-usage` (issue read access is
required) and `issueContextUsage` service. It reads only the assignee's
current session (the session of its newest usage report), and returns
that session's latest context reading, non-zero totals, cost, and the
count of compactions across all of the session's runs. Added to OpenAPI.
Shared type `IssueContextUsage`.
- DB: migration 0432 adds two partial indexes on `heartbeat_run_events`,
built concurrently. One indexes usage items by run and time. The other
indexes `context.compacted` events by company and session. `EXPLAIN`
shows that both lookups use them.
- UI: new `ContextUsageMeter` (bar, percentage, hover card) and
`IssueContextUsageRow` in the Execution section of Properties. The row
refreshes every 8 s while a run is active, and once more when the run
ends.
- UI transcript: `native-run-events.ts` maps `context.compacted` to a
`context` activity entry with pre/post sizes from neighbouring usage
items. `transcript-adapter.ts` turns it into a new `context_compacted`
marker variant. The live runner turn keeps that marker as its own
timeline row.
- Storybook: `task-context-window.stories.tsx` (Properties panel in a
task, all states, hover breakdowns, divider).

## Verification

- `cargo test -p paperclip-runner-core --lib provider_events` (new
tests: ACPX context, Codex context, banner compaction, notice
compaction)
- `pnpm --filter @paperclipai/paperclip-runner exec vitest run
src/live/runnerd-codex-transport.test.ts`
- `cd ui && npx vitest run src/components/ContextUsageMeter.test.tsx
src/components/transcript src/components/task-chat
src/components/issue-properties`
- `cd server && npx vitest run
src/__tests__/issue-context-usage.test.ts` (embedded Postgres)
- `node scripts/typecheck-low-memory.mjs`, `pnpm check:token-gates`,
`pnpm --filter @paperclipai/paperclip-runner run check:protocol-types`
- Live check on a local worktree instance through the runnerd path:
- A Codex `gpt-5.5` task showed "10% of 258k" (27k of 258k, input 77k
with 49k cached, output 1.1k).
  - A Claude Sonnet 4.6 task showed "17% of 200k" (34k of 200k).
- Storybook: Tasks and conversations / … / Task chat / Context window.
- Not verified live: a real compaction. The divider is covered by unit
tests through the same projector path the task chat uses.

## Risks

- The schema field is optional, and older runners omit it. The UI
renders nothing until a harness reports a window.
- Migration 0432 builds two partial indexes concurrently on
`heartbeat_run_events`, which is a large table. The indexes cover only
usage items and compaction events, so they stay small. They add a little
write overhead to those events.
- Claude in-progress usage rows still record zero tokens with `requests:
1`. The final turn usage is correct. This PR does not change accounting.
- A "Compact now" action is out of scope. It needs a compaction-only run
that does not produce a run result. It will come in a follow-up.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits, in-app browser for Storybook and
the 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
- [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 <noreply@anthropic.com>
2026-11-05 16:54:27 -06:00
Devin FoleyandPaperclip c3550cfb38 fix(daytona): send archive chunks in bounded windows (#19339)
## Thinking Path

> - Paperclip manages agents and saves their work when execution stops.
> - The Daytona plugin saves an archive through a terminal when disk
capacity prevents the normal archive or download copy.
> - That fallback sent one 40 KiB chunk and waited for its host write
acknowledgement.
> - Network delay then applied to every small chunk. A large archive
could exceed the recovery deadline even while bytes were moving safely.
> - This pull request sends a bounded window of chunks and still
acknowledges every completed host write.
> - The benefit is faster recovery over delayed connections, with
bounded memory and the existing size, space and completion checks.

## Linked Issues or Issue Description

Refs #19034, which added the capacity fallback.

**What happened?**

The archive fallback waits for one network round trip after each 40 KiB
chunk. A saved workspace archive can be much larger than that chunk.
Repeating a timed-out transfer does not remove this throughput limit.

**Expected behavior**

Allow a small amount of data to travel before waiting for
acknowledgements. Keep memory bounded, write bytes in order, and stop
after any write or protocol failure.

**Steps to reproduce**

1. Run the real Bash archive sender against a host that delays each
acknowledgement by 400 ms.
2. Transfer 1 MiB plus a partial final chunk with a four-second
deadline.
3. The original implementation times out. The bounded-window
implementation completes and preserves the exact source bytes.

**Paperclip version or commit**

The failing regression uses the original implementation from
`6a66c09f7f7`.

## What Changed

- Send at most sixteen 40 KiB chunks before collecting their
acknowledgements. Drain the final partial window before the completion
receipt.
- Serialize host writes and reject more than sixteen pending frames.
Stop queued writes on the first failure.
- Keep per-frame size checks, total archive limits, the 1 GiB host space
reserve, the transfer deadline and bounded teardown.
- Add a real Bash regression with delayed acknowledgements, plus
overflow and failed-write coverage. Update the plugin documentation.
- Update the agent-creator test fixture for the required directory
membership role introduced by #15183, which otherwise breaks merged CI
typechecking.

## Verification

- Failing first: the final real-Bash regression fails against the
original source with `Daytona archive stream timed out`. It passes with
the change. The empty-stream case passes too.
- Standalone Daytona package `pnpm test`: 578 passed, 8 skipped. The
skips are the existing opt-in live tests.
- Standalone package `pnpm typecheck` and `pnpm build`: passed.
- `pnpm test src/file-sync-capacity.live.test.ts` with a staging Daytona
key: 2 passed on isolated test sandboxes. This covers actual terminal
transport, stop/start, archive and file-copy capacity failures, exact
saved hashes, safe links, cleanup and a second export. The tests removed
their own sandboxes.
- The production dependency assumption is bounded: at most 640 KiB of
decoded payloads can wait for host writes. The parser also has a bounded
encoded buffer. No background work or idle timer is added.
- Initial hosted CI failed with TS2741 in
`ui/src/lib/agent-creator.test.ts` after #15183 made `membershipRole`
required. Rebased onto that change and added the nullable field to the
fixture. UI `pnpm typecheck`, all 4 existing creator-label tests, and
`pnpm check:token-gates` now pass locally. Hosted CI subsequently passed
on the updated head.
- `git diff --check` and a local secret and PII scan: passed.
- Full repository checks were not repeated locally for this standalone
plugin change. Hosted verification at
`bf6b3cb6a622a9523c72b86fdfe616555c8f3ef8`: all 53 check runs passed,
with the 2 opt-in Storybook checks skipped; both required gates (`ci /
verify`, `ci / e2e`) passed. Greptile is 5/5 at this exact head, with no
review threads or merge conflicts. The separate non-required
`security/snyk (cryppadotta)` status remains pending; it is not claimed
as passed.

## Risks

- A burst can queue up to sixteen chunks instead of one. The receiver
enforces this bound and acknowledges a chunk only after its ordered host
write.
- The timeout budget is unchanged. A connection that cannot finish
within that budget can still fail. This change does not retry a provider
turn or change file-recovery authority.
- Source confinement, archive validation, successful producer completion
and publication rules are unchanged. No schema migration.

## Model Used

- OpenAI `gpt-6.1-sol` with xhigh reasoning, in Codex through T3 Code.
Tool use included source edits, real Bash and filesystem tests, and
isolated Daytona tests. The runtime does not expose its 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>
2026-11-05 13:12:11 -08:00
DottaandClaude Opus 5.5 22281d4b3a feat(task-chat): approve harness-proposed plans (Codex plan mode, Claude ExitPlanMode) (#19291)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through the native runner. Codex and Claude Code each
have their own plan mode, and each produces a proposed-plan document.
> - Paperclip already has a planning work mode, an issue `plan`
document, and a `request_confirmation` approval gate for plans.
> - These parts were not connected. The Codex proposed-plan item was
transcript text only. The server path never put Codex into plan mode.
Claude was never put into plan mode, and its ExitPlanMode plan was
dropped. A planning run could close its task as done with no approval.
> - This pull request turns a harness's own proposed plan into the
issue's Plan document and the existing approval gate, for Codex and
Claude.
> - The benefit is that users approve or send back the agent's real plan
from the task chat. Approve resumes the agent. Request changes sends the
reason back to the agent.

## Linked Issues or Issue Description

Refs #19252

**Subsystem affected**
Native runner provider events (Codex and ACPX Claude), the runner Codex
provider (Rust), server native plan synchronization, and the task chat
UI.

**Problem or motivation**
Codex and Claude both produce a proposed-plan document in their native
plan mode. Paperclip did not use it. In live tests on the server path, a
Codex planning run was not in Codex plan mode, saved its own
`implementation-plan` document, and marked the task done without
approval. Claude planning runs were not in Claude plan mode, and the
ExitPlanMode plan text never reached Paperclip.

**Proposed solution**
Add a canonical `plan.proposed` event for a harness's proposed plan. Put
Codex and Claude into their native plan mode for planning runs. Save a
completed proposal as the issue `plan` document with the existing
`request_confirmation` gate. Agents can also propose a plan on a
standard task; the work mode stays standard.

**Alternatives considered**
Reuse `plan.updated`. Its schema is the within-turn checklist and is not
the durable Plan document, so a separate event keeps the two clear.
Stronger prompt text without native plan mode. Live tests showed that
the model kept its own document workflow.

**Roadmap alignment**
This reuses the issue plan document, planning work mode and approval
gate that already exist. It adds one canonical provider event.

## What Changed

- `packages/paperclip-runner/protocol/schemas/*`: add the
`plan.proposed` event (`paperclip.plan.proposed.v1`: `proposalId`,
`status`, `markdown` up to 60k characters, `source`). Regenerate the
schema bundle, validators and manifest.
- `packages/paperclip-runner/src/provider-events.ts`: map the Codex
`plan` item and a `<proposed_plan>` block in a completed Codex message
to `plan.proposed`. Add `claudePlanProposalEvent`.
-
`packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs`:
send Codex's native `collaborationMode: plan` on `turn/start` for a
read-only (planning) Codex run. Before this change the server path never
sent it.
- `runner-core/src/acpx_event_payload.rs`: admit `plan.proposed` as ACPX
rich display activity.
- `drivers/acpx/codex-runtime-adapter.ts`: set Claude Code to native
plan mode for a read-only run. Capture Claude's ExitPlanMode plan from
its permission request, record it, and decline the native "Ready to
code?" prompt.
- `drivers/acpx/codex-acpx-driver.ts`, `cli/acpx-runtime-sidecar.ts`:
emit the captured proposal as a rich event. Complete that turn instead
of reporting it as cancelled.
- `backends/codex-native-backend.ts`: per-harness plan-mode
instructions, and a short opt-in hint for Claude on standard tasks.
- `server/src/services/native-runtime/native-session-executor.ts`:
synchronize a completed `plan.proposed` into the `plan` document and a
`request_confirmation` in planning and standard mode. Do not change the
issue status during the run (that revoked the run's lease). The run
finalizer turns the pending confirmation into the review wait.
- `server/src/services/heartbeat-run-summary.ts`: keep a
`<proposed_plan>` block out of the posted reply.
- UI: new `plan_proposal` provider family. The task chat shows a live
"Writing plan" card while a proposal streams and a one-line record when
it completes. Resolved plan reviews keep a receipt ("Approved Plan v1",
"Requested changes to Plan v1: reason"). Plan previews skip section
headings.
- Storybook: `Tasks and conversations/.../Task chat/Proposed plan
approval` shows the journey.
- `doc/SPEC-implementation.md`: describe harness-proposed plan approval.

## Verification

- `cargo test -p paperclip-runner-core --lib` (new:
`read_only_codex_turns_use_native_plan_collaboration_mode`)
- `cd packages/paperclip-runner && npx vitest run
src/provider-events.test.ts
src/drivers/acpx/codex-runtime-adapter.test.ts`
- `cd server && npx vitest run
src/__tests__/native-plan-proposal-sync.test.ts
src/__tests__/heartbeat-run-summary.test.ts`
- `cd ui && npx vitest run
src/components/transcript/native-run-events.test.ts
src/components/task-chat`
- Live test on a local worktree instance with real harnesses:
- Codex (`gpt-5.6-sol`), planning task: Codex asked one question, then
produced a native plan item. The task showed Plan v1 with Approve plan /
Request changes and moved to in review. Approve changed the work mode to
standard. Codex then implemented the plan and its tests passed.
- Codex, Request changes with a reason: Codex woke in planning mode and
produced Plan v2 that includes the change.
- Claude (`claude-opus-5-5`), planning task: Claude was in plan mode.
ExitPlanMode became Plan v1 and the turn completed. Approve resumed
Claude in standard mode, and Claude implemented the plan.
- Claude, standard task that asks for a plan first: Claude stopped
before code changes and asked for plan approval. The work mode stayed
standard.
- Storybook: open `Proposed plan approval` and check each stage.

## Risks

- Codex planning runs now really use Codex plan mode on the server path.
Codex can ask a clarifying question before it proposes. This matches
Codex plan mode.
- Claude planning runs now use Claude plan mode. A declined ExitPlanMode
ends the Claude turn; the runner reports it as completed. Claude's tool
row for that request records a rejection from Claude's side.
- Codex does not stream the proposed plan on the server path yet, so the
live card shows a placeholder until the plan completes.
- A `<proposed_plan>` block in any completed Codex message on a standard
task now asks for approval. This is the documented opt-in 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

- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), via Claude Code with
tool use (shell, file edits, browser). Long context. No extended
thinking mode was 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
- [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.5 <noreply@anthropic.com>
2026-11-05 20:53:33 +00:00
Svetlana ZolotenkovaandPaperclip 1b0f78100d fix(access): return membershipRole in the company user directory (#15183)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents often check a human approval. The check must confirm the role
of the approving user in the company.
> - An agent can read the company user directory. The directory removes
the membership role from the server record.
> - The admin member list requires `users:manage_permissions`. An
approval check must not need that permission.
> - This pull request returns `membershipRole` in the existing user
directory and keeps all access limits.
> - The benefit is that agents can verify an approver's role without
admin permissions.

## Linked Issues or Issue Description

**What happened?**

`GET /api/companies/{companyId}/user-directory` returns active users
without `membershipRole`. An agent that checks the authority of an
approving user cannot tell an `owner` from other roles. `/members`
requires `users:manage_permissions`.

**Expected behavior**

The directory returns the exact `membershipRole` from
`companyMemberships`. A missing role stays `null`. Reading this field
does not require admin permissions. Member management keeps its current
limits.

**Steps to reproduce**

1. Use a company with an active `owner` user and an active user without
a role.
2. Call `/user-directory` with an agent key.
3. Before this change, neither record has `membershipRole`. After this
change, the field is `owner` and `null`.
4. The same agent still cannot read `/members` or change a member.

## What Changed

- Add `membershipRole` to the select and result of
`loadCompanyUserDirectory`.
- Test the exact `owner`/`null` values, agent read access, denied admin
read and change, denied other company, and denied unauthenticated
request.
- Keep the existing `assertCompanyAccess` and the company, user type,
and active filters.
- The client type includes a nullable `membershipRole`. The management
test rejects only `users:manage_permissions` and checks the exact calls.
The first review findings are fixed in a0d1ad3.

## Verification

- `pnpm --filter @paperclipai/server typecheck`: pass.
- `pnpm --filter @paperclipai/server build`: pass.
- `pnpm --filter @paperclipai/ui typecheck`: pass.
- `pnpm exec vitest run
server/src/__tests__/company-user-directory-route.test.ts
server/src/__tests__/access-service.test.ts
server/src/__tests__/access-routes-hidden-floor.test.ts`: 16 passed, 17
skipped. `ui/src/lib/company-members.test.ts` is added.
- The regression test fails on the old `access.ts` because
`membershipRole` is missing. It passes after the change.
- `git diff --check`: pass.

## Risks

The directory response gets one more field. There are no migrations. The
role comes directly from the server record, with no `owner` fallback.
User management permissions do not change.

## Model Used

OpenAI Codex, GPT-6, with shell tools, code editing, and test runs. The
environment does not expose the exact model identifier.

## 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>
2026-11-05 14:43:02 -06:00
DottaandClaude Opus 5.5 d807887534 fix(ui): use one status glyph mapping for every task view (#19329)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task status is the main signal on every task surface: sidebar rows,
the task header, the status menu, lists, the inbox, pills and chat
cards.
> - One task state rendered different status icons in different views. A
blocked task that waits on an active sub-task showed the blue "in queue"
glyph in the task header. The status menu, the chat chip and the sidebar
rows showed the red "Blocked" glyph for the same task.
> - The cause: each view passed its own subset of the presentation
fields to `StatusIcon`. Many views dropped `blockerAttention` and some
dropped `externalConversationState`. The status menu rendered its
current row from the bare status. The summaries API did not return
`blockerAttention`, so the recent-task sidebar could not know the state.
> - This pull request puts the state-to-glyph/label mapping in one
resolver and makes every task view pass all of its fields.
> - The benefit is that the same state uses the same icon and label
everywhere.

## Linked Issues or Issue Description

No public issue. Found in a hands-on onboarding test on a staging cloud
workspace.

**What happened?**

The sidebar and the task pane/status menu showed different status icons
for the same state. On the test task (blocked, waiting on an active
sub-task), the task header and the status button showed the blue "in
queue" glyph. The selected "Blocked" row in the status menu showed the
red blocked glyph. The coordinator sidebar "Recents" and project rows
also showed the red blocked glyph, and the chat "Subtask created" chip
showed a red "Blocked" chip.

**Expected behavior**

The same underlying state uses the same icon and label everywhere.

**Steps to reproduce**

1. Create a task with a sub-task. Block the parent on the sub-task, and
let the sub-task's agent run (the parent is "blocked, covered").
2. Open the parent task. The header shows the blue "in queue" glyph.
3. Open the status menu. The current "Blocked" row shows the red glyph.
4. With the coordinator sidebar on, look at the task in Recents or in
its expanded project. It shows the red glyph.

**Paperclip version or commit**

`master` at 03eb49053f.

## What Changed

- Add `ui/src/lib/task-status-presentation.ts`.
`resolveTaskStatusPresentation()` is the one mapping from `status`,
`displayStatus`, `externalConversationState` and `blockerAttention` to
glyph, label and accessible copy. `taskStatusIconProps()` picks all four
fields from a task record.
- `StatusIcon` and `IssueStatusBadge` use the resolver. The status menu
draws the current row with the trigger's presentation (in queue, idle,
monitoring).
- `IssueStatusBadge` now accepts `blockerAttention`.
`taskStatusVar.in_queue` maps to the existing in-progress chip hue, the
same as the existing `--status-task-icon-in_queue` icon token.
- Task call sites spread `taskStatusIconProps(task)`: coordinator
sidebar recents and project rows, classic recent tasks, lists, rows,
columns, inbox, blocked inbox, my issues, dashboard, properties,
relation pills, search, sibling navigation, chat chips, case summaries,
status cards and user profile. Step/check statuses in protocol cards
stay unchanged because they are not task statuses.
- `GET /api/companies/:companyId/issues/summaries` returns
`blockerAttention` for blocked rows (at most five IDs, through the
existing `listBlockerAttention` service). `IssueSummary` gets the
optional field.
- Recent-task snapshots store and refresh `blockerAttention`. Sidebar
project rows request the `rows` projection, which includes blocker state
(the `index` projection skips it).
- Storybook: the Foundations/Status Language story gets a "Same state,
every view" section.

## Verification

Red on `master` (tests written first, then run against the unchanged
code):

```
cd ui && npx vitest run src/components/StatusIcon.picker.test.tsx src/components/CoordinatorSidebar.test.tsx src/lib/recent-tasks.test.ts
     × keeps the covered-blocker state so the sidebar shows the same queued glyph as the task pane (BUG-07)
     × marks the current row with the same glyph as the trigger for a covered-blocked task
     × marks the current row with the same glyph as the trigger for a waiting review and a monitored task
     × shows a covered-blocked recent task with the same queued glyph and label as the task pane
     × shows project tasks with their full status presentation
 FAIL  StatusIcon.picker.test.tsx > ... covered-blocked task
AssertionError: expected 'var(--status-task-icon-blocked)' to be 'var(--status-task-icon-in_queue)'
 FAIL  StatusIcon.picker.test.tsx > ... waiting review and a monitored task
AssertionError: expected 'var(--status-task-icon-in_review)' to be 'var(--status-task-icon-backlog)'
 FAIL  CoordinatorSidebar.test.tsx > ... covered-blocked recent task ...
AssertionError: expected 'var(--status-task-icon-blocked)' to be 'var(--status-task-icon-in_queue)'
 FAIL  CoordinatorSidebar.test.tsx > ... project tasks with their full status presentation
AssertionError: expected "vi.fn()" to be called with arguments: [ 'company', ObjectContaining{…} ]
 FAIL  recent-tasks.test.ts > ... keeps the covered-blocker state ...
AssertionError: expected undefined to deeply equal { state: 'covered', …(7) }
      Tests  5 failed | 32 passed (37)

cd server && npx vitest run src/__tests__/issue-summaries-routes.test.ts
     × projects the covered-blocker state so every task view picks the same queued glyph (BUG-07)
AssertionError: expected undefined to match object { state: 'covered', …(2) }
      Tests  1 failed | 14 passed (15)
```

Green with this change (same commands):

```
 Test Files  3 passed (3)
      Tests  37 passed (37)
 Test Files  1 passed (1)        # server summaries route
      Tests  15 passed (15)
```

Neighbouring and broader checks:

- `StatusIcon`, `StatusBadge`, `StatusGlyph`, picker, sidebar and
recent-task suites: 73/73 pass.
- Full UI Vitest run: 11106 pass, 1 skipped. Three `IssuesList.test.tsx`
tests timed out at 5 s under full-suite load. The same file passes 73/73
when run alone.
- `tsc --noEmit` for `ui` and `server`: no errors in changed files.
Remaining errors are pre-existing (unbuilt
`@paperclipai/paperclip-runner`, generated Storybook catalog).
- `pnpm check:token-gates`: all gates clean.

Screenshots. The staging task before the fix: the status button is blue,
the current "Blocked" row in the menu is red.

| Staging task (before) | Staging status menu (before) |
| --- | --- |
| ![staging
before](https://raw.githubusercontent.com/paperclipai/paperclip/75e148aa1b100400d406d952e66cfac6c4afa5c3/before-staging-onb1.jpg)
| ![staging menu
before](https://raw.githubusercontent.com/paperclipai/paperclip/75e148aa1b100400d406d952e66cfac6c4afa5c3/before-staging-onb1-menu.png)
|

Storybook "Same state, every view" (Foundations/Status Language). Before
uses the `master` `StatusIcon`/`StatusBadge`; after uses this branch.

| Before (master) | After (this PR) |
| --- | --- |
| ![storybook
before](https://raw.githubusercontent.com/paperclipai/paperclip/75e148aa1b100400d406d952e66cfac6c4afa5c3/before-storybook-menu.jpg)
| ![storybook
after](https://raw.githubusercontent.com/paperclipai/paperclip/75e148aa1b100400d406d952e66cfac6c4afa5c3/after-storybook-menu.jpg)
|

Before: the row glyph is blue, but the chip and the current menu row are
red. After: the row glyph, the chip and the current menu row are all the
blue "in queue" glyph.

Review follow-up (commits 1d3a1a0a9e, d5c798ec53): blocker state on
summaries, search and case responses is glyph-only (state, reason,
counts). Blocker titles and IDs are never returned; a sampled identifier
stays only when the actor can read that blocker. Producer-level tests
failed before the follow-up and pass after it
(\`task-created-items.test.tsx\`,
\`CoordinatorTaskCommitment.test.tsx\`,
\`company-search-service.test.ts\`, \`cases-routes.test.ts\`,
private-blocker case in \`issue-summaries-routes.test.ts\`). Before
merge, the branch was test-merged locally with current \`master\`:
typecheck clean, 1095 UI and 80 server tests in the affected suites
pass.

## Risks

- Low risk. Presentation only. Status mutations still use the canonical
`status`.
- The summaries endpoint now runs the blocker-attention query for
blocked rows. The endpoint accepts at most five IDs and runs the query
only when a row is blocked.
- Sidebar project rows use the `rows` projection instead of `index`.
This adds the blocker and review attention reads for at most 12 rows,
and only when a person expands a project.
- `IssueStatusBadge` shows the blue "in queue" chip for covered-blocked
tasks where callers pass `blockerAttention`. The label stays "Blocked"
and the full reason is in the tooltip.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`, Anthropic), through Claude Code
with tool use (shell, file edits, browser). Default 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:42:11 -06:00
Devin FoleyandPaperclip 088f85c5fe fix: rename the managed environment to Paperclip Sandbox (#19296)
## Thinking Path

> - Paperclip manages AI agents and their execution environments.
> - Managed installations supply a default sandbox through managed
configuration.
> - Most selectors show the configured environment name.
> - A fallback label, two errors, and examples still use Paperclip
Computer.
> - This pull request changes that stock copy to Paperclip Sandbox.
> - Existing and custom environment names continue to come from the
stored row.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The managed environment label in agent settings and the errors shown
when the managed sandbox is unavailable.

**Subsystem affected**

The agent configuration UI, runner selection, and environment
reconciliation tests.

**Current behavior**

Fallback copy and current examples call the managed sandbox Paperclip
Computer. The actual environment name is already configurable.

**Proposed behavior**

Use Paperclip Sandbox for stock copy and current examples. Keep
configured names unchanged in selectors, including legacy names and
custom names.

**Reason and benefit**

The name identifies this environment as a managed sandbox and removes
ambiguity.

**Breaking changes**

The displayed fallback and error text change. Error codes, execution
behavior, and environment identity remain the same. No migration is
needed.

**Additional context**

No duplicate rename issue or pull request was found. This is a copy
update, with no new roadmap feature.

## What Changed

- Update the managed fallback label and two unavailable-sandbox error
messages.
- Update current test and Storybook fixtures without changing story IDs
or behavior.
- Test configured names, fallback copy, and the unavailable-sandbox
message.
- Add a PostgreSQL regression test for a rename that retains the
environment ID, instance default, agent binding, environment variables,
and lease.

## Verification

- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- Focused coverage: 956 cases across nine changed suites. The new
fixture initially tried to insert an existing singleton; its corrected
upsert passed with all 52 environment-service cases. The updated runner
suite also passed all 243 cases.
- The two UI suites passed all 189 cases, including configured
legacy/custom names and the missing-default fallback.
- `pnpm check:token-gates` passed.
- `node scripts/storybook-catalog.mjs --check` passed. The catalog is
structural evidence only.
- `node --test scripts/check-maps.test.mjs
scripts/storybook-catalog.test.mjs
scripts/storybook-feature-index.test.mjs` passed.
- Full `pnpm test:run` is in progress. No live provider or browser
journey is claimed by these tests.
- CI passed on the current head after one server-shard rerun. The first
attempt hit a 10 ms deadline assertion in the unchanged status-card
timing test; its isolated local run and the complete rerun shard passed.
The original failure and rerun are retained in the PR comment.
- Reviewed the diff for secrets, personal data, and run artifacts.

## Risks

Low risk: runtime edits change three display strings. Managed
reconciliation is unchanged. The regression test exercises the existing
rename against PostgreSQL. The configured name still controls normal
display, so this code change alone does not rename existing database
rows. A managed configuration update uses the existing reconciliation
path. Roll back the copy by reverting this commit.

## Model Used

OpenAI GPT-6.1 Sol (`gpt-6.1-sol`) through Codex in T3 Code, xhigh
reasoning, with 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, version, and capabilities
- [x] I have checked ROADMAP.md; this copy update does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs
- [x] I have described the issue in the PR with the enhancement template
fields
- [x] No internal or instance-local issue references
- [x] The branch name describes the change and contains no internal
ticket ID
- [x] Focused tests pass locally
- [x] Relevant tests were added or updated
- [x] Relevant copy and examples were updated; no API or schema
documentation changed
- [x] Risks are documented
- [x] All CI gates passed on the current head, including the server
11/12 rerun and aggregate verify
- [ ] Full local test suite completed (still running; six chat shards
passed)
- [x] Greptile is 5/5 on the current head with no unresolved findings
- [x] I will address all reviewer comments before requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-05 10:23:43 -08:00
DottaandClaude Opus 5.5 b0fa2ef9bf feat(task-chat): show web searches, model switches, waits and verification checks as dedicated rows (#19262)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through the native runner. The runner turns harness
activity into typed canonical events such as `research.*`,
`model.route.changed`, `wait.*` and the run's own verification checks.
> - The task chat flattened these events into generic tool rows. A web
search showed as "Ran Research eventType: research.started, summary: …".
The run's verification checks were dropped completely.
> - Live tests found a second break: the Rust runner core forwarded
Codex `webSearch` items without their query, action or results. The
canonical `research.*` events therefore had `query: null`.
> - This pull request keeps the full payload for these families (as
#19252 did for plans), adds small dedicated rows, and keeps the
web-search fields through the durable runner.
> - The benefit is that users see what the agent searched for, which
model actually ran, how long a wait lasts, and whether each check
passed, without opening the run log.

## Linked Issues or Issue Description

Refs #19252 (same pattern for the plan card)

**Subsystem affected**
Task chat activity rows (UI), the native run-event transcript mapping,
and the Rust runner core Codex normalizer.

**Problem or motivation**
Provider activity that the runner already maps to canonical events
reaches the task chat as unreadable generic rows. Verification checks
never appear. For Codex, the web-search query and action are lost before
they reach the canonical event.

**Proposed solution**
Keep `research`, `model_identity`, `artifact`, `review` and `wait`
events as `provider_activity` entries with their full payload. Project
the runner's `verification` items as a new `verification` family. Render
dedicated rows: a web search with its quoted query and source-domain
chips, a model switch as `from → to · reason`, a wait with a live
countdown, and green/red check lines. Keep bounded web-search and sleep
fields in the Rust normalizer.

**Alternatives considered**
A separate "Checks" block beside the final answer. We kept the checks in
the activity group, because the group summary already leads with "Check
failed" when a check fails.

## What Changed

- `ui/src/components/transcript/native-run-events.ts`: keep research,
model, artifact, review and wait events as `provider_activity` with the
full payload. Map `item.completed` items of kind `verification` to the
new `verification` family. Context, hook, safety and delegation stay on
the generic path, because other work items own them.
- `packages/adapter-utils/src/types.ts`,
`ui/src/components/task-chat/task-chat-model.ts`,
`ui/src/lib/issue-chat-messages.ts`: add the `verification`
provider-activity family.
- `ui/src/components/task-chat/task-chat-activity-presentation.ts`: add
labels: "Searched the web “query”", "Opened a web page host/path",
"Switched models a → b · reason", "Waiting 30s" / "Waited 30s", and
"Check passed" / "Check failed" / "Check not run".
- `ui/src/components/task-chat/TaskChatRunnerActivityGroup.tsx` and
`TaskChatProtocolActivityRow.tsx`: show up to three source-domain chips
under a finished search, a live countdown for a running wait, and a
check line with a tinted icon and its detail.
- `ui/src/components/task-chat/completed-activity-summary.ts`: the group
summary leads with "Check failed" when a check fails, and says "Switched
models" for a reroute.
- `ui/src/components/task-chat/transcript-adapter.ts`: verification
detail keys, and a `startedAt` timestamp that stays across lifecycle
merges (used by the countdown).
-
`packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs`:
keep bounded `query`, `action` and http(s) `results` for Codex
`webSearch` items, and `durationMs` for `sleep` items. Other item types
do not change.
- `ui/storybook/stories/tasks/task-chat-provider-activity.stories.tsx`:
new story. Its fixtures are persisted run events that go through the
production transcript path.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/components/transcript src/components/task-chat
src/components/TaskChatThread.test.tsx src/adapters/paperclip-runner
src/lib`: 275 files and 3669 tests pass.
- `cargo test -p paperclip-runner-core`: all tests pass, including the
new `keeps_bounded_web_search_and_sleep_identity`.
- UI typecheck is clean. `pnpm check:token-gates` is clean. `cargo fmt
--check` is clean.
- Storybook: open "Tasks and conversations / … / Task chat / Provider
activity". It shows a settled run, a live search, a live wait countdown
and a narrow layout.
- Live test on a local worktree instance with a real Codex agent
(gpt-5.6-sol, Codex app-server 0.160). The task asked for a web search,
a parser with tests, a pause, and two checks. The task chat showed
"Searched the web “ISO 8601 week date first week Thursday conversion
calendar date”" and two green check rows ("node --test week-date.test.js
· 8 tests passed; 0 failed.", "node --check week-date.js"). The group
summary read "Ran checks". Before the runner fix, the same search had
`query: null`.

Not verified live:
- The source-domain chips. Codex 0.160 sends `results: null`, because it
keeps search results on the server. The chips appear only when a
provider reports sources. Unit tests and the story cover them.
- The wait countdown and the model-switch row. In the live run Codex
paused with a shell `sleep`, not a native `sleep` item, and no reroute
occurred. Unit tests and the story cover both.
- Claude. Its WebSearch and WebFetch calls arrive as generic tool
executions, because ACPX drops the tool metadata. They show as tool
rows, as before. The ACPX `_meta` work is separate.

## Risks

- Low risk. The change only affects how canonical events display. The
generic path stays for the families that this PR does not change.
- The Rust normalizer now persists the web-search query and the source
URLs/titles in the run events. This data was already intended for the
canonical `research.v1` event, and it is bounded (4,000 characters per
text field, 64 results, http(s) URLs only).
- A search that Codex reports with `action: other` and no query shows as
"Searched the web" with no subject.

> 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 Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use
(shell, file edit, browser automation) and extended 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
- [ ] 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 <noreply@anthropic.com>
2026-11-05 16:37:05 +00:00
TonioandPaperclip 56517dda6c fix(ui): bundle connector, toolbar, empty-state and agent roster fixes (#18716)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators use connector setup, collection headers, empty states, and
agent lists to manage that work.
> - Provider redirects replace the setup page, and collection controls
use different sizes.
> - Agent selection inserts a bar above the list. The row link
highlights only part of the row.
> - This pull request keeps sign-in in a separate window and applies the
Tasks control sizes to collection and audit headers.
> - It also reuses artifact artwork for empty collections and gives
agent selection a fixed action card.
> - These changes preserve the operator's place and keep controls and
rows stable.

## Linked Issues or Issue Description

**What happened?**

Connector sign-in replaced the current page. Inline collection controls
had different heights. Projects and Artifacts used small placeholder
empty states. Agent selection moved the list, hover covered only the
identity cell, and chat/status shared that cell without matching
headings.

**Expected behavior**

Provider sign-in opens in a new tab or window. Collection controls match
Tasks. Empty collections use the large artifact graphics. Agent actions
float above the page while rows remain aligned.

**Steps to reproduce**

Start connector sign-in from a page or dialog. Compare collection and
Audit headers. Open an empty Projects or Artifacts collection. Select an
agent, then hover across its row.

Related public work: #13584 covers an earlier connector new-tab
implementation, including same-page fallbacks that this bundle does not
use. #8528 covers Tasks toggle sizing. #17742 covers scoped agent Audit
sections. This bundle does not change server callback or audit
authorization contracts.

## What Changed

- Reserve a separate authorization window during the click. Use
validated sign-in URLs, retain a new-tab fallback, and close blank
windows after failed requests. Store Cloud reauthentication state in the
destination window.
- Use the Tasks 44px control foundation for shared collection headers
and Audit, Costs, Timeline, Secrets, Teams, Gateways, and agent skill
controls.
- Use a large artifact stack with an off-white Paperclip image with a
dark icon for Projects and artifact Tasks empty states. Use one matching
artifact for other types. Set 20px gaps and use the label “Create
project”. Action hover and keyboard focus animate the artwork.
- Portal agent bulk actions to a fixed card 48px above the bottom safe
area. Use quintic entry/exit motion and respect reduced motion. Retain
card contents during exit.
- Give chat and status separate cells, add a Status heading, use the
MessagesSquare chat action with a hover tooltip, and highlight the
entire row.
- Add linked agent chips and a manager picker to the agent profile.
Block self and descendant manager choices. Keep the name crown and
custom title inline during edits.
- Update design guidance, the connection maps, regression tests, and
empty-state Storybook fixtures.

## Verification

- Final commit `a41a6c6494a839deaaec1ef7cc50c90078ab6381`: all 55 checks
passed, three skipped. [Canonical CI
run](https://github.com/paperclipai/paperclip/actions/runs/41999792769)
includes all eight browser shards and all server/runner/workspace
groups. Greptile: 5/5, zero unresolved review findings.
- Workspace typecheck/build passed. Local UI suite passed 11,006 tests
in 880 files, plus 542 CLI tests. Subsequent connector callback changes
passed all 245 AppsConnect tests, UI typecheck, token gates, and
query-error policy checks. Manager cache/error regressions passed four
tests. Map checks passed.
- Local connector browser suite: 20 passed. All six chat integration
shards: 1,931 passed. Workspace B groups passed; database tests passed
313 cases across 72 files with one worker.
- Browser preview verified agent manager changes, overview links, inline
name/title geometry, empty-state colors, row actions and the chat
tooltip. Live provider consent and post-deployment feature verification
remain separate from these fixtures.
- Preserved failures: ten bulk-roster tests initially missed the
portaled action card; their selectors were fixed and all twelve roster
tests passed. A nested fixture selected pnpm 11 and removed local
dependencies; the local shim now pins pnpm 9. Embedded PostgreSQL native
links were restored. A Python 3.9 fixture failure passed all 19 tests
with the current runtime. Concurrent local database startup failures
passed on the one-worker rerun.
- The broad local serialized server run passed 3,791 tests with one
scheduling assertion failure, which passed in isolation. The local
general-server run reproduced a native recovery provider/model fixture
failure and has not completed. These server sources are unchanged by
this bundle; all final canonical CI server shards passed. This is not a
claim that every broad local run passed.
- [Earlier CI
run](https://github.com/paperclipai/paperclip/actions/runs/41995119880)
failed a browser test during cleanup when heartbeat-run cancellation hit
a PostgreSQL lock timeout. All final browser shards passed without
weakening that test.
- Review follow-ups cover Slack popup/fallback recovery, a single
artwork hover transform, shared manager error rendering, detached
standalone OAuth windows, and verified task-bound callbacks in both
hosts.

## Risks

- Browser popup policy and provider callback behavior can affect sign-in
completion. The original setup page stays open; blocked windows have an
explicit recovery path.
- Shared toolbar styles affect several surfaces. Menu content remains in
its existing portals; canonical label tabs and search clear buttons
retain their own geometry.
- No schema, server, API authorization, or migration change.
- Required CI and review are complete. Deployment provenance and hosted
feature verification are checked separately after merge.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, repository tools, test
execution, and browser inspection. The exact model ID and context-window
size 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 relevant tests locally and they pass (broader local
exceptions are recorded 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
- [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>
2026-11-05 01:23:53 -08:00
2ccc7d37ef feat(ui): retain approved reads across outage reloads (#18825)
## Thinking Path

> - Paperclip lets people manage agent work through the board.
> - The board already retains loaded reads during a temporary server
failure.
> - Reloading the page removes that in-memory data.
> - A deploy can then leave a returning user without the shell or recent
tasks.
> - This pull request saves an explicit, limited set of reads in the
browser.
> - Cached identity supports display while fresh session checks protect
writes.

## Linked Issues or Issue Description

**What happened?**
A reload during an API outage loses the board's loaded reads. An
automatic worker update can also reload a hidden tab while a write is
pending.

**Expected behavior**
Show the cached shell and recent tasks during a temporary outage. Limit
saved content by user, company, age and build. Clear it on sign-out.
Wait for a fresh session before sending account-bound writes. Defer
automatic reloads while work is pending.

**Steps to reproduce**
Open a task and settings while online. Return 503 from the API and
reload. Restore the API. Sign out and confirm that saved query records
are gone. Discover a worker update while offline or during an autosave
and confirm that it waits.

Stacked on Refs #18822. Related durable-write foundation: Refs #17960.
The outage browser suite follows in a separate test PR.

## What Changed

- Persist an exact read allowlist through TanStack Query dehydration and
hydration. Save shell preferences, company/sidebar data, agent/project
lists, plugin UI metadata, the first inbox page, and 20 recent tasks.
Task UUID and identifier reads share one recent-task slot.
- Namespace records by user and company. Retain eligible reads across
normal memory garbage collection, expire each read after 24 hours, and
discard them after a build change. Unstamped builds disable hydration
and persistence while retaining sign-out cleanup. Use the existing
instance-wide plugin contribution query in the user's root record.
- Exclude session credentials and bootstrap data, embedded plan
documents, comments, preview work products, provider credentials and
environment values, browser capabilities, pairing data, other queries,
and mutations. Restore only display identity from session data. Preserve
safe agent provider/model display fields and configuration object
shapes. Apply the projection on save and restore.
- Clear saved reads on sign-out, cross-tab account changes, and
definitive session denial. Keep revocation working on plain HTTP without
crypto.randomUUID. Use per-user revocation revisions to reject delayed
writes and stale records. Preserve a newer account. Remove every
UUID/identifier projection and nested inbox copy of a denied task.
- Detach shared GETs when the account changes or signs out. Load
complete, freshly authorized agents before opening bulk settings from a
display-only roster. Retire a pending bulk-editor result or error when
row, select-all or clear changes the selection, including a
clear/reselect of the same agent.
- Require a fresh server lookup before account-bound writes, pending
task creation, or mounted autosaves resume. This includes automatic task
read receipts, inbox archives, workspace preferences and primary-agent
choices, with optimistic archive changes gated too. Cancel the old write
scope when the verified user changes. Release completed archives through
account-independent cleanup without restoring old cached rows; an old
operation cannot clear a newer hide flag. Retry unconfirmed read
receipts after a fresh session succeeds for the original account, and
remember confirmed receipts across refreshes. Track pending reads per
task so opening another task sends its receipt immediately; revisiting
reuses an outstanding receipt and its original account scope.
- Defer a hidden-tab worker reload until connectivity, mutations,
durable sends, and autosaves are safe. Synchronously count each
unresolved operation across online rate-limit retries. Release a
cancelled target’s unused dirty token when an editor changes tasks; keep
stored drafts, admitted saves and queued retries. Cover autosave
debounce, newer edits, navigation and undo.

## Verification

- Current head `fe4f682c879` is merge-ready: all 54 check runs completed
(52 successful, one skipped, one neutral), the legacy Snyk status is
successful, and both `ci / verify` and `ci / e2e` pass in run
`41752927822`. Greptile Apex reviewed this exact head at 5/5 with zero
unresolved threads; the branch has no merge conflicts. All three PRs now
meet the merge-ready bar; the stack remains unmerged pending operator
approval.
- Greptile Apex reviewed exact current head `fe4f682c879` at 5/5 with no
actionable findings and zero unresolved threads (summary updated
2026-11-04 at 01:24:40 UTC). Both settings-account and pending-selection
findings are fixed, replied to and resolved. The branch has no merge
conflicts. Current-head CI now passes in run `41752927822`.
- Seventeen new cases cover settings restored from actual saved query
records, matching/changed/signed-out identities, denied verification,
late crown responses without overwriting newer reads, and bulk-selection
changes during successful/failed reads. Sixteen fail the preceding
source and one preserves denial compatibility. Both settings paths reuse
the existing account-bound mutation guard; its optional explicit
QueryClient supports the workspace hook’s existing detached-host mode.
Primary-agent optimistic updates begin only after verification and
cleanup checks ownership and the cache revision.
- Earlier CI run `41749456480` on prior head `2cf7516f18c` has one real
server test failure: the owned-runtime case in
`idle-completed-history.test.ts` could not reach its HTTP child on port
34757 within 10 seconds (99 refused probes). Its other 1,829 cases pass
and three skip; the only other unsuccessful job is the downstream
`verify` gate. The exact selected case passes locally, and the entire
75-case file passes in 72.19 seconds on the exact `2cf7516f18c` source
tree with isolated PostgreSQL and a real child process on macOS
arm64/Node 25.6.1. The Linux CI startup cause remains unknown; these
local results are not a fix or CI acceptance. One failed-jobs retry with
debug logging was accepted on that unchanged head, then superseded by
the later settings fixes. Current-head CI remains required; no source
change was made for this failure.
- Current cache head `fe4f682c879`, based on read head `09d1db29dea` and
master `683fa4725e9`, passes all 1,198 focused tests across twenty-eight
files, UI typecheck, token gates and the enforcing query-error guard.
Dependent browser head `228c1b68d30` contains this exact application
source and passes standalone strict checking of all three browser specs.
Current-head CI now passes in run `41752927822`.
- Three new navigation regressions fail the preceding application source
and pass with the fix. They open three cached tasks before any receipt
resolves, leave the page before success or failure, revisit an
outstanding read without duplicating it, and recover an older failed
receipt after a fresh session succeeds while a later task remains
pending.
- The fixture repair passes all seven deadline integration cases and the
complete server typecheck, including the server test layer. The old
assertion deterministically fails at 23:59:06.888 UTC; its
one-second-old run also fails the first-second-of-day probe. The
corrected fixture passes noon and both midnight probes. The permanent
three-case coverage uses seeded 2099 clocks.
- Nine new cases cover archive success/failure after sign-out and
another account takes over, both mounted and unmounted; cleanup
ownership when a newer archive uses the same task; session-check 429
exhaustion followed by matching, changed or signed-out identity; and a
failed read receipt followed by a new session read. They verify no
old-cache restoration, toast or navigation, retained newer hide flags,
no failed-check loop and no duplicate confirmed receipt. Seven fail the
previous implementation; two compatibility cases retain account-change
suppression. Initial test error-constructor and query-notification
timing assumptions were corrected before the passing checks.
- The inherited skills/app error-priority fix passes all 60 affected
tests and UI typecheck on both application branches. Its twelve new
regression cases fail the preceding read implementation. The
persisted-cache suite includes 48 cases.
- Six new regressions fail against the preceding application source and
pass with the fix. Actual worker-update and mutation-cache listeners
cannot reload a hidden tab between online 429 retries; the original
create key survives, and the update reloads once after a receipt. Real
self-hosted and Cloud sign-out work without crypto.randomUUID, with
stamped and unstamped builds. Saved reads are removed and account write
scopes are invalidated. An initial test-fixture type annotation error
was corrected before the passing typecheck.
- The 48 cache tests cover actual five-minute memory GC, the 20-task
bound across saved and live reads, original 24-hour expiry, denial
before session verification, unstamped cleanup, cross-tab revocation and
delayed flushes. Private GETs cannot cross account transitions. Bulk
edits require complete current agent data. Undo releases the update
blocker while preserving the Saved indicator.
- All 23 built-UI outage/write/loading cases pass in 4.5 minutes on
exact integrated head `228c1b68d30`, containing current cache revision
`fe4f682c879`: ten outage, six durable-write and seven loading cases.
The run uses bundled Chromium, the built UI, a fresh local-trusted
server and isolated embedded PostgreSQL on macOS arm64/Node 25.6.1. All
three specs pass standalone strict TypeScript checking. Owned temporary
server and database stopped.
- Earlier browser runs exposed and fixed cached-header, native Storage
cleanup and replay/session defects. A prior run passed 21/22 because
real WebSockets contradicted the simulated HTTP outage; the fixture now
closes existing socket pairs and holds new sockets through the same
outage deadline. An older CI run exposed a disposed response in the
Inbox route callback; pre-navigation snapshots remove that lifetime, and
the current full run includes that repair. Earlier failures and their
diagnosis remain in the discussion. The current full run passes. Broader
historical workspace failures remain documented in Refs #18822; this
does not claim a clean full workspace suite.
- Current-head CI passes on `fe4f682c879`. Controlled local outages do
not complete hosted authentication or a real Cloud restart/deploy
acceptance.

Commands for the current cache change and inherited read fix (the
current focused run also includes src/lib/inboxArchiveCache.test.ts and
src/pages/Inbox.test.ts):

```sh
pnpm --filter @paperclipai/ui exec vitest run src/pages/CompanySkills.test.tsx src/pages/apps/AppNotConnected.test.tsx
pnpm --filter @paperclipai/ui exec vitest run src/hooks/useDurableSubmit.test.tsx src/hooks/useDurableAutosave.test.tsx src/hooks/useSignOut.test.tsx src/lib/persisted-query-cache.test.ts src/lib/service-worker-updates.test.ts src/lib/service-worker-cache-policy.test.ts src/lib/pending-send.test.ts src/lib/issue-execution-policy.test.ts src/components/InlineEditor.test.tsx src/components/MarkdownEditor.test.tsx src/components/NewIssueDialog.test.tsx
pnpm --filter @paperclipai/ui exec vitest run src/components/agents/AgentBulkRoster.test.tsx src/components/agents/AgentBulkSettingsSheet.test.tsx src/hooks/useAccountBoundMutation.test.tsx src/pages/IssueDetail.test.tsx src/components/TaskChatThread.test.tsx src/components/TaskChatThread.task-creation-order.test.tsx src/api/client.test.ts src/api/agents.test.ts
pnpm --filter @paperclipai/ui exec vitest run src/hooks/useCoordinatorWorkspaceEnabled.test.tsx src/context/PrimaryAgentProvider.test.tsx src/components/CoordinatorWorkspaceSetting.test.tsx src/components/primary-agent/PrimaryAgentPresentation.test.tsx src/components/MobileBottomNav.coordinator.test.tsx src/components/CoordinatorSidebar.test.tsx src/pages/Issues.coordinator-routing.test.tsx
pnpm --filter @paperclipai/ui typecheck
pnpm check:token-gates
node scripts/check-query-error-rendering.mjs --enforce --summary
```

## Risks

This deliberately saves task content on the local browser for up to 24
hours. The allowlist excludes credentials and ephemeral capabilities.
Local storage can be disabled or full; persistence then fails without
blocking the app. Restored data can be stale and never grants server
access. A user or access change clears cached account reads and
invalidates pending writes. The browser outage suite and real hosted
deploy acceptance remain release requirements. There are no migrations
or new dependencies.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with xhigh reasoning effort, tool
use, and code execution. The 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
- [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 described the issue in-PR following the bug report 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] The focused local tests for this change pass
- [x] I have added tests and updated relevant documentation
- [x] I have considered and documented 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: devinfoley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Bender (Fable) <bender@agents.paperclip.ing>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 15:40:30 -08:00
a3313f6944 fix(ui): complete read recovery and enforce query error rendering (#18822)
## Thinking Path

> - Paperclip lets people manage agent work through the board.
> - The board reads task, agent, settings, plugin, and run data from the
server.
> - A deploy can interrupt those reads after content is already visible.
> - Local error branches can replace that content with an error or show
raw codes.
> - This pull request moves the remaining reads to the shared recovery
behavior.
> - The query-error guard prevents new local error patterns from
returning.

## Linked Issues or Issue Description

**What happened?**
Loaded lists, settings, task panels, and manual reads could clear their
content or show errors during a server restart. Some reads also disabled
the shared retry policy.

**Expected behavior**
Keep loaded content during a temporary failure. Show the shared
connection banner. Retry reads after recovery. Replace content after a
real access denial or missing resource, with readable copy.

**Steps to reproduce**
Open a task, settings page, or run log. Return 503 with
`tenant_app_unavailable` from the API. Restore the API and check that
content refreshes. Repeat with 403 and 404 to check the failure state.

Related merged work: Refs #15754, Refs #15772, Refs #17960. This change
covers remaining reads and enforcement. Browser persistence and the
outage browser suite follow separately.

## What Changed

- Repair an existing PostgreSQL deadline fixture at UTC boundaries. A
daily cap defers a heartbeat until both its original interval and the
next UTC day allow it. Keep the counted run in its seeded day and cover
noon, just before midnight and the first second after midnight with
explicit clocks and real PostgreSQL. Production scheduling is unchanged.

- Integrate the saved list, settings, shell, task, and manual-read
recovery changes against current master. Preserve the current task,
Inbox, and run-log structure.
- Add `useResilientFetch` for component-local reads. Preserve loaded
data, cancel stale requests, and retry through the shared connectivity
policy. Keep browser and pairing capabilities in memory.
- Retain run log chunks across stream changes and catch up after
recovery without duplicate chunks or stalled requests.
- Keep genuine authorization and missing-resource failures visible.
Translate known single-word codes such as `forbidden`. Show known skills
and disconnected-app failures before sibling loading states. Keep Retry
bound to its failed request.
- Make the query-error guard enforce by default and run it in the CI
policy job. Document the narrow exceptions and the read recovery
contract.
- Invalidate pending OAuth discovery when the operator leaves setup or
its context changes. Keep reserved-popup cleanup bound to the current
attempt.

## Verification

- Current read head `09d1db29dea` is merge-ready: all 59 check runs are
completed (54 successful, three skipped, two neutral), the legacy Snyk
status is successful, and both required `ci / verify` and `ci / e2e`
gates pass. Greptile Apex reviewed this exact head at 5/5 with no
unresolved threads; the branch has no merge conflicts. The current CI
run is `41748339602`.
- Current read head `09d1db29dea` includes master `683fa4725e9`. The
latest change touches only the existing server deadline test fixture.
Application source remains the previously verified `86376fa751e`
revision: all 60 affected skills/disconnected-app tests and both
application UI typechecks passed, along with token gates and the
enforcing query-error guard. Those UI suites were not repeated for this
test-only repair.
- The fixture repair passes all seven deadline integration cases and the
complete server typecheck, including the server test layer. The old
assertion deterministically fails at 23:59:06.888 UTC; its
one-second-old run also fails the first-second-of-day probe. The
corrected fixture passes noon and both midnight probes. The permanent
three-case coverage uses seeded 2099 clocks.
- Twelve new regressions fail against the previous implementation. Both
skills variants and disconnected-app pages show either request's 403 or
429 while the sibling pauses or remains fetching. Permission failures
stay visible. Retry stays usable for its failed request and recovers
independently. An initial test-fixture query-key typo was corrected
before the passing run. The finding is replied to and resolved. Greptile
reviewed prior application revision `86376fa751e` at 5/5 with no new
actionable findings or unresolved threads. The current test-only head
has now received its own Apex 5/5 review with no unresolved threads, and
its CI is successful as recorded above.
- The preceding OAuth fix passed all 237 setup tests on both application
branches. Its ten regressions prove that Back, Cancel, unmount, or
context changes retire initial discovery without creating a connection,
caching old identities, navigating an abandoned popup, or closing a
newer attempt.
- Earlier master integration `e992f770339` passed all 309 tests across
eight affected Slack, recovery, retry, submission and task files, plus
UI typecheck, token gates, enforcing query-error scan and seven guard
tests. The integration preserves Slack workspace reconnect and shared
recovery, readable execution errors, source-inspection company scoping,
and current submission behavior. Newly inherited retained-file recovery
controls keep cached data through transient failures and hide it after
definitive denial.
- The preceding cache head `97c0bc3966f` passed 1,038 focused
cache/write/editor/account/task/API tests. All 22 built-UI
outage/write/loading cases passed in 4.7 minutes on exact browser head
`b7deaaee5da`, with strict checking of all three specs. The latest
skills/app rendering fix is covered by its affected component suites;
those broader cases were not repeated for this separate read path.
- Historical broader UI, CLI and build results and unresolved aggregate
server/DB/adapter failures remain in the PR discussion. Interrupted CI
runs were audited and recorded separately; they are not counted as
passes. Controlled local outages do not prove hosted authentication or a
real Cloud restart/deploy. Real hosted acceptance remains required after
merge and deployment.

Commands for the latest fix:

```sh
pnpm --filter @paperclipai/ui exec vitest run src/pages/CompanySkills.test.tsx src/pages/apps/AppNotConnected.test.tsx
pnpm --filter @paperclipai/ui typecheck
pnpm check:token-gates
node scripts/check-query-error-rendering.mjs --enforce --summary
```

## Risks

This migration touches many read surfaces. The main risks are initial
loading, request cancellation, retained authorization state, and run-log
paging. The tests cover those states. There are no server, schema,
dependency, or API contract changes. The follow-up browser suite and
hosted deploy acceptance are required before release acceptance.

Recovered commits retain their original authors. If squash-merging,
preserve `Co-Authored-By: Bender (Fable) <bender@agents.paperclip.ing>`
and the Paperclip trailer.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with xhigh reasoning effort, tool
use, and code execution. The context window size is not exposed. Saved
source commits keep their original 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 described the issue in-PR following the bug report 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] The affected local tests pass; broader limitations are recorded
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 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: devinfoley <139239+devinfoley@users.noreply.github.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Bender (Fable) <bender@agents.paperclip.ing>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 14:07:18 -08:00
Dotta c6cb7e2a2d fix(ui): accept file drops across task and new-task views (#18849)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - People add images and files through task message composers.
> - A file drop requires a small target inside the composer.
> - A screen-wide target makes this action easier.
> - Task pages and new-task dialogs use the same composer.
> - This pull request adds one shared file-drop target.
> - The open dialog receives the drop when it covers a task page.

## Linked Issues or Issue Description

Refs #13206. This improves the task composer drop area.
Related PRs: #18689 adds image previews. #15964 adds direct uploads in
the Artifacts panel.

**What existing behavior does this improve?**

Image and file drops in the task composer and new-task dialog.

**Current behavior**

People must drop a file into the composer area.

**Proposed behavior**

People can drop a file anywhere on the screen when an available composer
is present. A full-screen overlay shows the destination.

**Reason and benefit**

People can add an image without aiming at the text field.

**Subsystem affected**

React board UI.

**Breaking changes**

The available composer captures file drops across the screen. Text and
link drags retain their current behavior. Upload rules and APIs stay the
same.

**Additional context**

ROADMAP.md lists task discussion and attachments as existing features.
This change improves their input area.

## What Changed

- Add a shared screen-wide drop target to the task composer.
- Give an open task dialog exclusive ownership of file drops. Keep file
drops available while a model or assignee picker is open.
- Give explicit editor, task attachment, and Artifacts drop areas
priority over the screen-wide composer target. Share one marker for
these areas.
- Keep image and file uploads in description, question, and protocol
editors. Read-only editors and editors that delegate uploads to a parent
keep the composer fallback.
- Prevent browser navigation from file drops while the active composer
is read-only.
- Use the existing file staging and upload callbacks.
- Disable drops during pause, queued message edits, uncertain sends,
submission, and read-only states.
- Exclude hidden and inert composers.
- Clear the overlay after drop, drag exit, cancellation, window blur,
and unmount.
- Add component tests and two Storybook drag-over examples.

## Verification

- 316 tests passed across the drop target, real MarkdownEditor, task
composer, new-task dialog, and Artifacts tab. Tests cover image and
text-file drops in a real editor, overlay reset, parent and read-only
editors, and task attachments.
- Two new cases failed before the editor and attachment fixes. Both
passed after the fixes.
- An earlier expanded run had three new-task upload assertion failures.
The five affected cases passed on the unchanged implementation in
isolation. The expanded run then passed all 316 tests. The cause of the
earlier failures remains unverified.
- UI typecheck and UI production build passed. The first typecheck
attempt lacked plugin SDK declarations. Building the SDK restored these
declarations; typecheck then passed.
- Design-token gates, Storybook catalog generation, and whitespace
checks passed.
- Chromium checks from the design review confirmed one image per screen
drop in each view. The editor stayed empty and the overlay cleared.
These checks used local Storybook fixtures.
- The requested screenshots were accepted before this PR was opened.
- Full repository typecheck and build were attempted. Both stopped in
the native Runner package because this environment has no cargo
executable.
- The full repository test run was attempted. It was stopped during the
first chat integration shard before a full-suite verdict. The focused UI
suite passed.
- The first CI attempt did not complete. Typecheck and build logs report
runner shutdown signals. Most browser shards were cancelled; their logs
could not be retrieved. No complete CI verdict is available. The new
commit starts a fresh CI run.
- Review findings about Artifacts drops, portaled pickers, read-only
drop navigation, and editor drop ownership are fixed. Current-head CI
and review are pending.
- The previous CI run passed build, typecheck, general tests, and six
browser shards. Browser shards 5/8 and 8/8 failed. Their logs returned
BlobNotFound; the causes remain unverified. The new commit starts a
fresh CI run.

Manual check: open a task, drag a PNG over the page header, and drop it.
Confirm one image preview. Open a new-task dialog over the task and
repeat. Confirm the file appears only in the new task. Cancel the drag
and confirm the overlay clears. Open the description editor. Drop an
image and then a text file into it. Confirm each upload stays with the
description and does not enter the chat draft.

## Risks

- Screen-wide file capture changes which surface receives file drops.
- Dialog ownership and disabled-state tests limit unintended uploads.
- No server, API, database, or migration changes are required.
- Full repository acceptance still requires the native Rust toolchain
and CI results.

## Model Used

OpenAI GPT-6 through Codex. The exact runtime snapshot ID and
context-window size were not exposed. Capabilities used: repository
inspection, code editing, shell commands, and browser automation.

## 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
2026-11-04 15:41:48 -06:00
DottaandPaperclip c99bcd89fe fix: preserve Product TestOps evidence and CI readiness (#19000)
## Thinking Path

> - Paperclip manages agent work through governed product workflows.
> - Product E2E tests check those workflows through the app and Runner.
> - A trusted reporter sends retained results to TestOps.
> - Free-form metadata and a short upload observer can lose safe, valid
evidence.
> - UUID navigation can also discard task metadata before the canonical
query returns.
> - Hermetic CI files start child processes that need CPU capacity
within their readiness limits.
> - This change closes the reporting contract, retains authenticated
core aliases, and reserves CI CPU capacity.
> - Original case identities, conditions, assertions, verdicts and
deadlines remain intact.

## Linked Issues or Issue Description

Refs #18983. This addresses its late [metadata disclosure
finding](https://github.com/paperclipai/paperclip/pull/18983#discussion_r4470789114)
and [upload readiness
finding](https://github.com/paperclipai/paperclip/pull/18983#discussion_r4470790366).

**What happened?**

The reporter accepted model IDs and matcher names through syntax
filters. A secret-shaped string could pass those filters. First-task
results also use their top-level model field for an observed alias.
Treating it as configured metadata can reject valid retained evidence.
The upload observer stopped after 22 seconds of waits.

[Required CI on the earlier
head](https://github.com/paperclipai/paperclip/actions/runs/41886885193)
also found two real browser title failures and five native package
readiness failures. A deterministic control reproduced metadata loss
during UUID-to-identifier navigation. A separate fixed-resource control
reproduced startup deadline failures with four concurrent hermetic
fixtures, while one and two fixtures passed.

**Expected behavior**

Reject unknown transmitted metadata before creating upload files.
Preserve every original matcher verdict and attempt receipt. Observe
server processing without rerunning the product. Keep authenticated task
metadata visible through canonical navigation. Reserve capacity for
hermetic child processes without changing the original readiness
deadlines.

**Steps to reproduce**

Use a secret-shaped model ID or cleanup state in retained evidence,
return an observed first-task model alias, or let upload processing
settle after 40 seconds of virtual time. Hold the canonical core query
pending after an authenticated UUID read. For the CPU control, run the
same providerless startup/recovery fixture alone, four times
concurrently, and twice concurrently under a fixed 4-vCPU quota and its
original 8-second readiness limit.

## What Changed

- Bind configured model receipts to each exact declared case model. Four
first-task cases use captured agent configuration; their observed
aliases and fallback text remain digest-only. A missing capture stays
null / Not recorded. The other 16 cells use their configured profile
model. Reject wrong or unknown configuration before any upload file
exists.
- Keep the existing model distribution: 18 Codex cells on `gpt-5.6-sol`,
configured-repository case 9 on its existing `gpt-6.1-sol` override, and
task-workspaces case 54 on `openrouter/deepseek/deepseek-v4-flash-0731`.
The repository override exists at the [original canary
source](https://github.com/paperclipai/paperclip/blob/86814c6550f52e99ede150a883bdc773cbb540e3/tests/runner-e2e/catalog.ts#L1148).
No model configuration changes.
- Allow only existing Matcher kinds, closed failure classes, and cleanup
states `passed`, `failed`, or `not_started`. Publish each matcher's
kind, ordinal, original boolean verdict and SHA256 path receipt. Full
conditions remain in the linked original workflow artifact; raw dynamic
path bytes do not enter TestOps.
- Observe the same upload for at most 55 seconds of poll waits. Product
scenarios, provider retries and scenario deadlines do not change.
- Retain task core data under only the authenticated response's own UUID
and identifier core keys. Preserve existing canonical data. Keep
arbitrary references and full-detail keys empty until their own reads.
- Retain a first-failure trace for the existing Cloud recovery test.
Keep its assertions, timeout, retries, mock data and artifact policy
unchanged. This adds diagnosis evidence; it does not claim to fix that
separate Cloud title timeout.
- Reserve half the available CPUs for Runner package CI workers, with
the existing upper cap of four. A 4-vCPU host uses two workers; an
8-vCPU host still uses four. Local defaults and test selection do not
change.

## Verification

- Reporting controls first reproduced the two original gaps. All 14 Node
contract tests now pass. Unknown configuration creates neither a result
directory nor an export summary. A model valid for a different selected
case is also rejected. Original functional failures and earlier
accounting receipts survive.
- The catalog compatibility test passes for all 20 selected cells, exact
model tuples, real pure grader outputs and actual
`projectFirstTaskResult` shapes. It covers observed aliases, fallback
text, absent evidence and wrong or secret-shaped configured metadata.
These are contract controls, not live journey passes.
- Virtual-clock controls accept a correct result that settles at 40
seconds and reject a persistently wrong verdict after 55 seconds. They
make no product or provider calls.
- Two new core-cache controls failed before repair. The query/cache
suites now pass all 8 tests. They hold canonical refresh pending and
verify title/company retention, newer canonical data, full-detail
enrichment and unrelated keys. UI typecheck and token gates pass.
- The unchanged exact UUID cross-company link/upload browser test passed
once after repair: 5.8-second scenario, 32.0-second complete command.
The unchanged exact Cloud task-entry diagnostic passed once: 5.4-second
scenario, 23.3-second complete command. Its original canonical-only
timeout remains unexplained. All five Cloud tests still collect after
the trace setting.
- The providerless Linux control used the retained Runner and compiled
controller from source `3a2249dd`, with a fixed 4-vCPU quota and
original 8-second readiness limit. Solo passed 1/1; four concurrent
fixtures failed 4/4; two concurrent fixtures passed 2/2. At each
failure, Rust had accepted command 2 but startup was still pending;
checkpoint and published status agreed, one connection was
authenticated, no replay backlog existed, and the sidecar had not
launched. Passing controls preserved the expected startup failure
through controller replacement. No network or credentials were
available.
- That control uses Linux amd64 emulation and Node 24.21; hosted CI used
Node 24.22.1. It supports the worker-cap change, but does not prove that
the original `m5n.xlarge` host caused every CI failure. The original red
attempts remain recorded.
- Actual config-load controls verify 1 CPU → 1 worker, 2 → 1, 4 → 2, 8 →
4 and 16 → 4, with no local, timeout or retry override. The isolated
policy change preserves the same 297 test-file inventory; integration of
current master `89703c3ff7` retains all 298 current native package
files. The original PR package/native command plans remain unchanged.
- Final head `877e28da32f2d2f0d12906c89aff085e2c6ea943` passes both
required checks, `ci / verify` and `ci / e2e`, in [CI run 41897677652,
attempt
2](https://github.com/paperclipai/paperclip/actions/runs/41897677652/attempts/2).
The native package passes 4,950 tests with 10 existing skips. All eight
browser shards pass; the Cloud shard passes all 41 tests, including all
four unchanged recovery cases.
- Attempt 1 preserved 46 successful jobs but its one-file heartbeat
recovery shard hit the original 20-minute job limit without a named
assertion or completion verdict. Its cause remains unidentified. One
explicitly bounded debug replay retained those 46 successes and ran only
that leaf and its dependent rollup. The unchanged file passed all 412
tests (346.91-second file, 371.34-second Vitest command, 7m11s job).
Source, cases and deadlines did not change. This establishes
intermittency, not a causal repair. The original timeout and debug pass
both remain recorded.
- Fresh [Greptile
5/5](https://github.com/paperclipai/paperclip/pull/19000#issuecomment-6637604918)
and [Ponytail
5/5](https://github.com/paperclipai/paperclip/pull/19000#issuecomment-6637489454)
reviews name exact head `877e28`. The single review thread is resolved.
Live TestOps bootstrap and complete 20-case acceptance remain held until
reviewed delivery. CI and focused controls do not count as product plan
passes.

## Risks

- Future model or Matcher kind changes require a reviewed
reporting-contract update.
- TestOps shows a closed Matcher kind and ordinal. The path digest binds
the original condition in retained evidence.
- Core alias data retains the existing authenticated query-client and
server company boundaries. It never becomes full-detail evidence.
- Fewer CI workers can increase one job's duration. The full suite must
verify the final policy. The paid evaluation matrix and its parallelism
do not change.
- The original Cloud title timeout and the first-attempt heartbeat CI
hang do not have proven causes. Current unchanged assertions pass, but
this does not establish repeatability. The Cloud test now retains its
first-failure trace. A successful upload or focused control alone cannot
establish a complete product pass.

## Model Used

OpenAI Codex with reasoning, tool use and code execution. The host does
not expose the exact model ID or context window 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
#` 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>
2026-11-04 14:41:28 -06:00
DottaandPaperclip 5996f25122 fix(ui): replace selected text on ordinary paste (#18796)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - People edit task documents and comments in the Markdown editor.
> - Its link validator accepts safe text that is not a URL.
> - Lexical uses that validator to turn pasted text into a link around
the selection.
> - Pasting JSON or prose can keep the old text and put the new text in
its link destination.
> - This pull request handles plain selected-text replacement in the
existing paste plugin.
> - The benefit is standard text replacement with existing manual link
controls intact.

## Linked Issues or Issue Description

Fixes #2930.

Related: #8506 by `joegalbert-ai` identifies the same validator cause.
Thanks for that analysis. This change adds current Lexical command
coverage and preserves the separate manual and decorator link paths. The
contributor PR remains unchanged.

Reproduce by selecting an existing paragraph and pasting
`{"status":"ready"}`. The old code keeps the previous paragraph as a
link label and uses the JSON as its destination. The expected result is
the pasted JSON as text.

## What Changed

- Replace a selected text range, including soft line breaks, when the
clipboard text is not an unambiguous link target.
- Keep selected-label link wrapping for valid scheme, network, web and
email targets, including international addresses. Treat ambiguous
relative paths and fragments as ordinary pasted text.
- Leave the manual link validator unchanged. Keep manual relative,
internal, fragment, email and decorator links, with existing
executable-scheme guards.
- Use the same native Lexical rich-paste insertion API for selected HTML
payloads. Preserve HTML formatting and the paste undo boundary. Keep
files, Markdown, code and other selection types with their existing
handlers.
- Declare the clipboard API and test rich-text API at the existing
Lexical 0.48.0 version. The scheduled repository bot owns the lockfile
refresh; this PR has no lockfile delta.
- Add real Lexical paste and link-command regressions through the
production editor policy and paste plugin. This restores expected
editing behavior; it adds no setting, endpoint or new feature-map entry.

## Verification

- Before the fix, real selected-text paste tests fail for JSON, prose
and multiline text. They retain the old label and create a link with the
replacement as its URL. The valid-URL control passes.
- A test-only experiment with the exact recognition predicate from #8506
fails four current decorator relative/fragment cases. The chosen
paste-handler fix keeps the manual validator intact. The experiment was
removed after preserving its result.
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/MarkdownEditor.test.tsx src/lib/markdownPaste.test.ts
src/lib/normalize-markdown.test.ts
src/lib/mention-aware-link-node.test.ts
src/lib/mention-deletion.test.ts`: 119 editor-focused tests passed.
After the baseline update, the same editor suites plus App and
ShellConfigurationGate passed 146 tests.
- The React MDXEditor shell is mocked. The configured production
predicate, actual paste-normalization plugin, native rich-text paste
handler and real Lexical paste/link commands are not mocked. Tests cover
URL wrapping, plain replacement, manual/decorator links,
executable-scheme handling and paste-owner handoff.
- UI typecheck and production build pass. UI token gates, module
boundaries and diff checks pass. Initial test event-type errors were
fixed. Greptile found that JSON with an email still formed a link. Its
real-command regression failed before narrowing whole-address
recognition and passes after the fix. An isolated frozen install and SDK
build resolved local workspace prerequisite errors.
- No live task edit or deployed replay was run. Full repository checks
run in current-head CI. The initial quality gate rejected generated
lockfile edits; the policy correction removes them without changing
tested source. The earlier current-head workspaces-a CI failure was the
independently fixed invitation-access fixture in App.test.tsx. Merge
#18799 supplied that baseline fixture fix. Its local regression and the
editor suites now pass together; all final-head CI checks now pass.
Final head `5156085b83c908e1439927bbebf25ab0aa42f81b` has 60 terminal
contexts: 56 success, three skipped, and the neutral Paperclip review at
5/5. Greptile is 5/5 with no unresolved findings.
- Review regressions also failed before repair for a selected soft line
break, formatted HTML with JSON, and international email addresses.
These cases now pass. The HTML test checks that bold formatting
survives.

## Risks

- URL recognition must not turn ordinary text into a link. Ambiguous
relative paths use text replacement on paste; the manual link controls
still accept them.
- The selected HTML path must preserve formatting. It delegates
insertion to the native rich-paste API with the original clipboard. File
payloads and non-simple selection types still yield.
- Existing Markdown normalization still applies. Script/data scheme
checks and rendered-link sanitization remain unchanged.
- No visual tokens, schema, public API, provider or deployment
configuration changes are required. No additional user documentation is
needed for this expected editing behavior.

## Model Used

OpenAI Codex, `gpt-6.1-sol`, Extra High reasoning, with source
inspection, code execution and regression testing. Context-window
capacity 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 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>
2026-11-04 07:09:37 -06:00
DottaandPaperclip 652cd76026 fix(ui): expose Stop for failed native retries (#18899)
## Thinking Path

> - Paperclip manages agents and their tasks.
> - A native run can fail while it still owns a same-run retry.
> - The server supports Stop for that pending execution.
> - Run details show Cancel only for queued or running records.
> - A failed pending retry therefore has no ordinary cleanup control.
> - This pull request exposes the existing Stop eligibility and uses the
existing cancellation endpoint.

## Linked Issues or Issue Description

Refs #14743 and #14733 for correlated native Stop. Those changes cover
backend cancellation and protocol qualification. They do not add the
failed-run control to run details. Searches of current master and open
PRs found no matching UI repair.

**What happened?**

A native run can show failed while its coordinator still owns a
retryable failure with no accepted result. The backend can cancel that
retry. Both run-detail implementations hide the cancellation button
because the record is terminal.

**Expected behavior**

The selected run offers Stop when the server confirms that pending
native retry. Unknown, legacy, terminal and accepted-result states do
not gain this control. Existing queued and running cancellation stays
available.

**Steps to reproduce**

Render either run-detail implementation for a failed native run with an
eligible retry. The original UI has no Stop button. The original batched
execution projection has no eligibility field. The regression tests
exercise both real components and the actual PostgreSQL projection
query.

**Paperclip version or commit**

The missing control and projection field were reproduced on
44a4ca1c61. Fresh master 8fd917aedf has
identical affected production blobs before this repair.

**Deployment mode**

Board run details for a native Runner task. Local verification uses
rendered React components and PostgreSQL. No hosted task mutation or
provider retry was performed.

## What Changed

- Add optional `execution.canStop` to the existing batched projection.
Read the matching retryable coordinator and absence of an accepted
result. The cancellation endpoint remains the authority owner. Add no
database queries.
- Exclude Grok failed retries from the new hint and both consumers. The
existing Grok Stop endpoint cannot accept a failed retry before
assignment. Keep this limitation explicit and preserve its active-work
authority checks.
- Show Stop for eligible failed runs in both run-detail consumers. Keep
the existing cancellation endpoint and its company, actor and row-lock
checks.
- Use the existing request-correlation contract for failed-native Stop.
Persist one scoped identity before sending and recover it after remount
or reload. Read a matching canonical claim only for the same board
actor, company and run. Use the existing UUID fallback on ordinary HTTP.
Never retry Stop automatically. Refresh the selected run and history
after either outcome.
- Report a stale conflict or a true no-op200 response. Accept a matching
durable pending cancellation intent without requiring terminal
acknowledgement. Keep running, queued and Grok calls unchanged.
- Add actual rendered interaction tests, real batched-query controls and
a short execution contract. Keep maps unchanged for this bug fix.

## Verification

- Untouched UI and real Postgres producer reds cover missing Stop and
missing eligibility. A valid Grok review found an unsupported failed-run
action; actual broker, projection and rendered controls reproduce it
before exclusion. No backend authority was expanded.
- The original60-case focused suite passed, including26 rendered cases,
the batched producer, and actual Grok broker/assignment fences. The
related57-case scope passed before the Grok correction; it is not
presented as final-head proof.
- A fresh native4/5 review found a stale200 no-op. Both actual rendered
consumers failed that new assertion. The real authenticated
HTTP/Postgres fixture proves `canStop=true`, coordinator revocation,
uncorrelated unchanged200 with a cancellation activity, and existing
correlated409 with no new activity or run mutation.
- The correlated repair passed82 affected cases before master
reconciliation. Exact `74c60c9ffe11c05bc85f3d0fa5a136f7e9d445cd`
passed80 rendered/API, HTTP and native-correlation cases. The render
scope verifies request bodies, stable identity after an unknown outcome,
no-op200 error, pending intent acceptance, wrong-run/request denial and
existing active/Grok behavior.
- Subsequent current-head reviews found remount identity loss and
unavailable `crypto.randomUUID` on ordinary HTTP. The corrected helper
passed 52 rendered cases across both consumers, including an unknown
response, initial scoped storage persistence, a prior browser record
with a unique never-mounted run identity, exact ID reuse, canonical
claim recovery, actor switch, wrong run and HTTP UUID fallback. A real
local-board identity mismatch was reproduced with two additional reds
before using the actual server-projected actor ID.
- Real authenticated HTTP and existing native-correlation suites passed
45 cases for canonical claim recovery and actor fences. Production UI,
server and vendored Runner builds and all server/UI type layers passed
at the prior reviewed helper head; exact final
`e0f723b1c86f1529029bf141943323764de28c22` UI types, token gates and
production build passed.
- Fixture failures are retained separately: a missing local SDK build
dependency, and a missing coordinator FK teardown in the new HTTP
fixture. Neither is a behavior red. Both fixtures were corrected and the
affected tests passed.
- Previous-head3d CI required one diagnostic rerun after a Runner
shutdown and a real account-list migration assertion. Two
untouched157-test runs did not reproduce the assertion. Complete
original row equality is unavailable. The assertion remains unresolved;
no test was sorted, skipped or changed to green CI.
- Previous-headf12 CI lost eight child runners. Its one authorized
failed-job retry also lost e2e5 to an explicit shutdown. No second retry
was issued. Those failures remain recorded. Current-head CI and both
substantive reviews are required; older checks and grades are not
credited.
- Previous-head74c CI found a real monitor scheduler timeout. An
untouched local replay and diagnostic-only replay reproduced it: the
original manual-monitor run was still running at the unchanged
three-second deadline. The cause remains unresolved; temporary
diagnostics were removed, and no assertion, timeout or production
monitor policy was changed. No CI retry was issued.
- Exact local `906af5c942ee0963eb4b64552160b57a18bcd2d1` integrates
normal master after #18930 merged. All reviewed Stop production and test
blobs remain unchanged from `e0f723b1c86f1529029bf141943323764de28c22`.
The four rendered, projection, HTTP and correlation files passed 99
cases after integration. All UI/server type layers, token gates and
production UI/server/Runner builds passed at that exact head; the server
build stamp records it.
- Previous-head e0 CI reported a Discord ownership error. That original
failure is preserved. Separate #18930 proves and repairs receiver
construction/renewal races with the same error; the original CI
scheduling order remains unproved. This branch now contains that
ordinary master merge and requires fresh CI rather than rerunning the
old failed attempt.
- Full repository checks were not run locally for this narrow slice. No
hosted task, provider execution or Stop receipt was created by this
work.

## Risks

Grok failed retries remain unsupported by this UI change. The existing
Grok running and queued Stop action, revoke path and assignment fences
are unchanged. A valid initial review caught the overbroad hint; actual
broker, projection and rendered regressions reproduce it before
correction. No backend cancellation authority was widened.

The optional hint can become stale. It grants no permission. The
existing cancellation path rechecks current authority and can reject the
request. Run details show that rejection. Older servers omit the hint,
so failed runs keep their existing behavior until the server updates. No
retry, task hold, accepted-result decision or provider receipt is
changed.

## Model Used

OpenAI GPT-6.1 Sol (`gpt-6.1-sol`) in Codex, with source editing,
terminal execution and tool use. The host did not report 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
- [ ] 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>
2026-11-03 23:18:08 -06:00