Simplify the sidebar into a led primary group + demoted rest

The flat 13-item list read as clutter and reused icons (Team/Meetings,
Work/Sprints, Inbox/Approvals all shared a glyph). Now:
- Primary group leads with the core loop: Home, Work, Team, Messages,
  Runs, Files, Approvals.
- A divider demotes the occasional surfaces: Inbox, Documents,
  Knowledge, Meetings, Sprints, Social — still one click away, nothing
  orphaned (search can't route to several of them).
- Distinct calendar/target/inbox/folder icons end the collisions.
- Every nav glyph is the 18px icon bound in a 24px box; the collapsed
  rail centers icons instead of hiding them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.8 committed 2026-06-10 19:44:59 -04:00
1 parent 02c9da6fe9
commit ea9f27916e
4 files changed
+59 -22

No files matched your search

@@ -180,9 +180,11 @@
@media (max-width: 980px) {
.sidebar { width: 64px; }
.sidebar .brand-name,
.sidebar .nav-item span:not(.nav-badge),
.sidebar .nav-item span:not(.nav-badge):not(.icon-box),
.sidebar-foot-name,
.sidebar .search-bar > span:not(.search-kbd) { display: none; }
.sidebar .nav-item { justify-content: center; }
.sidebar .nav-badge { display: none; }
.sidebar .search-bar { justify-content: center; padding: var(--sp-2); }
.sidebar .search-kbd { display: none; }
.content { padding: var(--sp-5) var(--sp-4) var(--sp-8); }
@@ -41,6 +41,8 @@ const PATHS: Record<string, ReactNode> = {
refresh: <><path d="M21 12a9 9 0 1 1-3-6.7" /><path d="M21 3v6h-6" /></>,
pause: <><rect x="6" y="5" width="4" height="14" rx="1" /><rect x="14" y="5" width="4" height="14" rx="1" /></>,
shield: <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z" />,
calendar: <><rect x="3" y="4.5" width="18" height="16" rx="2" /><path d="M3 9h18M8 2.5v4M16 2.5v4" /></>,
target: <><circle cx="12" cy="12" r="8" /><circle cx="12" cy="12" r="3.6" /></>,
};
export type IconName = keyof typeof PATHS | (string & {});
@@ -32,22 +32,29 @@ interface NavSpec {
* internal scroll regions, so the content area must not scroll as one block. */
const FLUSH_ROUTES = new Set<RouteId>(["chat", "settings", "files", "social", "runs"]);
/** The daily-driver surfaces — the core operating loop. */
const primaryNav: NavSpec[] = [
{ id: "home", label: "Home", icon: "home" },
{ id: "inbox", label: "Inbox", icon: "approve" },
{ id: "work", label: "Work", icon: "work" },
{ id: "approvals", label: "Approvals", icon: "approve" },
{ id: "team", label: "Team", icon: "team" },
{ id: "chat", label: "Messages", icon: "chat" },
{ id: "files", label: "Files", icon: "docs" },
{ id: "runs", label: "Runs", icon: "send" },
{ id: "files", label: "Files", icon: "folder" },
{ id: "approvals", label: "Approvals", icon: "approve" },
];
/** Less-frequent surfaces, demoted under a divider so the primary set leads. */
const secondaryNav: NavSpec[] = [
{ id: "inbox", label: "Inbox", icon: "inbox" },
{ id: "docs", label: "Documents", icon: "file" },
{ id: "knowledge", label: "Knowledge", icon: "sparkle" },
{ id: "runs", label: "Runs", icon: "send" },
{ id: "meetings", label: "Meetings", icon: "team" },
{ id: "sprints", label: "Sprints", icon: "work" },
{ id: "meetings", label: "Meetings", icon: "calendar" },
{ id: "sprints", label: "Sprints", icon: "target" },
{ id: "social", label: "Social", icon: "social" },
];
const allNav: NavSpec[] = [...primaryNav, ...secondaryNav];
export function Shell({
snapshot,
route,
@@ -104,17 +111,13 @@ export function Shell({
</button>
<nav className="nav">
{primaryNav.map((item) => (
<button
key={item.id}
type="button"
aria-label={item.label}
className={`nav-item ${route === item.id ? "active" : ""}`.trim()}
onClick={() => setRoute(item.id)}
>
<Icon name={item.icon} size={15} />
<span>{item.label}</span>
{renderBadge(badges[item.id])}
</button>
<NavButton key={item.id} item={item} active={route === item.id} badge={badges[item.id]} onSelect={setRoute} />
))}
</nav>
<div className="nav-divider" />
<nav className="nav">
{secondaryNav.map((item) => (
<NavButton key={item.id} item={item} active={route === item.id} badge={badges[item.id]} onSelect={setRoute} />
))}
</nav>
<div className="sidebar-spacer" />
@@ -122,10 +125,11 @@ export function Shell({
<button
type="button"
aria-label="Settings"
aria-current={route === "settings" ? "page" : undefined}
className={`nav-item ${route === "settings" ? "active" : ""}`.trim()}
onClick={() => setRoute("settings")}
>
<Icon name="settings" size={15} />
<span className="icon-box"><Icon name="settings" size={18} /></span>
<span>Settings</span>
</button>
</nav>
@@ -167,8 +171,34 @@ export function Shell({
}
function NavButton({
item,
active,
badge,
onSelect,
}: {
item: NavSpec;
active: boolean;
badge: number | undefined;
onSelect: (route: RouteId) => void;
}) {
return (
<button
type="button"
aria-label={item.label}
aria-current={active ? "page" : undefined}
className={`nav-item ${active ? "active" : ""}`.trim()}
onClick={() => onSelect(item.id)}
>
<span className="icon-box"><Icon name={item.icon} size={18} /></span>
<span>{item.label}</span>
{renderBadge(badge)}
</button>
);
}
function labelFor(route: RouteId): string {
const spec = primaryNav.find((item) => item.id === route);
const spec = allNav.find((item) => item.id === route);
if (spec) return spec.label;
if (route === "settings") return "Settings";
throw new Error(`Unknown route: ${route}`);
+5 -2
View File
@@ -195,14 +195,17 @@ a { color: inherit; }
.nav-item:active { transform: scale(0.985); }
.nav-item.active { background: var(--panel-3); color: var(--text-1); font-weight: 560; }
.nav-item.active svg { color: var(--text-1); }
.nav-item svg { color: var(--text-3); flex-shrink: 0; transition: color var(--motion-fast) var(--ease-out); }
.nav-item span:not(.nav-badge) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-item svg { color: var(--text-3); transition: color var(--motion-fast) var(--ease-out); }
.nav-item .icon-box { color: var(--text-3); }
.nav-item.active .icon-box { color: var(--text-1); }
.nav-item span:not(.nav-badge):not(.icon-box) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-badge {
margin-left: auto;
font-size: var(--fs-caption);
color: var(--text-3);
font-variant-numeric: tabular-nums;
}
.nav-divider { height: 1px; background: var(--border-soft); margin: var(--sp-2); }
.org-menu {
position: absolute;
left: 0;