From 9acce52aa5f13b5fd74142452f761fe58677287a Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Thu, 9 Jul 2026 17:36:53 -0500 Subject: [PATCH] [codex] Polish operator UI work state details (#9320) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Operators use the issue list, blocked-work notices, pipeline body documents, and issue documents to understand what agents are doing > - Some of those surfaces had low-information details: live-work blockers were visually easy to miss, revision history showed generic actor labels, and selected active subtasks carried an extra accent border > - These details matter because Paperclip's default UI should make agent state and work history legible without requiring users to inspect raw logs > - This pull request groups a small set of related operator UI polish changes for those work-state and document-history surfaces > - The benefit is clearer review context for blocked work, better attribution in document revision history, and cleaner active-subtask row styling ## Linked Issues or Issue Description No public GitHub issue found during dedup search. ### Bug Report **Pre-submission checklist** - Searched existing open PRs/issues for `document revision authors` and `blocked notice live work`; no duplicates found. - Reproduced against the current `master` base for this PR. - Confirmed this is core board UI behavior, not adapter/provider/local configuration. **What happened?** Blocked live-work notices, document revision history, pipeline body document revision history, and active subtask rows exposed technically correct but low-signal UI details. Revision menus could show generic `Board`/`Agent` labels instead of the specific author, and selected active subtasks had an extra accent border. **Expected behavior** Operators should see clear blocked-live-work copy, recognizable revision authors using available agent/user profile data, and visually consistent selected task rows. **Steps to reproduce** 1. Open an issue with a live-work blocker and inspect the blocked notice. 2. Open an issue document revision menu with agent/user-authored revisions. 3. Open a pipeline item body document revision menu with agent/user-authored revisions. 4. Inspect a selected active subtask row in the issue list. **Paperclip version or commit** `8b6a06ee2` (`origin/master` at branch creation). **Deployment mode** Local dev / self-hosted board UI. **Installation method** Built from source. **Agent adapter(s) involved** Not adapter-specific (core board UI). **Database mode** Not database-related. **Access context** Board operator UI. **Relevant logs or output** Not applicable. **Relevant config** Not applicable. **Additional context** This PR is a small polish/fix contribution. `ROADMAP.md` allows tightly scoped bugs and polish without roadmap-level coordination. **Privacy checklist** Reviewed the PR body for private instance references and omitted internal ticket links. ## What Changed - Polished the live-work blocked notice copy and tests so operators get a clearer explanation of what is blocking progress. - Show issue document revision authors with agent/user names and avatar treatment instead of generic actor labels. - Show pipeline body document revision authors with the same available agent/user profile data. - Removed the extra left accent border from selected active subtask rows. - Stabilized the document revision test helper for React runtimes where `act` is not exported as a function. ## Verification - `git diff --check origin/master..HEAD` - `git diff --name-only origin/master..HEAD -- .github/workflows pnpm-lock.yaml` produced no output. - `pnpm check:token-gates` passed: all color literal, arbitrary bracket value, and raw font-size gates clean. - `pnpm exec vitest run ui/src/components/IssueBlockedNotice.test.tsx ui/src/components/IssueDocumentsSection.test.tsx ui/src/components/IssueRow.test.tsx` passed: 3 test files, 32 tests. - `pnpm --filter @paperclipai/ui typecheck` passed. - Remote PR checks passed on head `3e18fe3f`: 22 complete, 0 pending, 0 failing. - Greptile completed at 5/5 with no blocking issues after addressing the pipeline revision author feedback. ## Risks Low risk. The change is limited to board UI rendering and component tests. The main risk is a subtle visual regression in issue/document surfaces that are not covered by screenshots; the affected component tests cover the intended copy, attribution, and row-style behavior. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex coding agent, GPT-5 model family, tool-enabled CLI session. Exact runtime context window is not exposed by this execution environment. ## 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 - [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 --- ui/src/components/DocumentFrameHeader.tsx | 38 ++++++- ui/src/components/IssueBlockedNotice.test.tsx | 64 +++++++++++ ui/src/components/IssueBlockedNotice.tsx | 40 ++++--- .../components/IssueDocumentsSection.test.tsx | 101 +++++++++++++++++- ui/src/components/IssueDocumentsSection.tsx | 34 ++++-- ui/src/components/IssueRow.test.tsx | 37 ++++++- ui/src/components/IssueRow.tsx | 2 +- .../components/PipelineItemBodyDocument.tsx | 34 +++++- ui/src/index.css | 2 - 9 files changed, 317 insertions(+), 35 deletions(-) diff --git a/ui/src/components/DocumentFrameHeader.tsx b/ui/src/components/DocumentFrameHeader.tsx index 5ba3e9dbe6..bb88c48e12 100644 --- a/ui/src/components/DocumentFrameHeader.tsx +++ b/ui/src/components/DocumentFrameHeader.tsx @@ -3,6 +3,7 @@ import { ChevronDown, ChevronRight } from "lucide-react"; import { cn, relativeTime } from "../lib/utils"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { DropdownMenu, DropdownMenuContent, @@ -12,12 +13,21 @@ import { DropdownMenuRadioItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { AgentIcon } from "./AgentIconPicker"; +import { deriveInitials } from "./Identity"; + +export type DocumentFrameHeaderRevisionActor = { + kind: "agent" | "user" | "system"; + name: string; + agentIcon?: string | null; + imageUrl?: string | null; +}; export type DocumentFrameHeaderRevision = { id: string; revisionNumber: number; createdAt: string | Date; - actorLabel: string; + actor: DocumentFrameHeaderRevisionActor; }; export type DocumentFrameHeaderRevisionMenu = { @@ -46,6 +56,23 @@ export interface DocumentFrameHeaderProps { actionsSlot?: ReactNode; } +function RevisionActorAvatar({ actor }: { actor: DocumentFrameHeaderRevisionActor }) { + return ( + + {actor.kind === "agent" ? ( + + + + ) : ( + <> + {actor.imageUrl ? : null} + {deriveInitials(actor.name)} + + )} + + ); +} + export function DocumentFrameHeader({ documentKey, documentLabel, @@ -124,9 +151,12 @@ export function DocumentFrameHeader({ ) : null} - - {relativeTime(revision.createdAt)} • {revision.actorLabel} - +
+ + + {relativeTime(revision.createdAt)} • {revision.actor.name} + +
); diff --git a/ui/src/components/IssueBlockedNotice.test.tsx b/ui/src/components/IssueBlockedNotice.test.tsx index f052ee41a3..5e08311800 100644 --- a/ui/src/components/IssueBlockedNotice.test.tsx +++ b/ui/src/components/IssueBlockedNotice.test.tsx @@ -333,6 +333,70 @@ describe("IssueBlockedNotice", () => { expect(stepLinks[0]).toContain("TASK-9"); expect(stepLinks[1]).toContain("TASK-10"); expect(stepLinks[2]).toContain("TASK-11"); + + const runningStep = node.querySelectorAll('[data-testid="issue-blocked-notice-steps"] a')[2]; + if (!runningStep) throw new Error("Expected a running live-work step."); + expect(runningStep.querySelector('svg[aria-label="In Progress status"]')).not.toBeNull(); + expect(node.querySelector('[data-testid="issue-blocked-notice-now-running"]')).toBeNull(); + }); + + it("shows external now-running blockers beneath the label on a separate line", () => { + const node = render( + , + ); + + const nowRunning = node.querySelector('[data-testid="issue-blocked-notice-now-running"]'); + expect(nowRunning).not.toBeNull(); + expect(nowRunning!.children[0]?.textContent?.trim()).toBe("Now running"); + expect(nowRunning!.children[1]?.querySelector("a")?.textContent).toContain("TASK-99"); + const stepText = node.querySelector('[data-testid="issue-blocked-notice-steps"]')?.textContent; + expect(stepText).not.toContain("TASK-99"); }); it("renders a recovery indicator on a blocker chip when the blocker has an active recovery action", () => { diff --git a/ui/src/components/IssueBlockedNotice.tsx b/ui/src/components/IssueBlockedNotice.tsx index aee4fa7d51..279c99eeb4 100644 --- a/ui/src/components/IssueBlockedNotice.tsx +++ b/ui/src/components/IssueBlockedNotice.tsx @@ -22,6 +22,7 @@ import { RECOVERY_CHIP_DEFAULT_TONE, recoveryChipLabel, } from "../lib/recovery-display"; +import { StatusGlyph } from "./StatusGlyph"; function BlockerRecoveryIndicator({ action }: { action: IssueRecoveryAction }) { const state = deriveActiveRecoveryDisplayState(action); @@ -133,6 +134,10 @@ const WAITING_STEP_RANK: Record = { queued: 2, }; +function waitingTaskStatusLabel(status: string): string { + return status.replace(/_/g, " ").replace(/\b\w/g, (character) => character.toUpperCase()); +} + function WaitingChipLink({ blocker, running = false, @@ -147,6 +152,11 @@ function WaitingChipLink({ to={createIssueDetailPath(issuePathId)} className="inline-flex max-w-full items-center gap-1 rounded-md border border-blue-300/70 bg-background/80 px-2 py-1 font-mono text-xs text-blue-950 transition-colors hover:border-blue-500 hover:bg-blue-100 hover:underline dark:border-blue-500/40 dark:bg-background/40 dark:text-blue-100 dark:hover:bg-blue-500/15" > + {blocker.identifier ?? blocker.id.slice(0, 8)} {blocker.title} @@ -208,11 +218,13 @@ function WaitingOnLiveWorkNotice({ const runningCount = steps.filter((step) => step.status === "running").length; // "Now running" replaces "Ultimately waiting on": prefer live terminal - // leaves; otherwise fall back to whichever chain blocker is live. + // leaves that are not already shown in the ordered queue list. + const stepIds = new Set(steps.map((step) => step.blocker.id)); const nowRunningSeen = new Set(); const nowRunning: IssueRelationIssueSummary[] = []; for (const blocker of [...terminalBlockers, ...chainBlockers]) { if (!liveIds.has(blocker.id)) continue; + if (stepIds.has(blocker.id)) continue; if (nowRunningSeen.has(blocker.id)) continue; nowRunningSeen.add(blocker.id); nowRunning.push(blocker); @@ -276,7 +288,7 @@ function WaitingOnLiveWorkNotice({ {steps.map(({ blocker, status }) => (
- +
- {status === "running" ? ( -
- -
- ) : ( - - )} +
))}
@@ -313,14 +319,16 @@ function WaitingOnLiveWorkNotice({ {nowRunning.length > 0 ? (
- +
Now running - - {nowRunning.map((blocker) => ( - - ))} +
+
+ {nowRunning.map((blocker) => ( + + ))} +
) : null} diff --git a/ui/src/components/IssueDocumentsSection.test.tsx b/ui/src/components/IssueDocumentsSection.test.tsx index bc3580d299..7a1c54339d 100644 --- a/ui/src/components/IssueDocumentsSection.test.tsx +++ b/ui/src/components/IssueDocumentsSection.test.tsx @@ -1,7 +1,8 @@ // @vitest-environment jsdom -import { act } from "react"; +import { act as reactAct } from "react"; import type { ComponentProps } from "react"; +import { flushSync } from "react-dom"; import { createRoot } from "react-dom/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { DocumentRevision, Issue, IssueDocument } from "@paperclipai/shared"; @@ -25,6 +26,24 @@ const markdownEditorMockState = vi.hoisted(() => ({ emitMountEmptyChange: false, })); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; + +async function act(callback: () => T | Promise): Promise { + if (typeof reactAct === "function") { + return await (reactAct(callback) as T | Promise); + } + + let result: T | Promise | undefined; + flushSync(() => { + result = callback(); + }); + const resolved = await result; + await new Promise((resolve) => setTimeout(resolve, 0)); + flushSync(() => {}); + return resolved as T; +} + vi.mock("../api/issues", () => ({ issuesApi: mockIssuesApi, })); @@ -425,6 +444,86 @@ describe("IssueDocumentsSection", () => { queryClient.clear(); }); + it("shows revision authors with names and avatars in the revision history menu", async () => { + const currentDocument = createIssueDocument({ + body: "Current plan body", + latestRevisionId: "revision-agent", + latestRevisionNumber: 4, + updatedByAgentId: "agent-1", + updatedByUserId: null, + updatedAt: new Date("2026-03-31T12:05:00.000Z"), + }); + const issue = createIssue(); + const root = createRoot(container); + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + mutations: { + retry: false, + }, + }, + }); + + mockIssuesApi.listDocuments.mockResolvedValue([currentDocument]); + queryClient.setQueryData( + queryKeys.issues.documentRevisions(issue.id, "plan"), + [ + createRevision({ + id: "revision-agent", + revisionNumber: 4, + body: "Current plan body", + createdByAgentId: "agent-1", + createdByUserId: null, + createdAt: new Date("2026-03-31T12:05:00.000Z"), + }), + createRevision({ + id: "revision-user", + revisionNumber: 3, + body: "Board-written plan body", + createdByAgentId: null, + createdByUserId: "user-1", + createdAt: new Date("2026-03-31T11:00:00.000Z"), + }), + ], + ); + + await act(async () => { + root.render( + + + , + ); + }); + await flush(); + await flush(); + + const revisionButton = Array.from(container.querySelectorAll("button")) + .find((button) => button.textContent?.includes("rev 4")); + expect(revisionButton).toBeTruthy(); + + await act(async () => { + revisionButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flush(); + + expect(document.body.textContent).toContain("CodexCoder"); + expect(document.body.textContent).toContain("Dotta"); + expect(document.body.textContent).not.toContain("• agent"); + expect(document.body.querySelectorAll('[data-slot="avatar"]').length).toBeGreaterThanOrEqual(2); + + await act(async () => { + root.unmount(); + }); + queryClient.clear(); + }); + it("shows the restored document body immediately after a revision restore", async () => { const blankLatestDocument = createIssueDocument({ body: "", diff --git a/ui/src/components/IssueDocumentsSection.tsx b/ui/src/components/IssueDocumentsSection.tsx index 6628b976b0..64f2b3ae89 100644 --- a/ui/src/components/IssueDocumentsSection.tsx +++ b/ui/src/components/IssueDocumentsSection.tsx @@ -34,7 +34,7 @@ import { } from "@/components/ui/dropdown-menu"; import { Check, Copy, Diff, Download, FilePenLine, FileText, Lock, MoreHorizontal, Plus, Trash2, Unlock, X } from "lucide-react"; import { DocumentDiffModal } from "./DocumentDiffModal"; -import { DocumentFrameHeader } from "./DocumentFrameHeader"; +import { DocumentFrameHeader, type DocumentFrameHeaderRevisionActor } from "./DocumentFrameHeader"; import { SourceTrustBadge } from "./SourceTrustBadge"; import { Badge } from "@/components/ui/badge"; @@ -116,10 +116,30 @@ function downloadDocumentFile(key: string, body: string) { URL.revokeObjectURL(url); } -function getRevisionActorLabel(revision: DocumentRevision) { - if (revision.createdByUserId) return "board"; - if (revision.createdByAgentId) return "agent"; - return "system"; +function getRevisionActor( + revision: DocumentRevision, + maps: { + agentMap?: ReadonlyMap & Partial>>; + userProfileMap?: ReadonlyMap; + }, +): DocumentFrameHeaderRevisionActor { + if (revision.createdByAgentId) { + const agent = maps.agentMap?.get(revision.createdByAgentId); + return { + kind: "agent", + name: agent?.name ?? revision.createdByAgentId.slice(0, 8), + agentIcon: agent?.icon ?? null, + }; + } + if (revision.createdByUserId) { + const profile = maps.userProfileMap?.get(revision.createdByUserId); + return { + kind: "user", + name: profile?.label ?? (revision.createdByUserId === "local-board" ? "Board" : revision.createdByUserId.slice(0, 8)), + imageUrl: profile?.image ?? null, + }; + } + return { kind: "system", name: "System" }; } function documentHasUnsavedChanges(doc: IssueDocument, draft: DraftState | null) { @@ -182,7 +202,7 @@ export function IssueDocumentsSection({ options?: { allowSharing?: boolean; reason?: string }, ) => Promise; extraActions?: ReactNode; - agentMap?: ReadonlyMap>; + agentMap?: ReadonlyMap & Partial>>; userProfileMap?: ReadonlyMap; /** * Seed which document annotation panels are open on first render. Mostly useful @@ -939,7 +959,7 @@ export function IssueDocumentsSection({ id: revision.id, revisionNumber: revision.revisionNumber, createdAt: revision.createdAt, - actorLabel: getRevisionActorLabel(revision), + actor: getRevisionActor(revision, { agentMap, userProfileMap }), })), selectedRevisionId, currentRevisionId: currentRevision.id, diff --git a/ui/src/components/IssueRow.test.tsx b/ui/src/components/IssueRow.test.tsx index 85255d5930..1817037a7b 100644 --- a/ui/src/components/IssueRow.test.tsx +++ b/ui/src/components/IssueRow.test.tsx @@ -1,6 +1,7 @@ // @vitest-environment jsdom -import { act } from "react"; +import { act as reactAct } from "react"; +import { flushSync } from "react-dom"; import { createRoot } from "react-dom/client"; import type { Issue } from "@paperclipai/shared"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -28,6 +29,15 @@ vi.mock("@/lib/router", () => ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; +function act(callback: () => void) { + if (typeof reactAct === "function") { + reactAct(callback); + return; + } + + flushSync(callback); +} + function createIssue(overrides: Partial = {}): Issue { return { id: "issue-1", @@ -251,6 +261,31 @@ describe("IssueRow", () => { }); }); + it("marks the current checklist step without adding a left border", () => { + const root = createRoot(container); + + act(() => { + root.render( + , + ); + }); + + const link = container.querySelector("[data-inbox-issue-link]") as HTMLAnchorElement | null; + + expect(link).not.toBeNull(); + expect(link?.getAttribute("aria-current")).toBe("step"); + expect(link?.className).toContain("bg-primary/5"); + expect(link?.className).not.toContain("border-l-"); + + act(() => { + root.unmount(); + }); + }); + it("does not render a planning mode marker for planning work mode issues", () => { const root = createRoot(container); diff --git a/ui/src/components/IssueRow.tsx b/ui/src/components/IssueRow.tsx index 6b8ddfea01..dfb7706f82 100644 --- a/ui/src/components/IssueRow.tsx +++ b/ui/src/components/IssueRow.tsx @@ -143,7 +143,7 @@ export function IssueRow({ "group flex items-start gap-2 rounded-lg py-2.5 pl-2 pr-3 text-sm no-underline text-inherit sm:items-center sm:py-2 sm:pl-1", !hideDivider && "border-b border-border last:border-b-0", selected ? "hover:bg-transparent" : "hover:bg-accent/50", - checklistCurrentStep ? "border-l-2 border-l-primary bg-primary/5 pl-(--sz-calc-11) sm:pl-(--sz-calc-12)" : null, + checklistCurrentStep ? "bg-primary/5" : null, className, )} > diff --git a/ui/src/components/PipelineItemBodyDocument.tsx b/ui/src/components/PipelineItemBodyDocument.tsx index 128c1b6c4d..7b784e6417 100644 --- a/ui/src/components/PipelineItemBodyDocument.tsx +++ b/ui/src/components/PipelineItemBodyDocument.tsx @@ -14,7 +14,7 @@ import type { CompanyUserProfile } from "../lib/company-members"; import { queryKeys } from "../lib/queryKeys"; import { useToastActions } from "../context/ToastContext"; import { DocumentAnnotationLayer, type PendingAnchor } from "./DocumentAnnotationLayer"; -import { DocumentFrameHeader } from "./DocumentFrameHeader"; +import { DocumentFrameHeader, type DocumentFrameHeaderRevisionActor } from "./DocumentFrameHeader"; import { DocumentAnnotationsCountChip, IssueDocumentAnnotations } from "./IssueDocumentAnnotations"; import { EmptyState } from "./EmptyState"; import { FoldCurtain } from "./FoldCurtain"; @@ -35,6 +35,34 @@ type CaseBodyDocument = PipelineCaseDocumentPayload["document"] & { createdByUserId?: string | null; }; +function getPipelineRevisionActor( + revision: { createdByAgentId?: string | null; createdByUserId?: string | null }, + maps: { + agentMap?: ReadonlyMap & Partial>>; + userProfileMap?: ReadonlyMap; + }, +): DocumentFrameHeaderRevisionActor { + if (revision.createdByAgentId) { + const agent = maps.agentMap?.get(revision.createdByAgentId); + return { + kind: "agent", + name: agent?.name ?? revision.createdByAgentId.slice(0, 8), + agentIcon: agent?.icon ?? null, + }; + } + + if (revision.createdByUserId) { + const profile = maps.userProfileMap?.get(revision.createdByUserId); + return { + kind: "user", + name: profile?.label ?? (revision.createdByUserId === "local-board" ? "Board" : revision.createdByUserId.slice(0, 8)), + imageUrl: profile?.image ?? null, + }; + } + + return { kind: "system", name: "System" }; +} + function isNotFound(error: unknown) { return error instanceof ApiError && error.status === 404; } @@ -48,7 +76,7 @@ export interface PipelineItemBodyDocumentProps { /** Active conversation issue the body document is/should be anchored to. */ conversationIssueId: string | null; conversationIssue: Issue | null; - agentMap?: ReadonlyMap>; + agentMap?: ReadonlyMap & Partial>>; userProfileMap?: ReadonlyMap; mentions?: MentionOption[]; imageUploadHandler?: (file: File) => Promise; @@ -396,7 +424,7 @@ export function PipelineItemBodyDocument({ id: revision.id, revisionNumber: revision.revisionNumber, createdAt: revision.createdAt, - actorLabel: revision.createdByUserId ? "board" : revision.createdByAgentId ? "agent" : "system", + actor: getPipelineRevisionActor(revision, { agentMap, userProfileMap }), })), selectedRevisionId, currentRevisionId: doc?.latestRevisionId ?? null, diff --git a/ui/src/index.css b/ui/src/index.css index 2c0e3fb0da..14e234e4e3 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -1586,8 +1586,6 @@ span.paperclip-mention-chip[data-mention-kind="external-object"] { --sz-calc-9: min(80vh,42rem); /* Extracted from ui/src/components/IssueFiltersPopover.test.tsx (max-h-[min(80vh,42rem)]). */ --sz-calc-10: min(780px,calc(100vw - 2rem)); /* Extracted from ui/src/components/IssueFiltersPopover.tsx (w-[min(780px,calc(100vw-2rem))]). */ --sz-24ch: 24ch; /* Extracted from ui/src/components/IssuePlanDecompositionsSection.tsx (max-w-[24ch]). */ - --sz-calc-11: calc(0.5rem - 2px); /* Extracted from ui/src/components/IssueRow.tsx (pl-[calc(theme(spacing.2)-2px)]). */ - --sz-calc-12: calc(0.25rem - 2px); /* Extracted from ui/src/components/IssueRow.tsx (pl-[calc(theme(spacing.1)-2px)]). */ --sz-calc-13: calc(0.75rem + 0.5rem); /* Extracted from ui/src/components/IssueRow.tsx (ml-[calc(theme(spacing.3)+theme(spacing.2))]). */ --sz-140px: 140px; /* Extracted from ui/src/components/JsonSchemaForm.tsx (min-h-[140px]). */ --sz-52px: 52px; /* Extracted from ui/src/components/KanbanBoard.tsx (w-[52px]). */