fix(ui): improve mobile task spacing (#13304)

## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - The task detail view is the main place for task conversation.
> - The mobile view used too much horizontal space for gutters and
controls.
> - The status control and identifier also reduced the title width.
> - This pull request gives the title its own mobile row and reduces
mobile padding.
> - The benefit is a clearer task view with more space for useful
content.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The mobile task detail header, conversation list, and message composer.

**Current behavior**

The title shares one row with status and identifier controls. The
conversation and composer also use larger mobile gutters than needed.

**Proposed behavior**

The title uses the full mobile width. Status and metadata use the next
row. The conversation and composer use smaller mobile gutters.

**Reason and benefit**

Long titles have more readable line lengths. The reduced padding gives
task content more room on small screens.

**Breaking changes**

None.

## What Changed

- Put the task title on a full-width mobile row.
- Move the mobile status and metadata below the title.
- Reduce the mobile conversation and composer padding.
- Update the composer dock class test.

## Verification

- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm exec vitest run ui/src/components/TaskChatThread.test.tsx
ui/src/components/task-chat/TaskChatComposer.test.tsx
ui/src/pages/IssueDetail.test.tsx --reporter=dot`
- `pnpm --filter @paperclipai/ui build`
- `pnpm check:token-gates`
- `git diff --check`

## Risks

- Low risk. The layout changes apply only at the mobile breakpoint.
- Desktop spacing stays unchanged.

> 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, GPT-5.6, with reasoning and tool use.

## 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)
- [ ] 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 <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-12 08:27:14 -05:00 committed by GitHub
parent 8f40b4ad4b
commit 586b5ec828
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 17 additions and 9 deletions

View File

@ -423,9 +423,9 @@ describe("TaskChatThread draft pass-through", () => {
const thread = container.querySelector('[data-testid="task-chat-thread"]');
expect(thread?.classList).not.toContain("h-(--tc-thread-max-h)");
expect(thread?.classList).toContain("flex-1");
expect(dock?.classList).toContain("px-2");
expect(dock?.classList).toContain("px-1");
expect(dock?.classList).toContain("md:px-0");
expect(dock?.classList).not.toContain("px-1");
expect(dock?.classList).not.toContain("px-2");
expect(dock?.classList).not.toContain("-mt-(--radius-task-composer)");
expect(dock?.classList).not.toContain("pt-1");
expect(dock?.classList).toContain("md:pb-0");

View File

@ -2889,7 +2889,7 @@ export function TaskChatThread(props: TaskChatThreadProps) {
isMobile
? "bottom-(--tc-composer-bottom) z-20 transition-[bottom] duration-200 ease-out"
: "bottom-0 z-10",
"mx-auto flex w-full max-w-(--tc-shell-max-w) flex-col gap-2 px-2 pb-2 md:px-4",
"mx-auto flex w-full max-w-(--tc-shell-max-w) flex-col gap-2 px-1 pb-1 md:px-4 md:pb-2",
streamlinedUiEnabled && "md:px-0 md:pb-0",
(!streamlinedUiEnabled || isMobile) &&
"bg-background/80 pt-1 backdrop-blur supports-[backdrop-filter]:bg-background/60 dark:bg-transparent dark:backdrop-blur-none dark:supports-[backdrop-filter]:bg-transparent",

View File

@ -1074,7 +1074,7 @@ export function TaskChatComposer({
streamlined
? "paperclip-task-chat-composer rounded-(--radius-task-composer) border border-border bg-card p-(--sz-18px) shadow-(--shadow-task-composer) dark:border-0 dark:bg-muted dark:shadow-none"
: "paperclip-task-chat-composer rounded-xl bg-card p-(--sz-18px)",
mobile && "p-3",
mobile && "p-2",
)}
onKeyDownCapture={(e) => {
// Capture mode shortcuts on the wrapper so they work while the rich

View File

@ -429,7 +429,7 @@ export function TaskChatThreadView({
const body = (
<div
className={cn(
"paperclip-mobile-thread mx-auto flex w-full max-w-(--tc-shell-max-w) flex-col px-2 py-4 md:px-4",
"paperclip-mobile-thread mx-auto flex w-full max-w-(--tc-shell-max-w) flex-col px-1 py-3 md:px-4 md:py-4",
streamlined ? "md:px-0" : "gap-5",
className,
)}

View File

@ -6752,8 +6752,8 @@ export function IssueDetail({ tasksTab }: { tasksTab?: TaskSidePanelProps["tasks
)}
>
{streamlinedTaskDetailEnabled ? (
<div className="flex min-w-0 items-center gap-2 pr-8">
{issueStatusControl}
<div className="flex min-w-0 items-start gap-2 md:items-center md:pr-8">
<div className="hidden md:block">{issueStatusControl}</div>
<div
data-slot="task-detail-title"
className="flex min-w-0 flex-1 items-baseline gap-2"
@ -6766,7 +6766,7 @@ export function IssueDetail({ tasksTab }: { tasksTab?: TaskSidePanelProps["tasks
/>
<span
data-slot="task-title-identifier"
className="shrink-0 font-mono text-sm text-muted-foreground"
className="hidden shrink-0 font-mono text-sm text-muted-foreground md:inline"
>
{issue.identifier ?? issue.id.slice(0, 8)}
</span>
@ -6777,9 +6777,17 @@ export function IssueDetail({ tasksTab }: { tasksTab?: TaskSidePanelProps["tasks
<div
className={cn(
"flex min-w-0 flex-wrap items-center gap-2",
streamlinedTaskDetailEnabled && "gap-x-6 gap-y-2 pl-7",
streamlinedTaskDetailEnabled && "gap-x-3 gap-y-2 md:gap-x-6 md:pl-7",
)}
>
{streamlinedTaskDetailEnabled ? (
<div className="md:hidden">{issueStatusControl}</div>
) : null}
{streamlinedTaskDetailEnabled ? (
<span className="shrink-0 font-mono text-sm text-muted-foreground md:hidden">
{issue.identifier ?? issue.id.slice(0, 8)}
</span>
) : null}
{!streamlinedTaskDetailEnabled ? issueStatusControl : null}
{/* PAP-411: priority UI hidden behind SHOW_TASK_PRIORITY_UI. */}
{SHOW_TASK_PRIORITY_UI && (