From 7d90cb644d1a77140725ab9ea4b16bf325c1a94a Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 27 Jun 2026 03:25:28 +0900 Subject: [PATCH] ui: compact workspace overview --- web/workspace/src/app.css | 179 +++++++++++++++-- .../lib/workspace-pages/WorkspacePage.svelte | 190 ++++-------------- .../RepositoriesNavSection.svelte | 4 - .../WorkersNavSection.svelte | 9 +- 4 files changed, 206 insertions(+), 176 deletions(-) diff --git a/web/workspace/src/app.css b/web/workspace/src/app.css index 45724bf7..95e19e3b 100644 --- a/web/workspace/src/app.css +++ b/web/workspace/src/app.css @@ -52,6 +52,8 @@ --radius-soft: 8px; --radius-panel: 12px; + --interactive-hover: color-mix(in oklch, var(--bg-subtle) 88%, white 12%); + --interactive-selected: color-mix(in oklch, var(--bg-subtle) 88%, var(--accent) 12%); --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; @@ -143,7 +145,8 @@ } .shell { - display: grid; + display: flex; + flex-direction: column; gap: var(--space-6); min-width: 0; min-height: 0; @@ -296,18 +299,32 @@ display: grid; gap: 3px; min-width: 0; - padding: var(--space-2) 0 var(--space-2) var(--space-3); - border-left: 2px solid transparent; + margin-inline: calc(-1 * var(--space-2)); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-soft); color: inherit; text-align: left; text-decoration: none; + transition: + background-color 140ms ease, + color 140ms ease; } - .nav-item.active, - .objective-link.active, - .nav-item:hover, - .objective-link:hover { - border-left-color: var(--accent); + a.nav-item:hover, + a.nav-item:focus-visible, + a.objective-link:hover, + a.objective-link:focus-visible { + background: var(--interactive-hover); + } + + a.nav-item.active, + a.objective-link.active { + background: var(--interactive-selected); + } + + a.nav-item.active .item-title, + a.objective-link.active .item-title { + color: var(--accent); } .item-title, @@ -322,6 +339,26 @@ font-weight: 650; } + .worker-nav-item { + gap: 2px; + } + + .worker-title-row { + display: grid; + grid-template-columns: minmax(0, max-content) minmax(0, 1fr); + align-items: baseline; + gap: var(--space-2); + min-width: 0; + } + + .worker-task-title { + overflow: hidden; + color: var(--text-muted); + font-size: 0.82rem; + text-overflow: ellipsis; + white-space: nowrap; + } + .item-meta, .section-note, .section-state, @@ -338,22 +375,107 @@ .card { min-width: 0; - padding: var(--space-5) 0 0; - border-top: 1px solid var(--line); - } - - .card:first-child { - padding-top: 0; - border-top: 0; + padding: 0; } .runtime-card { - padding: var(--space-4) 0 0; - border-top: 1px solid var(--line); + padding: 0; } - .selected-card.selected { - border-top-color: var(--accent); + .objective-list { + display: flex; + flex-direction: column; + gap: var(--space-2); + margin-top: var(--space-4); + } + + .objective-row { + display: grid; + grid-template-columns: minmax(0, 1fr) max-content; + align-items: start; + gap: var(--space-4); + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-panel); + background: var(--bg-raised); + color: inherit; + text-decoration: none; + transition: background-color 140ms ease; + } + + .objective-row:hover, + .objective-row:focus-visible { + background: var(--interactive-hover); + } + + .objective-row.selected { + background: var(--interactive-selected); + } + + .objective-row.selected .objective-title { + color: var(--accent); + } + + .objective-main { + display: grid; + gap: var(--space-1); + min-width: 0; + } + + .objective-title-row { + display: flex; + align-items: baseline; + gap: var(--space-2); + min-width: 0; + } + + .objective-title { + min-width: 0; + overflow: hidden; + color: var(--text-strong); + font-weight: 700; + text-overflow: ellipsis; + white-space: nowrap; + } + + .state-pill { + flex: 0 0 auto; + color: var(--success); + font-size: 0.76rem; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; + } + + .objective-summary { + margin: 0; + color: var(--text-muted); + font-size: 0.88rem; + line-height: 1.4; + } + + .objective-meta { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: var(--space-2); + color: var(--text-faint); + font-size: 0.78rem; + line-height: 1.35; + text-align: right; + white-space: nowrap; + } + + @media (max-width: 900px) { + .objective-row { + grid-template-columns: 1fr; + gap: var(--space-2); + } + + .objective-meta { + justify-content: flex-start; + text-align: left; + white-space: normal; + } } .runtime-heading, @@ -384,20 +506,37 @@ dl { display: grid; + gap: var(--space-1); + margin: 0; + } + + dl > div { + display: grid; + grid-template-columns: minmax(6.5rem, 10rem) minmax(0, 1fr); + align-items: baseline; gap: var(--space-3); + min-width: 0; } dt { color: var(--text-faint); - font-size: 0.78rem; + font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; + white-space: nowrap; } dd { + min-width: 0; margin: 0; } + dd small { + display: inline; + margin-top: 0; + margin-left: var(--space-2); + } + .table-wrap { overflow-x: auto; } diff --git a/web/workspace/src/lib/workspace-pages/WorkspacePage.svelte b/web/workspace/src/lib/workspace-pages/WorkspacePage.svelte index b628195e..5b13ea25 100644 --- a/web/workspace/src/lib/workspace-pages/WorkspacePage.svelte +++ b/web/workspace/src/lib/workspace-pages/WorkspacePage.svelte @@ -8,7 +8,6 @@ ObjectiveDetail, ObjectiveListResponse, RepositoryDetailResponse, - RepositoryLogResponse, RepositorySummary, RepositoryTicketsResponse, Worker, @@ -33,7 +32,6 @@ { label: 'Tickets', path: '/api/tickets' }, { label: 'Objectives', path: '/api/objectives' }, { label: 'Repositories', path: '/api/repositories' }, - { label: 'Repository log', path: '/api/repositories/local/log' }, { label: 'Repository tickets', path: '/api/repositories/local/tickets' }, { label: 'Runs', path: '/api/runs' }, { label: 'Hosts', path: '/api/hosts' }, @@ -44,7 +42,6 @@ let hosts = $state | null>(null); let workers = $state | null>(null); let repository = $state(null); - let repositoryLog = $state(null); let repositoryTickets = $state(null); let objectives = $state(null); let objectiveDetail = $state(null); @@ -53,7 +50,6 @@ let hostsError = $state(null); let workersError = $state(null); let repositoryError = $state(null); - let repositoryLogError = $state(null); let repositoryTicketsError = $state(null); let objectivesError = $state(null); let objectiveDetailError = $state(null); @@ -111,16 +107,6 @@ } } - async function loadRepositoryLog() { - repositoryLogError = null; - try { - repositoryLog = await getJson('/api/repositories/local/log?limit=10'); - } catch (error) { - repositoryLogError = error instanceof Error ? error.message : String(error); - repositoryLog = null; - } - } - async function loadRepositoryTickets() { repositoryTicketsError = null; try { @@ -196,16 +182,11 @@ return value ?? 'not recorded'; } - function shortHash(hash: string | null | undefined): string { - return hash ? hash.slice(0, 12) : 'unknown'; - } - $effect(() => { void loadWorkspace(); void loadHosts(); void loadWorkers(); void loadRepository(); - void loadRepositoryLog(); void loadRepositoryTickets(); void loadObjectives(); }); @@ -237,116 +218,35 @@
{#if route.page === 'repository'} -
-
-

Repository summary

- {#if repository} -
-
-
ID
-
{repository.id}
-
-
-
Kind
-
{repository.kind}
-
-
-
Workspace root
-
{repository.workspace_root}
-
-
-
Record authority
-
{repository.record_authority}
-
-
-
Git
-
{repository.git.status}
-
-
- {:else if repositoryError} -

{repositoryError}

- {:else} -

Waiting for /api/repositories/local

- {/if} -
- -
-

Git summary

- {#if repository} - {#if repository.git.status === 'available'} -
-
-
Root
-
{repository.git.root ?? 'unknown'}
-
-
-
Branch
-
{repository.git.branch ?? 'unknown'}
-
-
-
HEAD
-
{shortHash(repository.git.head)}
-
-
-
Dirty
-
{repository.git.dirty === null || repository.git.dirty === undefined ? 'unknown' : repository.git.dirty ? 'yes' : 'no'} {repository.git.dirty_scope}
-
-
-
Remote
-
- {#if repository.git.remote} - {repository.git.remote.name} · {repository.git.remote.url} - {#if repository.git.remote.redacted}credentials redacted{/if} - {:else} - not configured - {/if} -
-
-
- {:else} -

Git metadata is unavailable for this local Repository.

- {/if} - {:else if repositoryError} -

{repositoryError}

- {:else} -

Waiting for Git summary…

- {/if} -
-
-
-

Recent Git log

- {#if repositoryLog} - {#if repositoryLog.items.length === 0} -

No recent commits are available from the bounded Git log API.

- {:else} -
- - - - - - - - - - - {#each repositoryLog.items as commit (commit.hash)} - - - - - - - {/each} - -
CommitSubjectAuthorTimestamp
{shortHash(commit.hash)}{commit.subject}{commit.author_name} {commit.author_email}{commit.timestamp}
+

Repository summary

+ {#if repository} +
+
+
ID
+
{repository.id}
- {/if} - {:else if repositoryLogError} -

{repositoryLogError}

+
+
Kind
+
{repository.kind}
+
+
+
Workspace root
+
{repository.workspace_root}
+
+
+
Record authority
+
{repository.record_authority}
+
+
+
Git
+
{repository.git.status}
+
+
+ {:else if repositoryError} +

{repositoryError}

{:else} -

Waiting for /api/repositories/local/log

+

Waiting for /api/repositories/local

{/if}
@@ -364,7 +264,7 @@ {/if} - {@const repositoryDiagnostics = diagnosticsFor(repository?.git.diagnostics, repositoryLog?.diagnostics, repositoryTickets?.diagnostics)} + {@const repositoryDiagnostics = diagnosticsFor(repository?.git.diagnostics, repositoryTickets?.diagnostics)} {#if repositoryDiagnostics.length > 0}

Repository diagnostics

@@ -389,30 +289,22 @@ {#if objectives.items.length === 0}

No Objective records are present.

{:else} -
+
{#each objectives.items as objective (objective.id)} - +
+ Updated {formatDate(objective.updated_at)} + {objective.linked_tickets?.length ? `${objective.linked_tickets.length} linked ticket(s)` : 'No linked tickets'} + {objective.id} +
+ {/each}
{/if} diff --git a/web/workspace/src/lib/workspace-sidebar/RepositoriesNavSection.svelte b/web/workspace/src/lib/workspace-sidebar/RepositoriesNavSection.svelte index f64f44e7..62aa83c7 100644 --- a/web/workspace/src/lib/workspace-sidebar/RepositoriesNavSection.svelte +++ b/web/workspace/src/lib/workspace-sidebar/RepositoriesNavSection.svelte @@ -23,8 +23,4 @@ - -

- Repository authority remains the current workspace root and canonical project records. -

diff --git a/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte b/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte index 8cd50e3f..26dc0ad3 100644 --- a/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte +++ b/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte @@ -64,10 +64,13 @@ {:else}