diff --git a/studio/knip.ts b/studio/knip.ts index 8c20a3df87..db5052d3f6 100644 --- a/studio/knip.ts +++ b/studio/knip.ts @@ -18,9 +18,7 @@ const config: KnipConfig = { "src/features/**", // Reached only through the UI kit until later PRs in the stacked series // land their first app-level consumers; each line leaves with that PR. - "src/hooks/use-is-truncated.ts", "src/hooks/use-mobile.ts", - "src/lib/typography.ts", ], ignoreDependencies: [ // Tailwind v4 is imported via CSS (@import "tailwindcss"), not JS @@ -31,17 +29,13 @@ const config: KnipConfig = { // (these leave the list as their first reached consumers land later in // the stacked series: lucide-react/cmdk/tooltip with the workspace shell, // select/switch/tabs with the surface pages) - "lucide-react", - "cmdk", "@radix-ui/react-alert-dialog", - "@radix-ui/react-dialog", "@radix-ui/react-dropdown-menu", "@radix-ui/react-label", "@radix-ui/react-scroll-area", "@radix-ui/react-select", "@radix-ui/react-switch", "@radix-ui/react-tabs", - "@radix-ui/react-tooltip", "@radix-ui/react-avatar", "@radix-ui/react-checkbox", "@radix-ui/react-popover", diff --git a/studio/src/app/workspace/layout.tsx b/studio/src/app/workspace/layout.tsx new file mode 100644 index 0000000000..31a8200472 --- /dev/null +++ b/studio/src/app/workspace/layout.tsx @@ -0,0 +1,52 @@ +import { TopNav } from "@/components/shell/top-nav"; +import { RuntimeStatusProvider } from "@/features/agent/runtime-status"; +import { StorageHealthBanner } from "@/features/agent/storage-health-banner"; +import { ShortcutsProvider } from "@/lib/shortcuts/use-shortcuts"; + +/** + * The workspace shell: a fixed dark-green radial gradient carrying the top + * navigation bar, with all five surfaces rendered inside one rounded card + * that follows the theme. The gradient itself is a fixed brand colour — + * identical in light and dark themes — so only the card interior themes. + * + * `RuntimeStatusProvider` stays outermost: its offline banner renders above + * the top nav at full width. Workspace sections manage their own scrolling + * and padding inside the card (`h-full overflow-y-auto …`). + */ +export default function WorkspaceLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {/* The design's green radial gradient; dark mode deepens each stop so + the shell recedes behind the dark card instead of outglowing it. + Top padding tracks the status-bar safe area: the installed iOS + PWA (black-translucent status bar + viewport-fit cover) draws + under the clock, so the nav must start below it while the + gradient still paints behind it. */} +
+ {/* Storage health rides the same full-width banner band as the + runtime status banners: visible from every surface, because a + degraded store shows up as chats missing from the sidebar. */} + + + {/* relative makes the card the containing block for absolutely- + positioned descendants with no positioned ancestor of their own + — notably the hidden form-integration checkbox Radix renders + beside each Switch inside a
. Without it those boxes + resolve to the document and grow the page itself. */} + {/* On mobile the card goes full-bleed — no gradient margin, only + the top corners stay rounded where it meets the nav band — so + content spans the screen; the gradient survives only behind the + top nav and the tab bar. The inset card is a ≥500px treatment. */} +
+ {children} +
+
+
+
+ ); +} diff --git a/studio/src/app/workspace/shortcuts/page.tsx b/studio/src/app/workspace/shortcuts/page.tsx new file mode 100644 index 0000000000..1e9aade517 --- /dev/null +++ b/studio/src/app/workspace/shortcuts/page.tsx @@ -0,0 +1,65 @@ +import type { Metadata } from "next"; +import { keycaps, SHORTCUT_GROUPS, SHORTCUTS } from "@/lib/shortcuts/registry"; +import { pageTitleClass } from "@/lib/typography"; + +export const metadata: Metadata = { title: "Keyboard shortcuts — Workspace" }; + +/** A single keycap. */ +function Key({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +/** + * Reference page listing every keyboard shortcut, rendered directly from the + * shortcut registry so it can't drift from the live bindings. Reached from the + * profile menu and by pressing `?`. + */ +export default function KeyboardShortcutsPage() { + return ( +
+
+
+

+ Keyboard shortcuts +

+

+ Work faster with the keyboard. On Windows and Linux, use Ctrl + wherever ⌘ is shown. +

+
+ +
+ {SHORTCUT_GROUPS.map((group) => ( +
+

+ {group} +

+
    + {SHORTCUTS.filter((s) => s.group === group).map((s) => ( +
  • + + {s.description} + + + {keycaps(s.combo).map((k, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: positional keycaps + {k} + ))} + +
  • + ))} +
+
+ ))} +
+
+
+ ); +} diff --git a/studio/src/components/app/nav-items.ts b/studio/src/components/app/nav-items.ts new file mode 100644 index 0000000000..d0dbfd1ccd --- /dev/null +++ b/studio/src/components/app/nav-items.ts @@ -0,0 +1,76 @@ +/** + * The Atrium workspace navigation configuration — the single canonical + * nav-items module. The top nav is generic over the `ShellNav` built here: + * a single row of workspace destinations — the in-app chat plus the state + * that outlives a turn (Skills, Memory, Scheduled). The wordmark and home + * both point at Chats. + */ + +import { Clock, GraduationCap, MessageCircle, Settings } from "lucide-react"; +import type { ComponentType } from "react"; +import { ATRIUM_WORKSPACE_HOME } from "@/lib/feature-flags"; + +interface NavItem { + /** Stable key for React keys and test selectors. */ + readonly key: string; + /** Nav label (pill text when active, tooltip when inactive). */ + readonly label: string; + /** Absolute route this destination points at. */ + readonly href: string; + /** Nav icon. */ + readonly icon: ComponentType<{ className?: string }>; +} + +/** + * The console's navigation configuration. One value drives the whole shell: + * the top-nav destinations and the wordmark's home link. + */ +export interface ShellNav { + /** The console's root route (used for the "home" active-state rule). */ + readonly homeHref: string; + /** Where the wordmark links, if different from `homeHref` (e.g. Atrium + sends the logo to the workspace chats rather than the gateway home). */ + readonly logoHref?: string; + /** `aria-label` for the `