web: refine workspace sidebar navigation

This commit is contained in:
2026-08-14 02:20:50 +09:00
parent f9d328f2db
commit d039359386
7 changed files with 62 additions and 35 deletions
@@ -159,6 +159,9 @@ Deno.test("workspace Tickets surface provides Kanban and lifecycle controls", as
const ticketsNav = await Deno.readTextFile( const ticketsNav = await Deno.readTextFile(
new URL("../sidebar/TicketsNavSection.svelte", import.meta.url), new URL("../sidebar/TicketsNavSection.svelte", import.meta.url),
); );
const objectivesNav = await Deno.readTextFile(
new URL("../sidebar/ObjectivesNavSection.svelte", import.meta.url),
);
const ticketsLoad = await Deno.readTextFile( const ticketsLoad = await Deno.readTextFile(
new URL( new URL(
"./../../../routes/w/[workspaceId]/tickets/+page.ts", "./../../../routes/w/[workspaceId]/tickets/+page.ts",
@@ -204,8 +207,15 @@ Deno.test("workspace Tickets surface provides Kanban and lifecycle controls", as
assert( assert(
ticketsNav.includes("workspaceRoute(workspaceId, '/tickets')") && ticketsNav.includes("workspaceRoute(workspaceId, '/tickets')") &&
ticketsNav.includes("Open Tickets"), ticketsNav.includes('class="primary-nav-link"') &&
"Tickets sidebar section should link to the workspace Tickets surface", ticketsNav.includes(">Tickets</a>") &&
!ticketsNav.includes("Open Tickets") &&
!ticketsNav.includes("workspace tickets") &&
objectivesNav.includes('class="primary-nav-link"') &&
objectivesNav.includes(">Objectives</a>") &&
!objectivesNav.includes("Open Objectives") &&
!objectivesNav.includes("workspace objectives"),
"Tickets and Objectives should each be a single sidebar link",
); );
assert( assert(
ticketsLoad.includes("?limit=1000") && ticketsLoad.includes("?limit=1000") &&
@@ -33,8 +33,8 @@
min-width: 0; min-width: 0;
align-items: center; align-items: center;
gap: 0.55rem; gap: 0.55rem;
color: var(--workspace-muted, #53606e); color: var(--text-muted);
font-family: var(--workspace-font-mono, monospace); font-family: var(--font-mono);
font-size: 0.84rem; font-size: 0.84rem;
line-height: 1; line-height: 1;
} }
@@ -53,7 +53,7 @@
} }
.workspace-breadcrumbs a:hover { .workspace-breadcrumbs a:hover {
color: var(--workspace-ink, #151b23); color: var(--text-strong);
text-decoration: underline; text-decoration: underline;
text-underline-offset: 0.22rem; text-underline-offset: 0.22rem;
} }
@@ -68,7 +68,7 @@
} }
.workspace-breadcrumbs span[aria-current='page'] { .workspace-breadcrumbs span[aria-current='page'] {
color: var(--workspace-ink, #151b23); color: var(--text-strong);
font-weight: 600; font-weight: 600;
} }
</style> </style>
@@ -11,12 +11,10 @@
</script> </script>
<section class="nav-section"> <section class="nav-section">
<header class="section-header"> <a
<span>Objectives</span> class="primary-nav-link"
</header> class:active={currentPath.startsWith(objectivesHref)}
href={objectivesHref}
<a class="objective-link" class:active={currentPath.startsWith(objectivesHref)} href={objectivesHref}> aria-current={currentPath.startsWith(objectivesHref) ? 'page' : undefined}
<span class="item-title">Open Objectives</span> >Objectives</a>
<span class="item-meta">workspace objectives</span>
</a>
</section> </section>
@@ -15,6 +15,12 @@
</script> </script>
<aside class="sidebar-frame" class:folded aria-label="Sidebar"> <aside class="sidebar-frame" class:folded aria-label="Sidebar">
{#if !folded}
<div class="sidebar-frame-content">
{@render children()}
</div>
{/if}
<div class="sidebar-control-row"> <div class="sidebar-control-row">
<button <button
class="sidebar-fold-button" class="sidebar-fold-button"
@@ -37,10 +43,4 @@
{/if} {/if}
</button> </button>
</div> </div>
{#if !folded}
<div class="sidebar-frame-content">
{@render children()}
</div>
{/if}
</aside> </aside>
@@ -11,12 +11,10 @@
</script> </script>
<section class="nav-section"> <section class="nav-section">
<header class="section-header"> <a
<span>Tickets</span> class="primary-nav-link"
</header> class:active={currentPath.startsWith(ticketsHref)}
href={ticketsHref}
<a class="objective-link" class:active={currentPath.startsWith(ticketsHref)} href={ticketsHref}> aria-current={currentPath.startsWith(ticketsHref) ? 'page' : undefined}
<span class="item-title">Open Tickets</span> >Tickets</a>
<span class="item-meta">workspace tickets</span>
</a>
</section> </section>
@@ -78,9 +78,9 @@
<nav class="sidebar-sections" aria-label="Workspace sections"> <nav class="sidebar-sections" aria-label="Workspace sections">
<RepositoriesNavSection {repositories} {repositoriesError} {currentPath} {workspaceId} /> <RepositoriesNavSection {repositories} {repositoriesError} {currentPath} {workspaceId} />
<TicketsNavSection {currentPath} {workspaceId} />
<ObjectivesNavSection {currentPath} {workspaceId} /> <ObjectivesNavSection {currentPath} {workspaceId} />
<MemoryNavSection {currentPath} {workspaceId} /> <MemoryNavSection {currentPath} {workspaceId} />
<TicketsNavSection {currentPath} {workspaceId} />
<WorkersNavSection {currentPath} {workspaceId} /> <WorkersNavSection {currentPath} {workspaceId} />
</nav> </nav>
</div> </div>
@@ -4,17 +4,26 @@
.sidebar-frame { .sidebar-frame {
grid-column: 1; grid-column: 1;
grid-row: 1 / 3; grid-row: 1 / 3;
display: flex;
flex-direction: column;
width: clamp(220px, 20vw, 280px); width: clamp(220px, 20vw, 280px);
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
overflow-y: auto; overflow: hidden;
padding: var(--space-4) var(--space-3); padding-block: var(--space-4);
border-right: 1px solid var(--line); border-right: 1px solid var(--line);
} }
.sidebar-frame.folded { .sidebar-frame.folded {
width: max-content; width: max-content;
overflow: hidden; overflow: hidden;
padding-inline: var(--space-2); padding-inline: 0;
}
.sidebar-frame-content {
flex: 1 1 auto;
min-width: 0;
min-height: 0;
overflow-y: auto;
padding-inline: var(--space-3);
} }
.sidebar-frame-content, .sidebar-frame-content,
.global-sidebar, .global-sidebar,
@@ -40,11 +49,13 @@
gap: var(--space-1); gap: var(--space-1);
} }
.sidebar-control-row { .sidebar-control-row {
margin-bottom: var(--space-2); margin-top: auto;
padding: var(--space-4) var(--space-3) 0;
} }
.sidebar-frame.folded .sidebar-control-row { .sidebar-frame.folded .sidebar-control-row {
justify-content: center; justify-content: center;
margin-bottom: 0; padding-inline: var(--space-2);
padding-top: 0;
} }
.sidebar-title-row { .sidebar-title-row {
display: flex; display: flex;
@@ -116,7 +127,7 @@
} }
.sidebar-sections { .sidebar-sections {
display: grid; display: grid;
gap: var(--space-5); gap: var(--space-3);
min-width: 0; min-width: 0;
} }
.nav-section { .nav-section {
@@ -165,6 +176,7 @@
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
.primary-nav-link,
.nav-item, .nav-item,
.objective-link, .objective-link,
.sidebar-link { .sidebar-link {
@@ -179,6 +191,13 @@
text-decoration: none; text-decoration: none;
transition: background-color 140ms ease, color 140ms ease; transition: background-color 140ms ease, color 140ms ease;
} }
.primary-nav-link {
color: var(--text-strong);
font-size: 0.9rem;
font-weight: 700;
}
a.primary-nav-link:hover,
a.primary-nav-link:focus-visible,
a.nav-item:hover, a.nav-item:hover,
a.nav-item:focus-visible, a.nav-item:focus-visible,
a.objective-link:hover, a.objective-link:hover,
@@ -187,11 +206,13 @@
a.sidebar-link:focus-visible { a.sidebar-link:focus-visible {
background: var(--interactive-hover); background: var(--interactive-hover);
} }
a.primary-nav-link.active,
a.nav-item.active, a.nav-item.active,
a.objective-link.active, a.objective-link.active,
a.sidebar-link.active { a.sidebar-link.active {
background: var(--interactive-selected); background: var(--interactive-selected);
} }
a.primary-nav-link.active,
a.nav-item.active .item-title, a.nav-item.active .item-title,
a.objective-link.active .item-title, a.objective-link.active .item-title,
a.sidebar-link.active { a.sidebar-link.active {