From a97f80aded569c90708fa30667b64dd0199aa5b6 Mon Sep 17 00:00:00 2001 From: Dean Stratakos <29683763+dastratakos@users.noreply.github.com> Date: Sun, 18 Oct 2026 17:08:17 +0000 Subject: [PATCH] refactor(desktop): draw the issue page on @luke/ui's DetailPage The issue page and its unread frame stand on DetailPage under the window's toolbar instead of borrowing the Automations page's view and scroll. The document inside keeps the plan's type, the editor's accent, and the plan's room for its last line to scroll clear of the floating captions. DetailPage stands a lone EmptyState or centred ErrorState a little above the room's middle, as CollectionPage does. Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com> --- .../src/renderer/desktop/issue-detail.tsx | 75 ++++++++++--------- .../src/renderer/desktop/issues-page.tsx | 34 +++++---- apps/desktop/src/renderer/styles/issues.css | 16 +++- .../components/detail-page/detail-page.css | 28 +++++++ .../components/detail-page/detail-page.tsx | 5 +- 5 files changed, 105 insertions(+), 53 deletions(-) diff --git a/apps/desktop/src/renderer/desktop/issue-detail.tsx b/apps/desktop/src/renderer/desktop/issue-detail.tsx index e12acde4a2..15ff16352b 100644 --- a/apps/desktop/src/renderer/desktop/issue-detail.tsx +++ b/apps/desktop/src/renderer/desktop/issue-detail.tsx @@ -6,8 +6,10 @@ import { BREADCRUMBS_VARIANT, Breadcrumbs, ConfirmDialog, + DetailPage, EllipsisIcon, IconButton, + PAGE_FRAME, SKELETON_SHAPE, Skeleton, SkeletonRegion, @@ -241,16 +243,21 @@ export function IssueDetail({ ); const identifier = issueIdentifier(workspaceName, issue.number); return ( -
- - {/* The issue is drawn as a task's document is (`version-view.tsx`): its title, then its body, at the document's measure. */} -
-
+ <> + + } + > + {/* The issue is drawn as a task's document is (`version-view.tsx`): its title, then its body, at the document's measure. */} +
@@ -267,9 +274,9 @@ export function IssueDetail({ />
-
+ -
+ ); } @@ -282,31 +289,29 @@ export function IssueDetail({ */ export function IssueDetailSkeleton({ label }: { label: string }): React.JSX.Element { return ( -
-
- -
-
-
- -
-
-
- -
-
- {SKELETON_CHIPS.map((width) => ( - - ))} -
-
- {SKELETON_DESCRIPTION.map((width) => ( - - ))} +
+ +
+
+
+
- -
+
+ +
+
+ {SKELETON_CHIPS.map((width) => ( + + ))} +
+
+ {SKELETON_DESCRIPTION.map((width) => ( + + ))} +
+
+
); } diff --git a/apps/desktop/src/renderer/desktop/issues-page.tsx b/apps/desktop/src/renderer/desktop/issues-page.tsx index c0d4bfd068..64113b3585 100644 --- a/apps/desktop/src/renderer/desktop/issues-page.tsx +++ b/apps/desktop/src/renderer/desktop/issues-page.tsx @@ -12,6 +12,7 @@ import { COLLECTION_STATE, CollectionPage, type CollectionState, + DetailPage, EmptyState, ERROR_STATE_PLACEMENT, ErrorState, @@ -323,23 +324,26 @@ function DetailFrame({ }): React.JSX.Element { useAppCommand(APP_COMMAND.EXIT_ISSUES, onBack); return ( -
-
-
- - -
-
+ +
+ + +
+ + } + > {failure === undefined ? ( ) : ( -
- - {failure === ISSUE_CALL_FAILURE.NOT_FOUND ? GONE_LINE : UNREAD_ONE_LINE} - -
+ + {failure === ISSUE_CALL_FAILURE.NOT_FOUND ? GONE_LINE : UNREAD_ONE_LINE} + )} -
+ ); } @@ -348,7 +352,7 @@ function DetailFrame({ * holds it until then, and every change sent through the list, so the list * it goes back to already holds what was kept. */ -function DetailPage({ +function IssuePage({ tasks, issueId, issues, @@ -473,7 +477,7 @@ function IssuesPages({ tasks }: { tasks: TasksControl }): React.JSX.Element { /> ) : ( // Keyed by the issue, so another issue's page holds nothing of the last's. - .luke-detail-page-content > .luke-empty-state:only-child) { + display: flex; + flex-direction: column; + } + + .luke-detail-page-content:has(> .luke-empty-state:only-child) { + display: flex; + flex: 1 0 auto; + flex-direction: column; + } + /* Content across the room scrolls sideways in its own column. */ .luke-detail-page[data-width="full"] .luke-detail-page-main { overflow-x: auto; @@ -190,6 +205,19 @@ padding-inline: var(--luke-detail-page-side); } + /* A lone empty or failed state fills the one scroll, as it fills the + main column above. */ + .luke-detail-page-body:has( + > .luke-detail-page-main > .luke-detail-page-content > .luke-empty-state:only-child + ) { + display: flex; + flex-direction: column; + } + + .luke-detail-page-main:has(> .luke-detail-page-content > .luke-empty-state:only-child) { + flex: 1 0 auto; + } + /* The secondary column follows the main one's end gap, no more. */ .luke-detail-page[data-frame="toolbar"] .luke-detail-page-secondary diff --git a/packages/ui/src/components/detail-page/detail-page.tsx b/packages/ui/src/components/detail-page/detail-page.tsx index 4b4e44f635..4d31fd274f 100644 --- a/packages/ui/src/components/detail-page/detail-page.tsx +++ b/packages/ui/src/components/detail-page/detail-page.tsx @@ -12,7 +12,10 @@ import { PAGE_FRAME, PAGE_WIDTH, type PageHeading } from "../page-frame/page-fra * edge and the header's acts end on the secondary column's. Each scroll * reaches the page's edge on its own side, so the wheel works anywhere * over the column it moves. In a pane too narrow for two columns, the - * secondary column follows the main content in the one scroll. + * secondary column follows the main content in the one scroll. A main + * content that is only an `EmptyState` (or a centred `ErrorState`, where the + * thing could not be read) stands it a little above the room's middle, as a + * collection's page stands its empty and failed states. * * Under the window's toolbar (`PAGE_FRAME.TOOLBAR`) the page draws no * header: the toolbar stands still above the columns, and the page is named