Commit Graph

1572 Commits

Author SHA1 Message Date
Devin Foley 5ca752dc81
fix(server): raise company import zip upload limit to 1 GB and make it operator-configurable (#11184)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Company import/export lets an operator move a full company package
between instances, with the Import page uploading the package as one
compressed `.zip`
> - The server caps that upload at 128 MB, and real company packages
with attachments now exceed it — imports fail at the preview step
> - The failure message tells the user to use the CLI folder import, but
that path posts inline JSON capped at 64 MB, so the advice is a dead end
for exactly these packages
> - This pull request raises the zip upload cap to a 1 GB default, makes
it operator-configurable through an environment variable, scales the
decompression-bomb guards from the cap in effect, and replaces the
misleading hint
> - The benefit is that large real-world company packages import
successfully, and operators with unusual needs can tune the cap without
a code change

## Linked Issues or Issue Description

**What happened?**

A company import fails at the preview step with `Preview failed: Import
package exceeds 134217728 bytes`. The package is a valid Paperclip
export. Its compressed size is larger than the 128 MB server cap (one
reported package is 257 MB). The error panel suggests the CLI folder
import, but that path sends the package as one inline JSON body capped
at 64 MB, so it also fails.

**Expected behavior**

A valid company package of realistic size imports successfully through
the Import page. If a package is too large, the error must state the
limit clearly and suggest a step that can work.

**Steps to reproduce**

1. Export a company with enough attachments to make the compressed
package larger than 128 MB.
2. Open the Import page and upload the `.zip`.
3. Click "Preview import".
4. The preview fails with `Import package exceeds 134217728 bytes`.

**Deployment mode**

Reported from a managed deployment; the limit applies to all deployment
modes.

## What Changed

- Raise `PORTABLE_ZIP_UPLOAD_LIMIT_BYTES` from 128 MB to a 1 GB default
(`server/src/http/body-limits.ts`).
- Add the `PAPERCLIP_IMPORT_ZIP_MAX_BYTES` environment override. Invalid
or non-positive values fall back to the default.
- Scale the zip decompression-bomb guard from the configured cap at the
import route: the aggregate inflated ceiling is 4x the cap. The
per-entry ceiling stays at 512 MB because V8's string length limit
applies to an entry regardless (`server/src/routes/companies.ts`,
`packages/shared/src/portability-zip.ts`).
- Report the 422 limit error in MB instead of raw bytes.
- Replace the "use the CLI folder import for very large packages" hint
on preview failure with advice that works: re-export the package without
large attachments (`ui/src/pages/CompanyImport.tsx`).
- Update the stale comment in `ui/src/lib/import-preflight.ts` that made
the same CLI claim.
- Add tests for the new default, the env override, and the
invalid-override fallback.

## Verification

- `pnpm vitest run server/src/__tests__/body-limits.test.ts
packages/shared/src/portability-zip.test.ts
server/src/__tests__/company-portability-routes.test.ts
server/src/__tests__/company-portability.test.ts
server/src/__tests__/company-portability-import-batching.test.ts` — all
pass.
- `pnpm vitest run ui/src/pages/CompanyImport.test.tsx` — passes,
including the updated failure-panel copy assertion.
- `pnpm typecheck` — clean across the workspace.
- Manual: upload a `.zip` larger than the configured cap; the preview
fails with `Import package exceeds the 1024 MB upload limit` and the new
hint. A package between 128 MB and 1 GB now previews and imports.

## Risks

- Peak per-import memory rises with the cap: the upload is buffered in
memory and unzipped in one pass. A 1 GB compressed package can use
several GB transiently. Imports are instance-admin actions, so the
exposure is a deliberate operator action, not anonymous traffic.
Operators on small hosts can lower the cap with
`PAPERCLIP_IMPORT_ZIP_MAX_BYTES`.
- The aggregate bomb guard moves from a fixed 512 MB to 4x the
configured cap. It still bounds expansion far below what a decompression
bomb needs.
- No migration and no API shape change. The 422 message text changes; no
code matches on the old text.

## Model Used

- Claude Fable 5 (`claude-fable-5`) via Claude Code CLI, with extended
thinking and tool use (file edits, local test runs, live-instance
inspection).

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The agent detail page has a Costs section. That section renders a
data table of per-run spend.
> - The table has a header row, but its `<th>` elements carry no `scope`
attribute.
> - A screen reader uses `scope="col"` to bind each data cell to its
column header. Without it, the reader announces a number without telling
the user which column it belongs to.
> - A table of costs is exactly the case where that hurts. Every cell is
a bare figure.
> - This pull request adds `scope="col"` to the five header cells in
that table.
> - The benefit is that assistive technology announces the cost table
correctly. The change is markup only, so sighted users see no
difference.

## Linked Issues or Issue Description

No public GitHub issue covers this. The problem is described in-PR,
following the enhancement template.

**What existing behavior does this improve?**

The Costs table rendered by `CostsSection` in
`ui/src/pages/AgentDetail.tsx`.

**Subsystem affected**

ui/ — React + Vite board UI

**Current behavior**

The table renders five header cells: Date, Run, Input, Output, and Cost.
None of them set `scope`. A screen reader must guess the header-to-cell
relationship, so a user hears a value with no column name attached to
it.

**Proposed behavior**

Each header cell sets `scope="col"`. A screen reader then announces the
column name together with each cell, so a cost figure is read as part of
the Cost column.

**Reason and benefit**

`scope` is the standard way to associate header cells with data cells in
an HTML table. The attribute has no visual effect, so the fix carries no
design cost and makes the table usable with a screen reader.

**Breaking changes**

None. `scope` is a presentational-neutral HTML attribute. No component
API, no styling, and no test changes.

**Related pull requests**

- #2215 proposed the same attribute for the Routines table. It is
closed, because that table no longer exists on master.
- #1524 and #1522 applied `scope="col"` to other tables. Both are
closed.

## What Changed

- Added `scope="col"` to the five `<th>` elements in the `CostsSection`
table in `ui/src/pages/AgentDetail.tsx`.
- Rebased the branch onto current master.
- Dropped the original `ui/src/pages/Routines.tsx` hunks. Master rebuilt
the Routines page around folder-grouped rows, so the table those hunks
targeted no longer exists.
- Dropped the original `HintIcon` opacity change. It altered a visible
colour, which is out of scope for a markup-only accessibility fix.

## Verification

- Run `pnpm --filter @paperclipai/ui typecheck` and `pnpm --filter
@paperclipai/ui build`. This is a markup-only change, so a clean
type-check and build is the relevant automated signal.
- Open an agent detail page and go to the Costs section. Inspect the
header row. Each `<th>` now carries `scope="col"`.
- Navigate the same table with a screen reader, cell by cell. Each cell
is announced with its column name.
- Compare the rendered page before and after. It is unchanged, because
`scope` has no styling effect.

## Risks

Low risk. The change adds one standard HTML attribute to five header
cells in a single table. It introduces no code path, changes no
component API, and has no visual effect. The worst case is that the
attribute is redundant for a reader that already infers the column,
which is harmless.

## Model Used

Anthropic Claude Opus 5, exact model ID `claude-opus-5`. It ran with
extended thinking and repository read/write tools, inside a
maintainer-operated triage agent. The model rebased the branch, dropped
the two out-of-scope hunks, and wrote this description. The original
change was authored by @bluzername, and the model used for that work is
not recorded here.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` references)
- [x] I have considered and documented any risks above
- [x] My branch name describes the change and contains no internal
ticket id
- [ ] I have run tests locally and they pass — not run. This is a
markup-only change and the package has no test covering this table.
- [ ] I have added or updated tests where applicable — no test added,
which is why this PR is titled `refactor:`.
- [ ] I have updated relevant documentation to reflect my changes — no
documentation describes this markup.
- [ ] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work — not checked by the maintainer who rebased this.
- [ ] All Paperclip CI gates are green — CI re-runs on this push.
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
Greptile re-reviews on this push.

---------

Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
2026-08-07 20:46:47 -05:00
scotttong cc35c3c395
feat: structure and humanize recovery notices (#11075)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip posts system comments when automatic run recovery cannot
continue
> - These comments currently mix the main event with recovery
identifiers and routing details
> - The task chat shell also renders these comments as large raw text
blocks
> - Operators need a short explanation first and inspectable evidence on
demand
> - This pull request emits structured recovery notices and renders them
as compact humanized rows
> - The benefit is a quieter task thread that keeps the full recovery
evidence available

## Linked Issues or Issue Description

Related prior extraction source: #11070. This pull request replaces only
its structured recovery notice slice with a focused branch based on
current master.

**What existing behavior does this improve?**

Paperclip recovery escalations and the experimental task chat
system-comment renderer.

**Current behavior**

Recovery escalation comments put action identifiers, owner details, run
details, and failure codes into the visible markdown body. The task chat
shell renders the complete system comment as a large text block.

**Proposed behavior**

The server emits a short system notice with typed metadata sections. The
task chat shell classifies known recovery families and renders one
compact row. An operator can expand the row to inspect the full body and
metadata.

**Reason and benefit**

The main thread stays readable during repeated recovery activity. Typed
links and evidence remain available without exposing raw failure text in
the default view.

**Breaking changes**

The visible recovery comment body is shorter. Recovery action
deduplication now reads the structured metadata and still recognizes
legacy body markers. No API schema or database migration changes.

## What Changed

- Emit stranded recovery escalations with `system_notice` presentation
and typed recovery, owner, run, and failure-code metadata.
- Share bounded metadata row builders across recovery notice producers
and preserve legacy deduplication compatibility.
- Humanize known recovery notice families and render compact expandable
task-chat rows.
- Route system-authored comments ahead of derived agent authorship so
recovery notices do not appear as agent bubbles.
- Add focused server and UI regression coverage.

## Verification

- `pnpm check:token-gates` — 3/3 clean.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/shared exec vitest run
src/validators/issue.test.ts` — 32 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/services/recovery/stranded-notice.test.ts
src/__tests__/issue-recovery-actions.test.ts` — 57 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/services/recovery/successful-run-handoff.test.ts
src/services/recovery/stranded-notice.test.ts` — 39 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-process-recovery.test.ts -t 'escalates an
exhausted failed successful-run handoff without using generic
continuation recovery first|escalates an exhausted successful handoff
run that still leaves no disposition'` — 2 tests passed.
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-process-recovery.test.ts -t 'blocks assigned
todo work after the one automatic dispatch recovery was already used'` —
passed.
- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/system-notice-humanizer.test.ts
src/components/task-chat/TaskChatSystemNotice.test.tsx
src/components/task-chat/task-chat-adapter.test.ts` — 15 tests passed.
- Storybook visual baselines were not updated because this chat-shell
path has no affected snapshot baseline. Focused rendering tests and
token gates cover this change.

## Risks

- Consumers that parse recovery action identifiers from comment markdown
must move to structured metadata. Server deduplication remains backward
compatible with legacy comments.
- The humanizer uses stable recovery-family phrases. Unknown notices use
a generic truncated first-sentence fallback.
- The UI changes only the experimental task chat presentation. The
stored comment body and expanded metadata remain available.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex with GPT-5, reasoning mode, repository tools, shell
execution, and GitHub integration. The runtime did not expose a
context-window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 18:41:52 -07:00
scotttong 4e9a78db58
feat(ui): persist task chat composer drafts (#11076)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - People send task instructions through the board task chat.
> - A page refresh or task switch can discard an unfinished message in
the redesigned composer.
> - The existing task chat already supplies a task-specific draft key.
> - The redesigned composer must use that key without changing
attachment or send behavior.
> - This pull request restores, saves, and clears text drafts in the
redesigned composer.
> - The benefit is that users can return to unfinished task messages
without losing their text.

## Linked Issues or Issue Description

Related prior work: #11070. This pull request extracts only the final
composer draft behavior from that larger draft.

**Subsystem affected**

ui/ — React + Vite board UI.

**Problem or motivation**

The redesigned task chat composer does not use the draft key that the
task thread already provides. A refresh, navigation, or unmount can lose
an unfinished message.

**Proposed solution**

Persist text drafts by task key in local storage. Restore a draft when
the composer mounts. Save changes after a short delay and flush pending
text during unload or unmount. Clear the draft only after a successful
send.

**Alternatives considered**

The composer could save on every keystroke. A short delay avoids
unnecessary synchronous storage writes. The feature could also stay in
the larger predecessor PR, but a focused PR is easier to review and
verify.

**Roadmap alignment**

This is a focused usability improvement for the task conversation
surface. It does not add or duplicate a roadmap capability.

## What Changed

- Added safe draft storage helpers for load, save, and clear operations.
- Connected the task-specific draft key to the redesigned task chat
composer.
- Preserved drafts across debounce windows, unmounts, page unloads,
failed sends, and React Strict Mode probes.
- Cleared drafts after successful sends without changing current
attachment safeguards.
- Added focused composer and thread integration tests.

## Verification

- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat/TaskChatComposer.test.tsx
src/components/TaskChatThread.test.tsx`

## Risks

- Local storage can be unavailable or full. The helpers catch storage
errors and keep the composer usable.
- Only text is persisted. Attachments, work mode, and assignee
selections remain session state.
- Draft keys remain task-scoped, so text does not cross task boundaries.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex with model `gpt-5`. The context-window size is not exposed
in this environment. The model used agentic reasoning, tool use, code
execution, and test execution.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 18:32:49 -07:00
Evyatar Bluzer 1791979512
refactor(a11y): add ARIA progressbar attributes to BudgetPolicyCard (#1805)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Humans oversee those agents in teams, so each team needs its own
spend controls
> - The BudgetPolicyCard component shows how much of a budget is
consumed
> - The utilization bar in that card is a styled div with no ARIA role,
value, or label
> - A screen reader user therefore cannot hear how much budget is used
> - This pull request adds role="progressbar" and the matching ARIA
value attributes
> - The benefit is that assistive technology announces budget
utilization the same way sighted users see it

## Linked Issues or Issue Description

No existing issue. Related pull requests: #1869 and #1878 add the same
progressbar
semantics to ProviderQuotaCard and QuotaBar. They touch different files.

The problem follows the bug report template:

**What happened?**
The budget utilization bar in `BudgetPolicyCard` renders as a plain
`div`. It has no
`role`, no `aria-valuenow`, and no accessible name. A screen reader
announces nothing
for it. The user can read the "Remaining" amount, but not the
utilization percentage.

**Expected behavior**
The bar is announced as a progress bar. It reports the current
utilization percentage,
with its minimum and maximum.

**Steps to reproduce**
1. Open a project or agent page that shows the budget card.
2. Start VoiceOver (Cmd+F5 on macOS).
3. Move the cursor to the budget utilization bar.
4. VoiceOver announces nothing.

**Paperclip version or commit**
master, `ui/src/components/BudgetPolicyCard.tsx`.

**Deployment mode**
Local dev (pnpm dev).

## What Changed

- Added `role="progressbar"` to the inner bar element.
- Added `aria-valuenow` with the rounded utilization percentage.
- Added `aria-valuemin={0}` and `aria-valuemax={100}`.
- Added `aria-label` in the form `Budget utilization: 73% used`.

`aria-valuenow` and `aria-label` use the same `progress` value. That
value is already
capped at 100 by `Math.min(100, summary.utilizationPercent)`, so the
reported value
stays inside the min/max range when a scope is over budget.

An earlier revision also changed the budget amount `Input` to
`type="number"`. That
change is removed. It changed input behavior and was not an
accessibility fix.
See "Risks".

## Verification

1. Open a page that shows the budget card.
2. Start VoiceOver (Cmd+F5 on macOS) and move to the utilization bar.
3. VoiceOver announces "Budget utilization: X% used, progress
indicator".
4. Inspect the element. `aria-valuenow` equals the displayed percentage,
and it stays
   at 100 when utilization is above 100%.

The change adds attributes only. There is no visual change.

## Risks

Low risk. The change adds ARIA attributes to one element in one
component. It changes
no logic, no styling, and no layout.

The `type="number"` change is removed on purpose. With `type="number"`,
a browser
reports an empty string for text it cannot parse. `parseDollarInput("")`
then returns
`0` instead of `null`, so the existing "Enter a valid non-negative
dollar amount."
error never appears and unparseable input silently becomes a $0.00
budget. The
`inputMode="decimal"` input keeps that validation path working.

## Model Used

Original implementation by @bluzername. The author did not state a
model.
Scope reduction, branch update, and this description: Claude Opus 5 (1M
context,
extended thinking, tool use), prepared under maintainer review.

## Checklist

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

---------

Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
2026-08-07 19:51:03 -05:00
Tonio 384e5f6178
revert(ui): back out onboarding port (#10786) (#11067)
This reverts commit 11e56654f8.

#10786 ported the onboarding flow from a standalone prototype and
repointed `/onboarding` at the new `CloudOnboardingFlow`, deleting the
existing `OnboardingWizard.tsx` in the process. The ported flow is not
ready to be the shipping onboarding experience: it landed as a single
large port rather than an incremental migration, it pulled `motion`,
`three` and `@types/three` onto the UI dependency list for prototype
visuals, and it deleted the wizard that four in-flight pull requests
(#9900, #9501, #8982 and one more) were building on — those went
CONFLICTING the moment the file disappeared.

Rather than keep the half-migrated state on master while that is sorted
out, back the port out whole and re-land it incrementally. This restores
`OnboardingWizard.tsx` and the previous versions of the four e2e specs,
drops the `onboarding-preview.html` Vite entry, the DesignGuide
onboarding section and the `data-viz-misc` storybook story, and removes
the three prototype dependencies from `ui/package.json`.

This is an exact mechanical inverse of the squash commit — 41 files,
+2089/-3647, no hand edits. Reverting this commit restores all 41 files
byte for byte, so the port is recoverable in full when it is ready.

`pnpm-lock.yaml` is deliberately not touched. #10786 never updated it;
bot commit 4683f26c9 (#11036) added the `motion`/`three` entries
afterwards, so the lockfile is now ahead of the manifest. CI owns
lockfile updates (`.github/workflows/pr.yml`) and the policy job
regenerates it from the changed manifest.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 14:55:34 -07:00
Dotta 0a511ed1b0
feat(apps): support multiple provider connections (#11060)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Apps subsystem connects company tools through governed provider
connections.
> - A company can need more than one account for the same provider.
> - The current database constraint and Apps flow assume one named
connection per company.
> - New quarantined actions also need an explicit review decision before
activation.
> - This pull request supports multiple provider connections and
complete action review decisions.
> - The benefit is safer access control and a clear multi-account Apps
workflow.

## Linked Issues or Issue Description

Refs: #11040

**Subsystem affected**

Cross-cutting. This change affects the Apps UI, the tool access API, the
shared request contract, and the database schema.

**Problem or motivation**

The connection name constraint prevents a company from keeping more than
one connection for a provider. The Apps UI also reuses an existing OAuth
connection when a user asks to connect another account. Action review
can enable selected entries without recording a decision for every
quarantined action.

**Proposed solution**

Remove the company and connection name uniqueness constraint. Let users
open, count, edit, and create multiple provider connections. Require the
finish request to cover every quarantined action exactly once before the
server activates reviewed entries.

**Alternatives considered**

The UI could generate unique internal names and keep the database
constraint. This would preserve a one-connection assumption in the data
model and would make display names part of identity. The server could
also infer review decisions from enabled actions. This would not
distinguish a reviewed disabled action from an action that the user did
not review.

**Roadmap alignment**

This change extends the completed MCP Tool Gateway and Apps milestone.
It also supports the Connected Apps roadmap item. It follows the
navigation and connection management work in #11040.

## What Changed

- Remove the company-scoped connection name uniqueness index with an
ordered and idempotent migration.
- Add a reviewed action list to the finish-app contract and reject
incomplete or duplicate review decisions.
- Activate reviewed entries and keep unreviewed quarantined entries
blocked.
- Enable a completed connection and preserve the company and connection
scope in all updates.
- Show provider connection counts and open the provider setup page from
Browse.
- Let users edit existing connections or connect another account without
reusing an active OAuth connection.
- Update focused server and UI coverage for multiple connections and
action review.

## Verification

- Ran the focused Apps UI suite. All 116 tests passed in 11 files.
- Ran the focused server and CLI suite. All 276 tests passed in 3 files.
- Ran `pnpm --filter @paperclipai/db check:migrations`. The migration
safety check passed.
- Ran `pnpm -r typecheck`. All projects passed.
- Ran `pnpm build`. All projects built successfully.
- Ran `pnpm test:run`. It passed 3,735 tests and skipped 4 tests. One
worktree-safety assertion failed because the execution workspace reloads
its worktree marker. The same test passed with an isolated non-worktree
marker.
- Ran `pnpm check:token-gates`. It reports 12 existing violations in the
unchanged `PaperclipOrbit3D.tsx` file from the target branch.
- Started the six affected Playwright specifications. Chromium could not
start because the host does not provide `libatk-1.0.so.0`. The GitHub
e2e jobs will verify these specifications.
- GitHub Actions passed every final-head CI gate, including all three
e2e shards and the aggregate `e2e` and `verify` jobs.
- Greptile reviewed final commit `9af9200426` at 5/5 with zero review
threads.

## Risks

- Removing the name uniqueness index permits duplicate display names.
Stable connection IDs and UIDs remain unique within a company.
- The finish-app endpoint accepts the new review field as optional for
backward compatibility. When clients send it, the server requires a
complete decision for all quarantined actions.
- Multiple OAuth connections depend on the explicit new-connection route
flag. Focused tests cover active and draft connection reuse.
- The migration is ordered after migration 0210. Its `DROP INDEX IF
EXISTS` statement is safe to repeat.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex with the `gpt-5.6-sol` model assisted this change. The
agent used repository tools, code execution, test execution, and agentic
reasoning. The Codex runtime manages the context window.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 16:28:04 -05:00
Dotta b18b0fc39b
feat: refine app connections and legacy worktree startup (#11040)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Apps UI manages app discovery and app connections.
> - The managed worktree runtime starts agent work in repository
worktrees.
> - The Apps routes do not match the main discovery flow, and the
connections view lacks a delete action.
> - Legacy managed worktrees can also start before their pending seed
operation runs.
> - This pull request makes app discovery the main Apps route and makes
connection management explicit.
> - It also seeds legacy managed worktrees before runtime startup and
makes the CLI read the repository-local config.
> - The benefit is a clearer Apps workflow and a safer managed-worktree
startup path.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

This improves the Apps navigation, app connection management, managed
git-worktree startup, and CLI worktree selection.

**Subsystem affected**

Cross-cutting. The change affects `ui/`, `server/`, `cli/`, and
development documentation.

**Current behavior**

The `/apps` route opens the connections list while discovery uses a
nested route. The connections list has no delete action. Some legacy
managed worktrees can start runtime work before their pending seed
operation runs. The CLI can also read an ambient Paperclip config
instead of the repository-local config.

**Proposed behavior**

The `/apps` route opens Browse, and `/apps/connections` opens the
connection list. Users can delete a connection after confirmation.
Runtime startup seeds legacy managed worktrees when required. The CLI
resolves the current worktree from the repository-local
`.paperclip/config.json` file.

**Reason and benefit**

Users can discover apps from the canonical Apps route and can manage
existing connections from a dedicated route. Legacy worktrees receive
their required repository content before agent runtime starts. CLI
worktree selection stays scoped to the current repository.

**Breaking changes**

The `/apps` and `/apps/browse` route behavior changes. Old Browse links
redirect to `/apps`. The change does not modify an API schema or
database schema.

## What Changed

- Make Browse the canonical `/apps` page and move the connection list to
`/apps/connections`.
- Align Apps navigation, redirects, attention links, empty states, and
connection actions with the new routes.
- Add connection deletion with confirmation and clear failure feedback.
- Seed legacy managed git worktrees before runtime startup when their
seed status is pending.
- Read the CLI worktree selection from the repository-local Paperclip
config.
- Update focused UI, server, CLI, and development documentation
coverage.

## Verification

- Ran 202 focused UI, server, and CLI tests. All tests passed.
- Ran `pnpm -r typecheck`. All projects passed.
- Ran `pnpm build`. All projects built successfully.
- Ran `pnpm test:run`. The server and UI stages passed 7,168 tests. The
CLI stage found one environment-sensitive secrets test because this
workspace injects static AWS credentials. The isolated CLI file passed
all 8 tests after those injected variables were unset.
- Ran `pnpm check:token-gates`. It reports 12 existing color-token
violations in the unchanged `PaperclipOrbit3D.tsx` file from the target
branch. This pull request does not modify that file.
- Ran focused regression coverage for repository-root CLI config
resolution and connection deletion state. All tests and affected package
typechecks passed.
- Collected all 27 tests in the six changed Playwright specifications
successfully.
- GitHub Actions passed every latest-head CI gate, including all three
e2e shards and the aggregate `e2e` and `verify` jobs.
- Greptile reviewed the final commit at 5/5 with zero unresolved
threads.

## Risks

- Existing bookmarks for `/apps/browse` redirect to `/apps`.
- Connection deletion changes visible connection state and requires user
confirmation.
- The legacy seed path runs only for managed git worktrees with pending
seed state. Tests cover the startup condition.
- The rebase preserves the target branch's direct OAuth policy for the
Notion connection flow.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex with the GPT-5 model family assisted this change. The agent
used reasoning, repository tools, code execution, and test execution.
The runtime does not expose the exact model snapshot or context-window
size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 14:26:40 -05:00
Nicky Leach 01b51dc0e5
fix(runtime): stop Live badge and Working shimmer after task teardown (#10985)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task list and the chat views show a Live badge and a Working
shimmer for an issue that has an active run.
> - A finished task kept the Live badge and the Working shimmer after
the run ended and the sandbox stopped.
> - The user interface reads run liveness from the
`heartbeat_runs.status` row. The run finalizer writes the terminal
status in a step that is separate from the agent `status=done` update.
When the sandbox or the run process stops between the two steps,
`heartbeat_runs.status` stays `running` forever.
> - A run row that stays `running` makes a finished task look
perpetually Live, and the user interface has no guard for an issue that
already reached a terminal status.
> - This pull request closes the invariant "environment lease released
implies the run is terminal" on the server, and adds a user interface
guard that suppresses live state for a terminal issue.
> - The benefit is that a finished task stops showing Live and Working,
both at the source (the run row) and at the surface (the badge and the
shimmer).

## Linked Issues or Issue Description

**Bug description**

- A completed task kept the Live badge and the Working shimmer after its
run ended and the sandbox was torn down.

**Steps to reproduce**

- Run an agent task to completion. Let the sandbox tear down while the
run finalizer is between the `status=done` update and the terminal
run-status write.
- Open the task list or the chat view for the finished task.

**Expected behavior**

- A finished task shows no Live badge and no Working shimmer.

**Actual behavior (before this change)**

- The finished task showed the Live badge and the Working shimmer
because its `heartbeat_runs.status` row stayed `running`.

This pull request supersedes the two separate pull requests #10954
(frontend) and #10955 (backend). It carries all of their changes for the
same race.

## What Changed

Server:

- Run teardown terminalizes a still-running or still-queued run before
it releases the environment lease. It writes `succeeded` when the issue
already reached `done`, `cancelled` when the issue is `cancelled`, and
`interrupted` otherwise. It never overwrites a status that another path
already made terminal.
- The recovery stale-lock sweep terminalizes an orphaned running run to
`interrupted` after it confirms the process and the sandbox are both
gone. It requires recorded process metadata, so it never terminalizes a
live run, a queued run, or a scheduled retry.
- Each terminal transition writes a run event.
- The stale-lock sweep continues and clears the lock when the audit
write fails. It logs the failure loudly.
- New server tests cover both invariants.

User interface:

- A shared guard suppresses the Live badge and the Working shimmer when
the issue status is terminal.
- The guard keeps non-terminal `queued` and `running` issues live.
- The guard prefers the newest issue live-status snapshot.
- New user interface tests cover the guard and the snapshot preference.

## Verification

Server:

- `pnpm --filter @paperclipai/plugin-sdk ensure-build-deps && tsc
--noEmit` in `server/` — 0 errors.
- `pnpm --filter server test
heartbeat-run-lease-release-terminalization.test.ts
recovery-stale-issue-lock-sweep.test.ts` — 12 tests pass.

User interface:

- `pnpm --filter @paperclipai/ui typecheck` — 0 errors.
- `pnpm exec vitest run ui/src/lib/liveIssueIds.test.ts
ui/src/lib/issue-chat-messages.test.ts` — 40 tests pass.

## Risks

- Low risk. The server change only forces a still-live run row to a
terminal status when the lease releases or when the recovery sweep
confirms the process is dead. It never overwrites an existing terminal
status, and it guards the recovery path with process metadata to avoid
terminalizing a live run.
- The user interface change is additive. The guard only suppresses live
state for a terminal issue and keeps queued and running issues live.
- No database migration. No change to any external endpoint.

## Model Used

- Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, tool use, and
code execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 07:23:56 -07:00
Tonio 11e56654f8
feat(ui): port onboarding flow from prototype; add cloud + local variants (#10786)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - First-run onboarding is the subsystem that turns a brand-new install
into a working company: it creates the company, its goal, a lead agent,
and that agent's first task
> - The existing `OnboardingWizard` carried all of that wiring
correctly, but its UI had drifted from the current design direction, and
a separate design prototype (`paperclip-onboard`) existed as a
standalone visual mock with no backend
> - Porting the prototype's *logic* would have thrown away working,
well-tested backend orchestration; leaving the two apart meant the
design never shipped
> - Separately, cloud and local (self-hosted) installs need meaningfully
different first runs — local has no sign-in and must let the user pick a
locally-installed CLI adapter — so a single linear wizard could not
serve both
> - This pull request rebuilds the presentational layer from the
prototype on top of the existing backend orchestration, and splits it
into two thin flow containers over a shared core
> - The benefit is that the shipped onboarding matches the intended
design, cloud and local can diverge without duplicating logic, and each
can later ship to a different app version while sharing one set of step
components

## Linked Issues or Issue Description

No existing issue — describing inline (feature request).

**What problem does this solve?**
Onboarding is the first thing a new user sees, and the shipped wizard
had drifted from the current design. In parallel, cloud and local
installs need different first-run paths: local has no hosted sign-in,
and its agent runs on a CLI adapter installed on the user's machine,
which the cloud path never has to ask about. There was no way to express
that difference without either forking the whole wizard or bolting
conditionals onto a single linear flow.

**Proposed solution**
Extract the onboarding step views and shell into a shared core, then
compose two thin flow containers (cloud and local) over it. Keep all
backend orchestration in the existing `useOnboardingFlow` hook so no
working logic is rewritten.

**Alternatives considered**
- *Single flow with a `variant` prop* — most DRY, but the two flows are
intended to ship on different app versions, and a shared file would have
to be split later anyway.
- *Two fully independent copies* — simplest per-flow, but every shared
refinement (spacing, motion, copy) would have to be made twice and would
drift.

## What Changed

- **Shared core** under `ui/src/components/onboarding/`:
`OnboardingScaffold` owns the full-screen shell and the single
`AnimatePresence` step crossfade, so both flows transition identically;
step views (Start / Company / Agent / Task), `FooterNav`, `AgentPreview`
and the motion constants are extracted for reuse.
- **`CloudOnboardingFlow`** — `start → company → agent → task`; mounted
in the real app via `OnboardingWizardVariant`. Behaviour matches the
retired wizard, including `previewMock` and the existing-company ("add
an agent") entry point.
- **`LocalOnboardingFlow`** — skips sign-in and adds an optional email
ask (with a privacy assurance), a local model/adapter step that hires
with `requireEnvProbe: true`, and a "star us on GitHub" interstitial
before completing. **Harness-only for now** — the real app still mounts
the cloud flow.
- **Deleted `OnboardingWizard.tsx`** (1,786 lines); updated its
Storybook stories and the `OnboardingWizardVariant` test to the new
components.
- **Orbiting 3D paperclip backdrop** behind the auth and welcome screens
(`three`), code-split so it only downloads on those screens; honours
`prefers-reduced-motion` and disposes its GL context on unmount.
- **`motion`** added for step transitions and the agent-capsule
choreography.
- Visual values routed through design tokens per `DESIGN.md`; `Stepper`
generalized to take a step total (backward compatible); `/design-guide`
page and the component index updated.
- **Standalone preview harness** (`ui/onboarding-preview.html`) with
`?flow=` and `?step=` for backend-free review, wired as a second Vite
rollup input.
- **Adapter env probe bound to the adapter it ran against.**
`hireLeadAgent` reused `adapterEnvResult` for any adapter, so when a
hire failed and the user picked a *different* local adapter and retried,
the previous adapter's verdict satisfied the `requireEnvProbe` guard
while the hire posted the new adapter's config — hiring it unprobed. The
cache is now keyed on the adapter type plus the exact config posted to
the test endpoint, the config is built once and shared by probe and
hire, a failed probe clears the cache, and `clearAdapterEnvResult()`
(called on adapter change) stops the step displaying a stale verdict.
Cloud is unaffected — it hires with `requireEnvProbe: false`. Reported
by Greptile.
- **E2E specs re-pointed at the new flow.** Four specs still drove the
deleted wizard (`onboarding`, `conference-room-typing-intro`,
`planning-mode-visual-verification`, `nux-phase4-screenshots`) and
failed with `element(s) not found` on `"Name your company"` /
`input[placeholder="Acme Corp"]`. Rather than repeat the new drive
sequence four times, `tests/e2e/onboarding-flow.ts` adds one driver per
step (`startCloudOnboarding`, `completeCompanyStep`,
`completeAgentStep`, `completeTaskStep`, `completeCloudOnboarding`) and
the specs import it, so the next flow change touches a single file. Two
now-dead `**/test-environment` route stubs went with it — the cloud flow
hires with `requireEnvProbe: false`, so that probe never fires.

## Verification

- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `npx vitest run` over the onboarding suites
(`OnboardingWizardVariant`, `AgentCapsule`, `onboarding-launch`,
`onboarding-goal`, `onboarding-route`, `onboarding-adapter-config`) — 33
tests pass.
- `pnpm --filter @paperclipai/ui build` — succeeds; the three.js chunk
splits out separately (522 kB raw / 133 kB gzip) rather than entering
the main bundle.
- Both flows driven end-to-end in the preview harness in `previewMock`
(no database writes), plus the cloud flow rendered in the real
authenticated app at `/onboarding` to confirm the mount swap.
- The four re-pointed e2e specs pass locally against the new flow.
- New `ui/src/hooks/useOnboardingFlow.test.tsx` — 4 cases pinning the
adapter-probe cache (switch-adapter retry, cold path, explicit clear,
and the cloud flow's `requireEnvProbe: false`). Verified non-vacuous:
the switch-adapter case fails against the pre-fix code.
- Rebased onto current `master`; `pnpm-lock.yaml` is deliberately
**not** committed — `.github/workflows/pr.yml` regenerates it when a
manifest changes and shares it with downstream jobs as the `pr-lockfile`
artifact.

## Risks

- **Deleting `OnboardingWizard.tsx` is the one change that alters
existing app behaviour.** The cloud flow is intended to be
behaviour-equivalent, and its entry points are covered by the updated
`OnboardingWizardVariant` test, but this is the area to review most
closely.
- **Conflict risk with open PRs that touch the old wizard**: #9900,
#9501, #8982 and #6636 all modify
`ui/src/components/OnboardingWizard.tsx`, which this PR removes.
Whichever lands second will need its change re-applied to the new step
components. Flagging so ordering can be decided deliberately.
- **New dependencies**: `motion` and `three` (+ `@types/three`). `three`
is large, so it is lazily imported and code-split — it does not affect
the main bundle. Both are MIT.
- The **local flow is not reachable in the app** yet (harness/canary
only), so it carries no runtime risk today; wiring it up is a follow-up.
- The auth screens remain **presentational only** — they are not wired
to real auth, unchanged from before this PR.
- **Pre-existing, not introduced here:** `OnboardingWizardVariant`
renders outside `<Routes>` in `App.tsx`, so its `useParams()` never
resolves `:companyPrefix` and `/{prefix}/onboarding` opens the welcome
screen instead of jumping to the agent step. `master` has the identical
structure, so this PR faithfully ports existing behaviour; the working
"add an agent" entry is the launcher card behind the overlay, which is
what the screenshot spec drives. Worth a separate fix.

## Model Used

Claude Opus 5 (`claude-opus-5`) via Claude Code, with extended thinking
and tool use (repo search/edit, local test + build execution, and
browser-driven visual verification of the rendered flows). Portions of
the session also ran on `claude-opus-4-8` and `claude-fable-5`.

## Checklist

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

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 23:35:05 -07:00
scotttong b67c512f82
feat(ui): live run label → run detail, running row → task detail (#11034)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The agent detail page has a Dashboard tab. The Dashboard tab shows a
"Live Run" section for the agent's current heartbeat.
> - The "Live Run" section has two clickable pieces: the section heading
and the running row. Both pieces linked to the same run detail page.
> - Two controls that go to the same place waste a navigation affordance
and hide the task the agent runs.
> - This pull request splits the two destinations. The heading goes to
the run. The running row goes to the task.
> - The benefit is that a user reaches the run internals from the label
and the work item from the row, in one click each.

## Linked Issues or Issue Description

No public GitHub issue exists for this change. The description follows
the enhancement issue template.

**What existing behavior does this improve?**
The "Live Run" section on the agent detail page, Dashboard tab (the
`LatestRunCard` component in `ui/src/pages/AgentDetail.tsx`).

**Current behavior**
The "Live Run" heading and the running row both link to the run detail
page (`/agents/:agentId/runs/:runId`). The row shows the run code and an
invocation-source chip. There is a separate "View details →" link that
also goes to the run detail page. A user cannot reach the task the run
works on from this section.

**Proposed behavior**
The heading becomes a link to the run detail page and appends the short
run code, shown as `Live Run · <run code>`. The redundant "View details
→" link is removed. The running row links to the task detail page when
the run's context snapshot resolves to a known issue, and the row then
shows the task status glyph, the task slug, and the task title. A pure
timer heartbeat with no resolvable task keeps the previous behavior: run
code plus source chip, linking to the run detail page.

**Reason and benefit**
The heading and the row now go to distinct, intuitive destinations. A
user reaches the run internals from the label and the work item from the
row, each in a single click. The fallback keeps heartbeats with no task
readable and avoids a blank or broken row.

## What Changed

- Made the "Live Run" / "Latest Run" heading a `Link` to the run detail
page and appended the short run code (`run.id.slice(0, 8)`) in a mono
span, formatted `Live Run · <run code>`. Kept the pulsing live dot.
- Removed the redundant "View details →" link.
- Changed the running row `Link` target to the task detail page
(`/issues/:identifier`) when a task resolves, falling back to the run
detail page otherwise.
- Resolved the task from the run context snapshot
(`contextSnapshot.issueId`, falling back to `contextSnapshot.taskId`)
against a `Map` of the agent's assigned issues threaded in from
`AgentOverview`.
- When a task resolves, replaced the run code and source chip in the row
with the task status glyph (`StatusGlyph`), the task slug, and the task
title. Kept the running spinner, the run status badge, and the
timestamp.

## Verification

- `pnpm check:token-gates` → 3/3 gates clean.
- `pnpm --filter ui typecheck` → passes.
- `pnpm --filter ui exec vitest run
src/pages/AgentDetail.progress.test.ts
src/pages/AgentDetail.instructions.test.tsx` → 10/10 pass.
- Manual (needs a reviewer with a browser): open an agent detail page →
Dashboard tab.
- For a live issue-execution run: the heading reads `Live Run · <run
code>` and opens the run detail page; the row shows the task status
icon, slug, and title and opens the task detail page.
- For a pure timer heartbeat with no task: the row falls back to run
code + source chip and opens the run detail page. No blank row.
  - Confirm both states in light and dark mode.

## Risks

Low risk. The change is presentational and scoped to one component. The
task lookup is defensive: it reads the context snapshot with a fallback
key and only renders the task row when the issue is present in the
already-loaded assigned-issue set, so an unknown or missing issue
degrades to the previous run-detail behavior rather than breaking.

## Model Used

- Provider: Anthropic (Claude).
- Model: claude-opus-4-8 (Opus 4.8).
- Context window: 200K.
- Reasoning mode: extended thinking, tool use.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 23:18:32 -07:00
Dotta 5da382fd59
feat(skills): require explicit merge modes (#10978)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents can select company skills and synchronize them to adapter
runtimes
> - The skill sync API replaced the complete selection without an
explicit destructive choice
> - Company package import also replaced conflicting skills by default
> - These defaults could remove operator edits during setup and import
reruns
> - This pull request adds explicit assignment merge modes and safe
package conflict handling
> - The benefit is that reruns preserve operator work unless the caller
explicitly requests replacement

## Linked Issues or Issue Description

**What existing behavior does this improve?**

This change improves agent skill synchronization and company package
import.

**Subsystem affected**

This is a cross-cutting change across the shared contracts, server, CLI,
and UI.

**Current behavior**

Agent skill synchronization replaces the full desired skill set from a
modeless request. Package import replaces a conflicting skill when the
caller does not select a conflict mode.

**Proposed behavior**

Agent skill synchronization requires `add`, `remove`, or `replace`.
Package import skips conflicts by default. Each imported skill reports
whether it was created, renamed, replaced, or skipped.

**Reason and benefit**

Setup and import reruns must preserve operator edits by default.
Explicit destructive modes make data loss less likely and make each
outcome inspectable.

**Breaking changes**

Callers of the agent skill sync API must now send `mode`. Callers that
need the former behavior must send `replace`. Package import now uses
`skip` when `onConflict` is absent.

## What Changed

- Added required `add`, `remove`, and `replace` modes to the shared
agent skill sync contract.
- Added actionable `422` validation for missing or invalid modes.
- Updated first-party UI and CLI callers with explicit modes.
- Changed package skill conflict handling to use `skip` by default.
- Kept plugin-owned and built-in stock skill imports on explicit
`replace`.
- Added created, renamed, replaced, and skipped results to company
imports.
- Added regression coverage for merge modes and package conflict
outcomes.

## Verification

- `pnpm check:token-gates`
- `pnpm -r typecheck`
- `pnpm build`
- `pnpm test:run:serialized` (128 suites passed)
- `pnpm --filter @paperclipai/skills-catalog test` (20 tests passed)
- Focused agent skill route, company skill service, portability, CLI,
and UI tests passed.
- GitHub CI passed build, typecheck, canary, all general and serialized
test shards, all browser shards, policy, security, and final
verification on commit `2cfbb3e4c5`.
- Greptile reviewed the latest commit at 5/5 with zero unresolved
threads.

## Risks

- This change intentionally rejects modeless agent skill sync requests.
- The safe package default can leave an existing skill unchanged where
the old default overwrote it.
- All first-party callers now select a mode. Regression tests cover each
outcome.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI `gpt-5.6-sol` through Codex. The runtime used agentic
reasoning, tool use, code execution, and repository editing. The runtime
did not expose the context window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-07 00:42:08 -05:00
Dotta 03cfad7ceb
feat(apps): connect Notion through MCP OAuth (#11009)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Apps give agents governed access to external tools.
> - The Apps gallery lists Notion, but the server required manually
configured OAuth credentials.
> - Notion's hosted MCP server supports OAuth discovery and dynamic
client registration.
> - Notion also requires HTTPS or a loopback HTTP redirect URI.
> - This pull request adds a direct Notion MCP OAuth path with PKCE and
reusable dynamic clients.
> - It also adds the current Apps UI states for connect and
reauthorization.
> - The benefit is a secure Notion connection with no manual client
credential setup.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The Apps gallery, Apps connect route, OAuth token lifecycle, and managed
MCP gateway.

**Subsystem affected**

`server/`, `packages/shared/`, `scripts/`, and `ui/`.

**Current behavior**

The Notion gallery cards are disabled. The server uses the classic
Notion OAuth endpoints and requires operator-supplied client
credentials. It does not register an OAuth client from provider
metadata. Concurrent refreshes can also replay a rotating refresh token.

**Proposed behavior**

Enable the Notion Apps flow. Discover OAuth metadata from
`https://mcp.notion.com/mcp`. Register and reuse a public RFC 7591
client with PKCE. Require HTTPS or loopback HTTP callbacks. Serialize
refreshes, store each rotated refresh token before the new access token
can be used, and show a reconnect state for `invalid_grant`.

**Reason and benefit**

Operators can connect the built-in Notion MCP app without creating or
copying OAuth credentials. Paperclip keeps dynamic clients and rotating
tokens in the company secret store.

**Breaking changes**

None. Explicit environment client credentials still take priority.
Existing Slack and Linear OAuth endpoint hints remain unchanged. Other
OAuth apps remain disabled unless they are allowlisted.

**Additional context**

PR #10910 is a related, broader Connections v3 wizard replacement. This
PR is the focused current Apps flow. The MCP Tool Gateway and Connected
Apps items in `ROADMAP.md` cover this planned capability.

## What Changed

- Classify all 20 reviewed Notion MCP tools with provider-scoped read
and write defaults.
- Require approval for selected Notion mutations, including move,
duplicate, and convert actions that generic verb matching missed.
- Preserve company-scoped connection and catalog resolution for Notion
profiles and policies.
- Add RFC 7591 dynamic client registration with
`token_endpoint_auth_method=none` and mandatory PKCE.
- Store the dynamic client ID on the connection and store any returned
client secret in the company secret store.
- Reuse the registered client for later connects and keep explicit
environment credentials as the first choice.
- Discover protected-resource and authorization-server metadata from the
Notion MCP endpoint.
- Add `redirectConstraints: "https-or-loopback-http"` to the generated
Notion app definition and shared contract.
- Reject non-loopback plain HTTP callbacks before network access with a
TLS setup error.
- Serialize client registration and token refresh operations within the
server process.
- Store a rotated refresh token before publishing the refreshed access
token.
- Treat `invalid_grant` as terminal and move the connection to a clear
reauthorization state.
- Add focused coverage for registration reuse, callback constraints,
refresh rotation, and terminal grants.
- Enable the Notion Apps route and add connect, redirect, success,
error, and reconnect UI states.
- Keep non-allowlisted OAuth apps blocked and cover the UI policy with
regression tests.

## Verification

- The focused Notion policy integration test passed with embedded
PostgreSQL.
- The focused 20-tool classification test passed.
- The server typecheck passed on the governance head.
- `pnpm -r typecheck` passed on the rebased head.
- `pnpm --filter @paperclipai/server typecheck` passed after the
security follow-up.
- `pnpm exec vitest run server/src/__tests__/tool-access-service.test.ts
-t \u0027DCR|refresh tokens|invalid_grant|abandoned lease\u0027` passed
10 focused security tests.
- `pnpm build` passed on the rebased head.
- `pnpm exec vitest run server/src/__tests__/tool-access-service.test.ts
-t 'OAuth|oauth'` passed 14 tests.
- `pnpm exec vitest run packages/shared/src/app-definitions.test.ts`
passed 5 tests.
- The complete server group passed 3,686 tests with 4 skipped.
- The complete UI group passed 3,656 tests.
- The full local runner found one environment-only CLI failure because
this agent runtime injects static AWS credentials into a test that
expects `AWS_PROFILE` only. `env -u AWS_ACCESS_KEY_ID -u
AWS_SECRET_ACCESS_KEY pnpm exec vitest run
cli/src/__tests__/secrets.test.ts` passed all 8 tests.
- The prior UI verification passed 55 focused tests, `pnpm
check:token-gates`, the Storybook build, and review of six 1440 x 1000
screenshots.
- OAuth request sequence: protected-resource metadata `GET
https://mcp.notion.com/.well-known/oauth-protected-resource/mcp`;
authorization metadata `GET
https://mcp.notion.com/.well-known/oauth-authorization-server`; dynamic
registration `POST https://mcp.notion.com/register`; authorization `GET
https://mcp.notion.com/authorize`; token exchange and refresh `POST
https://mcp.notion.com/token`; MCP traffic `POST
https://mcp.notion.com/mcp`.
- The live metadata and registration probe confirmed that Notion accepts
HTTPS and loopback HTTP redirects. It rejects a plain HTTP private
hostname.
- A later QA task owns the full browser consent and managed gateway
tool-list dry run against a configured HTTPS deployment.

## Risks

- Notion can add tools. Unrecognized names use the generic classifier,
and new or changed risky tools stay quarantined after connection
activation.
- A deployment that uses a private non-loopback hostname must configure
HTTPS before it can connect Notion.
- Dynamic registration creates a provider-side client. Paperclip reuses
it because registration does not provide a standard delete operation.
- Refresh coordination uses a database CAS lease across service
instances. An unclean crash leaves an uncertain lease and requires
reconnect instead of risking refresh-token replay.
- The current Apps surface overlaps with PR #10910. Merge order can
require a small conflict resolution if that PR lands first.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex on a GPT-5 runtime. The exact deployment ID and context
window are not exposed. The runtime used reasoning, repository tools,
code execution, and network tools.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 22:18:08 -05:00
scotttong ea83c5c822
feat(task-chat): bring back copy/👍/👎 actions on the agent bubble footer (#11025)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task view shows a threaded conversation between the user and the
agent, with agent replies rendered as bubbles that carry a "✓ Worked · N
tools" summary line.
> - The conference-room chat already offers per-message copy and
thumbs-up / thumbs-down feedback, but the redesigned task thread dropped
these controls from the agent bubble footer.
> - Users lose a quick way to copy an agent reply or send feedback on
it, and the redesign silently ignored the feedback-vote props it was
already given.
> - This pull request prepends a copy · thumbs-up · thumbs-down cluster
to the bubble summary line and wires the existing feedback-vote props
through.
> - The benefit is a consistent feedback surface across both chat views,
with no new API.

## Linked Issues or Issue Description

No public GitHub issue exists for this change; the underlying issue is
described inline below following the feature template
(`.github/ISSUE_TEMPLATE/feature_request.yml`).

#### Problem or motivation

The redesigned task thread renders each agent reply with a "✓ Worked · N
tools · <timestamp>" summary line, but it dropped the copy and thumbs-up
/ thumbs-down controls that the conference-room chat still shows. Users
can no longer copy an agent reply or vote feedback from the task thread.
The redesign component already received `feedbackVotes` and `onVote`
props but ignored them.

#### Proposed solution

Prepend a copy · 👍 · 👎 cluster to the summary line, leading the
always-visible timestamp, reusing the shared `IssueChatFeedbackButtons`
so both chat views speak the same feedback language. Anchor the cluster
to the turn's summary row (a sibling of the expandable tool-history
fold) so it stays on the summary line whether the tool history is
collapsed or expanded.

#### Alternatives considered

Placing the cluster inside the expandable fold — rejected because
expanding the tool history then re-centered the actions to the middle of
the tall fold.

#### Roadmap alignment

UI polish to the task thread; no core-roadmap overlap.

## What Changed

- Add `TaskChatBubbleActions`: a copy · thumbs-up · thumbs-down cluster
built on the shared `IssueChatFeedbackButtons`.
- Render the cluster on the agent bubble's "✓ Worked · …" summary line,
leading the timestamp; runless agent replies get the same cluster with
the timestamp trailing. Human and system bubbles are unchanged.
- Add a `leading` slot to `TaskChatTurn` so the actions sit on the
summary row, a sibling of the tool-history fold, and stay anchored when
the fold expands.
- Wire the redesign to the `feedbackVotes` / `onVote` props it already
received.
- Add a demo binding in the `TaskChatLab` dev harness.

## Verification

- `pnpm check:token-gates` — 3/3 gates CLEAN.
- `pnpm typecheck` — clean across all packages.
- `cd ui && pnpm vitest run
src/components/task-chat/TaskChatBubble.test.tsx
src/components/task-chat/TaskChatTurn.test.tsx` — 31/31 pass.
- Manual: open a task thread, confirm the copy / 👍 / 👎 cluster shows on
the agent bubble summary line before the timestamp, copy works, votes
toggle, and the cluster stays on the summary line when the tool history
is expanded.

Visual change: snapshot baselines are intentionally not updated, per the
`doc/design/DECISION-SHEET.md` entry "Per-change snapshot verification
demoted to dormant (Jul 13 2026)".

## Risks

Low risk. UI-only change scoped to the redesigned task-chat bubble
footer. It reuses an existing shared feedback component and existing
vote props; no API, schema, or server change. Human and system bubbles
are untouched.

## Model Used

Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, tool use.

## Checklist

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 18:26:44 -07:00
scotttong d84c5eae7a
feat(ui): hide task priority from the UI (keep data model) (#11024)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Tasks and issues carry a `priority` field that renders across many
product surfaces: the detail header, the Triage properties panel, Kanban
and thread cards, the New Task composer, list Sort/Group/Filter menus,
search filters, and the dashboard chart.
> - Product feedback found the priority level adds visual noise and
decision cost without clear value in day-to-day task flow.
> - We want to remove priority from the interface, but keep the data
model, API, validation, and search DSL fully intact so the choice is
reversible with no migration.
> - This pull request hides every priority indicator and control behind
one compile-time flag, `SHOW_TASK_PRIORITY_UI`, set to `false`.
> - The benefit is a calmer, simpler UI now, with a single-boolean
revive path and zero data loss.

## Linked Issues or Issue Description

<!-- No public GitHub issue exists. Describing in-PR per the feature
template. -->

**Subsystem affected**
The web UI (`ui/src`): issue detail, properties panel, Kanban/thread
cards, New Task dialog, issues list Sort/Group/Filter menus, search
filter bar/sheet, dashboard charts, and the design-guide showcase.

**Problem or motivation**
The task/issue priority level appears across many surfaces and adds
visual clutter and decision overhead without pulling its weight in
normal task flow. We want it gone from the interface without discarding
the underlying data or breaking anything that depends on it.

**Proposed solution**
Add a single compile-time UI flag, `SHOW_TASK_PRIORITY_UI` (default
`false`), and gate every priority indicator and control behind it. Leave
the data model, API params, Zod validation (including the `"medium"`
default), and the search filter DSL untouched. Reviving priority is a
one-line flip of the flag back to `true`.

**Alternatives considered**
Deleting the priority code and schema outright. Rejected: it is
irreversible, needs a data migration, and throws away a field the API
and search still support. A gated flag keeps the change reversible and
low risk.

**Roadmap alignment**
UI simplification. This is a presentation-only change; it does not alter
core agent or data behavior.

## What Changed

- Added `ui/src/lib/ui-flags.ts` exporting `SHOW_TASK_PRIORITY_UI:
boolean = false` (typed `boolean` so gated branches are not flagged as
dead code).
- Gated the priority row in the Triage properties panel and the editable
priority control in the issue detail header (plus its skeleton seed).
- Gated the per-card priority icon in `KanbanBoard` and in
`IssueThreadInteractionCard`.
- Hid the priority chip and the mobile "more" menu priority section in
the New Task dialog. The submit path still sends the `"medium"` default.
- Removed the Priority options from the issues list Sort and Group-by
menus; the comparator and grouping logic stay dormant.
- Hid the Priority sections in the issue filters popover and in the
search filter bar and sheet. The `priority:` search DSL and filter state
stay functional at the data layer.
- Suppressed active-filter priority pills for consistency.
- Gated the "Tasks by Priority" dashboard chart and the design-guide
priority showcase subsection.
- Left activity-feed "changed priority" history text intact as a
historical record.
- Updated call-site tests to assert priority UI is absent while the flag
is off, added focused hidden-surface tests, and added a test that proves
creating a task still persists `priority: "medium"`.

## Verification

- `pnpm check:token-gates` — all 3 gates clean.
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `pnpm --filter @paperclipai/ui exec vitest run` on the touched
surfaces (IssueProperties, IssueFiltersPopover, IssuesList,
NewIssueDialog, IssueDetail, PriorityIcon and its interaction test) —
all green under `TZ=UTC`.
- Manual: with the flag off, priority does not appear in the detail
header, Triage panel, New Task composer, Sort/Group/Filter menus, or the
dashboard chart. Creating a task still persists `priority: "medium"`,
and the `priority:` search token still filters at the data layer.

## Risks

Low risk. The change is presentation-only and additive: no data model,
API, validation, or search-DSL changes. The priority code paths remain
compiled and tested; flipping `SHOW_TASK_PRIORITY_UI` to `true` restores
the full UI. Visual snapshot baselines are intentionally not updated per
the `doc/design/DECISION-SHEET.md` entry "Per-change snapshot
verification demoted to dormant (Jul 13 2026)".

## Model Used

Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, with tool use / code execution in an agentic coding
harness.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 16:19:22 -07:00
Dotta f258b34bbd
fix(ui): unify cloud-managed sign-out (#10994)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip can run as a self-hosted app or as a Cloud-managed tenant.
> - These modes need different sign-out sequences because Cloud owns
three sessions.
> - Several visible controls implemented sign-out separately and could
choose different paths.
> - This pull request adds one Cloud-aware sign-out action and moves
every visible control to it.
> - The benefit is one safe sign-out path in Cloud and unchanged local
sign-out in self-hosted deployments.

## Linked Issues or Issue Description

Refs #2073. That older PR adds a separate company-settings sign-out
surface. This change centralizes the existing account, company, and
instance-settings surfaces and preserves the self-hosted behavior
described there.

**What happened?**

Visible sign-out controls used separate implementations. A Cloud-managed
control could call the app-local sign-out endpoint and open the local
auth page. That path did not enter the Cloud-owned logout sequence for
the tenant, Cloud, and identity sessions.

**Expected behavior**

Every visible sign-out control must use one action. Cloud-managed
instances must navigate the top-level window to the same-origin
`/cloud/logout` route without a local sign-out call first. Authenticated
self-hosted instances must keep the local sign-out API and cache
invalidation behavior.

**Steps to reproduce**

1. Open a Cloud-managed tenant.
2. Use the account menu, company menu, or instance-settings sign-out
control.
3. Observe that independently implemented controls can enter different
sign-out paths.

**Paperclip version or commit**

The problem reproduces at `656ecfa585b31938e2685ffab3db22e794474803`.

**Deployment mode**

Cloud-managed tenant built from source. The regression tests also cover
authenticated self-hosted mode.

## What Changed

- Added `useSignOut` as the shared Cloud-aware sign-out action.
- Navigated Cloud-managed sessions to `/cloud/logout` exactly once
without calling local auth first.
- Preserved local API sign-out and cache invalidation for authenticated
self-hosted sessions.
- Migrated the account menu, company menu, and instance general settings
to the shared action.
- Added focused tests for mode selection, menu closure, pending state,
failure state, and settings behavior.

## Verification

- `pnpm exec vitest run ui/src/hooks/useSignOut.test.tsx
ui/src/components/SidebarAccountMenu.test.tsx
ui/src/components/SidebarCompanyMenu.test.tsx
ui/src/pages/InstanceGeneralSettings.test.tsx` — 26 tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — passed.
- `git diff --check origin/master..HEAD` — passed.

## Risks

- Low risk. The change centralizes existing behavior and adds no schema,
API, telemetry, or style-token changes.
- Cloud mode depends on the existing health decision. Tests pin both
mode branches.
- The change does not alter Fetch Metadata, CSRF, cookie, prefetch, or
return-URL protections owned by the Cloud logout route.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, GPT-5. The runtime does not expose a more specific model
ID or context-window size. The agent used high-reasoning mode, shell
tools, and API tools.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-06 12:13:14 -05:00
Dotta 656ecfa585
fix(server): keep Date fields intact through secret redaction; harden chat notice timestamps (#10984)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The task chat thread renders issue comments, system notices, and run
transcripts
> - The server routes comment payloads through the run-secret redaction
walker before it sends them
> - The walker rebuilds each object with `Object.entries`, and this
collapses `Date` instances to `{}`
> - The chat renderer then calls `.toISOString()` on an invalid date and
throws, and the thread falls back to the error banner
> - This pull request keeps `Date` instances intact in redacted
responses and makes the renderer safe against bad timestamps
> - The benefit is that task threads with system notices render
correctly again

## Linked Issues or Issue Description

**What happened**

Task threads that contain a system notice showed the banner "Chat
renderer hit an internal state error." in place of the conversation.
This occurred on many tasks.

**Expected behavior**

The thread renders all comments and system notices with correct
timestamps.

**Steps to reproduce**

1. Open a task that has at least one system notice comment (for example
a "Workspace ready" notice).
2. `GET /api/issues/{id}/comments` returns `createdAt: {}` for every
comment because the secret-redaction walker collapses `Date` objects.
3. The system-notice row calls `new Date({}).toISOString()`. This throws
`RangeError: Invalid time value` and trips the thread error boundary.

**Version / deployment**

Regression from #9934 (`e43f187ca`). It applies to all deployments that
include that commit.

## What Changed

- `server/src/services/run-secret-redaction.ts`:
`redactRegisteredSecretValues` now returns `Date` instances as-is. Dates
hold no redactable text, and the `Object.entries` rebuild turned them
into `{}`.
- `ui/src/components/IssueChatThread.tsx`: the system-notice row formats
its timestamp with a new `toValidIsoString` helper. A value that does
not parse as a date now degrades to "no timestamp" instead of a render
crash.
- Regression tests at three layers:
- Walker unit tests: `Date` values survive with and without registered
secret values.
- Route test: `GET /issues/:id/comments` serializes `createdAt` /
`updatedAt` as ISO strings.
- Render test: a system notice with a malformed `createdAt` renders
without the error boundary.

## Verification

- `npx vitest run --root server
src/__tests__/run-secret-redaction.test.ts` — 5 passed.
- `npx vitest run --root server
src/__tests__/issue-comment-redaction.test.ts` — 4 passed (embedded
Postgres route test).
- `cd ui && npx vitest run src/components/IssueChatThread.test.tsx
src/components/IssueChatThreadSystemNotice.test.tsx
src/lib/issue-chat-messages.test.ts` — 121 passed.
- Each new test was run against the unfixed code and failed there, which
confirms it guards the regression.
- A local sweep rendered 47 real issue threads through
`IssueChatThread`: 7 tripped the boundary before the fix, 0 after.

## Risks

- Low risk. The server change only preserves `Date` objects that the
walker destroyed before. String redaction behavior does not change, and
the registry-key stripping does not change.
- The UI change only affects the timestamp of system-notice rows and
omits it when the value is invalid.

## Model Used

- Claude Fable 5 (`claude-fable-5`), Anthropic. Agentic coding session
with tool use (file edits, shell, Vitest). No extended-context or
special reasoning mode.

## Checklist

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

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-06 10:29:07 -05:00
scotttong 2ea22d6cea
fix(ui): white text on light-mode user chat bubbles (#10952)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task detail view shows the conversation as chat bubbles. The
requester's own messages sit in a solid accent-colored bubble.
> - The bubble container sets `text-white`, but the message body renders
through `MarkdownBody`. Tailwind prose tokens (`--tw-prose-body`) win
over the inherited container color.
> - `prose-invert` only lightens the prose text in dark mode. In light
mode the prose body stayed its default dark color, so the text read as
near-black on the blue bubble and was hard to read.
> - This pull request maps the human bubble's prose tokens to the
inherited text color in both themes.
> - The benefit is that the requester's chat text is readable
white-on-blue in light mode, and dark mode stays exactly as it was.

## Linked Issues or Issue Description

<!-- No public GitHub issue exists; described in-PR per the bug report
template. -->

**What happened?**
In light mode, the text inside the user's own chat bubbles in the task
detail view rendered as dark (near-black) on the solid blue accent
background. This made the requester's messages hard to read.

**Expected behavior**
The text inside the user's accent-colored chat bubbles should be white
in light mode, matching the bubble's `text-white` intent. Dark mode
already rendered correctly and should not change.

**Steps to reproduce**
1. Open a chat-style task detail view in light mode.
2. Post a message as the requester (human) so it renders in the solid
blue accent bubble.
3. Observe the body text renders dark on blue instead of white.

**Paperclip version or commit**
Reproduces on `master` (branched from `814cb3367`).

**Agent adapter(s) involved**
Not adapter-specific (core UI bug).

## What Changed

- Add the existing `paperclip-markdown-on-accent` class to the
human-branch `MarkdownBody` in `TaskChatBubble.tsx`. This class (already
used by `IssueChatThread` for the same accent bubble) maps prose
body/heading tokens to `currentColor`, so the text follows the bubble's
`text-white` in both themes.
- Apply the same class to the human-branch `MarkdownBody` in
`TaskChatDescriptionBubble.tsx` (the description-as-first-bubble
surface) for consistency.
- Add unit tests covering that the human accent bubble carries the
on-accent class and the agent/neutral bubbles do not.

## Verification

- `pnpm check:token-gates` → 3/3 CLEAN.
- `pnpm --filter ./ui vitest run
src/components/task-chat/TaskChatBubble.test.tsx` → 9/9 passing.
- Manual: in light mode, the requester's chat bubble text renders white
on blue; agent/neutral bubbles unchanged; dark mode unchanged.

This is a visual change. Snapshot baselines are intentionally not
updated, per `doc/design/DECISION-SHEET.md` → "Per-change snapshot
verification demoted to dormant (Jul 13 2026)".

## Risks

Low risk. The change is scoped to the human-branch `MarkdownBody`
className on two chat-bubble components and only remaps prose color
tokens to the inherited text color. Agent and neutral bubbles are
untouched, and dark mode behavior is unchanged.

## Model Used

Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, ~200K context
window, extended thinking mode, with tool use / code execution.

## Checklist

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

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 01:08:04 -07:00
Dotta f554d67377
fix(server): add explicit review verdict policies (#10931)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Issues use `in_review` to request a final decision from an
authorized writer
> - The server rejected an assignee agent that tried to close its own
review, even when the issue had no independent-review rule
> - This rejection stopped the default agent workflow and did not
represent the configured execution-stage rules
> - Paperclip needs an open default and explicit issue-level constraints
for teams that require an independent or human verdict
> - This pull request removes the unconditional rejection and adds
`anyone`, `not_creator`, and `human_only` review policies
> - The benefit is a working default path with opt-in, authenticated
verdict controls

## Linked Issues or Issue Description

Refs #10635, #4429, and #10671.

The related public work covers execution-stage independence,
self-approval fallback behavior, and durable review paths. This change
is distinct. It controls who can resolve an issue review verdict. It
keeps configured execution stages active.

## What Changed

- Added a nullable `review_policy` issue column. Null has the same
meaning as `anyone`. The migration does not backfill existing issues.
- Added shared create, update, response, and compact issue contracts for
`anyone`, `not_creator`, and `human_only`.
- Removed the unconditional agent self-approval rejection for
`in_review` issues.
- Added one reusable verdict-actor check for terminal status changes and
pending interaction accept or reject actions.
- Used the authenticated principal type for `human_only`. Agent keys and
run tokens remain agent principals.
- Used the latest transition into `in_review` to identify the requester
for `not_creator`.
- Added actionable 403 responses that name the policy, the allowed
actor, and the next step.
- Kept the configured execution-stage transition and signoff behavior.
- Added focused contract, helper, status-route, interaction-route, and
execution-stage regression tests.
- Updated the implementation specification for the new issue field.

## Verification

- `pnpm exec vitest run packages/shared/src/validators/issue.test.ts
server/src/__tests__/issue-review-policy.test.ts
server/src/__tests__/issue-stalled-review-decision-routes.test.ts
--reporter=dot` passed: 42 tests.
- `pnpm --filter @paperclipai/shared typecheck` passed.
- `pnpm --filter @paperclipai/db typecheck` passed, including migration
numbering and safety checks.
- `pnpm --filter @paperclipai/server typecheck` passed.
- `pnpm run typecheck:build-gaps` passed across server, CLI, plugin
SDK/examples, plugin wiki, and UI.
- `git diff --check origin/master...HEAD` passed.
- SecurityEngineer review approved the authenticated-principal checks
and accepted policy-relaxation tradeoff with no required changes.
- Greptile reviewed the latest head at 5/5 with zero inline comments or
follow-ups.
- The latest-head GitHub rollup passed build, typecheck,
server/workspace tests, serialized suites, canary, e2e, and external
security checks.

## Risks

- The migration adds one nullable text column. It has no default and no
backfill.
- `not_creator` reads the latest recorded transition into `in_review`.
It denies the verdict when it cannot identify the requester.
- Agents can change or relax `reviewPolicy` when they have issue write
access. This is intentional for this issue-level control.
- Null and `anyone` do not add a database query to the verdict path.
- Configured execution-stage checks still run after the issue-level
policy check.

> This work aligns with the completed "Agent Reviews and Approvals" and
"Enforced Outcomes" roadmap items. It does not add a new roadmap
capability.

## Model Used

- OpenAI Codex, GPT-5. The exact deployment ID and context-window size
are not exposed to the agent. The run used reasoning, repository tools,
code execution, and GitHub CLI access.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-05 23:12:41 -05:00
Dotta 5b62a3883f
feat(settings): add experimental Simplified English Interactions flag (#10934)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents ask humans for decisions through interaction blocks: plan
confirmations, structured questions, suggested tasks, and checkbox
prompts
> - Each agent writes these decision prompts in its own style, so
operators can get long or unclear text at the exact moment they must
decide
> - There is no instance-level control that makes agents use a
controlled language for these decision points only
> - This pull request adds an experimental setting that tells agents to
write all user-interaction content in ASD-STE100 Simplified Technical
English, with the context the user needs and the effect of each choice
> - The benefit is faster, clearer human decisions, with agent thinking
and normal responses unchanged

## Linked Issues or Issue Description

Refs #10410 (optional `/simplified-english` skill in the skills catalog;
this PR adds the instance-level toggle for interactions).

**Problem or motivation**

Agent-posted user interactions (plan confirmations, structured
questions, suggested-task proposals, checkbox prompts) are written in
each agent's default style. Operators who want fast, unambiguous
decisions have no way to ask agents to use a controlled language for
exactly those decision points.

**Proposed solution**

Add an experimental instance setting,
`enableSimplifiedEnglishInteractions` ("Simplified English
Interactions"). When it is on, the server sets
`simplifiedEnglishInteractions: true` in the heartbeat wake payload. The
shared wake-prompt renderer, used by every adapter, then emits a
directive: write all user-interaction content in ASD-STE100 Simplified
Technical English, state what information the user needs to decide, and
state what happens for each choice. The directive applies to interaction
content only. Thinking, comments, documents, and other responses keep
their usual style.

**Alternatives considered**

Per-agent instructions work today, but someone must maintain them on
every agent. An instance-level toggle applies uniformly and turns off in
one place. Server-side rewriting of interaction payloads was rejected:
post-hoc translation is lossy and cannot add the decision context that
only the agent has.

**Roadmap alignment**

Extends the experimental settings surface with another opt-in
agent-behavior refinement, consistent with existing prompt-side flags.

## What Changed

- Added `enableSimplifiedEnglishInteractions` to the experimental
instance-settings zod schema, mirror type, and feature catalog (default
off, tier preference) in `packages/shared`.
- Server `instance-settings.ts` normalizes the flag on both read
branches; `heartbeat.ts` reads it once and passes
`simplifiedEnglishInteractions` into `buildPaperclipWakePayload`.
- Shared adapter renderer
(`packages/adapter-utils/src/server-utils.ts`): added the field to
`PaperclipWakePayload`, normalization, and an `- interaction language
(experimental): ...` directive emitted in both fresh and resume prompt
lanes, so one injection point covers all adapters.
- UI: new experimental settings card "Simplified English Interactions"
in `ui/src/pages/InstanceExperimentalSettings.tsx`, in alphabetical card
order; fixtures updated.
- Tests: renderer coverage for flag on/off in both lanes, plus
schema/catalog/UI fixture updates.

## Verification

- From the repo root: `node_modules/.bin/vitest run
packages/adapter-utils` (88/88), `packages/shared` validators (25/25),
server instance-settings + heartbeat suites (47/47 and 70/70 consumer
tests), `ui` settings tests (32/32).
- Typecheck is clean in all four touched packages.
- Manual check: turn the flag on in Settings → Experimental, wake an
agent, and confirm the wake prompt contains the interaction-language
directive; turn it off and confirm the directive is absent.

## Risks

- Low risk: the flag defaults to off, and the only behavior change is
one extra directive line in the wake prompt when an operator turns it
on.
- The directive is advisory to the agent; models can still deviate from
STE. No data or API shape changes; no migration.

## Model Used

- Claude Fable 5 (Anthropic, model ID `claude-fable-5`), extended
thinking, agentic tool use via Claude Agent SDK.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-05 21:50:41 -05:00
Dotta e43f187cad
feat(secrets): add human-approved secret proposals (#9934)
## Thinking Path

> - Paperclip is the control plane people use to manage AI-agent
companies.
> - Agents can encounter credentials during work.
> - Directly creating live secrets or bindings would bypass human
governance.
> - Proposal records must remain inert and separate from live secret
resolution until an authorized human approves them.
> - Approval must reuse the existing secret-create and protected
agent-config write paths.
> - This pull request adds the propose, review, approve, and reject
lifecycle.
> - The benefit is that agents can safely hand credentials into
Paperclip without exposing plaintext or gaining authority to activate
them.

## Linked Issues or Issue Description

Follow-on to #9921, which established run-bound agent secret access.

**Problem / motivation:**

Agents can receive credentials during work. There is no governed way for
them to propose a credential or binding without exposing plaintext in
work artifacts or immediately creating live access.

**Proposed solution:**

Store agent-authored proposals outside live secret tables. Encrypt each
proposed value and register exact-value redaction when Paperclip
receives it. Require an authorized human to approve or reject each
proposal. Approval executes through the normal write paths as the human
approver. Binding proposals can target only the proposer or its downward
reporting chain under the restrictive V1 policy.

**Alternatives considered:**

We rejected live secrets with a `proposed` status. That design would put
untrusted rows in resolver, list, and sync paths. It would also allow
uniqueness squatting. We rejected direct agent binding writes because a
binding is an agent-config write and must keep the existing human
permission gate.

**Roadmap alignment:**

This change extends the run-bound agent secret-access foundation in
#9921 with a governed proposal workflow.

## Security Verdict

Q0 SecEng verdict: **PASS-with-required-changes**. The review accepted
the separate proposal-table design and required the implementation to:

- fail closed unless both encryption and exact-value run redaction
registration succeed;
- scrub ciphertext idempotently on reject, withdraw, and expiry, with
audit-visible state;
- treat agent justification as hostile input and foreground action,
target, provenance, and approver permissions;
- snapshot and re-check the target agent plus reports-to chain at
approval to prevent org-chart laundering;
- make cascade approval atomic and fail closed if either secret creation
or binding authorization fails;
- deny low-trust, `skill_test`, `task_bridge`, and non-run-bound sources
consistently; and
- execute approval through the normal human secret/config write paths,
including protected-change gates.

Those requirements are implemented and covered by focused service,
route, and UI tests. Residual V1 risk remains the accepted 14-day
encrypted retention window. Proposal-time redaction also cannot clean a
value that leaked before the propose call.

## What Changed

- Added `company_secret_proposals`, migration `0207`, shared proposal
contracts, and a state-machine service for create, approve, reject,
withdraw, cascade, expiry, and ciphertext scrubbing.
- Added run-bound agent proposal routes and board review routes. The
routes derive provenance from authentication and enforce source
restrictions, company isolation, chain-of-command checks,
approval-as-approver, wake-on-resolution, and dual audit trails.
- Added durable per-run exact-value redaction registration so proposal
values remain redacted on later read surfaces.
- Added the Secrets **Proposals** tab and agent configuration **Proposed
access** rows. The UI shows fingerprint and length only. It also frames
agent justification as untrusted input, runs permission preflight,
supports approve and reject actions, and confirms cascades.
- Updated OpenAPI, agent skill guidance, API reference documentation,
and focused server and UI regression coverage.
- Rebased the branch onto current `master` and renumbered the proposal
migration after `0206`.

## QA Acceptance Results

Q5 QA verdict: **PASS — 9/9 acceptance criteria met**, with one Minor
non-blocking follow-up.

- **AC1:** proposed values never echo, never appear in live
lists/resolvers, and expose only fingerprint + length to board
reviewers.
- **AC2:** restrictive `self_and_reports` matrix passes: self/downward
allowed; upward/lateral denied.
- **AC3:** secret approval uses the normal create path, honors rename
overrides, records proposer/approver provenance, and scrubs ciphertext.
- **AC4:** approved bindings materialize and resolve through the target
agent's runtime list/fetch routes.
- **AC5:** pending-secret bindings require cascade; cascade succeeds
atomically and permission failures leave nothing applied.
- **AC6:** reject, withdraw, dependent rejection, and expiry paths scrub
ciphertext and preserve reasons/audit state.
- **AC7:** token/source and approver denial matrix passes through live
checks plus focused route tests.
- **AC8:** proposal lifecycle events and reused
`secret.created`/config-write events form the required dual audit trail;
origin-issue notification and wake are queued.
- **AC9:** both review surfaces render and execute correctly; UI
approval materializes the binding.

QA also confirmed zero plaintext occurrences for all exercised proposal
values in server logs. The single finding is that the company-level
`bindingTargetPolicy` toggle is not wired yet. V1 is hardcoded to the
restrictive `self_and_reports` policy. The matrix is correct and the
follow-up is tracked separately, so QA classified it as non-blocking.

## Verification

- Focused server proposal and redaction suite: 83 tests pass.
- Focused proposal review UI suite: 54 tests pass.
- Embedded-Postgres migration reapply test: 1 test passes with the
documented 30-second timeout.
- `pnpm --filter @paperclipai/db typecheck` passes, including migration
numbering and safety checks.
- `pnpm --filter @paperclipai/shared typecheck` passes.
- `pnpm --filter @paperclipai/ui typecheck` passes.
- `pnpm check:token-gates` passes with all gates clean.
- Q5 exercised the complete propose, review, approve, bind, and
runtime-resolve flow over real HTTP, JWT, and database paths. It
verified 9/9 acceptance criteria.

## Risks

- Proposal ciphertext is retained encrypted for up to 14 days while
pending. Terminal-state and expiry scrub paths reduce but do not remove
server-compromise risk during that window.
- The V1 target policy is restrictive but not yet company-configurable.
A separate follow-up owns that change.
- A new migration can require another renumber if another migration
lands before maintainers merge this pull request.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex coding agent. The exact runtime model ID and
context-window size are not exposed. The agent used reasoning,
repository editing, terminal execution, Paperclip API, and GitHub CLI
capabilities. Q3 UI work also records Claude Opus 4.8 assistance in its
commit trailers.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 21:49:40 -05:00
scotttong f950952de7
fix: reliably show plans in the Plan pane and restore sticky plan confirmation CTAs (#10930)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Chat-style tasks show an agent's plan in a dedicated "Plan" pane,
and a plan confirmation lets the user accept or request changes to that
plan
> - When an agent asked for confirmation but never actually published
the plan document (it only wrote the plan in a comment or a question),
the Plan pane rendered empty, and the confirmation call-to-action that
used to sit pinned at the bottom of the pane had disappeared
> - A user asked to confirm a plan they cannot see, with no visible CTA,
is stuck — the feature silently fails
> - This pull request closes the gap on both sides: it prevents plan
confirmations that don't point at a real, latest plan revision, it
teaches agents to publish the plan document before confirming, and it
restores the sticky confirmation action bar and an explanatory empty
state so the pane never goes silently blank
> - The benefit is that when a plan is expected, it reliably shows up in
the right pane with reachable accept/revise actions

## Linked Issues or Issue Description

<!-- No public GitHub issue exists; describing in-PR per the bug
template. -->

**Bug report**

- **What happened:** A task in planning mode could present a plan
confirmation while the Plan pane stayed empty (no plan document
rendered), and the plan-card confirmation CTAs that were previously
pinned to the bottom of the Plan pane no longer appeared.
- **Expected behavior:** When a plan is expected, the plan document
appears in the Plan pane; when a plan is genuinely missing, the pane
explains why rather than showing nothing; and the accept/request-changes
CTAs stay visible and reachable while the plan scrolls.
- **Steps to reproduce:** Put a task in planning mode with the
chat-style task view enabled, have an agent create a plan confirmation
without first publishing the `plan` document, and open the Plan tab —
the pane is blank and the confirmation actions are missing.
- **Deployment mode:** Local dev and self-hosted; UI + server.

Related PR (not a duplicate): #9609 "Pin pending confirmations by
composer" pins confirmations in a different surface (the composer); this
PR restores the Plans-pane action bar and the server/agent guarantees
behind it.

## What Changed

- **Server:** Reject a `request_confirmation` whose target is a plan
document unless a plan document exists and the target points at its
*latest* revision, so a confirmation can never reference a plan the pane
cannot render (`readPlanTarget` is now exported for reuse).
- **Agent instructions:** The CEO and default agent instruction bundles
now spell out a plan-publish contract — publish the `plan` document,
re-`GET` it and capture `latestRevisionId`, then create the confirmation
targeting that revision; never present a plan only in a thread comment
or via `ask_user_questions`.
- **UI — sticky CTAs:** Restore the plan confirmation action bar pinned
to the bottom of the Plans tab so accept/revise stay reachable while the
plan scrolls.
- **UI — diagnostics:** Keep the Plan tab visible whenever an issue is
in planning mode (even before a plan document exists) and show an empty
state explaining why the pane is empty instead of rendering nothing.
- **UI — annotations:** Add a `panelPlacement="inline"` mode so the
plan-document annotation panel renders in document flow instead of as a
floating side panel when hosted in the narrow task properties pane.

## Verification

- `pnpm check:token-gates` → 3/3 CLEAN
- `pnpm typecheck` → clean (all packages)
- UI: `pnpm --filter @paperclipai/ui exec vitest run
src/components/issue-properties/IssuePlanConfirmationActionBar.test.tsx
src/components/IssueProperties.test.tsx
src/components/IssueDocumentAnnotations.test.tsx` → 69 passed
- Server: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/issue-thread-interaction-routes.test.ts
src/__tests__/agent-skills-routes.test.ts` → 60 passed
- Manual: with a planning-mode task, the Plan tab stays visible, shows
the plan document (or a diagnostic empty state), and the confirmation
CTAs stay pinned at the bottom.

Visual note: snapshot baselines are intentionally not updated — per
`doc/design/DECISION-SHEET.md` "Per-change snapshot verification demoted
to dormant (Jul 13 2026)". The `storybook-visual` label is intentionally
not added.

## Risks

Low-to-moderate. The server change adds a validation gate on
plan-document confirmations: an interaction that targets a stale or
nonexistent plan revision is now rejected with a 422 instead of being
created. This is the intended guarantee, but any caller that relied on
creating such confirmations will now need to publish the plan document
first (which the updated agent instructions cover). UI changes are
additive to the Plans tab and gated by the existing chat-style-task
experimental flag.

## Model Used

Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, with tool use (file editing, shell, test execution).

## Checklist

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 19:32:07 -07:00
scotttong dc71fef6bf
feat(ui): show task identifier in task-detail breadcrumb header (#10933)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task detail page shows a breadcrumb header with the task status
glyph and the task title.
> - The breadcrumb did not show the task identifier, so a reader could
not name the task without opening extra context.
> - Agents and people refer to tasks by identifier, so the identifier
belongs next to the title.
> - This pull request renders the task identifier in the breadcrumb
header, between the status glyph and the title.
> - The benefit is faster reference: a reader sees the task key and the
title together at the top of the page.

## Linked Issues or Issue Description

**What existing behavior does this improve?**
The task detail breadcrumb header. It shows the status glyph and the
task title, but not the task identifier.

**Subsystem affected**
Web UI — the breadcrumb bar on the task detail page
(`ui/src/components/BreadcrumbBar.tsx`,
`ui/src/context/BreadcrumbContext.tsx`, `ui/src/pages/IssueDetail.tsx`).

**Current behavior**
The breadcrumb header renders the status glyph and then the task title.
The task identifier does not appear in the header.

**Proposed behavior**
The breadcrumb header renders the task identifier between the status
glyph and the title. The identifier uses gray monospace styling from
design tokens (`font-mono text-muted-foreground`).

**Reason and benefit**
A reader can name and reference the task from the header without opening
more context. The identifier and the title appear together.

**Breaking changes**
None. The identifier field is optional. Crumbs without an identifier
render as before.

## What Changed

- Add an optional `identifier` field to the `Breadcrumb` type and
include it in the `breadcrumbsEqual` comparison so an identifier change
triggers a fresh render.
- Add a `CrumbIdentifier` helper in `BreadcrumbBar` that renders the
identifier in gray monospace (`font-mono text-muted-foreground`), placed
after the leading status glyph in each crumb variant.
- Wire the issue identifier onto the task crumb in `IssueDetail`.
- Add unit tests that cover the identifier field in `breadcrumbsEqual`
(fresh render on change, no-op on identical value).

## Verification

- `pnpm check:token-gates` → 3/3 gates CLEAN (color literals, arbitrary
bracket values, raw font-size).
- `pnpm --filter @paperclipai/ui exec vitest run
src/context/BreadcrumbContext.test.tsx` → 4/4 tests pass.
- `pnpm typecheck` → the four changed files typecheck clean.
- Manual: open a task detail page. The breadcrumb header shows the
status glyph, then the task identifier in gray monospace, then the
title.

Visual change. Snapshot baselines are intentionally not updated, per
`doc/design/DECISION-SHEET.md` → "Per-change snapshot verification
demoted to dormant (Jul 13 2026)".

## Risks

Low risk. The change is additive and the identifier field is optional.
It touches only the breadcrumb header rendering and the equality check.
No data model or API change.

## Model Used

Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, tool use enabled.

## Checklist

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 19:14:51 -07:00
Dotta ac3b2e1d7a
fix(ui): use cloud logout for managed sign-out (#10937)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip supports self-hosted and Cloud-managed authenticated
deployments
> - A Cloud-managed tenant uses the Cloud harness to own the full
browser session
> - The account menu treated every authenticated deployment as Cloud and
called the local sign-out API first
> - This pull request uses the existing Cloud health metadata as the
mode gate
> - Cloud-managed sign-out now starts the harness logout round trip with
a top-level navigation
> - Self-hosted authenticated sign-out keeps the existing local API flow
> - The benefit is a complete Cloud logout without changing self-hosted
behavior

## Linked Issues or Issue Description

Related prior work: Refs #10802.

**What happened?**

The account menu called the app-local sign-out endpoint before it moved
an authenticated browser to the Cloud logout route. It also used
authenticated deployment mode as the Cloud test. This test included
self-hosted authenticated instances.

**Expected behavior**

A Cloud-managed tenant must navigate the top-level browser directly to
`/cloud/logout`. A self-hosted authenticated instance must keep the
app-local sign-out flow.

**Steps to reproduce**

1. Open a Cloud-managed tenant.
2. Open the account menu.
3. Select **Sign out**.
4. Observe that the browser returns through the tenant auth route
instead of completing the Cloud logout round trip.

**Paperclip version or commit**

Reproduced on `master` after `76f442040c`.

**Deployment mode**

Paperclip Cloud-managed authenticated deployment.

## What Changed

- Read the existing Cloud instance metadata in the account menu.
- Navigate directly to `/cloud/logout` for Cloud-managed instances
without calling the local sign-out API.
- Keep the local sign-out API and cache refresh for self-hosted
authenticated instances.
- Add regression coverage for both sides of the mode gate.

## Verification

- `pnpm exec vitest run ui/src/components/SidebarAccountMenu.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`

## Risks

- Low risk. The change is limited to the account-menu action.
- The Cloud branch depends on the existing `health.cloud` metadata that
already gates other Cloud UI behavior.
- The self-hosted regression test verifies that authenticated mode alone
does not select the Cloud route.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex based on GPT-5. The runtime provided agentic reasoning,
repository tools, shell execution, and test execution. The exact
internal model ID and context window are not exposed to the agent.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-05 21:14:11 -05:00
Nicky Leach f5e9ca3e89
fix(adapters): keep user-scoped env bindings on the agent Test action (#10926)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The agent Test action builds adapter config from the form.
> - The build-config parser kept plain and secret_ref bindings.
> - It dropped user_secret_ref bindings on the create path.
> - This PR shares one parser that keeps every binding shape.
> - The test path now sends the same env binding set that a real run
sees.
> - The benefit is one fix across every adapter build-config path.

## Linked Issues or Issue Description

**What happened?**
The agent Test action dropped a user-scoped env binding in create mode.
The same agent config worked in a real run. Related public PRs: #10115,
#9321, #9921, #8825.

**Expected behavior**
The Test action should keep user-scoped env bindings and resolve them
like a real run.

**Steps to reproduce**
1. Set a user-scoped env binding on an agent config form.
2. Run Test in create mode.
3. The probe runs without the variable.

**Paperclip version or commit**
c09d2509e3

**Deployment mode**
Local dev (pnpm dev)

**Agent adapter(s) involved**
Not adapter-specific (core bug)

**Database mode**
Embedded PGlite (default — DATABASE_URL unset)

**Additional context**
This change is not Claude-specific.

## What Changed

- Added a shared env binding parser in `@paperclipai/adapter-utils`.
- Replaced the eight adapter build-config copies with the shared helper.
- Kept `plain`, `secret_ref`, and `user_secret_ref` bindings intact in
create mode and edit mode.
- Preserved the runtime merge behavior from the earlier env merge
change.

## Verification

- Author-recorded test run:
`packages/adapter-utils/src/env-bindings.test.ts`
- Author-recorded test run:
`packages/adapters/claude-local/src/ui/build-config.test.ts`
- Author-recorded test run: six adapter build-config test files
- Author-recorded typecheck: `tsc --noEmit` for adapter-utils and the
eight adapter packages
- GitHub checks: all required PR checks pass on PR #10926.
- Greptile review: 5/5 with no open comments.

## Risks

- The change touches adapter config assembly.
- A wrong binding shape would change test-time probe input.
- Tests cover the binding types and the create-mode path.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI GPT-5, code execution and repo inspection.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-05 17:18:41 -07:00
scotttong b1b7a9dff6
feat(settings): alphabetize experimental cards and drop the Experimental chip (#10924)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Instance Settings area exposes an Experimental page that lists
opt-in feature toggles as cards.
> - New experimental features were appended to the list over time, so
the cards sat in insertion order with no predictable arrangement.
> - An unordered list is hard to scan when you are looking for one
specific feature.
> - Each card also carried a small "Experimental" secondary badge, which
is redundant on a page that is itself titled Experimental.
> - This pull request sorts every card alphabetically by its title and
removes that redundant badge.
> - The benefit is a list that is faster to scan and headings that are
less cluttered.

## Linked Issues or Issue Description

No public GitHub issue exists for this change, so the enhancement is
described inline following `.github/ISSUE_TEMPLATE/enhancement.yml`:

**What existing behavior does this improve?**
The Instance Settings → Experimental page, which lists opt-in feature
toggles as a stack of cards.

**Subsystem affected**
UI — the Instance Experimental settings page
(`ui/src/pages/InstanceExperimentalSettings.tsx`).

**Current behavior**
Cards render in insertion order (the order features happened to be
added), so finding a specific feature means scanning the whole list.
Several headings also carry a redundant "Experimental" secondary badge.

**Proposed behavior**
Cards render top-to-bottom in A→Z order by title, and no card shows an
"Experimental" secondary badge. Toggle logic, footnotes, conditional
visibility, and the "Managed by Paperclip Cloud" badge are unchanged.

**Reason and benefit**
Alphabetical order makes the list predictable and quick to scan for a
specific feature. The "Experimental" badge repeats information already
conveyed by the page title, so removing it declutters the headings.

**Breaking changes**
None. This touches card render order and the removal of a decorative
badge only — no state, persistence, toggle, or visibility logic changes.

## What Changed

- Sorted every card on the Instance Experimental settings page
alphabetically by its heading title.
- Removed the redundant "Experimental" secondary badge from the card
headings (previously on Apps, Cases, and Chat-Style Tasks).
- Added tests asserting the cards render in case-insensitive
alphabetical order and that no card renders an "Experimental" secondary
badge.
- No behavior change: toggle handlers, footnotes, managed-key handling,
and conditional cards (Conference Room Chat, worktree-scoped run) are
untouched and now sort into their alphabetical slots.

## Verification

- `pnpm check:token-gates` → all 3 gates CLEAN.
- `npx vitest run ui/src/pages/InstanceExperimentalSettings.test.tsx` →
32/32 tests pass (the suite renders the real component and now covers
ordering + badge removal).
- `pnpm --filter @paperclipai/ui typecheck` (`tsc -b`) → clean.
- Manual: open Instance Settings → Experimental. The cards read A→Z and
no card shows an "Experimental" chip.

## Risks

Low risk. The change is limited to one page component: card render order
and the removal of a decorative badge, plus new tests. No state,
persistence, toggle, or visibility logic is modified.

## Model Used

Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, extended
thinking enabled, tool use enabled.

## Checklist

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 17:08:09 -07:00
Dotta 72b509c895
Recognize delivered workspaces and reap terminal worktrees (#10908)
## Thinking Path

> - Paperclip separates workspace provisioning lifecycle from whether
the work was actually delivered.
> - Git ancestry alone cannot recognize squash merges or deliveries into
a branch other than the workspace base.
> - A merged pull request linked from a terminal issue is stronger
delivery evidence for those cases.
> - The read contract should expose that evidence without changing
persisted workspace schema.
> - Cleanup must remain conservative: terminal descendants, delivered
work, and no active run checkout are all required.
> - Reusing the existing cleanup primitives keeps service shutdown,
lease cleanup, activity logging, and archival behavior consistent.
> - Focused regression coverage locks in both the honest read signal and
the fail-closed reaper guards.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Execution workspace close-readiness payloads and terminal workspace
cleanup.

**Current behavior**

Delivered squash-merged or cross-branch workspaces can remain `active`
and report a permanent “not merged” warning because git ancestry does
not contain their original commits.

**Proposed behavior**

Read payloads distinguish PR-confirmed delivery, ancestry delivery,
unmerged work, and unknown state. Fully terminal delivered workspace
trees are archived only when no active run holds the checkout.

**Reason and benefit**

Operators and automation receive an honest delivery signal, while
shipped worktrees stop looking active forever and genuinely unmerged
work retains its warning.

**Breaking changes**

The workspace payload gains a derived field. Existing fields and
persistence remain unchanged; no database migration is required.

**What happened?**

A delivered workspace can remain `active` and warn that it is not merged
forever after its issue ships through a squash or cross-branch pull
request.

**Expected behavior**

Pull-request delivery should be represented honestly, and a fully
terminal delivered workspace should become cleanup-eligible when no run
holds its checkout.

**Steps to reproduce**

1. Create an issue workspace with commits ahead of its configured base.
2. Deliver those commits with a squash merge or into a different target
branch.
3. Mark the source issue and descendants done, then read workspace close
readiness.

Before this change, the workspace remains active with a “not merged”
warning indefinitely.

## What Changed

- Added the derived `deliveryState` workspace contract: `merged_via_pr`,
`merged_by_ancestry`, `unmerged`, or `unknown`.
- Extracted a shared GitHub pull-request merge classifier and reused it
for merge confirmations and workspace delivery checks.
- Suppressed false ancestry warnings when a terminal issue has
ground-truth merged-PR evidence.
- Added an idempotent terminality reaper with descendant-terminal,
active-run, and delivered-work guards.
- Restricted PR delivery evidence to the source issue, then required
live merged state plus matching GitHub repository, head branch, and
current workspace HEAD; persisted status, stale PRs, lexical mentions,
inbound references, and descendant PRs cannot authorize cleanup.
- Preserved workspaces with modified or untracked files even when their
committed HEAD was delivered.
- Bounded both long-lived pull-request state caches to 1,000 entries
with oldest-entry eviction.
- Routed eligible workspaces through existing runtime shutdown, lease
cleanup, activity logging, and archival machinery with exclusive Git
index, HEAD, and branch-ref locks plus non-forced removal.
- Added regression coverage for delivery derivation, warning behavior,
reaper guards, scheduler wiring, and squash/cross-branch delivery.

## Verification

- `pnpm -r typecheck`
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/execution-workspaces-service.test.ts
src/__tests__/merged-pr-confirmation-sweep.test.ts
src/__tests__/server-startup-feedback-export.test.ts --reporter=verbose`
— 63 passed
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/execution-workspaces-service.test.ts --reporter=verbose`
after review hardening — 43 passed
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/execution-workspaces-service.test.ts
src/__tests__/merged-pr-confirmation-sweep.test.ts
src/__tests__/external-objects-service.test.ts --reporter=dot` on the
final local head — 73 passed
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-workspace-busy.test.ts --reporter=verbose` — 15
passed
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm test:run` —
server 3,662 passed (4 skipped), UI 3,599 passed, CLI 327 passed, shared
415 passed, and skills catalog 20 passed; the aggregate DB stage ran
both source and built copies of one unrelated embedded-Postgres
migration test and both reached its 5-second timeout
- `pnpm --filter @paperclipai/db exec vitest run
src/status-card-migrations.test.ts --reporter=verbose` — isolated
aggregate-timeout verification passed in 3.99 seconds
- `NODE_ENV=production pnpm build`
- `pnpm check:token-gates`

## Risks

The reaper intentionally fails closed when issue terminality,
pull-request state, git ancestry, or checkout ownership cannot be
proven. GitHub lookups can delay classification and cleanup but cannot
cause an unproven workspace to be archived. Automated terminal archival
holds exclusive Git index, HEAD, and branch-ref locks across validation
and removal, skips configured destructive hooks, and uses non-forced
removal so dirty writes fail closed. Reopening a source issue does not
restore an archived workspace; it emits an audit event so a human or
agent can re-provision explicitly.

## Model Used

OpenAI Codex, GPT-5. The runtime did not expose a more specific model ID
or context-window size. Reasoning, tool use, repository editing, test
execution, and GitHub CLI access were enabled.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-05 16:34:04 -05:00
Dotta c2b41bb7cd
fix(issues): quiet missing-disposition warnings while a live continuation is running (#10899)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - When an agent run ends without recording a disposition, Paperclip
raises a "missing disposition" handoff so the work does not silently
stall
> - The server already tracks whether such an issue has a live
continuation (a running or queued run, or a queued wake) in
`successfulRunHandoff.hasLiveContinuation`
> - But no UI surface read that flag, so an issue that an agent was
actively working on still showed the "This task still needs a next step"
banner, a loud thread warning, and "Needs next step" badges
> - This pull request makes every missing-disposition complaint respect
liveness: warn only when no live agent is on the issue and it is really
stuck
> - The benefit is that users see the warning only when action is
needed, and the noise disappears while an agent is already handling the
issue

## Linked Issues or Issue Description

No public GitHub issue exists for this bug. Description follows the
bug-report template:

**What happened?**

An issue that a live agent run was actively working on showed the
"missing disposition" warning banner, a loud thread notice, and "Needs
next step" badges at the same time. The API payload for that issue
showed `successfulRunHandoff.required: true` together with
`hasLiveContinuation: true` and a `liveRunId`, but the UI ignored the
liveness fields.

**Expected behavior**

The missing-disposition warning appears only when the issue has no live
run or queued wake. A live agent records a disposition when its run
ends. Paperclip complains only if the run ends and no disposition
exists.

**Steps to reproduce**

1. Let a run finish on an in-progress issue without a disposition.
Paperclip raises the handoff and queues a corrective wake.
2. Open the issue page while the corrective run (or any new run) is
live.
3. See the banner, the badges, and the loud thread notice — all visible
while the agent works.

**Paperclip version or commit**

Current `master` (reproduced at commit 6ffe9df842).

**Deployment mode**

Self-hosted development instance.

## What Changed

- `isSuccessfulRunHandoffRequired` (ui lib) returns `false` while a live
continuation exists. This quiets the Kanban card badge and the
issues-list badge. Exception: when the only continuation is a
not-yet-promoted scheduled retry, the notice stays visible so the
**Retry now** control stays reachable.
- `IssueBlockedNotice` also checks the real-time live-run set
(`liveIssueIds`). A run that starts after the issue payload was fetched
hides the banner at once.
- `IssueChatThread` derives an effective handoff state from the live
runs it already tracks. The loud "Missing issue disposition" thread
notice folds into the quiet collapsed row while a continuation is live,
and unfolds if the run ends without a disposition.
- Server: `hydrateSuccessfulRunHandoffLiveness` now hydrates escalated
handoffs too. The blocked-inbox `missing_disposition` attention is
suppressed for escalated handoffs with a live run or wake. This matches
the existing required-state suppression.

## Verification

- `cd ui && npx vitest run src/components/IssueBlockedNotice.test.tsx
src/components/IssueChatThreadSystemNotice.test.tsx
src/components/IssueChatThread.test.tsx` — 106 tests pass, including 6
new tests for the live/stale/scheduled-retry matrix
- `cd ui && npx vitest run src/components/IssuesList.test.tsx
src/components/KanbanBoard.test.tsx src/lib` — pass
- `cd server && npx vitest run
src/__tests__/issue-blocker-attention.test.ts
src/__tests__/issue-list-assignee-filter-routes.test.ts
src/services/recovery/successful-run-handoff.test.ts
src/__tests__/attention-service.test.ts` — pass, including new
escalated-liveness cases
- `pnpm typecheck` clean in `ui` and `server`; `node
scripts/check-token-gates.mjs` clean
- Manual check: a live issue's API payload showed `required: true` with
`hasLiveContinuation: true` and a `liveRunId` while the banner was still
on screen; with this change that state renders no complaint

## Risks

- Behavioral shift only; no schema or migration changes. All complaints
reappear as soon as the continuation stops without a disposition, so
nothing can get lost permanently.
- A queued wake counts as a live continuation. If a wake sits queued for
a long time, the warning stays hidden for that time. The blocked-inbox
path already behaved this way; the UI now matches it.
- The scheduled-retry carve-out keeps the current Retry-now workflow
intact.

## Model Used

- Claude Fable 5 (`claude-fable-5`), Anthropic — agentic coding session
with extended thinking and tool use (file edit, shell, test execution).

## Checklist

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

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 15:59:01 -05:00
Dotta 427509e6e0
fix(ui): show the synced company logo on the Cloud org switcher trigger (#10917)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - On Paperclip Cloud a tenant instance holds exactly one company, and
the cloud control plane pushes the stack's uploaded workspace icon into
that company's branding.
> - The cloud-mode organization switcher trigger always rendered the
deterministic monogram, so an uploaded organization logo never appeared
in the app chrome.
> - This pull request renders the trigger through the tenant company's
logo and brand color, with the monogram as the fallback.
> - The benefit is that the logo a customer uploads for their
organization actually shows up inside their Paperclip app.

## Linked Issues or Issue Description

No existing public issue found (searched open/closed PRs and issues for
"organization logo", "switcher logo", "company logo cloud" — closest
related PR is #10850, which introduced the cloud-mode switcher).
Describing in-PR:

**Subsystem affected**

Board UI: the sidebar organization switcher in Paperclip Cloud mode
(`SidebarCompanyMenu`).

**Problem or motivation**

A Cloud customer uploads an organization logo when creating their
workspace; the control plane syncs it into the tenant company's branding
(`company.logoUrl`). But the cloud branch of the switcher trigger
rendered `StackIcon` — monogram-only by design for stack rows — for the
trigger too, ignoring `selectedCompany.logoUrl`. Result: the uploaded
logo never appears in the app chrome; users see a letter tile instead.

**Proposed solution**

Add a `CurrentStackIcon` for the trigger that passes the selected
company's `logoUrl`/`brandColor` into `CompanyPatternIcon`, seeded by
the stack display name. Falls back to the exact previous monogram when
no logo is set. Stack rows are unchanged: the portfolio payload
deliberately carries no hot-linkable icon URL for other stacks.

**Alternatives considered**

Fetching per-stack icons for the rows was rejected: the cloud portfolio
payload carries no icon URLs (embedding signed, expiring control-plane
URLs would be wrong), and the defect is the current organization's
chrome, which the already-synced company logo covers.

## What Changed

- `ui/src/components/SidebarCompanyMenu.tsx`: cloud-mode trigger renders
the tenant company logo (fallback: monogram); stack rows untouched;
self-hosted path untouched.
- `ui/src/components/SidebarCompanyMenu.test.tsx`: new regression test
that the trigger carries the company logo while stack rows keep
monograms; the `CompanyPatternIcon` mock now exposes `logoUrl`.

## Verification

- `pnpm --dir ui exec vitest run
src/components/SidebarCompanyMenu.test.tsx`: 12/12 pass (11 existing + 1
new).
- `pnpm --dir ui exec tsc --noEmit`: clean.

## Risks

- Cloud-only rendering branch; self-hosted trigger rendering is
untouched.
- If the branding sync has not run yet, the trigger shows the same
monogram as before — no regression, and it upgrades in place once
`logoUrl` arrives.

## Model Used

- Claude Fable 5 (`claude-fable-5`), Anthropic. The run used extended
reasoning, repository tools, shell execution, and GitHub integration.

## Checklist

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

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 15:30:59 -05:00
Dotta 1fa36be353
fix(ui): use HTTP-safe clipboard copy everywhere (#10875)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators often open self-hosted Paperclip over plain HTTP on a LAN
or private network.
> - Browser Clipboard API writes are not reliable in that insecure
context.
> - Paperclip already has one shared helper with a legacy copy fallback,
but many current copy actions bypass it.
> - This pull request routes every core UI copy action and the
first-party workspace-diff plugin through the shared helper.
> - The benefit is consistent copy behavior on HTTPS, localhost, and
plain-HTTP private deployments.

## Linked Issues or Issue Description

Refs #3529.

This change supersedes the stale prior attempt in #3531. Current master
has more copy surfaces and a first-party plugin UI bridge that the prior
branch does not cover.

## What Changed

- Replaced direct Clipboard API writes and duplicate fallback
implementations across the current core UI with `copyTextToClipboard`.
- Added an HTTP-safe clipboard function to the plugin UI SDK and wired
the host bridge to the same implementation.
- Migrated the first-party workspace-diff plugin to the plugin SDK
clipboard function.
- Added unit coverage for native rejection fallback and plugin host
delegation.
- Added a source-level regression test that rejects new direct clipboard
writes outside the shared implementation.
- Documented the plugin UI clipboard function.

## Verification

- `NODE_ENV=test pnpm exec vitest run ...` for 14 affected suites: 164
tests passed.
- `pnpm exec vitest run tests/ui-clipboard.test.ts` in
`packages/plugins/sdk`: 1 test passed.
- `NODE_ENV=test pnpm -r typecheck`: passed for 31 workspace projects.
- `NODE_ENV=test pnpm test:run`: passed.
- `NODE_ENV=production pnpm build`: passed.
- `pnpm check:token-gates`: passed with all gates clean.

## Risks

Low risk. Secure contexts still use the modern Clipboard API. Plain HTTP
and rejected modern writes use the existing `execCommand("copy")`
fallback. That API is deprecated, but it is the compatibility path
required for insecure contexts. The change has no schema, API, or visual
design effect.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex, `gpt-5.6-sol`. The runtime did not expose a context-window
size. Reasoning, tool use, repository editing, test execution, and
GitHub CLI access were enabled.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-05 10:45:08 -05:00
Dotta 14d755824c
Remove decision and review summaries from issue headers (#10891)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators use issue pages to read task state and control task work
> - The issue header showed separate summaries for open decisions and
review paths
> - These summaries repeated state that belongs in the Decisions view
> - The extra sections added noise before the issue description and
thread
> - This pull request removes both header summaries and keeps decision
actions in the Decisions view
> - The benefit is a simpler issue header with one place for decision
work

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The issue detail header shows separate pending-decision and review-path
sections.

**Subsystem affected**

`ui/` — React and Vite board UI.

**Current behavior**

An issue header can show a decision strip and a larger review panel
before the issue content.

**Proposed behavior**

The issue header does not show either decision section. Operators
continue to manage decisions and stalled reviews in the Decisions view.

**Reason and benefit**

This removes duplicate decision state from the issue header and reduces
visual noise.

**Breaking changes**

The issue page no longer provides these summaries or shortcuts. Decision
data, review state, and the Decisions view do not change.

## What Changed

- Removed the pending-decision strip and review-path panel from the
issue detail header.
- Deleted the two unused header components and the panel-specific test.
- Kept stalled-review actions and their Storybook examples in the
Decisions queue.
- Added an issue-detail regression test that covers both removed
sections.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/pages/IssueDetail.test.tsx` (46 tests passed)
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
- `pnpm build-storybook`
- `git diff --check`

## Risks

- Low risk. This change removes two issue-header surfaces. It does not
change decision APIs or data.
- Users must open the Decisions view to find pending decisions and
stalled-review actions.

> This change does not duplicate planned core work in `ROADMAP.md`.
GitHub searches found no related open issue or pull request.

## Model Used

- OpenAI Codex, GPT-5. The exact deployment ID and context window are
not exposed. Tool use and code execution were enabled.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-05 10:23:16 -05:00
Dotta ef33c1d9ed
fix(decisions): retire completed-target decisions and link targets from the card (#10892)
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The decisions desk shows pending decisions that need an operator
response
> - A strict decision cannot apply its effects after its target task
changes
> - A decision still remained pending when every target task finished
after proposal
> - The card also linked only the origin task, even when the decision
acted on another task
> - This pull request expires those moot decisions and links their
target tasks
> - The benefit is an accurate queue and a clear path to the work that
each decision affects

## Linked Issues or Issue Description

Related PR: #10801 removes the issue-page decision strip, which makes
clear queue provenance more important.

**What happened?**

A strict decision stayed pending until its time-to-live limit after
every target task reached `done`. The decision card linked only the
origin task. The origin task is where the agent proposed the decision,
and it can differ from the task that the decision affects. An operator
could therefore open a finished task with no visible decision and no
explanation of the real target.

**Expected behavior**

Paperclip must expire a strict decision when all of its targets finish
after the decision is proposed. The card must show and link every target
task that differs from the origin task.

**Steps to reproduce**

1. Create a strict decision that targets an active task from a different
origin task.
2. Move the target task to `done` without resolving the decision.
3. Run the decision expiry sweep.
4. Observe that the old code keeps the decision open until its
time-to-live limit.
5. Observe that the old card links only the origin task.

**Paperclip version or commit**

The bug reproduces on upstream `master` before this pull request.

**Deployment mode**

Local dev and self-hosted server modes are affected because the behavior
is in the shared decision service and board UI.

## What Changed

- Expire an open strict decision with reason `target_completed` when
every strict target reached `done` after proposal.
- Keep decisions that intentionally target an already-finished task.
- Keep lenient-only decisions open.
- Keep continuation delivery consistent with other expiry reasons.
- Add target-task links to the decision card provenance line.
- Use one shared target-ID helper across signing, execution, expiry,
card provenance, and resolver preloading.
- Add service and UI regression tests for primary, secondary, and
target-completed cases.

## Verification

- `pnpm exec vitest run ui/src/components/DecisionCard.test.tsx
server/src/__tests__/decisions-service.test.ts` — 51 tests passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — all gates clean.
- `git diff --check origin/master...HEAD` — passed.

## Risks

- Low migration risk. This change does not alter the database schema.
- The expiry sweep performs the existing strict-target query and adds a
snapshot comparison before expiry.
- A decision remains open if any strict target is active or if a target
was already `done` at proposal time.

> The roadmap lists work queues as planned. This pull request fixes the
existing decisions desk. It does not add a new queue subsystem.

## Model Used

- Implementation: Anthropic Claude through Claude Code. The runtime did
not expose the exact model snapshot or context-window size. The model
used reasoning, repository tools, code execution, and test execution.
- PR preparation: OpenAI Codex with GPT-5. The runtime did not expose a
dated model snapshot or context-window size. The model used reasoning,
repository tools, code execution, and test execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-05 09:55:01 -05:00
Dotta 8142e54150
feat(activity): merge the audit page into one rich Activity page (#10838)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board has two different pages for change history: a basic
Activity list and a rich Audit feed
> - The two pages show the same kind of information, so an operator must
guess which page to open
> - The basic list also caps at 200 rows and has no filters, so it hides
older changes
> - This pull request merges both pages into one Activity page that is
built on the rich audit feed
> - The page adds a scope toggle for all actors or agent actions only,
and it hides privileged controls from members who do not have the audit
permission
> - The benefit is one obvious place to answer "who changed what", for
every member, with filters and full history

## Linked Issues or Issue Description

Related pull requests in this stack (open both before this one):

- Refs #10830 — adds the company prefix to the board audit route.
- Refs #10831 — adds the two-tier all-actors scope to the audit
endpoint. This pull request calls that scope.

This branch is stacked on those two pull requests. The diff therefore
shows their commits until they merge. After they merge, this pull
request contains only the last two commits: the page merge and the
actor-label fix.

**Problem or motivation**

The board has two overlapping history pages. `/:company/activity`
renders a plain list that is capped at 200 rows and has no filters. The
audit page renders a filtered, paginated feed of agent actions, but it
is a separate sidebar item and it was reachable only by members with the
audit permission. A member who wants to know who changed an issue must
know which of the two pages answers the question.

**Proposed solution**

Keep one sidebar item, "Activity", and build it on the rich feed. Add a
scope toggle: "All activity" reads every actor kind, and "Agent actions"
keeps the earlier audit behavior. Put the scope in the `mode` query
parameter so a person can link to it. Show the responsible-user filter
and the CSV export only to callers that the server answers at the
privileged tier. Redirect the earlier audit paths to the merged page
with the agent scope preset, so old links continue to work. Delete the
plain list page.

**Alternatives considered**

Keeping both pages and adding filters to the plain list. That duplicates
the feed logic and keeps the "which page?" problem. Deleting the audit
page instead was also rejected, because the audit feed has the
pagination, filters, and export that the plain list does not.

**Roadmap alignment**

The roadmap marks the activity log and action attribution as shipped.
This change improves that shipped capability. It does not add a new
subsystem.

## What Changed

- Added a scope toggle to `AuditFeed`. "All activity" requests
`actorScope=all`, and "Agent actions" keeps the earlier agent-only
request. Cursor pagination works in both scopes.
- Stored the scope in the `mode` query parameter, so a person can
bookmark or share a scope.
- Made the page chrome permission-aware. The toggle, the
responsible-user filter, and the CSV export appear only when the server
answers at the privileged tier. A basic member sees the shared feed and
no upsell wall.
- Replaced the sidebar "Audit" item. The sidebar now has one "Activity"
item.
- Redirected `/:company/audit` and the unprefixed `/audit` to
`/:company/activity?mode=agents`.
- Deleted the earlier `ui/src/pages/Activity.tsx` list page and the
`CompanyAudit` page wrapper. Added `CompanyActivity` as the single route
target.
- Fixed the actor label for stripped rows. The basic tier removes the
agent id but keeps the actor kind, so every agent row rendered as
"System". Rows now fall back to the actor kind: "Agent", "User",
"Plugin", or "System".
- Widened the responsible-user filter control, which truncated its own
label.
- Resolved agent names on the basic tier. The basic tier removes the
privileged `agentId` but keeps the acting principal `actorId`, and the
company agent directory this page already reads is
authorization-filtered. The feed therefore resolves an agent actor from
`agentId` first and from an agent-typed `actorId` second. Hiding the
name only in the UI gave no confidentiality benefit, because any reader
could join the retained id against the readable directory. Agents that
the directory filters out still fall back to the generic kind label. No
server payload or permission was widened.
- Fixed a stuck state in the access-downgrade recovery. A downgrade
between cursor requests leaves full-tier and basic-tier pages in one
cache, which starts a single recovery refetch. If that refetch did not
clear the mix, the cached pages kept the condition true, the "Refreshing
audit access…" banner rendered permanently, and it hid the error state
together with its "Try again" button. The banner is now tied to an
outstanding attempt. The refetch effect also depended on the whole query
object, which changes identity every render, so it repeated the request
on each render; the attempt is now tracked in state and runs once per
downgrade.
- Kept the agent detail "Audit" tab unchanged. That tab passes a locked
agent id, which keeps the earlier privileged scope and hides the toggle.

The `GET /companies/:id/activity` endpoint stays. The dashboard still
reads it. This pull request does not change that endpoint.

## Verification

- `pnpm exec vitest run ui/src/pages/audit/AuditFeed.test.tsx
ui/src/App.activity-routing.test.tsx ui/src/lib/company-routes.test.ts
ui/src/components/Sidebar.test.tsx
server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-action-audit-routes.test.ts` — all tests
pass.
- New `ui/src/App.activity-routing.test.tsx` drives the real route
table. It asserts that the company activity path resolves, and that both
the company audit path and the unprefixed audit path reach the activity
path with the agent scope preset.
- New `AuditFeed` tests cover the scope toggle, the basic tier without
privileged chrome, the locked-agent case, the actor-kind fallback label,
basic-tier name resolution, and both downgrade-recovery paths (the
refetch errors, and the refetch returns a still-mixed pair).
- Mutation-checked the three new guards: disabling each one fails the
test that covers it, so none of them pass vacuously.
- `pnpm -r typecheck` is clean. Both design token gates are clean.
- Rendered every state in a browser at 1440x900 and at 390x844: both
scopes, the basic member view, the loading state, the error state, the
filtered-empty state, and the true-empty state. A designer reviewed the
renders and approved them.

## Risks

- The default company page now reads the all-actors scope, which returns
more rows than the earlier agent-only query. Cursor pagination and the
existing page limit bound each request.
- The page is now visible to every company member. The server decides
what each member sees. The UI only hides controls that the caller cannot
use. Refs #10831 for the server rules and tests.
- The basic tier now shows agent names that the previous revision
withheld. The name was already recoverable from the retained `actorId`
through the readable agent directory, so this closes an inconsistency
rather than widening access. A security reviewer chose this outcome over
stripping `actorId`.
- Old audit links now redirect. The redirect keeps the agent scope, so a
person who bookmarked the audit page sees the same rows.
- Low migration risk. There is no database change.

> The roadmap marks activity log and action attribution as shipped. This
change improves that existing capability.

## Model Used

Claude Opus 5 (`claude-opus-5`, 1M context) with extended thinking and
tool use, run through Claude Code.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 23:30:23 -05:00
Dotta 68ddd6a7a0
feat(activity): add two-tier all-actors audit feed (#10831)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Operators need one activity feed for human, agent, plugin, and
system changes
> - The existing audit endpoint returns only rows that have agent
attribution
> - The full audit view also requires a dedicated permission
> - This pull request adds an explicit all-actors scope with basic and
privileged access tiers
> - The benefit is that company members can inspect the shared activity
history while sensitive attribution and export controls stay protected

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The company audit activity endpoint and the board audit route.

**Subsystem affected**

Server REST API and board UI routing/API contracts.

**Current behavior**

The agent-action audit endpoint excludes activity without an agent ID.
It also rejects company members who do not have the full audit
permission.

**Proposed behavior**

Callers can opt into `actorScope=all`. A company member receives all
actor kinds with sensitive attribution fields removed. A permitted board
user receives complete rows and can use attribution filters. The default
scope and CSV permission remain unchanged.

**Reason and benefit**

The board needs one chronological activity source for user, agent,
plugin, and system actions. A two-tier response keeps the feed useful
without widening access to detailed attribution or export capabilities.

**Breaking changes**

None. The endpoint keeps the existing agent-only scope and permission
behavior by default.

## What Changed

- Added `actorScope=all` to the unified audit query and included
activity from every actor type.
- Added a company-readable basic tier that removes run,
responsible-user, agent, and details attribution.
- Kept attribution filters and CSV export behind
`audit:view_agent_actions`.
- Added route and integration coverage for basic readers, permitted
readers, pagination, filter denial, and all actor kinds.
- Added the missing unprefixed `/audit` redirect and company route
classification.

## Verification

- `pnpm exec vitest run server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-action-audit-routes.test.ts
ui/src/lib/company-routes.test.ts --reporter=verbose` (35 tests passed)
- `pnpm -r typecheck`
- `pnpm test:run`
- `pnpm build`

## Risks

- The all-actors query can return more rows than the legacy agent-only
query. Cursor pagination and existing limits bound each request.
- The basic tier intentionally exposes action and actor-kind context. It
removes detailed run, agent, responsible-user, and details attribution.
- The legacy endpoint behavior remains the default, which reduces
compatibility risk.

> The roadmap marks activity log and action attribution as shipped. This
change improves that existing capability and does not introduce a
separate workflow system.

## Model Used

- OpenAI Codex, `gpt-5.6-sol`, 114K context, agentic reasoning with tool
use and code execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-04 23:07:38 -05:00
Dotta f0ed524ffa
fix(ui): prefix audit board route (#10830)
<!-- Write all pull request text in Simplified Technical English
(ASD-STE100): short sentences, one instruction per sentence, simple
approved vocabulary, and the active voice. -->

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI keeps company work under a company-prefixed route.
> - The Audit sidebar link used a bare `/audit` path.
> - The route helper treated `audit` as a company prefix because the
board-route list did not include it.
> - The router also had no redirect for a bare `/audit` deep link.
> - This pull request registers Audit in both places and adds regression
coverage.
> - The benefit is that the Audit sidebar link and old bare deep links
open the active company's audit feed.

## Linked Issues or Issue Description

Related PR: #9744

**What happened?**

The Audit sidebar link opened `/audit`. The router interpreted `AUDIT`
as a company prefix and showed the invalid-company page.

**Expected behavior**

The Audit sidebar link must open `/<company-prefix>/audit`. A bare
`/audit` deep link must redirect to the active company.

**Steps to reproduce**

1. Open a company board.
2. Select Audit in the sidebar.
3. Observe that the app opens `/audit` and shows an invalid-company
error.

**Paperclip version or commit**

Reproduced on master after #9744.

**Deployment mode**

Board UI in local or self-hosted deployments.

## What Changed

- Added `audit` to the board-route root list.
- Added the unprefixed `/audit` redirect route.
- Added regression tests for Audit prefixing, prefix extraction, and
relative-path conversion.

## Verification

- `pnpm exec vitest run ui/src/lib/company-routes.test.ts`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm check:token-gates`
- Manual check: select Audit in the sidebar and confirm the URL is
`/<company-prefix>/audit` and the audit feed renders.

## Risks

- Low risk. This change only reserves one existing board route and adds
one redirect.
- A company cannot use `AUDIT` as an issue prefix after this change.
That prefix already conflicts with the existing Audit board page.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex with GPT-5. The runtime does not expose a more specific
model ID or context-window size. The agent used reasoning, repository
tools, code execution, and test execution.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-04 23:07:24 -05:00
Dotta e8ae5286eb
feat(issues): explain cross-task agent writes with attribution, audit receipts, and actionable denials (#10843)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents write to tasks they do not own. They comment, they change
fields, and the control plane now permits this by default for
standard-trust agents on any task they can read
> - This makes a task thread ambiguous. A reader sees a comment from an
agent that is not the assignee, but no surface says whose authority that
write rode
> - The same gap applies to field edits. The activity stream named the
verb, but it did not show the before value, the after value, or the
reason the write was permitted
> - The remaining refusals are also opaque. An agent that hits a wall
receives a 403 with no boundary name, no actor who can act, and no
sanctioned path. One real incident spent a full detour to find the
workaround
> - This pull request adds the three surfaces that make open cross-task
writes legible: an attribution chip, a field-level audit receipt, and an
actionable denial contract shared by the API and the UI
> - The benefit is that a reader can answer "who did this, on whose
authority, and was it allowed?" on the task itself, and a blocked writer
is told what to do next

## Linked Issues or Issue Description

No public issue exists for this work, so the enhancement is described
here.

**What existing behavior does this improve?**

Cross-task agent writes are permitted, but they are not explained. A
task thread can hold comments from agents that are not the assignee, and
the activity stream can hold field changes made by those agents. Neither
surface names the responsible user behind the write. When a write is
refused, the error text does not name the boundary or the way forward.

**Subsystem affected**

Issue detail UI (comment thread and activity stream), the issue write
authorization responses in the server, and the shared copy contract that
both consume.

**Current behavior**

- An agent comment on a task the agent does not own looks the same as an
assignee comment.
- An `issue.updated` activity row states the verb only. It does not show
the field-level before and after values, the responsible user, or the
authorization reason.
- A refused write returns a short message such as an ownership error.
The message does not state which rule fired, who is able to perform the
action, or which alternative path is sanctioned.

**Proposed behavior**

- An agent comment on a task the agent does not own carries a chip that
reads "for {user}". The chip names the responsible user. Its tooltip
states that the author is not the assignee and cannot exceed that user's
permissions.
- Each `issue.updated` row shows a receipt: the changed fields with
before and after values, the responsible user, and the authorization
reason. This applies to board edits as well as agent edits.
- Each refusal states three things: the boundary that fired, who is able
to act, and the sanctioned path. The API error body and the in-app
notice use the same words, because both read one shared contract.

Related pull requests, found by searching this repository:

- Refs #10837 — merged. It added the default-open cross-task write rule,
the comment attribution data, and the per-run containment cap that this
pull request makes visible.
- Refs #10114 — open. It proposes a narrower authorization change in the
same area.
- Refs #7998 — open. It proposes append-only cross-assignee comments as
an alternative to opening writes.

## What Changed

- Adds `packages/shared/src/issue-write-denial.ts`. This is one copy
contract for eight ways an issue write can be refused: not visible,
responsible-user ceiling, responsible user unavailable, excluded actor
class, assignee run lock, per-run cross-task cap, missing run context,
and rejected attribution. Each entry names the boundary, who can act,
and the sanctioned path.
- Maps server authorization decisions onto that contract in
`server/src/routes/issues.ts` and
`server/src/services/cross-issue-influence-limit.ts`. The flattened
`error` string carries all three obligations, and `details.code` lets
the UI render the same words. The two cap codes keep the names they
already ship under.
- Adds `CommentAttributionChip`. It renders "for {user}" beside the
author name on agent comments where the author is not the assignee. It
renders nothing when no responsible user is recorded, so older rows stay
clean. It is wired into both `IssueChatThread` and the flagged
`TaskChatThread` redesign.
- Adds `IssueFieldChangeReceipt`. It renders the change receipt under
`issue.updated` rows in the activity stream. Ids resolve to agent and
user names where the directory is loaded. Server-truncated text is
labelled as a preview, so the receipt never implies that it shows a
whole value.
- Adds `IssueWriteDenialNotice`. It renders the shared copy in the app,
keyed off the denial events the server logs on a task.
- Adds a public `/ux-lab/cross-issue-collaboration` page. It renders all
three surfaces and their edge cases for review without a seeded thread.
This follows the existing `ux-lab` pages.

## Verification

Automated, all green:

```
pnpm --filter @paperclipai/shared exec vitest run src/issue-write-denial.test.ts        # 17 tests
pnpm --filter @paperclipai/ui exec vitest run src/components/IssueWriteDenialNotice.test.tsx \
  src/components/IssueFieldChangeReceipt.test.tsx src/components/CommentAttributionChip.test.tsx \
  src/lib/issue-change-receipt.test.ts src/lib/comment-attribution.test.ts                # 46 tests
pnpm --filter @paperclipai/server exec vitest run src/__tests__/cross-issue-influence-limit.test.ts \
  src/__tests__/issue-comment-attribution-audit-routes.test.ts \
  src/__tests__/issue-agent-mutation-ownership-routes.test.ts \
  src/__tests__/low-trust-red-team-routes.test.ts                                        # 98 tests
```

`tsc --noEmit` passes for the shared, ui, and server packages.

Manual, in a browser:

1. Start the UI only: `pnpm --filter @paperclipai/ui exec vite`.
2. Open `/ux-lab/cross-issue-collaboration`. No session is needed,
because `ux-lab` routes are public.
3. All three surfaces were captured at 1440x900 in light mode and dark
mode, and at 390x844. The page reported no errors.
4. The chip tooltip was opened by a hover and by a keyboard focus.

Rendering the page found defects that the tests had missed. Three copy
and contrast defects were fixed, and two of them are now pinned by a
test. A design review then found three layout defects, which are also
fixed: the denial notice orphaned its label when a value wrapped, the
receipt icon wrapped onto its own line at narrow widths, and the chip
tooltip was reachable by hover only.

## Risks

Low risk, and additive.

- Every new surface renders nothing when its data is absent. Comments
without a recorded responsible user show no chip, and activity events
without a receipt show no receipt, so existing rows do not change.
- No migration is included. The data these surfaces read already ships.
- The wire values of the two per-run cap denial codes are unchanged.
Only the human-readable text changes, plus six codes that had no
`details.code` before.
- The denial copy is read by agents as well as people. If wording must
change later, one shared module is the only place to change it.
- Roadmap check: this extends the completed "Activity log & action
attribution" area rather than duplicating planned core work.

## Model Used

Claude Opus 5 (Anthropic), model id `claude-opus-5[1m]`, 1M context
window, extended thinking, with tool use and code execution. It ran as
an agent in Claude Code and drove a real browser to capture the review
screenshots.

## Checklist

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 23:02:51 -05:00
Dotta 5858ccb981
feat: make in-app features cloud-aware (#10850)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators use the same board application in self-hosted and
Paperclip Cloud deployments.
> - A Cloud tenant contains one company, so an in-app company switch
does not change the active Cloud stack.
> - Cloud operators need the sidebar and company surfaces to use the
signed-in user's stack portfolio.
> - The server must derive Cloud identity and links from trusted
instance context instead of client input.
> - This pull request adds canonical Cloud context, a trusted stack
portfolio proxy, and Cloud-aware navigation.
> - The benefit is consistent stack switching on Cloud while self-hosted
company behavior stays unchanged.

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting: server REST routes and the React board UI.

**Problem or motivation**

A Cloud-managed instance contains one company. The existing company
switcher could only switch records inside that tenant. It could not move
the operator to another Cloud stack. The existing header also gave long
organization names too little width.

**Proposed solution**

Expose a canonical public Cloud context in health data. Add a trusted
server proxy for the current user's stack portfolio. Use that data in
the board UI to switch stacks with top-level navigation. Keep the
existing company behavior on self-hosted instances. Move search into the
navigation and keep long organization names inside the sidebar panel.

**Alternatives considered**

An in-app `/stacks` route was rejected because Cloud tenant hosts
reserve that path and stack selection must wake or authenticate another
tenant. Client-supplied user identity was rejected because the server
can derive the trusted Cloud actor.

**Roadmap alignment**

This change advances the Cloud deployments milestone. It keeps the
product local-first and Cloud-ready without changing the self-hosted
mental model.

## What Changed

- Added canonical Cloud instance context and public health metadata.
- Added a Cloud-only stack portfolio proxy with trusted actor forwarding
and per-user caching.
- Prevented normal company creation on Cloud-managed instances.
- Switched the sidebar and Companies page from company actions to stack
actions on Cloud.
- Added full-page stack navigation and Cloud create-stack links.
- Moved search into the sidebar navigation so the organization name
keeps more width.
- Added truncation and hover recovery for long organization and stack
names.
- Added server and UI regression coverage for Cloud and self-hosted
behavior.
- Updated the implementation specification for the Cloud contracts.

## Verification

- `node scripts/check-token-gates.mjs` passed. All three token gates are
clean.
- `pnpm --dir server exec vitest run src/__tests__/health.test.ts
src/__tests__/cloud-instance.test.ts src/__tests__/cloud-routes.test.ts
src/__tests__/company-cloud-floor.test.ts
src/__tests__/company-portability-routes.test.ts` passed: 5 files and 66
tests.
- `pnpm --dir ui exec vitest run
src/components/SidebarCompanyMenu.test.tsx` passed: 1 file and 11 tests.
- Pre-PR QA report `7da87ca7` passed all 8 acceptance criteria with real
HTTP route factories and real Chromium screenshots in Cloud and
self-hosted modes.
- Security reviews passed for the canonical Cloud context and stack
portfolio proxy.

## Risks

- Cloud stack switching depends on the configured Cloud application and
tenant portfolio URLs.
- The new health `cloud` block is public by design, but it contains only
canonical public instance metadata.
- The stack proxy fails closed on self-hosted instances and derives the
user identity from the trusted actor.
- Self-hosted navigation and company creation retain their existing
paths and behavior.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, model `gpt-5`. The run used reasoning, repository tools,
shell execution, and GitHub integration. The deployment did not expose
its context-window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-04 23:00:14 -05:00
sparkeros 7cff943fb8
chore(ui): remove deprecated baseUrl from ui/tsconfig.json (#4067)
## Thinking Path

- TypeScript editor integration surfaces the warning `Option 'baseUrl'
is deprecated and will stop functioning in TypeScript 7.0` on
`ui/tsconfig.json`.
- TS 5+ resolves `paths` relative to the `tsconfig.json` file when
`baseUrl` is absent.
- The existing `paths` entries already use `./` prefixes (`./src/*`,
`./node_modules/lexical/index.d.ts`), so removing `baseUrl: "."` is a
no-op at runtime.
- Clearing the warning now avoids the cliff when TypeScript 7 ships.

## What Changed

- Removed `"baseUrl": "."` from `ui/tsconfig.json`.

## Verification

- `pnpm --filter @paperclipai/ui typecheck` passes unchanged.
- `@/...` and `lexical` imports continue to resolve identically (same
prefixes work with or without `baseUrl` because they start with `./`).

## Risks

- None expected. `baseUrl` was only used for path-mapping resolution,
and every entry in `paths` is already relative.

## Checklist

- [x] Ran `pnpm typecheck` locally — passes
- [x] No runtime behavior change
- [x] Single-file, single-line cleanup

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-04 18:39:13 -05:00
Dotta 678728f650
feat: maintained in_review review-path contract + stalled-review actions (#10675)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents move issues to `in_review` and rely on a "review path" (an
interaction, an approval, a monitor, or a named reviewer) to tell them
who decides next.
> - That review path can silently disappear. A user comment supersedes
the pending interaction, a monitor is exhausted, or a run ends without
restoring a path. The issue then sits in `in_review` with nobody
reviewing it and no visible action.
> - Such issues become invisible zombies. Nobody knows a decision is
owed, so the work stalls forever.
> - This pull request makes the review path a maintained invariant,
exposes a `reviewAttention` surface, and gives every stalled review
three inline actions in the UI.
> - The benefit is that an `in_review` issue always shows who reviews
it, or shows an amber "nobody is reviewing this" notice with one-click
Approve, Request changes, and Send back to work.

## Linked Issues or Issue Description

This pull request describes the problem inline. The tracking issue is
internal.

**Subsystem affected**

The review and attention loop that agents and humans share: the
`in_review` status, the `reviewAttention` surface, the /decisions
attention feed, and the issue-page review panel.

**Problem or motivation**

Agent-owned issues in `in_review` can lose their last review path. A
user comment supersedes the pending interaction. A monitor is exhausted.
A run ends without restoring a path. The issue then sits in `in_review`
with no reviewer and no visible action. It becomes an invisible zombie
and the work never progresses.

**Proposed solution**

Maintain the review path as a server invariant. Expose a
`reviewAttention` field that says what is under review, who decides, and
since when. Render a persistent review panel on the issue page and
inline actions on the /decisions feed. Keep human PATCHes into
`in_review` ungated, but record the requesting user so the panel never
renders empty.

**Alternatives considered**

A pure background auto-recovery sweep. This stays opt-in and is not
enough on its own, because it is invisible to the human. A bare status
banner. This is rejected, because it gives no action to resolve the
stall.

**Roadmap alignment**

This improves the core review and attention loop that both agents and
humans use every day.

## What Changed

- **Server — maintained review-path invariant:** when an issue enters or
sits in `in_review`, the server derives and persists a review path
(interaction, approval, monitor, or the requesting user) and recovers a
stale path with one bounded wake instead of leaving the issue pathless.
- **Server — `reviewAttention` surface:** a new field describes what is
under review (bound target with links), who decides, since when, and
whether the review is stalled. Stalled agent-assigned reviews are now
included in the attention feed.
- **Server — inline stalled-review decisions:** secured routes let a
permitted responder Approve (→ `done`), Request changes (→ `todo` + wake
carrying the note), or Send back to work (→ `todo` + wake) directly from
the attention feed.
- **Server — resume-intent wake:** an `in_review -> todo` transition now
wakes the assigned agent so a resumed review is not dropped.
- **Server — user-entry symmetry:** user PATCHes into `in_review` stay
ungated (no 422 for humans) and record the requesting user, who becomes
the named responder when no other path exists.
- **UI — review panel:** a persistent `IssueReviewPanel` renders above
the thread whenever status is `in_review`. The covered state shows the
bound target, responder, and outcomes and hoists the pending
interaction/approval card. The stalled state shows the amber notice plus
the three actions.
- **UI — decisions card actions:** the same three actions render inline
on the /decisions `AttentionQueueRow`.
- **UI — responsive fix:** the stalled action row stacks to full-width
buttons at phone width and returns to a horizontal row at `sm` and up.
New 390px stories capture the phone layout.

## Verification

- `cd ui && npx vitest run src/components/IssueReviewPanel.test.tsx
src/components/AttentionQueueRow.test.tsx src/lib/attention.test.ts
src/api/issues.test.ts` — 91 tests pass.
- Server suites added and updated: `issue-review-attention`,
`issue-stalled-review-decision-routes`, `review-path-recovery`,
`recovery-observability`, and related route/liveness tests (run by CI).
- A designer reviewed the UI at 390px and desktop in light and dark
themes on both the issue-page panel and the /decisions card. The stalled
action row stacks cleanly at phone width with no overlap and keeps the
horizontal row on desktop.

## Risks

- **Migration:** adds migration `0200` (next after master `0199`, no
renumber). It extends the agent-wakeup-requests schema and is additive.
- **Behavioral shift:** `in_review -> todo` now dispatches a wake. This
is intended (resume intent) and covered by tests.
- **Authz:** the inline decision routes are permission-gated. Only a
permitted responder sees and can trigger the actions.
- Overall risk is moderate and contained to the review and attention
loop.

## Model Used

- Claude, Opus 4.8 (`claude-opus-4-8`), extended thinking, tool use and
code execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 13:54:40 -05:00
Dotta ded813ad6f
feat(interactions): add governed agent addressees (#10252)
## Thinking Path

> - Paperclip is the control plane that lets humans govern companies of
AI agents.
> - Issue-thread interactions are the structured handoff point for
confirmations, questions, suggested tasks, and other governed decisions.
> - Those interactions previously assumed that only board users could
resolve them, preventing one agent from explicitly addressing another
agent for a response.
> - Agent resolution needs company-level governance, auditable resolver
identity, safe terminal-state handling, and attention routing so
authorization is enforced server-side rather than inferred from UI
behavior.
> - This pull request adds governed agent resolution, withdrawal and
terminal expiry semantics, explicit agent addressees, lifecycle
reconciliation, and attention-feed filtering.
> - The benefit is that agents can participate in structured decisions
without weakening board control, company isolation, wake behavior, or
audit invariants.

## Linked Issues or Issue Description

### Subsystem affected

Issue-thread interactions across database, shared contracts, server
authorization/services, adapter callbacks, agent skill guidance, API
docs, and UI governance surfaces.

### Problem or motivation

Structured interactions were board-only, had no explicit agent
addressee, and lacked durable withdrawal/terminal-expiry semantics. That
made peer-agent decisions impossible to authorize and audit safely.

### Proposed solution

Persist requested/effective resolver policy and addressee identity,
enforce company governance and eligible agent resolution, reconcile
addressee lifecycle changes, expose withdrawal and terminal expiry, and
route attention to the intended active agent with board fallback.

### Alternatives considered

Implicitly authorizing the issue assignee or mentioned agents was
rejected as ambiguous and difficult to audit. Using comments alone was
rejected because it loses structured outcomes and continuation behavior.

### Roadmap alignment

Supports the ROADMAP direction for lightweight leadership-agent
communication that still resolves into governed decisions and work
objects.

### Additional context

Public GitHub issue/PR search found no duplicate implementation; open PR
search for interaction resolver governance and agent addressees only
returned this PR.

## What Changed

- Add company-scoped interaction resolver governance contracts and
persistence.
- Add requested/effective resolver policy, resolver identity,
withdrawal, and terminal-expiry behavior.
- Add explicit `addresseeAgentId` validation, authorization,
persistence, lifecycle reconciliation, API documentation, and skill
guidance.
- Route pending addressed interactions to the intended invokable agent
and fall back to board attention when that agent becomes ineligible or
is deleted.
- Preserve sandbox callback identity fields required by governed
resolution paths.
- Add migrations `0193` and `0194` plus route, service, attention,
adapter, CLI, and UI coverage.
- Add governance state and company settings UI, including responsive
mobile behavior and distinct withdrawn/expired audit presentation.

## Verification

- `pnpm check:token-gates` — passed.
- `pnpm -r typecheck` — passed, including migration numbering and safety
checks.
- `pnpm test:run` — feature/server and UI workspace suites passed; one
unrelated CLI AWS doctor test observed injected static AWS credentials
and warned instead of passing.
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm exec vitest
run cli/src/__tests__/secrets.test.ts --project paperclipai` — 8 tests
passed, confirming the failure was environment-sensitive.
- `pnpm build` — passed.
- Latest rebased head `e24cece6be9f1877bdbac7691bcb44fd583c0161`
completed all GitHub CI jobs successfully.

## Risks

- Migrations add interaction and company-governance fields; numbering is
conflict-free on current `master`, additive statements are idempotent,
and migration safety checks pass.
- Agent authorization behavior expands beyond board-only resolution, but
defaults remain board-only and coverage exercises company boundaries,
resolver eligibility, lifecycle invalidation, wake behavior, withdrawal,
expiry, and attention fallback.
- Attention routing depends on current agent invokability;
reconciliation and read-time filtering prevent stale addressees from
retaining visibility or resolution authority.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex using `gpt-5.6-sol` with reasoning, terminal tool use,
code execution, Git/GitHub integration, and Paperclip control-plane
tools. Context-window metadata was not reported by the runtime.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 12:51:06 -05:00
Dotta 76f442040c
fix(ui): follow managed sign-out redirects
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-03 22:34:14 -05:00
Dotta 2a90933433
fix(ui): remove duplicate create-task loading status (#10756)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators create tasks from the board UI.
> - The create form must show clear progress while it submits.
> - The form showed the same pending state twice.
> - One copy also used the old term "issue" instead of the UI term
"task."
> - This pull request keeps the pending state in the submit button and
removes the duplicate status.
> - The benefit is a clearer form with consistent task language.

## Linked Issues or Issue Description

No matching public GitHub issue exists for this focused UI bug.

**What happened?**

The create-task form showed `Creating issue...` beside a submit button
that already showed `Creating...`. A low-trust notice in the same form
also used the old UI term `issue`.

**Expected behavior**

The submit button shows the pending state once. Visible UI copy uses
`task` for the work object.

**Steps to reproduce**

1. Open the create-task dialog.
2. Enter a task title.
3. Select **Create Task**.
4. Observe the duplicate loading status beside the pending button.

**Paperclip version or commit**

Reproduced from upstream `master` at
`2c90cf0f2c60d3851880eca3c643c01313af9ffd`.

**Deployment mode**

Local development build.

## What Changed

- Removed the duplicate loading status beside the create-task submit
button.
- Kept inline create errors in the dialog footer.
- Changed the low-trust notice from `issue` to `task`.
- Added a regression test for the single pending-state presentation and
`aria-busy` state.

## Verification

- `cd ui && pnpm exec vitest run src/components/NewIssueDialog.test.tsx`
— 24 tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — passed.

## Risks

- Low risk. The change only removes duplicate pending copy and updates
one UI term.
- The regression test keeps the submit button pending indefinitely to
verify its accessible loading state.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, model ID `gpt-5`. The runtime did not expose the
context-window size. The session used reasoning, repository tools, and
code execution.

## Checklist

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

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-03 21:35:07 -05:00
Dotta 8e7f1c03eb
feat(decisions): improve desk triage and queue parity (#10785)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The decisions desk and queue help operators find work that needs a
human decision.
> - The current views use different grouping, sorting, and labels.
> - Repeated confirmation requests can also leave stale pending actions
in the queue.
> - Blocked-work attention can point at an intermediate issue instead of
the terminal blocker.
> - This pull request aligns the server contract and both user
interfaces.
> - The benefit is a smaller, clearer queue that ranks the decisions
with the largest impact.

## Linked Issues or Issue Description

Related PR: #10774

**What existing behavior does this improve?**

The decisions desk and queue currently use different triage rules. They
can show stale repeated confirmations and can rank blocked work by an
intermediate issue.

**Subsystem affected**

This change affects attention aggregation, issue thread interactions,
shared attention contracts, and the decisions user interface.

**Current behavior**

The desk uses a can-wait group that has no clear arrival meaning. The
queue has fewer controls than the desk. Repeated pending confirmations
remain actionable. Blocked-work rows do not always identify the terminal
actionable blocker.

**Proposed behavior**

Group desk items by arrival date, and reserve Decide now for explicit
due dates. Use one toolbar and shelf model on both pages. Supersede
older repeated pending confirmations. Aggregate blocked work under the
terminal actionable blocker and rank it by impact.

**Reason and benefit**

Operators get one consistent triage model. The badge reflects new and
overdue work. High-impact blockers move to the top. Duplicate
confirmation work no longer consumes attention.

**Breaking changes**

The attention summary field `decideNowCount` changes to
`deskBadgeCount`. Consumers must use the new field. Older repeated
confirmation interactions can now finish with the
`superseded_by_newer_request` outcome.

## What Changed

- Supersede older pending confirmation requests for the same issue and
record the mutation in activity history.
- Resolve blocked-work attention to actionable terminal blockers,
suppress live blocker trees, and rank rows by blocked-work impact.
- Group the decisions desk into New today and Earlier, and count new
plus overdue work in the desk badge.
- Share the decision toolbar and shelf components across the desk and
queue.
- Add queue grouping, sorting, filtering, aging, visible training
controls, and clearer recommendation copy.
- Add server, shared-contract, and user-interface tests for the new
behavior.

## Verification

- `pnpm check:token-gates`
- `pnpm -r typecheck`
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY pnpm test:run` (all
server, UI, CLI, shared, and catalog tests passed; one fixed five-second
DB timeout flaked under full-suite load)
- `NODE_ENV=test pnpm --filter @paperclipai/db exec vitest run
src/status-card-migrations.test.ts` (passed in isolation)
- `pnpm build`

## Risks

- The attention summary field rename requires synchronized consumers.
- Terminal-blocker traversal uses cycle and depth guards. A malformed
dependency graph can stop at the last safe node.
- The new arrival grouping changes which items contribute to the
decisions badge.
- Superseding repeated confirmations changes the terminal state of older
pending interactions.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex with GPT-5. The runtime did not expose a dated model
snapshot or context-window size. The model used reasoning, repository
tools, code execution, and test execution.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-03 21:31:45 -05:00
scotttong 9b9631b724
feat(ui): chat-style tasks polish — rich-text composer, attachment chips, live-turn interstitials, mobile layout (#10707)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators talk to their agents on the issue detail page. An
experimental "Chat-Style Tasks" view (#10606) makes that page read as a
conversation instead of a ticket form.
> - The first release of that view shipped with a plain-text composer,
no visible narration while an agent works, and a desktop-only layout.
> - Users write formatted replies, paste screenshots, and follow long
agent runs from their phones. The experimental view should support all
of that before it can graduate.
> - This pull request is the next iteration of the same experiment: a
rich-text composer with attachments, live-turn narration on the status
line, cleaner settled-turn history, and a mobile layout.
> - The benefit is a chat view that feels alive while the agent works
and stays readable after it finishes, on desktop and mobile, still fully
behind the existing opt-in flag.

## Linked Issues or Issue Description

Refs #49 (chat with agents is a much-wanted feature).
Refs #10606 (the merged first release of the experimental chat-style
task view; this PR iterates on it).

Related PRs found in the dedup search:
- #8228 — open PR that polishes the classic issue chat composer. It
targets the flag-off legacy path; this PR only changes the flag-on
experimental view.
- #10466 — merged blockquote-recovery fix in the shared MarkdownEditor.
This PR now reuses that editor inside the chat composer.

**What existing behavior does this improve?**

The experimental "Chat-Style Tasks" view on the issue detail page
(Settings → Experimental, `enableTaskChatRedesign`, default off).

**Subsystem affected**

UI (issue detail page, chat-style task view).

**Current behavior**

With the experiment enabled, the composer is a plain textarea with no
formatting, no attachment preview, and no mention support. While an
agent runs, the status line shows only a static label, and the agent's
narration text is hidden. Finished runs render one settled row per turn,
so a run with many short turns produces a long list of near-duplicate
"Worked" rows, and turns without a comment append at the bottom out of
order. On mobile, the desktop bounded-height thread makes the page
scroll poorly.

**Proposed behavior**

The composer uses the shared MarkdownEditor: markdown formatting,
mentions, image paste with thumbnail previews, and non-image attachment
chips. Sending posts on Cmd/Ctrl+Enter. While an agent runs, the status
line rotates playful status words and surfaces the agent's own narration
as short interstitial updates: each update holds for a minimum dwell, is
replaced only when superseded, and slides through a one-line viewport
with tokenized motion. Back-to-back settled turns coalesce into one
"Worked" row with summed durations and re-derived tool counts, and
comment-less settled turns insert chronologically at their run's start
time. On mobile, the thread renders in the document flow with window
auto-follow and a sticky safe-area composer; the desktop layout is
unchanged.

**Reason and benefit**

The chat view is only convincing if it feels like a conversation with a
working agent. Rich text and screenshots are table stakes for chat
input. Live narration gives moment-to-moment feedback without opening
transcripts. Coalesced history keeps long-running tasks readable. Mobile
support lets operators follow runs away from their desks.

**Breaking changes**

None. Every change is gated behind the existing `enableTaskChatRedesign`
flag, which is off by default. The flag-off page is unchanged.

## What Changed

- `TaskChatComposer` swaps its textarea for the shared `MarkdownEditor`:
markdown formatting, mentions, image paste with object-URL thumbnail
previews (revoked on clear and unmount), and posting on Cmd/Ctrl+Enter.
- Non-image attachments render as chips on a new shared
`ui/attachment.tsx` primitive (adds the `@base-ui/react` dependency it
builds on).
- New `status-whimsy.ts`: deterministic rotation of playful status words
on the live status line.
- Live interstitial narration: the transcript adapter tags agent
self-talk, and the live status line shows it as ephemeral one-line
updates with a ~4s minimum dwell, hold-until-superseded replacement, and
a slide transition driven by new `--motion-line-scroll` tokens
(cataloged in `motion-tokens.ts`, which a test keeps 1:1 with
`index.css`). Hover affordance applies only to the status line, with no
leading icon.
- Settled-turn history: `coalesceSettledTurns` merges back-to-back
settled agent turns into one "Worked" row (summed per-run durations,
tool counts re-derived from the merged turn); `assembleThreadItems`
inserts comment-less settled turns chronologically at run start instead
of appending them at the bottom; settled turns render tool rows only
(the separate thinking block component is removed).
- The "Worked" summary attaches to the reply timestamp row, and thread
timestamps are always visible.
- Mobile layout: the thread renders with `scroll={false}` in the page
scroll, a new `useWindowAutoFollow` hook keeps the window pinned to new
content, the composer is sticky with safe-area padding, and the editor
uses 16px text so iOS does not zoom on focus. The desktop bounded chain
is untouched.
- New `TaskChatDescriptionBubble` renders the issue description as the
first chat bubble, and `McpIcon` gives MCP tools a distinct icon.
- `IssueDetail.test.tsx` stubs `TaskChatThread`: the composer's
`@mdxeditor` dependency cannot load under jsdom's CSSOM, and the suite
exercises the flag-off path.

## Verification

- `pnpm check:token-gates` — 3/3 CLEAN.
- `node scripts/check-task-chat-motion.mjs` — OK (30 files scanned,
seams present).
- `cd ui && npx tsc -b` — clean.
- `cd ui && pnpm vitest run` — 3,482 of 3,483 tests pass locally. The
one failure is the `IssueProperties.test.tsx` monitor-row
time-formatting test, which is timezone-sensitive: it fails identically
on unmodified `origin/master` in a non-UTC timezone and passes with
`TZ=UTC`. It is not related to this change.
- Manual: enable "Chat-Style Tasks" in Settings → Experimental and open
an issue with an assigned agent. Comment to start a run: the status line
rotates status words and shows the agent's narration as short held
updates. After the run, consecutive turns fold into one "Worked" row
under the reply timestamp. Paste an image into the composer to see a
thumbnail chip; attach a non-image file to see a file chip; send with
Cmd+Enter. Open the same issue in a narrow viewport to see the
document-flow layout with the sticky composer.
- Visual snapshot baselines are intentionally not updated: per
`doc/design/DECISION-SHEET.md`, "Per-change snapshot verification
demoted to dormant (Jul 13 2026)".

## Risks

- The composer now loads the shared MarkdownEditor inside the chat view.
The editor is already used across the app (issue descriptions,
comments), so its behavior is well exercised; composer-specific handling
(paste, attachments, submit keys) is covered by new tests.
- The transcript adapter changes how live narration and settled turns
are derived from run logs. Malformed or legacy logs degrade to generic
rows rather than crashing, and the adapter suites cover the merge and
ordering rules.
- Object URLs for paste previews are revoked on send-clear and unmount
to avoid leaks; jsdom environments without `URL.createObjectURL` are
guarded.
- All changes are behind the default-off `enableTaskChatRedesign` flag.
Overall risk with the flag off is low.

## Model Used

- Claude (Anthropic), model id `claude-fable-5` (Claude Fable 5),
extended thinking enabled, agentic tool use (file editing, shell, test
execution) via Claude Code / Claude Agent SDK.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 16:25:31 -07:00
Dotta 18f391ef02
feat(ui): add shared workspace concurrency select to workspace policy editor (#10771)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The project workspace policy editor sets how agent runs share a
project's execution workspace.
> - The server now has a `sharedWorkspaceConcurrency` policy (Refs
#10759), but the UI had no control for it.
> - Users could not choose the concurrency mode without editing the API
directly.
> - This pull request adds a 3-option select (Auto / Serialize / Allow)
to the policy editor.
> - The benefit is that users set shared-workspace concurrency in the
UI, with clear helper text for each mode.

## Linked Issues or Issue Description

Refs #10759 (server contract this UI drives).

**Feature request**

- **Is your feature request related to a problem? Please describe.**
The `sharedWorkspaceConcurrency` policy field shipped on the server, but
the project workspace policy editor had no control to set it. Users
could not pick a concurrency mode from the UI.

- **Describe the solution you would like.**
Add a 3-option select (Auto / Serialize / Allow) to the
execution-workspace policy editor, with helper text that explains each
mode. An unset value must show as Auto.

- **Describe alternatives you have considered.**
A set of radio buttons was considered. A select matches the compact
style of the other controls in the same editor (environment, base ref).

## What Changed

- Added a "Shared workspace concurrency" select to the project
execution-workspace policy editor
(`ui/src/components/ProjectProperties.tsx`).
- The select offers three options with helper text:
- **Auto** (default): "Concurrent runs on local/SSH runners; runs take
turns in cloud sandboxes."
- **Serialize**: "Runs always take turns in the shared project
workspace."
- **Allow**: "Runs never wait for the workspace; concurrent edits are
possible."
- An unset or absent value shows as **Auto**. The UI writes a value only
after the user picks one, so the policy round-trips as Auto until then.
- Added a `SharedWorkspaceConcurrency` type import and a new
`execution_workspace_shared_concurrency` save-state key.
- Added a stateful Storybook story so the controlled select can be
exercised.

### Screenshots

**Before** (light / dark) — the editor had no concurrency control:

![before
light](https://pages.paperclip.ing/pap-16187-concurrency-select/before-light.png)
![before
dark](https://pages.paperclip.ing/pap-16187-concurrency-select/before-dark.png)

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

![after
light](https://pages.paperclip.ing/pap-16187-concurrency-select/policy-editor-light.png)
![after
dark](https://pages.paperclip.ing/pap-16187-concurrency-select/policy-editor-dark.png)

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


![serialize](https://pages.paperclip.ing/pap-16187-concurrency-select/concurrency-serialize-light.png)

![allow](https://pages.paperclip.ing/pap-16187-concurrency-select/concurrency-allow-dark.png)

## Verification

- `pnpm --filter @paperclipai/ui typecheck` passes.
- `pnpm --filter @paperclipai/shared build` passes.
- Rendered the editor in Storybook (light and dark). The select shows
Auto when the policy is unset. Selecting Serialize or Allow updates the
helper text and the stored value.

## Risks

- Low risk. UI-only change. The control is additive and only appears
when isolated task checkouts are enabled. An unset value keeps the
current Auto behavior, so existing projects are unaffected.

## Model Used

- Claude Opus 4.8 (claude-opus-4-8), extended thinking, tool use / code
execution.

## Checklist

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-03 15:13:43 -05:00
Dotta d5045d622c
fix(ui): keep mobile unread dots from indenting task rows (#10691)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Inbox helps operators scan task state on desktop and mobile
screens
> - Read and unread rows must keep the same title and status alignment
> - The mobile unread control was a flex item, so unread rows gained an
extra leading column
> - Moving the control out of flex flow fixes the indent, but the tap
target must stay inside clipped row containers
> - This pull request reserves one mobile gutter for both states and
overlays the unread control inside that gutter
> - The benefit is stable mobile alignment without clipping the control
or blocking the parent-row chevron

## Linked Issues or Issue Description

Related merged work: Refs #9383, Refs #9685, and Refs #9767.

**What happened?**

On mobile screens, an unread Inbox row placed the mark-as-read control
in the flex layout. The extra flex item moved the status and title to
the right. A first overlay position could also place part of the control
outside a row that clips overflow.

**Expected behavior**

Read and unread rows must use the same title and status positions. The
unread control must stay tappable inside the row. A parent-row chevron
must remain independently usable.

**Steps to reproduce**

1. Open the Inbox on a mobile viewport.
2. Compare read and unread rows with the same nesting depth.
3. Include an unread parent row with a collapse chevron.
4. Observe that the unread row content starts farther right than the
read row content.

**Paperclip version or commit**

The issue reproduced on the `master` parent of this pull request.

**Deployment mode**

Local dev (`pnpm dev`).

**Installation method**

Built from source.

## What Changed

- Reserve a mobile leading gutter whenever an Inbox row participates in
unread state.
- Position the mark-as-read control absolutely inside that gutter so it
does not add a flex column.
- Keep read and unread rows on the same mobile padding path.
- Keep the control inside overflow-clipping row containers and separate
from the parent-row chevron.
- Update the `IssueRow` regression test to verify absolute placement,
the internal gutter, and the absence of `order-first` layout.

## Verification

- `pnpm exec vitest run ui/src/components/IssueRow.test.tsx` — 16 tests
passed.
- `pnpm check:token-gates` — all token gates passed across 722 files.
- `pnpm -r typecheck` — passed.
- `pnpm build` — passed.
- `pnpm test:run` — the general server shard passed 3,448 tests and the
UI shard passed 3,362 tests. The CLI shard had one host-environment
failure because inherited temporary AWS access-key variables changed an
AWS doctor assertion from `pass` to `warn`.
- `env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY -u
AWS_SESSION_TOKEN pnpm exec vitest run
cli/src/__tests__/secrets.test.ts` — the affected file passed 8 tests in
an isolated environment.
- Mobile QA at 402 × 874 confirmed equal read/unread title positions, no
shift after marking a row as read, and an independently usable parent
chevron.

## Risks

- Low risk. The change affects only `IssueRow` mobile presentation and
its focused regression test.
- The main risk is breakpoint-specific placement. The component test
covers the responsive classes, and mobile QA covers the rendered
interaction.
- No API, schema, dependency, telemetry, or documentation contract
changes.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex based on GPT-5, with reasoning, repository tools, command
execution, and test execution. The runtime did not expose the exact
deployment ID or context-window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-03 10:20:04 -05:00
Devin Foley 97590ff8c4
feat(dev): add pnpm dev:mobile and dev:both for prebuilt UI preview (#10718)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI is a React SPA served by the paperclip server; the
standard local dev flow is `pnpm dev`, which runs vite in dev mode with
HMR and an unbundled module graph
> - The unbundled dev bundle is hundreds of MB of JS across many
requests, which is fine on a local machine but unusable from a phone or
tablet on slow/lossy links (airplane wifi, mobile data, distant tailnet
peers)
> - Contributors who want to iterate on the board from a mobile device
today have no supported way to preview a small production-shaped bundle
without stopping the dev server and running a one-off `vite preview`
with manual proxy plumbing
> - This pull request adds `pnpm dev:mobile` — build the UI and serve
`ui/dist` via `vite preview` on port 3101, with `/api` proxied to the
running dev server on 3100 — plus `pnpm dev:both` to run both flavors
together
> - The benefit is a supported second flavor of the dev server for
phones/tablets that runs alongside the normal one, without touching the
primary `pnpm dev` flow

## Linked Issues or Issue Description

**Subsystem affected**

ui/ — React + Vite board UI

**Problem or motivation**

The vite dev server serves an unbundled module graph, which is fine on
localhost but unusable from a phone or tablet on a slow link.
Contributors testing responsive behavior on mobile devices have no
supported way to serve a small production-shaped SPA against the running
dev API. Running `vite preview` directly does not work either — the
server's board mutation guard checks that the browser's Origin matches
the request Host, and a preview on a second port would fail every
mutation.

**Proposed solution**

Add two root scripts:

- `pnpm dev:mobile` — build `ui/dist` and serve it via `vite preview` on
port 3101, with `/api` proxied to the API server on 3100.
- `pnpm dev:both` — run `pnpm dev` and `pnpm dev:mobile` together in a
single terminal with prefixed output and shared signal handling.

The vite preview config binds `0.0.0.0`, sets `allowedHosts: true` so it
accepts arbitrary hostnames (LAN, tailnet, ngrok, etc.), and the shared
`/api` proxy forwards the client's original Host header as
`x-forwarded-host`. The paperclip server's mutation guard already
prefers `x-forwarded-host` over `host` when computing trusted origins,
so the browser's Origin becomes trusted automatically.

**Alternatives considered**

- Bespoke node proxy script — works but duplicates what vite preview
already does.
- Loosen the mutation guard to accept arbitrary origins — reduces
security for the primary server for the sake of a dev-only workflow.
- Second server config that binds a second port from the paperclip
server itself — much larger change and mixes runtime concerns with a
dev-tooling convenience.

## What Changed

- New `pnpm dev:mobile` script — build UI then run `vite preview` on
port 3101.
- New `pnpm dev:both` script — run `pnpm dev` and `pnpm dev:mobile`
together via `scripts/dev-both.mjs`, which prefixes each child's output,
propagates SIGINT/SIGTERM, and exits when either child exits.
- `ui/vite.config.ts` — add a `preview` block (port 3101, host
`0.0.0.0`, `allowedHosts: true`, shared `/api` proxy).
- New `ui/src/lib/vite-api-proxy.ts` — extracts the `/api` proxy factory
shared by dev and preview, and forwards the client Host as
`x-forwarded-host` (plus `x-forwarded-proto`).
- New unit test `ui/src/lib/vite-api-proxy.test.ts` covering the
header-injection behavior and the pass-through when no Host is present.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/lib/vite-api-proxy.test.ts` — 3 tests pass.
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `pnpm --filter @paperclipai/ui build` — clean.
- Manual: ran `vite preview` against an echo listener and confirmed the
request arrives with `x-forwarded-host` set to the client Host header
and `x-forwarded-proto: http`. Then ran `pnpm dev:mobile` against the
live dev server and verified board mutations (mark issue read, resolve
recovery action, run routine) succeed from a second-port browser session
that previously 403'd.

## Risks

Low risk. Changes are limited to dev tooling — no runtime code paths, no
server changes, no schema/migrations. The `apiProxy` refactor is a no-op
behaviorally for the existing dev server (same target, same `ws: true`);
the only new behavior is the two `x-forwarded-*` headers, and the server
side already prefers those headers when trusting origins. `dev:mobile`
and `dev:both` are additive; existing `pnpm dev` is untouched.

## Model Used

Claude Opus 4.7 (1M context), extended thinking, tool use (bash, file
edits).

## Checklist

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

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue properties panel can show external objects such as GitHub
pull requests.
> - Those objects are resolved by external-object providers and then
displayed as compact status labels.
> - A GitHub pull request could remain in the fallback `unknown` state
and appear as `Not yet resolved`.
> - That label is confusing when the object is known but has not been
refreshed yet.
> - This pull request refreshes due external objects from the heartbeat
scheduler and improves the unknown-status copy.
> - The benefit is a properties panel that moves from pending refresh to
the real pull request state without a manual refresh.

## Linked Issues or Issue Description

No public GitHub issue exists for this bug. I searched for related
public issues and pull requests using the terms `Not yet refreshed`,
`external objects refresh`, and `external PR status`, and did not find a
duplicate implementation.

**What happened?**

The issue properties panel could show a GitHub pull request as `Not yet
resolved` even when the referenced pull request was valid. The object
stayed stale unless a manual refresh path ran.

**Expected behavior**

A known external object should show pending-refresh copy while it waits
for provider data. When the scheduler refreshes it, the properties panel
should show the provider status such as open, merged, or closed.

**Steps to reproduce**

1. Create or view an issue that references a GitHub pull request.
2. Open the issue properties panel.
3. Observe the external object row before a manual refresh has run.

**Paperclip version or commit**

Current `master` before this pull request.

**Deployment mode**

Local dev and self-hosted server.

**Installation method**

Built from source.

**Agent adapter(s) involved**

Not adapter-specific.

**Database mode**

Not database-related.

**Access context**

Board view.

**Privacy checklist**

I reviewed this description and did not include logs, credentials,
private URLs, internal issue IDs, or PII.

## What Changed

- Added a heartbeat scheduler tick that refreshes due external objects
for active companies.
- Kept manual external-object refresh behavior on the same service path.
- Changed display copy so known provider objects use liveness labels
such as `Not yet refreshed`, while fresh unknown provider statuses show
`Status unavailable`.
- Added server and UI tests for scheduled refresh and label behavior.

## Verification

- `corepack pnpm install --frozen-lockfile`
- `pnpm check:token-gates`
- `pnpm exec vitest run
server/src/__tests__/external-objects-service.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts
ui/src/components/ExternalObjectPill.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/lib/external-objects.test.ts`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/server build`
- `pnpm --filter @paperclipai/ui build`

- `pnpm run typecheck:build-gaps`
- GitHub PR checks passed on head `0e7fcd30`
- Greptile reported 5/5 on head `0e7fcd30` with no unresolved review
threads

Notes:

- I ran recursive typecheck and build first. Both hit container resource
limits with exit 137 during concurrent package work, so I reran the
affected server and UI targets separately.
- An unrelated workspace-runtime auto-port test fails in this container
with a PID ownership mismatch. It is outside the files changed here.

## Risks

Low to medium risk.

The scheduler does more periodic external-object work, so the main risk
is extra provider refresh load. The implementation bounds the work to
active companies, due non-terminal objects, and 50 objects per company
per tick. The path also stays behind the external-objects experimental
setting.

## Model Used

OpenAI GPT-5 Codex in the Codex execution environment, with shell and
GitHub CLI tool use. The runtime did not expose a more specific internal
model ID or context window.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-02 20:24:52 -07:00
Devin Foley 799973f26a
fix(ui): hide empty inbox search sections (#10700)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Inbox helps operators scan issues that need attention.
> - Inbox search can add supplemental sections for archived matches and
other matches.
> - The supplemental search section builder still sent empty sections
into the grouped render path.
> - That made the Archived and Other results dividers appear even when
those sections had no rows.
> - This pull request drops empty supplemental sections before
rendering.
> - The benefit is a cleaner near-empty inbox search view.

## Linked Issues or Issue Description

No public GitHub issue exists for this report. Public GitHub search
found no duplicate or related open issues or pull requests for this
inbox search behavior.

**What happened?**

Inbox search could show Archived and Other results divider headers even
when those supplemental sections had no rows.

**Expected behavior**

Empty supplemental search sections should not render divider headers.

**Steps to reproduce**

1. Open the Inbox.
2. Search in a near-empty inbox with no archived matches and no
outside-inbox matches.
3. Observe that empty supplemental divider headers can appear.

**Paperclip version or commit**

`master` before this change.

**Deployment mode**

Built from source.

## What Changed

- Dropped empty supplemental inbox search sections before they reach the
grouped inbox render path.
- Added a unit regression test for empty Archived and Other results
sections.
- Refreshed the branch against current `master` to clear the merge
conflict.

## Verification

- `git diff --check origin/master...HEAD` passed.
- Public diff is limited to `ui/src/lib/inbox.ts` and
`ui/src/lib/inbox.test.ts`.
- Local focused Vitest could not run in this execution checkout because
dependencies are not installed and `corepack pnpm exec vitest ...`
reports `Command "vitest" not found`.
- Pull request CI is green for typecheck, build, server tests, e2e,
security checks, policy checks, canary dry run, and aggregate verify.
- Greptile Review passed on commit `dc2e224` with confidence score 5/5
and no comments.

## Risks

Low risk. The Inbox change only filters empty supplemental search
sections. Normal inbox sections and non-empty archived or other search
results keep their current behavior.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex, GPT-5, reasoning-enabled with terminal tool use and code
execution. The runtime context-window size is not exposed.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-02 19:05:38 -07:00
scotttong 772fa98393
fix(ui): move IssueRow divider and hover wash to the row root (#10702)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Inbox and Tasks screens render issues as a vertical list of
`IssueRow` components
> - A recent refactor split `IssueRow` into a root `div` plus a full-row
overlay `Link`, and the divider and hover styles moved onto that overlay
> - As a result every row shows a bottom border and hover greys the text
instead of tinting the background
> - This pull request moves the divider and hover/selected wash back
onto the root row band and keeps only positioning on the overlay
> - The benefit is the list reads cleanly again: no stray dividers, and
hover tints the background behind the text

## Linked Issues or Issue Description

No public GitHub issue exists. Describing the bug in-PR (bug report):

**What happened**

- In the Inbox and Tasks list views, every row shows a 1px bottom
border, including the last row.
- Hovering a row dims/greys the row text instead of showing a background
tint behind the content.

**Expected behavior**

- List rows in Inbox and Tasks show no separator lines by default.
- Hover shows a subtle background tint behind the row content; the text
stays fully legible.
- The blocked inbox view keeps its intentional separators.

**Steps to reproduce**

1. Open the Inbox or Tasks list view.
2. Note the horizontal border under every row, including the last.
3. Hover a row and note the text greys out rather than the background
tinting.

**Root cause**

- PR #10526 restructured `IssueRow` from a single root `Link` into a
root `div` plus a full-row `absolute inset-0` overlay `Link` (to keep
header controls clickable). The divider and hover/selected/checklist
background classes moved onto the overlay `Link`. `last:border-b-0` no
longer matched (the Link is the first child of a multi-child div), and
the hover wash painted on top of the content instead of behind it.

**Paperclip version/commit**

- Base commit: `8b83d69e3` (branched from current `master`).

**Deployment mode**

- UI (web) list views: Inbox and Tasks.

## What Changed

- `ui/src/components/IssueRow.tsx`: moved the divider classes and the
hover/selected/checklist background wash from the overlay `Link` to the
root row `div`, so the tint paints behind the content and
`last:border-b-0` matches the real last row. The overlay `Link` now
keeps only `absolute inset-0` positioning and the focus ring. Renamed
the `hideDivider` prop to an opt-in `showDivider` (default `false`).
Kept `[&_button]:relative [&_button]:z-10` on the root so the Archive
button stays clickable above the overlay, and kept the `isArchiving`
collapse animation on the root row.
- `ui/src/components/IssuesList.tsx`: dropped the old `hideDivider`
usage (dividers are now opt-in).
- `ui/src/pages/Inbox.tsx`: dropped the old `hideDivider` usage.
- `ui/src/components/BlockedInboxView.tsx`: added `showDivider` so this
view keeps its separators.

## Verification

- `cd ui && npx tsc -b` — typecheck passes with the change.
- Manual (recommended for reviewer): in the Inbox and Tasks list views,
confirm no per-row bottom border and that the last row has none. Because
dark-mode `--border` is 10% white and near-invisible in screenshots,
assert the computed `border-bottom-width` on a row element rather than
eyeballing pixels.
- Hover a row: text stays legible; a background tint appears behind the
content.
- Inbox: the Archive button appears on hover and is clickable (the
overlay does not swallow the click).
- Blocked inbox view: separators still render.

## Risks

- Low risk. The change relocates existing Tailwind classes between two
elements of the same row and renames one internal prop; no data or API
surface changes. All `IssueRow` call sites were updated in this PR
(verified: no remaining `hideDivider` references).

## Model Used

- Claude, Opus 4.8 (`claude-opus-4-8`), extended thinking with tool use.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-02 12:42:45 -07:00