Files
PaperClipAI/ui/src/components/ArtifactFileChip.tsx
T
DottaandPaperclip e68188c438 fix: upstream deployed document-comment, routine-annotation, workspace & board-polling fixes (#8536)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - This change touches several already-shipped subsystems —
document-comment annotations, the selected-agent Conference Room chat
surface, routine description annotations, workspace-operation tracking,
and the board polling/inbox UI
> - A batch of incremental fixes and two small backend additions had
accumulated on a local mainline and were deployed to a live instance,
but never landed upstream — so each `origin/master` sync kept
re-diverging
> - Leaving them un-upstreamed means the same delta has to be re-merged
on every sync and risks being lost or silently reverted
> - This pull request rebases that delta cleanly on top of current
`origin/master` (preserving recent upstream work such as reusable
sandbox leases and the relation-list collapse controls) and brings it up
for review
> - The benefit is that mainline and the deployed instance converge, and
these fixes/additions get normal review + CI + Greptile coverage

## Linked Issues or Issue Description

No single GitHub issue tracks this; it is a bundle of bug fixes and two
small feature additions. Following the issue-template fields:

**Bug fixes (what was wrong → what this does):**
- Document comments rendered out of document order, didn't live-update
across clients, swallowed save failures, and lost the markdown text
selection on re-render. Now: doc-order sort, live updates, surfaced
save-failure state, stable selection across re-renders.
- The board polling hot path returned oversized payloads on every poll.
Now: an opt-in `summary` projection trims the heartbeat-run list
payload.
- Assorted UI fixes: sidebar nav peek/streamlining edge cases, markdown
file-viewer re-mount/line-height issues on iOS Safari, inbox badge/skill
deep-link tab selection, and `⌘.` work-mode cycling on iOS.

**Feature additions:**
- `workspace_operations.issue_id` — associate a workspace operation with
the issue that triggered it (new migration `0106`, schema, service,
shared type).
- Routine **description annotations** — comment threads on a routine's
description document, mirroring issue document annotations (new
migration `0107`, `routine_documents` schema, routes/service,
editable-sections UI).
- Selected-agent **Conference Room chat** surface wiring and live
issue-thread updates.

**Related PR:** #8229 (`feat(control-plane): add annotation and
workspace controls`, draft) covers overlapping
annotation/workspace-control territory — flagging it so a reviewer can
reconcile the two rather than double-merging.

## What Changed

- `feat(workspace)`: `workspace_operations.issue_id` migration +
schema/service/type; issue workspace property controls reconciled with
upstream's evolved "Service" row.
- `feat(routines)`: routine description annotations —
`routine_documents` schema, migration `0107`, routes/service,
`editable-sections` UI.
- `fix(document-comments)`: doc-order sort, live updates, save-failure
surfacing, stable markdown selection (+ storybook story, rerender test).
- `feat(chat)`: selected-agent Conference Room chat surface and live
issue-thread updates (`LiveUpdatesProvider`, `issue-chat-messages`,
interactions service).
- `perf(board)`: opt-in `summary` projection for the board
polling/heartbeat-run list payload, plus assorted sidebar / file-viewer
/ inbox / IssueProperties UI fixes.

Organized into 5 logical commits. Migrations are numbered incrementally
after upstream's latest (`0105`) — `0106` then `0107`, no journal
collision.

## Verification

- Built by 3-way merging the deployed delta onto current
`origin/master`; the only merge conflict (`IssueProperties.test.tsx`,
two adjacent test blocks) was resolved in favor of upstream's evolved
"green service link above the workspace row" layout, which matches the
merged component's rendered output.
- Confirmed recent upstream work is preserved post-merge: reusable
sandbox lease teardown (`#8513`), the IssueProperties relation-list
collapse controls, and the sidebar streamlined-nav default.
- Confirmed the net diff vs `origin/master` is exactly the intended
feature delta (65 files) and that overlapping server files (`issues.ts`,
`agents.ts`, `heartbeat.ts`) only add feature code without disturbing
upstream logic.
- This delta is already running on a live deployed instance.
- Full typecheck/test suite + Greptile to run in CI (see checklist).

## Risks

- Two new migrations (`0106`, `0107`). Both are additive (new table /
new nullable column) and ordered after upstream's `0105`; no data
backfill, low risk. If another migration-bearing PR merges first,
renumber before merge.
- Largest blast radius is in the merged overlapping UI/service files;
covered by the existing test suites for those files plus CI.
- Overlaps thematically with draft PR #8229 — reviewers should reconcile
rather than merge both blindly.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`), extended thinking, with tool use
(git, shell). Agentic coding workflow.

## 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)
- [ ] 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>
2026-06-23 09:00:14 -05:00

104 lines
3.5 KiB
TypeScript

import type { MouseEvent, ReactNode } from "react";
import { FileCode2 } from "lucide-react";
import { cn } from "@/lib/utils";
import type { WorkspaceFileRef } from "@paperclipai/shared";
import { useFileViewer } from "@/context/FileViewerContext";
export interface ArtifactFileChipProps {
workspaceFileRef: WorkspaceFileRef;
/** Override the rendered label. Defaults to the display path. */
label?: ReactNode;
className?: string;
/** Optional override if the consumer wants to customize activation. */
onOpen?: (ref: WorkspaceFileRef) => void;
showIcon?: boolean;
title?: string;
}
function artifactFileDisplay(ref: WorkspaceFileRef) {
if (!ref.projectName) return ref.displayPath;
const prefix = `${ref.projectName} / `;
return ref.displayPath.startsWith(prefix) ? ref.displayPath : `${prefix}${ref.displayPath}`;
}
export function ArtifactFileChip({
workspaceFileRef,
label,
className,
onOpen,
showIcon = true,
title,
}: ArtifactFileChipProps) {
const viewer = useFileViewer();
const display = typeof label !== "undefined" ? label : artifactFileDisplay(workspaceFileRef);
const canOpen = !!(onOpen || viewer);
const lineSuffix = workspaceFileRef.line
? ` line ${workspaceFileRef.line}${workspaceFileRef.column ? ` column ${workspaceFileRef.column}` : ""}`
: "";
const ariaLabel = canOpen
? `Open ${workspaceFileRef.displayPath}${lineSuffix} in the file viewer`
: `Workspace file ${workspaceFileRef.displayPath}${lineSuffix}`;
const tooltip = title ?? (canOpen
? `Open ${workspaceFileRef.displayPath}${lineSuffix} in the file viewer`
: `Workspace file ${workspaceFileRef.displayPath}${lineSuffix}`);
const classNames = cn(
"paperclip-artifact-file-chip inline-flex items-center gap-1 rounded-sm border border-border bg-muted/60 px-1.5 py-0.5 font-mono text-xs leading-tight text-foreground/90 align-middle no-underline hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
canOpen ? "cursor-pointer" : null,
className,
);
const content = (
<>
{showIcon ? <FileCode2 aria-hidden="true" className="h-3 w-3 shrink-0 opacity-70" /> : null}
<span className="max-w-full whitespace-normal break-all text-left">{display}</span>
</>
);
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return;
}
if (event.button !== 0) return;
if (onOpen) onOpen(workspaceFileRef);
else {
const workspace = workspaceFileRef.workspaceKind === "execution_workspace" ? "execution" : "project";
viewer?.open({
path: workspaceFileRef.relativePath,
line: workspaceFileRef.line ?? null,
column: workspaceFileRef.column ?? null,
workspace,
projectId: workspaceFileRef.projectId ?? null,
workspaceId: workspaceFileRef.projectId ? workspaceFileRef.workspaceId : null,
});
}
};
if (!canOpen) {
return (
<span
data-artifact-file-chip="true"
data-workspace-file-path={workspaceFileRef.relativePath}
aria-label={ariaLabel}
title={tooltip}
className={classNames}
>
{content}
</span>
);
}
return (
<button
type="button"
data-artifact-file-chip="true"
data-workspace-file-path={workspaceFileRef.relativePath}
aria-label={ariaLabel}
title={tooltip}
className={classNames}
onClick={handleClick}
>
{content}
</button>
);
}