mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-12 18:06:11 +02:00
## 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>
104 lines
3.5 KiB
TypeScript
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>
|
|
);
|
|
}
|