System comments in the experimental chat shell now render as quiet centered
one-liners (tone icon + plain-English title + relative time + chevron) that
expand on click to the full markdown body and structured metadata sections,
instead of large gray paragraphs of raw recovery text.
- Thread presentation/metadata/createdAtIso through TaskChatMessageItem and
the comment adapter (system comments only).
- Fix authorKind ordering: authorType system now wins over a derivable
run->agent linkage, so recovery notices no longer render as agent bubbles.
- New pure classifier system-notice-humanizer.ts covering the recovery
families (claude_auth_required, configuration_incomplete, workspace
validation, no-live-execution-path with recovery-owner extraction) plus a
System update fallback with truncated first sentence.
- New TaskChatSystemNotice component; expanded state reuses the SystemNotice
metadata-section rows via a newly extracted SystemNoticeMetadataSections.
- Collapse is presentation-only; respects presentation.detailsDefaultOpen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chat-style task composers now keep unsent body text per task, matching the
default composer. Adds a shared ui/src/lib/composer-draft.ts (loadDraft /
saveDraft / clearDraft + DRAFT_DEBOUNCE_MS) and wires an optional draftKey
through TaskChatThread into TaskChatComposer: restore on mount/key change,
debounced save on change, explicit clear on successful send (so a
send-then-navigate within the debounce window can't resurrect the sent
message), and preserved draft on failed send.
Body text only — no attachments/mode/assignee, no server sync. The legacy
helper copies in CommentThread/IssueChatThread are intentionally left in place.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The human (blue --liveness-blue) chat bubble set text-white on its
container, but the message body renders through MarkdownBody, whose
Tailwind prose tokens (--tw-prose-body) win over the container color.
prose-invert only flips in dark mode, so light mode showed dark text
on blue while dark mode was already correct.
Add the existing paperclip-markdown-on-accent class (already used by
IssueChatThread for the same accent bubble) to the human-branch
MarkdownBody in TaskChatBubble and TaskChatDescriptionBubble. It maps
prose body/heading/etc. tokens to currentColor in both themes, so the
text follows the bubble's text-white. Agent/neutral bubbles unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render the task identifier (e.g. ) in gray monospace between the
status glyph and the title in the breadcrumb bar. Threads an optional
identifier through Breadcrumb + breadcrumbsEqual, adds a CrumbIdentifier
helper to BreadcrumbBar (font-mono text-muted-foreground design tokens),
and wires issue?.identifier onto the task crumb in IssueDetail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Expand button moves inside TaskChatStatusPill wrapping only the status
line (new onToggle prop); the interstitial row sits outside the hover/
click target
- Drop the Responding icon prepending the interstitial text (lead spacer
keeps column alignment)
- useHeldSelfTalk: a finished update now slides out of the 1lh viewport
after --motion-interstitial-dwell (~4s) with no successor, instead of
sitting until pushed out; same .tc-line-scroll transition, unmounts to
the reserved empty slot
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
useHeldSelfTalk reworked: a finished update stays visible, static and
truncate-ellipsized, until the next update supersedes it or the turn ends
(never fades). Swaps are paced by the new --motion-interstitial-dwell token
(4000ms; deliberately NOT zeroed under reduced motion — it is pacing, not
movement); rapid bursts collapse latest-wins via a queue of one. The
.tc-line-scroll slide now plays ONLY at swap moments (keyed remount); while
streaming, the tail-line offset applies with the transition disabled inline,
killing the per-wrap glide. Lab: responding-burst state + segmented replay
(blank-line-separated updates with gaps) to exercise dwell/swap.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A terminal run with no UI-linked reply comment fell into the unanchored
bucket and out.push(...unanchored) pinned its "Worked · Ns · N tools" row
under every new user bubble. Unanchored turns now interleave into the
backbone at meta.startedAt ?? createdAt (Infinity → tail when no meta yet);
anchored turns and live-turn-last ordering unchanged. 5 regression tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The interstitial row above the gerund status line is now permanently
reserved while a turn is live: text slides in/out of the always-mounted
1lh slot, so the layout above never jumps as messages appear/disappear.
On settle, a turn directly following its own agent's reply bubble
attaches to it (attachSettledTurns, after coalesceSettledTurns): the
"✓ Worked · …" summary renders appended to the bubble's timestamp line
— "2:34 PM · ✓ Worked · 38s · 2 tools" — still expandable to the tool
history. Bubble timestamps are always visible (hover-only reveal
removed). Turns without a reply bubble keep the standalone folded row.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Interstitial self-talk matches tool rows: text-xs wrapper + scoped
.tc-interstitial override (prose-sm sets its own font-size on
.paperclip-markdown, so the utility alone can't shrink it)
- tool-taxonomy: read family Book -> BookOpen, terminal SquareTerminal ->
Terminal, edit SquarePen -> Terminal (per the letter of the board ask;
the lucide link was pasted twice, flagged on the thread)
- Inline issue-reference status glyph in markdown bodies steps down
lg (20px) -> md (16px)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mid-run agent messages are tagged interstitial by the transcript adapter and
render bubble-less/avatar-less in muted text; the run's posted reply keeps its
bubble. Settled history keeps the self-talk interleaved at its streamed
timestamp (deduped against posted comment bodies). New --motion-typewriter-*
tokens drive a CSS-only steps() clip reveal per streamed block, zeroed under
prefers-reduced-motion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
While a run streams, the status line (whimsy/taxonomy gerund + elapsed +
tokens) is now the live turn's single visible row: TaskChatTurnItem gains
liveStatus, TaskChatTurn renders it as an expandable header (chevron, same
grammar as tool rows / the settled Worked line), and expanding nests the
chronological tool/thinking history via the existing .tc-turn-fold motion
with live append while open. On settle the header morphs in place into the
'Worked · …' summary, preserving expand state. Only tool/thinking/usage rows
nest (isNestableLiveChild); mid-run agent text, markers and interaction cards
stay in the thread. Retires the liveTurnFoldedIds window + tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Claude-Code whimsy pool trimmed to Brewing/Tinkering/Distilling/Deliberating
- analog-office pool drops Cataloguing/Carbon-copying/Archiving, adds Trimming,
Aligning, Combining, Whiteboarding, Diagramming, Sketching, Labeling, Sticky-noting
- Grep becomes its own informative family: Grepping + search-code icon (never whimsified)
- taxonomy icons: Search, Book, SquarePen, ChevronsLeftRightEllipsis, Network; MCP
tools now use the official MCP logo (McpIcon) instead of Plug
- Thinking/Responding statuses get Brain / MessageSquareReply glyphs in the live pill
- MDXEditor image chip: settings button removed, delete uses lucide X
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds ui/attachment.tsx (base-lyra registry port, @base-ui/react — the
current name of @base-ui-components/react) plus the shimmer /
scroll-fade-x / scrollbar-none utilities it expects. Composer chips get
kind icon, name, size, remove x, uploading/error states; file references
now ride as trailing [name](contentPath) lines appended at submit
(MDXEditor insertMarkdown collapses paragraph breaks, so no in-editor
insert), and an attached chip alone enables send. Posted bubbles fold
link-only reference lines back into the same chips (task-chat-attachments
extractor); prose-woven links keep their sentence. MarkdownEditor's
imperative insertMarkdown now focuses before inserting — it silently
no-oped on a never-focused editor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rich composer inside the v7 comp-bar chrome: @-mentions (wired through
TaskChatThread, previously dropped), data-driven /-command autocomplete via
EditorAutocompleteContext, rich lists, and inline pasted/dropped images at
the caret through the editor's image plugin -> issue attachments POST.
Non-image files keep the chip row; the round-13 object-URL thumbnail path
is superseded. Cmd/Ctrl+Enter posts natively; Shift+Tab mode-cycling is
captured on the wrapper ahead of Lexical. Motion-gate comment fix in
TaskChatStatusPill (doc said 100ms).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live (unsettled) turns keep the most recent 3 activity rows visible; older
completed/failed tool rows and finished thinking blocks fold into an
expandable group. Each live row gets its own .tc-turn-fold wrapper so rows
entering the fold animate with the same --motion-turn-fold grid-rows motion
(zeroed under prefers-reduced-motion). In-flight tools, messages, and other
non-activity rows never fold and end the foldable prefix. Settled-turn
summary behavior unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
buildTurnSummary now dedupes tool_call entries by toolUseId (with the
parser's tool-${i} fallback) so the folded 'Worked · N tools' line matches
the expanded row count. Composer attachments carry an object-URL preview
created at attach time, rendered as a size-10 thumbnail in the chip and
revoked on clear/unmount.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ACP emits tool_call (title "Terminal") → retitle-to-invocation → generic
tool_call_update lines that acpx fills with the literal "tool call". The
task-chat adapter replaced the whole tool item per update, so settled rows
degraded to "Tool call" + Wrench.
- transcript-adapter: merge updates instead of replacing; first real name is
the call's identity, a retitle becomes the mono target, generic names never
displace real ones (rows, status pill, tool_result upgrade path)
- tool-taxonomy: isGenericToolName() shared helper; classify multi-word ACP
titles by first word ("Read File" → read family)
- summarizeToolInput: ignore the { text, status } keys the log parser
synthesizes onto inputs
- acpx-engine emitRuntimeEvent: remember each call's first real title and
stamp it onto generic update lines so stored logs keep names durably
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Settled run turns now anchor after the run's last comment instead of
before its first, so the folded "Worked · …" line appears where the
live "Running…" pill sat — the pill reads as being replaced by the
summary in place.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- new shared tool-taxonomy.ts (family/icon/verb per raw ACP tool name,
mcp__ prefix collapse) used by the status pill, chat tool rows, and the
classic transcript's previously hardcoded Wrench sites
- deriveRunStatusLabel: tail tool_call now yields the taxonomy verb
("Searching", "Running a command") with "Tool · target" detail and
the raw toolName so the pill shows the family icon beside the pulse dot
- live status line: label/elapsed/detail/tokens share one baseline row
- TaskChatComposer: Shift+Enter posts, plain Enter inserts a newline
- tests: tool-taxonomy unit tests, deriveRunStatusLabel/toolDisplayName
tests, composer Enter-behavior tests updated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The running/working status line now shows elapsed time directly to the
right of the label (was pushed to the far right edge) and renders it
with one decimal (24.3s) so the readout visibly moves. The pill ticks
itself every 100ms from the run's start timestamp (new startedAtMs on
TaskChatStatusItem), replacing the 1s whole-thread re-render tick in
TaskChatThread.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Properties/Plan/Artifacts strip portaled into the pane header floated
its active underline ~6px above the header's border-b (list padding + the
bar's py-2). Move the bar's vertical padding onto the window-controls
cluster, stretch the portaled strip to the header's full height, and pin
the trigger underline to bottom-0 so it sits on the border line.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The header's Ask/Plan badge read as a task-global setting, but mode is a
per-request choice: with the redesign flag ON the badge is gone from the
issue header, and each agent reply's avatar row now shows the work mode
that was in effect when its run started, reconstructed from the activity
feed's workMode change history (issue.updated details + _previous).
Flag OFF is unchanged: the legacy header badge stays and the new
workModeChanges prop is ignored by IssueChatThread.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Revert the textless gradient capsule in TaskChatBubble to the legacy
Avatar + AgentIcon treatment: the adapter now maps the author agent's
assigned icon (agentMap icon) onto the message item, with an initials
fallback when no icon is set.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flag-gated (enableTaskChatRedesign), flag OFF unchanged:
- Extra scrollbar: with the flag ON the page content used a fixed
calc(100dvh - 8rem) thread height inside the padded outer <main>
(overflow-auto), so main itself also scrolled. The IssueDetail root now
fills main exactly (h-full flex column) and the chain down to the thread
is flexed with min-h-0, so the thread's internal viewport is the only
scroll surface. TaskChatThread's dvh height token still applies on
mobile, where main has no fixed height (flex-basis falls back to it).
- Pane-tab underline: the portal into the pane header bar kept React
context but broke the DOM ancestry that Tailwind's
group-data-[orientation]/tabs selectors need, so the active-tab
underline never applied. A display:contents group/tabs wrapper inside
the portal restores it.
- Conditional tabs: Plan/Artifacts triggers render only when the issue
has a plan document/accepted decompositions or attachments (same query
keys as the tab bodies — shared cache). With neither, the header bar
shows a plain "Properties" title and the pane body is the plain
properties stack. Tabs are controlled with a fallback to Properties if
the selected tab's content disappears.
Verified on :3093 light+dark via Playwright: main scrollHeight ==
clientHeight (923/923) on //; underline opacity 1 on
active portaled tab; Properties|Plan on , all three on ,
title-only on ; portaled tab clicks switch content; flag-off run
scrolls the legacy page (4454px) with no redesign testids. ui tsc clean;
issue-properties/task-chat/IssueDetail/IssueChatThread tests pass (1
pre-existing TZ flake in IssueProperties monitor row, fails on base).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flag-gated (enableTaskChatRedesign), flag OFF byte-identical:
- Properties | Plan | Artifacts tab strip portals from IssueProperties into
the pane's header bar (new slot left of the maximize/close controls).
- Center Chat/Activity/Related-work tab strip hidden — the thread is the page;
deep links that switched tabs stay on chat.
- Issue header (parent breadcrumb, title/badge block, plugin toolbars, load-
earlier button) renders INSIDE the thread scroll viewport, so it scrolls
away with the messages instead of staying pinned.
- --tc-thread-max-h reserve 21rem → 8rem: composer now sits ~26px above the
viewport bottom.
- New IssuePlanConfirmationActionBar: while a plan confirmation is pending,
its Confirm/Decline CTAs (with decline-reason flow) pin to a footer slot
below the pane's scroll area on the Plan tab.
Verified: ui tsc clean; motion gate (25 files) + token gates clean; 218 tests
across task-chat/pane/IssueDetail/IssueChatThread (1 pre-existing TZ flake in
IssueProperties monitor row, fails on base); Playwright on :3093 light+dark —
tabs in pane header, header scrolls away/returns, thread bottom 923/950, plan
bar pinned at pane bottom in footer slot, zero page errors; flag-off run shows
legacy tabs/pane title, 0 redesign testids.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flag-gated under enableTaskChatRedesign; flag-off byte-identical.
- TaskChatBubble: remove the tc-cursor caret from streaming message
bubbles (thinking-block caret and the blink token stay).
- PropertiesPanel (redesign variant): drop the "Properties" header
title that duplicated the tab label; header is now a right-aligned
maximize/close control row. Legacy flag-off header untouched.
- IssueDetail: flag-ON shell wrapper goes full-width and each
non-thread section re-centers itself at the 60rem cap, so the
thread's scroll viewport (which already centers its column) reaches
the properties pane; the chat tab extends under main's horizontal
padding so the scrollbar sits flush against the pane border
(gap 99-103px -> 0px light / 8px dark gutter, measured).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All flag-gated behind enableTaskChatRedesign; flag OFF stays byte-identical.
- --tc-shell-max-w (60rem) token; IssueDetail wrapper + skeleton become
mx-auto w-full max-w-(--tc-shell-max-w) when the flag is on, and the
thread body + pinned composer caps switch to the same token
- interaction kind added to the task-chat model; TaskChatThread merges
interactions chronologically (same sort keys + same-run handoff shift
as buildIssueChatMessages) and renders them via TaskChatInteractionCard,
a v7-grammar wrapper over the shared IssueThreadInteractionCard
(expired confirmations demote to a marker row)
- useIssuePlanDocument hook (404 → null) shared by the Plan tab, which
now renders the plan document via MarkdownBody with a revision line
above the decompositions, and by the thread's Plan updated/created
marker
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Close the gaps between the flagged redesign and the accepted v7 prototype,
verified against the v7 artifact side-by-side and a REAL streaming agent run:
- Bubbles: human = --liveness-blue #2563eb (was bg-primary near-black) white
text right; agent comment = borderless gray --bubble-agent bubble (new token,
light+dark) with v7 header (capsule avatar · bold name · one capitalized
mode chip). Bubble bodies now render markdown via MarkdownBody (code chips,
links, mentions) instead of raw text.
- Composer: per-mode v7 placeholders ("Message {agent} — describe what you
want done…"), neutral focus-within ring, borderless + attach iconbtn,
full "Agent/Plan/Ask mode" label on the status-chip trigger, icon-only
32px ArrowUp send with hover scale.
- Full-page chat shell: with enableTaskChatRedesign ON, IssueDetail gates the
legacy title (compact single-line editor instead) / description / sub-tasks
table / plan decompositions / Documents (pinned plan) / Output / attachments
wall / workspace card / artifact chips — thread owns the center column,
bounded by new --tc-thread-max-h so TaskMessageScroller owns scrolling with
the composer pinned (scroll viewport = absolute inset-0; percentage heights
don't resolve against flex-sized block parents). Flag OFF proven legacy:
0 redesign testids, all legacy sections present.
- Tool rows (v7 flat grammar): [glyph · name · mono target · hover chevron ·
status]; transcript adapter now emits real tool names (toolDisplayName —
mcp__ collapse + capitalization, tool_result.toolName backfill), summarized
primary params (summarizeToolInput probes file_path/command/pattern/…),
clipped mono result detail, generic "Tool" fallback for legacy stored logs.
- Streaming states: shimmer (.shimmer-text-muted variant) on live Thinking…
header + flat pulse-dot statusline (running/working no longer card chrome);
thinking settles to "Thought for Ns" (ts-span derived) and arrives collapsed
in settled history; turn summary label + mono metrics per v7 runsum.
Verified: motion gate (24 files, seams), token gates, workspace tsc, 177 ui
tests incl. task-chat/PropertiesPanel/IssueChatThread/IssueDetail; live
streaming captured on a real claude_local run on the worktree instance
(:3093 scratch task) — thinking/text deltas, live statusline with elapsed,
auto-follow, and fold-to-summary on completion; flag-off isolation screenshot.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This reverts commit 11e56654f8.
#10786 ported the onboarding flow from a standalone prototype and
repointed `/onboarding` at the new `CloudOnboardingFlow`, deleting the
existing `OnboardingWizard.tsx` in the process. The ported flow is not
ready to be the shipping onboarding experience: it landed as a single
large port rather than an incremental migration, it pulled `motion`,
`three` and `@types/three` onto the UI dependency list for prototype
visuals, and it deleted the wizard that four in-flight pull requests
(#9900, #9501, #8982 and one more) were building on — those went
CONFLICTING the moment the file disappeared.
Rather than keep the half-migrated state on master while that is sorted
out, back the port out whole and re-land it incrementally. This restores
`OnboardingWizard.tsx` and the previous versions of the four e2e specs,
drops the `onboarding-preview.html` Vite entry, the DesignGuide
onboarding section and the `data-viz-misc` storybook story, and removes
the three prototype dependencies from `ui/package.json`.
This is an exact mechanical inverse of the squash commit — 41 files,
+2089/-3647, no hand edits. Reverting this commit restores all 41 files
byte for byte, so the port is recoverable in full when it is ready.
`pnpm-lock.yaml` is deliberately not touched. #10786 never updated it;
bot commit 4683f26c9 (#11036) added the `motion`/`three` entries
afterwards, so the lockfile is now ahead of the manifest. CI owns
lockfile updates (`.github/workflows/pr.yml`) and the policy job
regenerates it from the changed manifest.
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Apps subsystem connects company tools through governed provider
connections.
> - A company can need more than one account for the same provider.
> - The current database constraint and Apps flow assume one named
connection per company.
> - New quarantined actions also need an explicit review decision before
activation.
> - This pull request supports multiple provider connections and
complete action review decisions.
> - The benefit is safer access control and a clear multi-account Apps
workflow.
## Linked Issues or Issue Description
Refs: #11040
**Subsystem affected**
Cross-cutting. This change affects the Apps UI, the tool access API, the
shared request contract, and the database schema.
**Problem or motivation**
The connection name constraint prevents a company from keeping more than
one connection for a provider. The Apps UI also reuses an existing OAuth
connection when a user asks to connect another account. Action review
can enable selected entries without recording a decision for every
quarantined action.
**Proposed solution**
Remove the company and connection name uniqueness constraint. Let users
open, count, edit, and create multiple provider connections. Require the
finish request to cover every quarantined action exactly once before the
server activates reviewed entries.
**Alternatives considered**
The UI could generate unique internal names and keep the database
constraint. This would preserve a one-connection assumption in the data
model and would make display names part of identity. The server could
also infer review decisions from enabled actions. This would not
distinguish a reviewed disabled action from an action that the user did
not review.
**Roadmap alignment**
This change extends the completed MCP Tool Gateway and Apps milestone.
It also supports the Connected Apps roadmap item. It follows the
navigation and connection management work in #11040.
## What Changed
- Remove the company-scoped connection name uniqueness index with an
ordered and idempotent migration.
- Add a reviewed action list to the finish-app contract and reject
incomplete or duplicate review decisions.
- Activate reviewed entries and keep unreviewed quarantined entries
blocked.
- Enable a completed connection and preserve the company and connection
scope in all updates.
- Show provider connection counts and open the provider setup page from
Browse.
- Let users edit existing connections or connect another account without
reusing an active OAuth connection.
- Update focused server and UI coverage for multiple connections and
action review.
## Verification
- Ran the focused Apps UI suite. All 116 tests passed in 11 files.
- Ran the focused server and CLI suite. All 276 tests passed in 3 files.
- Ran `pnpm --filter @paperclipai/db check:migrations`. The migration
safety check passed.
- Ran `pnpm -r typecheck`. All projects passed.
- Ran `pnpm build`. All projects built successfully.
- Ran `pnpm test:run`. It passed 3,735 tests and skipped 4 tests. One
worktree-safety assertion failed because the execution workspace reloads
its worktree marker. The same test passed with an isolated non-worktree
marker.
- Ran `pnpm check:token-gates`. It reports 12 existing violations in the
unchanged `PaperclipOrbit3D.tsx` file from the target branch.
- Started the six affected Playwright specifications. Chromium could not
start because the host does not provide `libatk-1.0.so.0`. The GitHub
e2e jobs will verify these specifications.
- GitHub Actions passed every final-head CI gate, including all three
e2e shards and the aggregate `e2e` and `verify` jobs.
- Greptile reviewed final commit `9af9200426` at 5/5 with zero review
threads.
## Risks
- Removing the name uniqueness index permits duplicate display names.
Stable connection IDs and UIDs remain unique within a company.
- The finish-app endpoint accepts the new review field as optional for
backward compatibility. When clients send it, the server requires a
complete decision for all quarantined actions.
- Multiple OAuth connections depend on the explicit new-connection route
flag. Focused tests cover active and draft connection reuse.
- The migration is ordered after migration 0210. Its `DROP INDEX IF
EXISTS` statement is safe to repeat.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex with the `gpt-5.6-sol` model assisted this change. The
agent used repository tools, code execution, test execution, and agentic
reasoning. The Codex runtime manages the context window.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Apps UI manages app discovery and app connections.
> - The managed worktree runtime starts agent work in repository
worktrees.
> - The Apps routes do not match the main discovery flow, and the
connections view lacks a delete action.
> - Legacy managed worktrees can also start before their pending seed
operation runs.
> - This pull request makes app discovery the main Apps route and makes
connection management explicit.
> - It also seeds legacy managed worktrees before runtime startup and
makes the CLI read the repository-local config.
> - The benefit is a clearer Apps workflow and a safer managed-worktree
startup path.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
This improves the Apps navigation, app connection management, managed
git-worktree startup, and CLI worktree selection.
**Subsystem affected**
Cross-cutting. The change affects `ui/`, `server/`, `cli/`, and
development documentation.
**Current behavior**
The `/apps` route opens the connections list while discovery uses a
nested route. The connections list has no delete action. Some legacy
managed worktrees can start runtime work before their pending seed
operation runs. The CLI can also read an ambient Paperclip config
instead of the repository-local config.
**Proposed behavior**
The `/apps` route opens Browse, and `/apps/connections` opens the
connection list. Users can delete a connection after confirmation.
Runtime startup seeds legacy managed worktrees when required. The CLI
resolves the current worktree from the repository-local
`.paperclip/config.json` file.
**Reason and benefit**
Users can discover apps from the canonical Apps route and can manage
existing connections from a dedicated route. Legacy worktrees receive
their required repository content before agent runtime starts. CLI
worktree selection stays scoped to the current repository.
**Breaking changes**
The `/apps` and `/apps/browse` route behavior changes. Old Browse links
redirect to `/apps`. The change does not modify an API schema or
database schema.
## What Changed
- Make Browse the canonical `/apps` page and move the connection list to
`/apps/connections`.
- Align Apps navigation, redirects, attention links, empty states, and
connection actions with the new routes.
- Add connection deletion with confirmation and clear failure feedback.
- Seed legacy managed git worktrees before runtime startup when their
seed status is pending.
- Read the CLI worktree selection from the repository-local Paperclip
config.
- Update focused UI, server, CLI, and development documentation
coverage.
## Verification
- Ran 202 focused UI, server, and CLI tests. All tests passed.
- Ran `pnpm -r typecheck`. All projects passed.
- Ran `pnpm build`. All projects built successfully.
- Ran `pnpm test:run`. The server and UI stages passed 7,168 tests. The
CLI stage found one environment-sensitive secrets test because this
workspace injects static AWS credentials. The isolated CLI file passed
all 8 tests after those injected variables were unset.
- Ran `pnpm check:token-gates`. It reports 12 existing color-token
violations in the unchanged `PaperclipOrbit3D.tsx` file from the target
branch. This pull request does not modify that file.
- Ran focused regression coverage for repository-root CLI config
resolution and connection deletion state. All tests and affected package
typechecks passed.
- Collected all 27 tests in the six changed Playwright specifications
successfully.
- GitHub Actions passed every latest-head CI gate, including all three
e2e shards and the aggregate `e2e` and `verify` jobs.
- Greptile reviewed the final commit at 5/5 with zero unresolved
threads.
## Risks
- Existing bookmarks for `/apps/browse` redirect to `/apps`.
- Connection deletion changes visible connection state and requires user
confirmation.
- The legacy seed path runs only for managed git worktrees with pending
seed state. Tests cover the startup condition.
- The rebase preserves the target branch's direct OAuth policy for the
Notion connection flow.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
OpenAI Codex with the GPT-5 model family assisted this change. The agent
used reasoning, repository tools, code execution, and test execution.
The runtime does not expose the exact model snapshot or context-window
size.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The task list and the chat views show a Live badge and a Working
shimmer for an issue that has an active run.
> - A finished task kept the Live badge and the Working shimmer after
the run ended and the sandbox stopped.
> - The user interface reads run liveness from the
`heartbeat_runs.status` row. The run finalizer writes the terminal
status in a step that is separate from the agent `status=done` update.
When the sandbox or the run process stops between the two steps,
`heartbeat_runs.status` stays `running` forever.
> - A run row that stays `running` makes a finished task look
perpetually Live, and the user interface has no guard for an issue that
already reached a terminal status.
> - This pull request closes the invariant "environment lease released
implies the run is terminal" on the server, and adds a user interface
guard that suppresses live state for a terminal issue.
> - The benefit is that a finished task stops showing Live and Working,
both at the source (the run row) and at the surface (the badge and the
shimmer).
## Linked Issues or Issue Description
**Bug description**
- A completed task kept the Live badge and the Working shimmer after its
run ended and the sandbox was torn down.
**Steps to reproduce**
- Run an agent task to completion. Let the sandbox tear down while the
run finalizer is between the `status=done` update and the terminal
run-status write.
- Open the task list or the chat view for the finished task.
**Expected behavior**
- A finished task shows no Live badge and no Working shimmer.
**Actual behavior (before this change)**
- The finished task showed the Live badge and the Working shimmer
because its `heartbeat_runs.status` row stayed `running`.
This pull request supersedes the two separate pull requests #10954
(frontend) and #10955 (backend). It carries all of their changes for the
same race.
## What Changed
Server:
- Run teardown terminalizes a still-running or still-queued run before
it releases the environment lease. It writes `succeeded` when the issue
already reached `done`, `cancelled` when the issue is `cancelled`, and
`interrupted` otherwise. It never overwrites a status that another path
already made terminal.
- The recovery stale-lock sweep terminalizes an orphaned running run to
`interrupted` after it confirms the process and the sandbox are both
gone. It requires recorded process metadata, so it never terminalizes a
live run, a queued run, or a scheduled retry.
- Each terminal transition writes a run event.
- The stale-lock sweep continues and clears the lock when the audit
write fails. It logs the failure loudly.
- New server tests cover both invariants.
User interface:
- A shared guard suppresses the Live badge and the Working shimmer when
the issue status is terminal.
- The guard keeps non-terminal `queued` and `running` issues live.
- The guard prefers the newest issue live-status snapshot.
- New user interface tests cover the guard and the snapshot preference.
## Verification
Server:
- `pnpm --filter @paperclipai/plugin-sdk ensure-build-deps && tsc
--noEmit` in `server/` — 0 errors.
- `pnpm --filter server test
heartbeat-run-lease-release-terminalization.test.ts
recovery-stale-issue-lock-sweep.test.ts` — 12 tests pass.
User interface:
- `pnpm --filter @paperclipai/ui typecheck` — 0 errors.
- `pnpm exec vitest run ui/src/lib/liveIssueIds.test.ts
ui/src/lib/issue-chat-messages.test.ts` — 40 tests pass.
## Risks
- Low risk. The server change only forces a still-live run row to a
terminal status when the lease releases or when the recovery sweep
confirms the process is dead. It never overwrites an existing terminal
status, and it guards the recovery path with process metadata to avoid
terminalizing a live run.
- The user interface change is additive. The guard only suppresses live
state for a terminal issue and keeps queued and running issues live.
- No database migration. No change to any external endpoint.
## Model Used
- Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, tool use, and
code execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>