mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-02 02:07:25 +08:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Operators spend most of their time on the issue detail page. They talk to the assigned agent there through comments. > - The current page reads as a ticket form. The thread sits below properties, the composer sits mid-page, and live agent activity renders as dense transcript logs. > - Talking to an agent is a conversation. A chat-first layout matches that mental model better than a ticket form. > - A layout change this large must not disrupt current users. It needs a safe opt-in path and full parity with the existing thread features. > - This pull request adds a chat-style task view behind a new "Chat-Style Tasks" experiment toggle. The flag is off by default and the existing page is unchanged when it is off. > - The benefit is a focused, readable conversation with the agent: live tool activity folds into compact summaries, the composer stays at the bottom, and properties, plan, and artifacts move into header tabs. ## Linked Issues or Issue Description Refs #49 (chat with agents is a much-wanted feature). Related PRs found in the dedup search: - #4489 — an earlier, closed attempt to promote the conversation to the primary surface on issue detail. This PR is a fresh, flag-gated take on the same goal. - #8837 — an open PR that proposes a two-column task layout. It restructures the same page but keeps the ticket paradigm; this PR is orthogonal because it is opt-in and chat-first. **Subsystem affected** UI (issue detail page). **Problem or motivation** The issue detail page presents agent conversations as a ticket: properties first, thread below, composer in the middle of the page, and raw transcript noise during live runs. Users who mainly converse with their agents must scroll past chrome to follow the conversation, and live activity is hard to read. **Proposed solution** An opt-in chat-style view of the issue detail page, gated by a new "Chat-Style Tasks" experiment toggle in Settings → Experimental. With the flag on, the thread fills the center pane, the composer docks to the bottom of the viewport, Properties / Plan / Artifacts become header tabs, live turns show a status pill with the current tool action and elapsed time, and settled turns collapse to a "Worked · N tools" summary that expands into per-tool rows. With the flag off, nothing changes. **Alternatives considered** Restyling the existing layout in place (rejected: too disruptive without an opt-out), and a separate chat page beside the issue page (rejected: splits the task's single source of truth). A per-request lab page (`/task-chat-lab`, dev-only) was kept for design iteration instead. **Roadmap alignment** ROADMAP.md "CEO Chat" wants lighter conversations that still resolve to real work objects. This PR keeps the core task-and-comments model — it only changes presentation, opt-in — so it does not duplicate that planned work. ## What Changed - New `enableTaskChatRedesign` instance setting, exposed as a "Chat-Style Tasks" experiment card in Settings → Experimental (shared feature catalog, validators, server instance-settings service, and UI settings page). - New `ui/src/components/task-chat/` component family: chat thread with turn grouping, agent reply bubbles, live status pill, collapsible turn summaries with per-tool rows, plan tab with a sticky CTA action bar, inline interaction cards, per-request mode chips, and a bottom-docked composer. - A shared tool taxonomy (`tool-taxonomy.ts`) maps tool names to verbs and icons; the status pill, tool rows, and the classic transcript view all use it. - A transcript adapter converts stored run logs into chat turns; it dedupes tool-call updates by `toolUseId` so tool counts match the expanded rows, and it keeps a tool row's first real name when later generic updates arrive. - Composer: posts on Cmd/Ctrl+Enter, supports image paste with object-URL thumbnail previews (revoked on clear/unmount), and uploads through the issue attachments route. - `IssueDetail.tsx`: with the flag on, pane tabs move to the header bar, the header is not sticky, and the chat fills the center; with the flag off, the previous layout renders unchanged. - Motion tokens for the new animations live in `ui/src/index.css` with a `motion-tokens.ts` catalog and a test that keeps the two in sync (the catalog now also covers the shared enter/exit/swap tokens that the decision/quicklook block declares). - A dev-only `/task-chat-lab` page with fixtures and a tweak panel for motion tuning. ## Verification - `pnpm typecheck` — clean across the workspace. - `pnpm check:token-gates` — 3/3 CLEAN. - `cd ui && pnpm vitest run` — 3,344 of 3,345 tests pass locally. The one failure is `IssueProperties.test.tsx` monitor-row time formatting, which is timezone-sensitive: it also fails on unmodified `origin/master` in a non-UTC timezone and passes with `TZ=UTC`. It is not related to this change. - `cd server && pnpm vitest run src/__tests__/instance-settings-service.test.ts` — 21/21 pass (covers the new setting). - Manual: start the dev server, open Settings → Experimental, enable "Chat-Style Tasks", and open any issue. The thread fills the page, the composer docks to the bottom, and Properties / Plan / Artifacts appear as header tabs. Assign an agent and comment to watch a live run: the status pill shows the current tool action with elapsed time, and the finished turn folds into a "Worked · N tools" summary. Disable the toggle and confirm the classic page is unchanged. - Visual snapshot baselines are intentionally not updated: per `doc/design/DECISION-SHEET.md`, "Per-change snapshot verification demoted to dormant (Jul 13 2026)". ## Risks - The flag-off path goes through the same `IssueDetail.tsx` file, so a regression there would affect current users. Mitigation: the classic markup renders through the same components as before behind explicit flag conditionals, and the full UI suite passes. - The transcript adapter interprets stored run-log formats, including legacy entries without `toolUseId`. Malformed logs degrade to generic tool rows rather than crashing. - The new view changes no server behavior other than one additive instance setting; it is additive and default-off. Overall risk with the flag off is low. ## Model Used - Claude (Anthropic), model id `claude-fable-5` (Claude Fable 5), extended thinking enabled, agentic tool use (file editing, shell, test execution) via Claude Code / Claude Agent SDK. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
107 lines
3.7 KiB
JavaScript
107 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Finish-line gate for the Task Chat Redesign (flag: enableTaskChatRedesign).
|
|
*
|
|
* 1. No hardcoded timing in the redesign surface: fails if any redesigned
|
|
* component/page contains a raw `<digits>ms` duration or a `cubic-bezier(`
|
|
* literal. All motion must reference the --motion-* tokens in
|
|
* ui/src/index.css (the single motion source), so the dev tweak panel can
|
|
* retune them live.
|
|
*
|
|
* Documented allowlist: ui/src/components/task-chat/motion-tokens.ts is the
|
|
* easing-preset catalog the tweak panel offers — those cubic-bezier values
|
|
* are data for the tool, analogous to the token layer, not component motion.
|
|
* Test files are excluded.
|
|
*
|
|
* 2. Flag-off isolation seams are single conditionals (grep-provable): asserts
|
|
* the IssueDetail chat-tab seam and the IssueProperties tab-shell seam exist.
|
|
*
|
|
* Exit non-zero on any violation.
|
|
*/
|
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, join, relative } from "node:path";
|
|
|
|
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
|
const SCAN_DIRS = ["ui/src/components/task-chat"];
|
|
const SCAN_FILES = [
|
|
"ui/src/components/TaskChatThread.tsx",
|
|
"ui/src/components/PropertiesPanel.tsx",
|
|
"ui/src/components/TaskChatRedesignGate.tsx",
|
|
"ui/src/pages/TaskChatLab.tsx",
|
|
"ui/src/components/issue-properties/IssuePropertiesPlansTab.tsx",
|
|
"ui/src/components/issue-properties/IssuePropertiesArtifactsTab.tsx",
|
|
];
|
|
const ALLOWLIST = new Set(["ui/src/components/task-chat/motion-tokens.ts"]);
|
|
|
|
const MS_RE = /\d+ms\b/;
|
|
const CUBIC_RE = /cubic-bezier\(/;
|
|
|
|
function isCheckable(path) {
|
|
if (!/\.(ts|tsx)$/.test(path)) return false;
|
|
if (/\.test\.(ts|tsx)$/.test(path)) return false;
|
|
return true;
|
|
}
|
|
|
|
function walk(dir, out) {
|
|
for (const entry of readdirSync(dir)) {
|
|
const full = join(dir, entry);
|
|
if (statSync(full).isDirectory()) walk(full, out);
|
|
else out.push(full);
|
|
}
|
|
}
|
|
|
|
const files = [];
|
|
for (const d of SCAN_DIRS) walk(join(repoRoot, d), files);
|
|
for (const f of SCAN_FILES) files.push(join(repoRoot, f));
|
|
|
|
const violations = [];
|
|
for (const file of files) {
|
|
const rel = relative(repoRoot, file).split("\\").join("/");
|
|
if (!isCheckable(rel) || ALLOWLIST.has(rel)) continue;
|
|
const lines = readFileSync(file, "utf8").split("\n");
|
|
lines.forEach((line, i) => {
|
|
if (MS_RE.test(line) || CUBIC_RE.test(line)) {
|
|
violations.push(`${rel}:${i + 1}: ${line.trim()}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Seam assertions (clause B: single grep-provable conditional in each file).
|
|
const seams = [
|
|
{
|
|
file: "ui/src/pages/IssueDetail.tsx",
|
|
needle: "? TaskChatThread : IssueChatThread",
|
|
label: "chat-tab seam",
|
|
},
|
|
{
|
|
file: "ui/src/components/issue-properties/IssueProperties.tsx",
|
|
needle: "if (!taskChatRedesignEnabled) return propertiesBody;",
|
|
label: "properties-pane seam",
|
|
},
|
|
];
|
|
const missingSeams = [];
|
|
for (const seam of seams) {
|
|
const text = readFileSync(join(repoRoot, seam.file), "utf8");
|
|
if (!text.includes(seam.needle)) missingSeams.push(`${seam.label} (${seam.file})`);
|
|
}
|
|
|
|
let failed = false;
|
|
if (violations.length > 0) {
|
|
failed = true;
|
|
console.error("Hardcoded timing found in the task-chat redesign surface:");
|
|
for (const v of violations) console.error(` ${v}`);
|
|
console.error("Use a --motion-* token from ui/src/index.css instead.");
|
|
}
|
|
if (missingSeams.length > 0) {
|
|
failed = true;
|
|
console.error("Missing flag-off isolation seam(s):");
|
|
for (const s of missingSeams) console.error(` ${s}`);
|
|
}
|
|
|
|
if (failed) process.exit(1);
|
|
console.log(
|
|
`check-task-chat-motion: OK (${files.filter((f) => isCheckable(relative(repoRoot, f).split("\\").join("/"))).length} files scanned, seams present).`,
|
|
);
|