From 20fce153ae9b23cc12f256308323702887f01edd Mon Sep 17 00:00:00 2001 From: Thomas Petersen Date: Tue, 11 Aug 2026 07:03:57 -0400 Subject: [PATCH] =?UTF-8?q?desktop:=20test=20layout=20=E2=80=94=20single?= =?UTF-8?q?=20workspace=20box=20for=20project=20detail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure the project detail view so the repository / source / branch selection row sits at the top of one panel box, with the tab strip and the active panel inside it. The readme header rows move up: source controls into the selection row, the last-changed timestamp into the tab strip. The files panel drops its duplicated source controls and keeps only its path breadcrumb. All selection dropdowns (repository, source, branch) now share one trigger style. Signed-off-by: Thomas Petersen --- .../projects/ui/ProjectDetailScreen.tsx | 60 +-- .../projects/ui/ProjectOverviewPanel.tsx | 4 + .../projects/ui/ProjectReadmePanel.tsx | 9 +- .../ui/ProjectRepositoryManagement.tsx | 2 +- .../projects/ui/ProjectRepositoryPanel.tsx | 2 - .../projects/ui/ProjectRepositoryPicker.tsx | 17 +- .../projects/ui/ProjectRepositorySource.tsx | 18 +- .../projects/ui/ProjectWorkspaceTabs.tsx | 499 ++++++++++-------- .../projects/ui/projectPanelStyles.ts | 7 + 9 files changed, 349 insertions(+), 269 deletions(-) diff --git a/desktop/src/features/projects/ui/ProjectDetailScreen.tsx b/desktop/src/features/projects/ui/ProjectDetailScreen.tsx index addabcd55..ef68ae26f 100644 --- a/desktop/src/features/projects/ui/ProjectDetailScreen.tsx +++ b/desktop/src/features/projects/ui/ProjectDetailScreen.tsx @@ -843,36 +843,6 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
- - {repoRemote.webUrl && - (repoRemote.host.kind !== "external" || - repoSource === "local") ? ( - - ) : null} - - } activeTabCrumb={activeTabCrumb} activeWorkItemCrumb={activeWorkItemCrumb} chromeRef={projectDetailHeaderChromeRef} @@ -932,6 +902,36 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) { onSelectedTabChange={setActiveTab} profiles={profiles} project={repository} + repositoryControls={ + <> + + {repoRemote.webUrl && + (repoRemote.host.kind !== "external" || + repoSource === "local") ? ( + + ) : null} + + } projectId={project.id} repoDiff={displayedRepoDiff} repoDiffError={displayedRepoDiffError} diff --git a/desktop/src/features/projects/ui/ProjectOverviewPanel.tsx b/desktop/src/features/projects/ui/ProjectOverviewPanel.tsx index 0cb06b3e9..9f56f9752 100644 --- a/desktop/src/features/projects/ui/ProjectOverviewPanel.tsx +++ b/desktop/src/features/projects/ui/ProjectOverviewPanel.tsx @@ -36,6 +36,8 @@ type ProjectOverviewPanelProps = { externalUrl?: string | null; files: ProjectRepoFile[]; gitDataState: GitDataState; + /** Hide the readme header rows when the workspace renders them itself. */ + hideReadmeHeader?: boolean; project: Project; onViewContributors: () => void; profiles?: UserProfileLookup; @@ -149,6 +151,7 @@ export function ProjectOverviewPanel({ externalUrl, files, gitDataState, + hideReadmeHeader, onViewContributors, project, profiles, @@ -182,6 +185,7 @@ export function ProjectOverviewPanel({ externalUrl={externalUrl} file={readmeFile} gitDataState={gitDataState} + hideHeader={hideReadmeHeader} sourceControls={sourceControls} unavailableReason={unavailableReason} /> diff --git a/desktop/src/features/projects/ui/ProjectReadmePanel.tsx b/desktop/src/features/projects/ui/ProjectReadmePanel.tsx index c398f1292..3519f5ea7 100644 --- a/desktop/src/features/projects/ui/ProjectReadmePanel.tsx +++ b/desktop/src/features/projects/ui/ProjectReadmePanel.tsx @@ -140,6 +140,7 @@ export function ReadmePanel({ gitDataState, externalHost, externalUrl, + hideHeader, sourceControls, unavailableReason, }: { @@ -149,13 +150,18 @@ export function ReadmePanel({ gitDataState: "checking" | "available" | "empty" | "unavailable"; externalHost?: string; externalUrl?: string | null; + /** + * Skip the header rows entirely — the workspace layout renders the source + * controls and last-changed timestamp itself. + */ + hideHeader?: boolean; unavailableReason?: ProjectRepoUnavailableReason; /** Branch picker + remote/local toggle rendered in the panel header. */ sourceControls?: RepoSourceHeaderControls; }) { // Two header rows, mirroring the files panel: controls on top, then the // file identity row. - const header = ( + const header = hideHeader ? null : ( <> {sourceControls ? (
@@ -163,7 +169,6 @@ export function ReadmePanel({ @@ -98,7 +103,7 @@ export function ProjectRepositoryPicker({ @@ -229,7 +225,7 @@ export function RepoSourceDropdown({ - ) : null} - {updatePullRequestAction ? ( - - ) : null} -
- ) : null} - {selectedPullRequest ? ( -
- {/* Two full-height columns: the meta rail runs all the way to the + {/* Single workspace box: selection header (repository / source / + branch), then the tab strip, then the active panel. Inner panels + keep their own PROJECT_DETAIL_PANEL_CLASS for standalone use, so + their border + radius are neutralized here via the data attribute + they all carry. */} +
+ {repositoryControls || sourceControls ? ( +
+ {repositoryControls} + {sourceControls ? ( + <> + + +
+ +
+ + ) : null} +
+ ) : null} + {repositoryLoaded ? ( +
+ + {onOpenTerminal ? ( + + ) : null} + {updatePullRequestAction ? ( + + ) : null} + {selectedTab === "overview" && readmeFile ? ( + + Last changed {formatLastChangedAt(readmeFile.lastChangedAt)} + + ) : null} +
+ ) : null} + {selectedPullRequest ? ( +
+ {/* Two full-height columns: the meta rail runs all the way to the top of the card, alongside the header and tabs. */} -
-
- -
- +
+ -
- {(["conversation", "commits", "checks"] as const).map((mode) => ( - - + +
+ {(["conversation", "commits", "checks"] as const).map( + (mode) => ( + + + + ), + )} + + - ))} - - - -
- -
-
- ) : null} - - - setSelectedTab("contributors")} - profiles={profiles} - project={project} - pullRequests={pullRequests} - readmeFile={readmeFile} - snapshot={displayedSnapshot} - sourceControls={sourceControls} - unavailableReason={unavailableReason} - /> - - - - {selectedCommitHash ? ( - commit.hash === selectedCommitHash, - ) ?? null - } - commitAuthorPubkeys={commitAuthorPubkeys} - commitHash={selectedCommitHash} - viewerGitIdentity={viewerGitIdentity} - diff={commitDiff} - diffError={commitDiffError} - diffLoading={commitDiffLoading} - originAgentName={selectedCommitPullRequest?.originAgentName} - originChannelId={selectedCommitPullRequest?.channelId} - profiles={profiles} - /> - ) : ( - onSelectedCommitHashChange(commit.hash)} - profiles={profiles} - pullRequests={pullRequests} - repoContributors={displayedContributors} - snapshot={displayedSnapshot} - viewerGitIdentity={viewerGitIdentity} - /> - )} - - - - setCreatePullRequestOpen(true)} - title="Pull Requests" - /> - - - - - setCreateIssueOpen(true)} - title="Issues" - /> - - - - - {repoSource === "local" && !localSnapshot && !localSnapshotLoading ? ( -
-
- No local checkout found. +
+
) : null} - - - - - + + setSelectedTab("contributors")} + profiles={profiles} + project={project} + pullRequests={pullRequests} + readmeFile={readmeFile} + snapshot={displayedSnapshot} + sourceControls={sourceControls} + unavailableReason={unavailableReason} + /> + + + + {selectedCommitHash ? ( + commit.hash === selectedCommitHash, + ) ?? null + } + commitAuthorPubkeys={commitAuthorPubkeys} + commitHash={selectedCommitHash} + viewerGitIdentity={viewerGitIdentity} + diff={commitDiff} + diffError={commitDiffError} + diffLoading={commitDiffLoading} + originAgentName={selectedCommitPullRequest?.originAgentName} + originChannelId={selectedCommitPullRequest?.channelId} + profiles={profiles} + /> + ) : ( + + onSelectedCommitHashChange(commit.hash) + } + profiles={profiles} + pullRequests={pullRequests} + repoContributors={displayedContributors} + snapshot={displayedSnapshot} + viewerGitIdentity={viewerGitIdentity} + /> + )} + + + + setCreatePullRequestOpen(true)} + title="Pull Requests" + /> + + + + + setCreateIssueOpen(true)} + title="Issues" + /> + + + + + {repoSource === "local" && !localSnapshot && !localSnapshotLoading ? ( +
+
+ No local checkout found. +
+
+ ) : null} + +
+ + + + +
{createPullRequestAction && createPullRequestOpen ? (