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

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

## Risks
Low risk. UI-only change scoped to the command palette; no backend or
data changes. Worst case is palette ordering for project-name queries.
Ranking is pure and unit-tested; the prefix-score length penalty is
clamped so the prefix > substring > description > fuzzy invariant holds
even for pathologically long project names.
## Model Used
Claude — claude-opus-4-8 (Opus 4.8), extended thinking, tool use (Claude
Code agent).
## Checklist
- [x] Thinking path included
- [x] Model used specified
- [x] Checked ROADMAP.md; small self-contained UX change
- [x] Searched GitHub for duplicate/related PRs
- [x] Described the issue in-PR (no public issue exists)
- [x] No internal/instance-local references
- [x] Branch name descriptive, no internal ticket id
- [x] Tests run locally and pass
- [x] Added/updated tests
- [x] Screenshots included for the visual change
- [ ] Docs updated (n/a — no user-facing docs for this)
- [x] Risks documented
- [ ] All CI gates green (verify after push)
- [ ] Greptile 5/5 (drive after push)
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The issue/task chat subsystem renders board and agent comments as
markdown inside chat bubbles.
> - Human board comments use a saturated blue bubble so the message can
be distinguished from agent output.
> - The shared `.paperclip-markdown` rule forced `var(--foreground)` for
body text and related markdown elements.
> - On the blue bubble, that foreground color is too dark, so
paragraphs, lists, links, and emphasis lose contrast.
> - This pull request adds an explicit on-accent markdown treatment for
blue/current-user message bubbles.
> - The benefit is readable markdown without changing neutral markdown
surfaces elsewhere in the app.
## Linked Issues or Issue Description
Fixes#8688
## What Changed
- Added a `paperclip-markdown-on-accent` class for markdown rendered on
accent-colored message bubbles.
- Updated current-user issue chat messages to use the accent-safe
markdown class.
- Added CSS overrides for body text, list markers, headings, emphasis,
links, blockquotes, and inline code on accent backgrounds.
- Added a regression test proving current-user blue-bubble markdown
receives the accent-safe class.
## Before Screenshot:
<img width="816" height="618" alt="Screenshot 2026-06-27 at 14 22 59"
src="https://github.com/user-attachments/assets/2a982326-9ff3-40ce-9955-ceb081d8a2e0"
/>
## After Screenshot:
<img width="821" height="624" alt="Screenshot 2026-06-27 at 15 05 17"
src="https://github.com/user-attachments/assets/4722a0db-9f6a-4775-a7eb-47328c2f71e0"
/>
## Verification
- `CI=true corepack pnpm exec vitest run
ui/src/components/IssueChatThread.test.tsx`
- `CI=true corepack pnpm --filter @paperclipai/ui typecheck`
- GitHub Actions PR workflow passed on latest head
`f645cca3fc9675071c201471c0df938880c0c351`, including Build, Typecheck +
Release Registry, e2e, general test shards, serialized server shards,
Canary Dry Run, and aggregate verify.
- Greptile Review passed with Confidence Score 5/5 on latest head
`f645cca3fc9675071c201471c0df938880c0c351`.
## Risks
Low risk. The new color treatment is opt-in and only applied to
current-user issue chat markdown on accent bubbles. Neutral comment,
document, artifact, and skill markdown surfaces keep the existing
`.paperclip-markdown` colors.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI GPT-5 Codex in Codex desktop, with repository tool use and local
command execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The experimental server info debug view helps local operators
inspect what code a running dev instance is actually serving
> - The view was moved into the account-menu drawer, which only mounts
while that drawer is open
> - That made stale health-query data easier to see after restarts, and
the server was also caching the running commit at process boot
> - A clean commit label alone is incomplete when the checkout has
uncommitted local changes
> - This pull request keeps the drawer health data fresh, refreshes git
metadata on demand, and adds a path-free checkout-state summary
> - The benefit is that the debug view reports restart time, running
commit, and dirty-checkout state without exposing local paths, secrets,
logs, or environment details
## Linked Issues or Issue Description
Fixes: #8752
## What Changed
- `SidebarServerInfo.tsx`: refetch the health query whenever the drawer
opens and poll every 2s while the dev server is active.
- `server-info.ts`: keep `processStartedAt` stable while refreshing git
HEAD through a short TTL cache instead of freezing commit metadata at
module boot.
- Shared health contract/OpenAPI: add `serverInfo.git.localChanges` with
only staged, unstaged, and untracked counts plus safe unavailable
fallbacks.
- `SidebarServerInfo.tsx`: add a `Checkout state` row that renders
clean/dirty/unavailable copy without file paths.
- Tests: cover stale drawer refresh, interval polling, TTL commit
refresh, health response shape, checkout-state count parsing, and
path-free UI rendering.
## Verification
- `npx vitest run server/src/__tests__/server-info.test.ts
server/src/__tests__/health.test.ts
ui/src/components/SidebarServerInfo.test.tsx` -> 3 files / 19 tests
passing.
- `pnpm install --frozen-lockfile --ignore-scripts` -> refreshed stale
workspace links without lockfile/source churn.
- `pnpm --filter @paperclipai/shared --filter @paperclipai/server
--filter @paperclipai/ui typecheck` -> passing.
- `pnpm --filter @paperclipai/ui typecheck` -> passing after the
Greptile test-coverage fix.
- `pnpm check:tokens` -> no forbidden tokens found.
- Local diff scans for obvious secrets, credentials, private URLs, local
paths, and PII patterns -> no matches.
- GitHub PR checks on head `56defd446` -> all green, including `verify`,
canary dry run, e2e, security scans, and Greptile Review.
- Greptile latest summary -> Confidence Score 5/5, 0 new comments; the
prior P2 polling-coverage thread is resolved.
## Risks
Low risk. The UI remains behind the experimental
`enableServerInfoDebugView` flag. The extra git status call is throttled
by the existing server-info TTL and reports only counts, not paths or
file names. If git status is unavailable, the commit row still works and
the checkout-state row shows clear fallback copy.
## Model Used
Claude Opus (claude-opus-4-8), extended thinking, with tool use / code
execution assisted the original stale-metadata fix. OpenAI GPT-5 via
Codex local, with tool use and code execution, added the checkout-state
follow-up, Greptile fix-up, and PR verification.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>