From c295c19985d38892516aa8e47a10ec55ad855fba Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 7 Jun 2026 02:18:11 +0000 Subject: [PATCH] Make DocumentRow responsive on mobile (PAP-10572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the PAP-10569 mobile regression where DocumentRow collapsed at 390px: the mono doc key overlapped the title, the summary line vanished, and the status pill / owner / count chips / relative time clipped at the right edge. EntityRow's single horizontal flex row gave meta + trailing `shrink-0` priority, so at narrow widths only the title cell shrank — squeezing it to ~120px while trailing chips ran off the edge. Add an opt-in `responsive` prop that stacks the row below `sm` (title → meta → trailing on their own lines, title keeps full width) and reflows to the unchanged single-row desktop layout at `sm`+. DocumentRow opts in; issues/projects lists are untouched. Also: - Storybook inline-card fixture rendered a raw "## Goal" markdown literal even though production renders via MarkdownBody; use plain prose so sign-off screenshots reflect what users actually see. - IssueDocumentsTab empty state now passes action/onAction so the "Add document" affordance sits at the empty state too. Co-Authored-By: Claude Opus 4.8 --- ui/src/components/EntityRow.test.tsx | 20 ++++++ ui/src/components/EntityRow.tsx | 70 +++++++++++++------ ui/src/components/IssueDocumentsTab.tsx | 2 + ui/src/components/documents/DocumentRow.tsx | 1 + .../issue-documents-integration.stories.tsx | 7 +- 5 files changed, 79 insertions(+), 21 deletions(-) diff --git a/ui/src/components/EntityRow.test.tsx b/ui/src/components/EntityRow.test.tsx index 90e7666f1e7..7f81f8bad9b 100644 --- a/ui/src/components/EntityRow.test.tsx +++ b/ui/src/components/EntityRow.test.tsx @@ -46,4 +46,24 @@ describe("EntityRow", () => { const markup = renderToStaticMarkup(); expect(markup).toContain("min-w-0 flex-1"); }); + + it("stacks title/meta/trailing below sm when responsive", () => { + const markup = renderToStaticMarkup( + draft} + trailing={2h} + />, + ); + + // inner wrapper stacks on mobile and reflows to a row at sm+ + expect(markup).toContain("flex flex-col gap-1.5 sm:flex-row"); + // the spacer that pins trailing right only exists at sm+ (no empty line on mobile) + expect(markup).toContain("hidden sm:block sm:flex-1"); + // leading column top-aligns while stacked + expect(markup).toContain("items-start sm:items-center"); + expect(markup).toContain("meta-cell"); + expect(markup).toContain("trailing-cell"); + }); }); diff --git a/ui/src/components/EntityRow.tsx b/ui/src/components/EntityRow.tsx index bc88e971ec9..bb4b63185a0 100644 --- a/ui/src/components/EntityRow.tsx +++ b/ui/src/components/EntityRow.tsx @@ -20,6 +20,14 @@ interface EntityRowProps { className?: string; titleClassName?: string; reserveSubtitleSpace?: boolean; + /** + * Stack the row vertically below `sm` (title → meta → trailing on their own + * lines) instead of forcing the single horizontal flex layout. Use for rows + * with heavy `meta`/`trailing` (e.g. `DocumentRow`) that otherwise collapse + * the title cell and clip trailing chips on narrow viewports. Desktop layout + * is unchanged. + */ + responsive?: boolean; } export function EntityRow({ @@ -35,36 +43,58 @@ export function EntityRow({ className, titleClassName, reserveSubtitleSpace, + responsive, }: EntityRowProps) { const isClickable = !!(to || onClick); const classes = cn( - "flex items-center gap-3 px-4 py-2 text-sm border-b border-border last:border-b-0 transition-colors", + "flex gap-3 px-4 py-2 text-sm border-b border-border last:border-b-0 transition-colors", + // Top-align the leading column while stacked so the status dot sits with the + // title line; recenter once the row collapses back to a single line at sm. + responsive ? "items-start sm:items-center" : "items-center", isClickable && "cursor-pointer hover:bg-accent/50", selected && "bg-accent/30", className ); - const content = ( + const titleBlock = ( +
+
+ {identifier && ( + + {identifier} + + )} + {title} +
+ {(subtitle || reserveSubtitleSpace) && ( +

+ {subtitle} +

+ )} +
+ ); + + // Responsive variant: below `sm` the title, meta, and trailing stack onto + // their own lines (title cell keeps full width, nothing clips); at `sm`+ the + // inner wrapper reflows to the same single-row layout as the default path + // (content-sized title, meta hugging it, flex-1 spacer, trailing pinned right). + const content = responsive ? ( + <> + {leading &&
{leading}
} +
+ {titleBlock} + {meta &&
{meta}
} +
+ {trailing &&
{trailing}
} +
+ + ) : ( <> {leading &&
{leading}
} -
-
- {identifier && ( - - {identifier} - - )} - {title} -
- {(subtitle || reserveSubtitleSpace) && ( -

- {subtitle} -

- )} -
+ {titleBlock} {meta &&
{meta}
} {meta &&
} {trailing &&
{trailing}
} diff --git a/ui/src/components/IssueDocumentsTab.tsx b/ui/src/components/IssueDocumentsTab.tsx index e6e40a04bff..d3f38415501 100644 --- a/ui/src/components/IssueDocumentsTab.tsx +++ b/ui/src/components/IssueDocumentsTab.tsx @@ -137,6 +137,8 @@ export function IssueDocumentsTab({ issueId, companyId, issueIdentifier, agentMa setLinkPickerOpen(true)} /> ) ) : ( diff --git a/ui/src/components/documents/DocumentRow.tsx b/ui/src/components/documents/DocumentRow.tsx index a963fc33e9e..f1f2946eea8 100644 --- a/ui/src/components/documents/DocumentRow.tsx +++ b/ui/src/components/documents/DocumentRow.tsx @@ -89,6 +89,7 @@ export function DocumentRow({ document, to, owner, identifier, selected, company
+ {/* + Production renders the body through `MarkdownBody`; this static fixture uses + plain prose (no raw markdown literal) so the sign-off screenshot reflects what + users actually see rather than printing unrendered "## Goal" syntax. + */} {docKey === "plan" - ? "## Goal\nPreserve inline plan editing while making linked docs discoverable…" + ? "Preserve inline plan editing while making linked docs discoverable from the issue." : doc.summary}