Files
PaperClipAI/DESIGN.md
T
DottaandPaperclip 52811c6ce6 fix(tasks): require resume before sending to paused tasks (#13232)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Task execution controls let board users pause a task or its subtree.
> - The composer still accepted messages while a pause hold was active.
> - A paused task must require an explicit resume before the user can
send another message.
> - This pull request replaces the composer with an amber pause card and
checks board comment writes on the server.
> - The user keeps their draft and resumes through the existing task
controls.

## Linked Issues or Issue Description

Refs #13104. Refs #13119.

**What existing behavior does this improve?**

The task composer and existing task/subtree pause controls.

**Current behavior**

A paused task can still receive a board message. The pause notice sits
outside the composer, which leaves the send action available.

**Proposed behavior**

Show an amber takeover in both task chat and the classic composer.
Preserve the draft. Require the user to resume the task or the ancestor
subtree before sending. Reject board comment writes through either
supported write route while the pause hold is active.

**Breaking changes**

Board comment writes to a paused task now return HTTP 409. Agent run
reports remain supported during a pause. There is no schema migration.

## What Changed

- Add a shared amber composer takeover with task, subtree, saved draft,
pending, and error states.
- Use effective ancestor pause state in both composer interfaces.
Refresh it after pause events, task updates, and rejected sends.
- Preserve draft text and attachments. Hide editor, send, queued edit,
and pending question controls while paused.
- Check active pause holds before board comment writes can mutate tasks,
store comments, or wake agents.
- Connect the approved Storybook examples to the production component
and update the design and behavior docs.
- Add browser coverage for both composers, draft persistence, resume,
inherited holds, and rejected writes. Update ACP continuation coverage
for the explicit resume requirement.

## Verification

- Passed: `pnpm -r typecheck`.
- Passed: `pnpm build`.
- Passed: `pnpm build-storybook`.
- Passed: `pnpm check:token-gates` and `git diff --check`.
- Passed: focused UI tests (398 tests) and server route tests (127
tests).
- Passed: `pnpm exec playwright test --config
tests/e2e/playwright.config.ts tests/e2e/paused-composer.spec.ts
tests/e2e/acp-stop-continuation.spec.ts` (5 tests).
- Passed: manual browser walkthrough in a disposable local instance.
Pause with a draft, refresh while paused, resume, send, and reopen. The
draft returned, and one message persisted. The amber card and resume
dialog were readable with no clipping.
- Full local `pnpm test:run` did not pass: the general-server stage
recorded 9,072 passing tests, 6 database setup failures from macOS
shared-memory exhaustion, and 4 failed tests. This stopped the script
before its later groups. Latest-head CI runs those groups independently.
- Local follow-up: the Git file-resource load test passed on rerun (4
tests); native finalization migration passed after clearing the
abandoned browser-test database allocation. Building the native debug
fixtures fixed the missing fake provider. The remaining native-session
recovery assertion also reproduces on untouched base commit `87b3e5fc6`
(36 pass, 1 fail on both base and PR). It expects a settled-session
error but receives a semantic-input-digest error.
- The final UI build, UI typecheck, token gates, both thread suites (182
tests), and all five browser tests passed after the queued-action review
fix. All 31 latest-head CI checks passed, including all server,
workspace, browser, build, release, and security gates. Two optional
Storybook jobs were skipped by workflow policy. Greptile reviewed
`32d8fb5f5` at 5/5 with no open findings.
- Review the Paused Composer and Tasks / Execution Controls stories.
Pause a task with a draft, verify the amber card, resume, and verify the
draft can be sent once.

## Risks

- Clients that used board comments to continue paused work must resume
first. The response is an explicit HTTP 409.
- Pause state can change while a page is open. Live updates refresh the
composer, and the server rejects stale sends before their side effects.
- Resume keeps the existing dialog and optional agent wake behavior.
Agent reports from interrupted runs remain allowed.

## Model Used

OpenAI Codex, based on GPT-6, assisted with design, implementation, code
execution, and browser verification. The exact runtime model ID and
context window are not exposed in this session. The agent used reasoning
and tool calls.

## 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 the relevant tests locally and they pass; the full
local-suite limits are documented above
- [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>
2026-09-11 10:28:00 -05:00

10 KiB

Paperclip Design Principles

Status: v0.3 — anchor document for design-language simplification. Governs structure, not brand. Brand values (color, type, iconography) are intentionally unspecified: they are being redesigned and will land as token values only. Nothing in ui/ may hardcode them. Spacing/radius scales are likewise TBD pending the token audit (see Principle 3).

Changes from v0.2: token layer location corrected to the repo's real source (ui/src/index.css); existing token tiers inventoried; snapshot-coverage scope bounded for Run 1; the issue→task copy rename moved out of the zero-visual-change run.

What this document is for

Agents and humans modifying ui/ treat this file as the source of truth for design decisions. Storybook is the verification surface — it documents the system; it does not define it. If a change conflicts with this document, change this document first (with review) or change the code.

Product stance

Paperclip is an operational control plane: org charts, tasks, heartbeat runs, budgets, approvals, audit logs. The user is an operator scanning state and making decisions. Every screen should answer, in order: what is happening, does it need me, what do I do about it. Density in service of scanning beats whitespace in service of aesthetics — but density comes from information, never from chrome.

The token layer (where visual values live)

The single token source is ui/src/index.css (Tailwind v4; there is no tailwind config file — tokens are CSS custom properties consumed via @theme). Do NOT create a parallel token source such as ui/src/tokens/ — that would produce two sources of truth. If index.css grows unwieldy, extracted values may live in a tokens.css imported by index.css so the pipeline still has one root.

Tailwind v4 gotcha: @theme inline bakes literal values at build time. Any token that must be runtime-tunable (theme editor, dark mode overrides) must be defined in a NON-inline block.

Existing tiers already in index.css (~80+ tokens) — extraction maps to these on exact value match before minting anything new:

  1. Semantic tier — shadcn core set: --background, --foreground, --card, --primary, --secondary, --muted, --accent, --destructive, --border, --input, --ring, --sidebar-*, --chart-1..5 (OKLCH, light/dark overrides).
  2. Brand tier — agent gradients --agent-1a/1b..10a/10b (fixed hex) and status hues --status-task-* / --status-agent-* (WCAG-tuned; see inline comments).
  3. Domain tier — match-chip tokens --chip-match-*, annotation highlights --paperclip-doc-annotation-highlight-*, plus motion/typography tokens.

Principles

  1. One way to say each thing. One component per job. One Button, one Card, one Badge, one Table, one EmptyState. Variants are props, not new components. Before creating a component, prove no existing one covers the job.
  2. Tokens are the only source of visual values. All color, spacing, radius, type size/weight, shadow, and motion values come from the token layer. No hex, no raw px, no ad-hoc Tailwind arbitrary values (p-[13px]) in components. If a needed value doesn't exist, add a token — don't inline it. Tailwind palette classes (bg-red-500, text-zinc-400, etc.) ARE hardcoded values in spirit: they name a literal color, not a semantic role. They are in-scope debt scheduled for a dedicated future run (Run 4, cluster-by-cluster mapping to semantic tokens per doc/design/DECISION-SHEET.md B2) and are not currently gated by check-token-gates. Exception (doc/design/DECISION-SHEET.md B1 user ruling): first-party intentional one-off decoration on demo/UX-lab surfaces stays inline and allowlisted rather than minted as singleton tokens.
  3. Spacing routes through tokens; the scale comes later. During simplification, extract every spacing and radius value verbatim into tokens — do not normalize, round, or invent a scale. The final scale is a design decision made by a human after reviewing the token audit. Structural rules apply now: vertical rhythm within a container uses one gap value, not per-element margins, and siblings never carry both margin and gap.
  4. Hierarchy through structure, not decoration. Prefer position, size, and weight over borders, backgrounds, and dividers. Every border, divider, and background fill must justify itself; when in doubt, remove it. A screen should survive the removal of one visual layer.
  5. Status is systematic. States like running / paused / blocked / awaiting-approval / over-budget map to a single semantic status token set used identically everywhere (badge, row, chart, log). An operator learns the vocabulary once.
  6. Machine values look machine-made. IDs, costs, token counts, timestamps, and log output use the monospace token and consistent formatting helpers. Never format these ad hoc per screen.
  7. Words are part of the system. One name per concept across the entire UI — the canonical term is task (never issue or ticket in copy, labels, or empty states). Buttons name the action ("Approve hire," not "Submit"). Errors say what happened and what to do. Empty states say what to do first. Note: enforcing the task rename is a visible change and is explicitly OUT of the zero-visual-change extraction run; it happens in its own follow-up run.
  8. Agent-modifiable by design. The system must be changeable via instructions: single token source, lint rules that enforce it, and this document kept current. A correct change should be expressible as "edit tokens + run checks," not "visit 40 files."

Contextual feedback

Do not show a toast for task or run state already visible on the current screen. This includes descendant runs represented by the open subtree. Show local action results in place; keep failures actionable inline. Notifications for other work remain useful. Expected cancellation is neutral gray, not an error. A paused task replaces the composer with an amber takeover. It says “Task is paused.” and “Resume this task to send a message.” with a “Resume task” action. Subtrees use “Subtree is paused.” and “Resume subtree.” The takeover cannot be dismissed, retains drafts, and hides message inputs until the pause is released.

Enforcement (what "compliant" means for the extraction run)

  • Zero visual change is proven, not promised: Storybook visual snapshots are baselined before any refactor, and all snapshots match baseline after it. A change that alters rendered output must be intentional and human-approved.
  • Baseline scope for Run 1: the shared primitives in ui/src/components/ui/ (each gets a story if missing — there are only ~24) plus the ~46 existing stories under ui/storybook/stories/. Do NOT attempt a story for every feature component (~277) in this run; full coverage is a later effort.
  • Mechanical rewrites (value extraction, renames) are done via committed codemod scripts in scripts/, not hand-edits — reviewable once, repeatable forever.
  • Token layer is the single source (ui/src/index.css, per above) consumed via CSS variables / Tailwind theme — never values copied into components.
  • Lint/grep gates pass: zero hardcoded hex values, zero arbitrary spacing values, zero raw font-size declarations in ui/src/components/** and ui/src/pages/** outside the token layer and a documented allowlist (third-party overrides, intentional opt-outs commented inline).
  • pnpm build, pnpm typecheck, and pnpm build-storybook pass.
  • AGENTS.md links here and states the token-only rule.

Aspirational (NOT gating this run): no duplicate components; every component has exactly one story covering its variants; all UI copy says "task".

Out of scope (do not do during simplification)

No visual redesign, no new colors or typefaces, no layout restructuring, no new dependencies beyond snapshot tooling, no component consolidation/merges (audit + recommend only), no copy renames, no changes to server code or app logic. Simplification means fewer parts, same product.

Prior art (read before auditing)

See doc/design/PRIOR-ART.md — a previous audit pass (PAP-280/283/284, on the PAP-282-playground branch, NOT on master) found that of ~220 hardcoded drift sites, only 6 were exact-value-mappable to existing tokens; expect the verbatim extraction to mint many new tokens that the human scale-collapse step later merges. It also drafted usage rules (radius tiers, CTA tiers, named type styles) that are good candidates for the post-audit scale decision.

How-to guide for day-to-day UI changes: see doc/design/CHANGING-THE-UI.md.

Motion tokens (Task Chat Redesign)

The redesigned task thread (flag enableTaskChatRedesign) is the first surface to tokenize motion. Principles — reasoning only; values live in ui/src/index.css:

  • One home, and it is :root, not @theme inline. @theme inline bakes literals at build time, so a value placed there cannot be moved at runtime. The dev tweak panel tunes motion by writing CSS custom properties live, so every motion token must resolve at runtime — hence :root.
  • Two tiers. Primitives (--motion-duration-*, --motion-ease-*) express the app's baseline motion feel; state/component-scoped tokens (--motion-<state>-*) reference the primitives so the whole thread retunes from a few knobs. Scoped tokens exist so the tweak panel can group controls by the state they affect.
  • Reuse the house curves. New easing defaults point at the two curves already used across the app rather than inventing a third feel.
  • No hardcoded timing in components. Durations, easings, delays, and staggers used by the redesigned thread must reference these tokens; a check script rejects raw ms / cubic-bezier values outside ui/src/index.css. This discipline is what makes the tweak panel structurally possible.
  • Values are placeholders. The committed numbers are sensible starting points, tuned live by a human and pasted back from the tweak panel's export — never treated as final during the baseline build.
  • Reduced motion is honored at the token layer. A prefers-reduced-motion: reduce block collapses the duration/stagger tokens to zero, cascading to every scoped token, in addition to each animation's own component-level guard.