Files
PaperClipAI/ui/src/components/side-panel
DottaandPaperclip 762d680daf feat(ui): upload files directly in task artifacts (#15964)
## Thinking Path

> - Paperclip helps people manage agent work and retain its context.
> - Task documents and attachments keep that context with the task.
> - The Artifacts panel showed output but could not accept a new file.
> - Uploading through a message can require resuming a paused task.
> - Existing document and attachment APIs can save files directly.
> - This PR adds direct uploads and a drop target to the task panel.
> - People can add context while keeping work paused and unassigned.

## Linked Issues or Issue Description

**What existing behavior does this improve?**
The task Artifacts panel and its existing document and attachment APIs.

**Subsystem affected**
ui/ — React board UI. No server, schema, or API changes.

**Current behavior**
The Artifacts panel cannot upload a file. It also excludes files
uploaded by people. The message composer can require a task to resume
before it submits attachments.

**Proposed behavior**
Use the upload icon or drop files anywhere in the open Artifacts panel.
The target includes the header, padding, and existing cards. Files save
directly to the task without a message or a pause change.

**Reason and benefit**
People can prepare a handoff on a paused, unassigned task without
starting agent work. Markdown becomes a task document. Other supported
files use attachment storage.

**Breaking changes**
Files from people now appear in Artifacts alongside agent output.
Promoted attachments still appear once. A repeated Markdown filename
creates a separate document instead of replacing an existing revision.
Existing APIs and limits stay the same.

**Additional context**
Related upload API work: Refs #7472 and Refs #7248. This PR adds the
missing task-panel entry point. ROADMAP.md lists Artifacts and Work
Products as available; this is a scoped UI improvement.

## What Changed

- Add an upload icon with no visible label or background.
- Accept file drops across the full task panel, including the desktop
portal header and mobile tabs.
- Show per-file progress and inline Retry and Dismiss actions.
- Import Markdown under a new document key and retain existing
revisions.
- Include files from people in Artifacts and dedupe promoted
attachments.
- Add component and task-journey stories for empty, populated, loading,
error, mobile, and drag-over states.
- Add real browser tests for persistence, pause preservation, duplicate
document names, full-panel drops, and partial failure recovery.

## Verification

- Five Playwright tests passed against an isolated instance started by
the source `test-drive` command. The document API, attachment storage,
pause holds, downloads, and task UI were real. Only the explicit upload
failure was injected.
- The browser tests verified desktop and mobile uploads, document
reading, persistence after reload, no task comments or runs, header
drops, preservation of original and hidden internal documents, reserved
filename handling, and upload retry.
- 163 focused UI tests passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `pnpm check:token-gates` passed.
- The full Vitest suite is running after repair of an exhausted local
PostgreSQL shared-memory pool. The initial run failed to initialize
PostgreSQL. Six unattached segments had no living creator or last-user
process; removing them restored free slots.
- CI and the current-head Greptile review are pending.

## Risks

- Artifacts now includes user files, so existing tasks can show more
cards.
- Uploads retain the server's authorization, file-type, size, and
revision checks. No migration is needed.
- Binary attachment uploads use the existing API. A transport failure
with an unknown save outcome can retain the existing API's retry
ambiguity.

## Model Used

OpenAI GPT-6 through Codex. The exact runtime snapshot ID and
context-window size were not exposed to this session. The model used
repository inspection, code editing, shell tools, and browser
automation.

## 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>
2026-10-12 08:11:31 -05:00
..

Portable side panel

The components exported by @/components/side-panel are controlled, domain-free navigation primitives. They do not import task data, APIs, routing, feature flags, or persistence.

Composition boundary

  • SidePanelFrame renders docked, sheet, or embedded chrome and applies the selected content layout.
  • SidePanelTabs and SidePanelTab render projected visual tab data and own keyboard navigation, close focus recovery, overflow visibility, and pointer/keyboard reordering.
  • SidePanelLauncher renders caller-provided sections from the same model as either a plus-button popover or the empty panel body.
  • useSidePanelTabs<TPayload> owns serializable open/select/close/reorder/reset state. Its optional onStateChange callback is the persistence boundary; it never reads storage itself.
  • The host adapter owns payload validation, data queries, URL synchronization, content rendering, and persistence scope.

Never put React nodes or fetched content in a persisted SidePanelTabRecord. Project icons and live status into SidePanelTabItem at render time.

SidePanelTabs also accepts appearance="streamlined-task" for the experimental task-detail shell. That appearance preserves the approved pre-rebase Codex-inspired treatment—equal-width text tabs, separated surfaces, edge fades, and hover-revealed close actions—while the default appearance remains master's portable side-panel chrome.

Use headerSize="task-detail" on SidePanelFrame when the panel shares the 60px task breadcrumb row. It keeps the task-detail footer treatment while matching the default portable header height.

SidePanelWindowControls defaults to the production visibility-toggle icon. Streamlined task-detail panels use closeControl="close" for an explicit X while retaining the same close behavior and accessible labeling.

The Streamlined task adapter registers a counted Subtasks tab only when the current task has child tasks. Closing it moves the surface back into the + launcher for the remainder of the session; tasks without children do not advertise the tab.

Registering another page

  1. Define a serializable payload union for that page, such as details | activity | history.
  2. Build stable SidePanelTabRecord<PagePayload> descriptors and initialize useSidePanelTabs with validated state.
  3. Project descriptors into SidePanelTabItem values for the tab strip.
  4. Render active content in the page adapter by switching on payload.kind.
  5. Supply the same launcher sections to the plus popover and empty panel.
  6. If state is persisted, version and scope it in the adapter and pass writes through onStateChange.

Accessibility and review

The tab strip uses the tabs pattern with roving focus, Arrow/Home/End navigation, named close actions, middle-click close, and live reorder/close announcements. Closing the active tab chooses the right neighbor, then the left; closing the final tab sends focus to the launcher. Reduced-motion preferences disable component transitions and smooth overflow scrolling.

Review Navigation/Side Panel in Storybook before changing production adapters. Compare light/dark, minimum/default/wide/maximized, mobile, overflow, empty, launcher, unavailable-resource, and task-composition stories. Tune spacing, radius, contrast, icon size, and motion centrally through the side-panel tokens in ui/src/index.css.