History

Merge pull request #733 from RhysSullivan/marketing/fix-grid-pattern-and-copy-button

Cloud sidebar: Beta badge, Get support modal, dev proxy auto-restart

Rhys Sullivan committed 6 months ago

Commit 14dfaa02 parents 28ccae2 51b097b4 files · +181 −128Browse files

Unified · Split

4 files
  1. apps/cloud/package.json +1 −1
  2. apps/cloud/src/routes/__root.tsx +3 −116
  3. apps/cloud/src/web/components/support-options.tsx +112 −0
  4. apps/cloud/src/web/shell.tsx +65 −11
apps/cloud/package.json+1−1
@@ -5,7 +5,7 @@ · 4 unmodified lines
5 "type": "module",5 "type": "module",
6 "scripts": {6 "scripts": {
7 "dev": "bun run dev:proxy && concurrently -n db,vite -c blue,green \"bun run dev:db\" \"bun run dev:vite\"",7 "dev": "bun run dev:proxy && concurrently -n db,vite -c blue,green \"bun run dev:db\" \"bun run dev:vite\"",
8 "dev:proxy": "portless proxy start --multiplex --shared-port --port 5394 || true",8 "dev:proxy": "portless proxy start --multiplex --shared-port --port 5394 || (portless proxy stop -p 5394 && portless proxy start --multiplex --shared-port --port 5394)",
9 "dev:db": "bun run scripts/dev-db.ts",9 "dev:db": "bun run scripts/dev-db.ts",
10 "dev:vite": "EXECUTOR_DIRECT_DATABASE_URL=true CLOUDFLARE_INCLUDE_PROCESS_ENV=true op run --env-file=.env.op -- portless --name executor-cloud vite dev",10 "dev:vite": "EXECUTOR_DIRECT_DATABASE_URL=true CLOUDFLARE_INCLUDE_PROCESS_ENV=true op run --env-file=.env.op -- portless --name executor-cloud vite dev",
11 "db:schema": "node --import jiti/register ../../packages/core/cli/src/index.ts generate --config ./executor.config.ts --output ./src/services/executor-schema.ts",11 "db:schema": "node --import jiti/register ../../packages/core/cli/src/index.ts generate --config ./executor.config.ts --output ./src/services/executor-schema.ts",
apps/cloud/src/routes/__root.tsx+3−116
@@ -6,22 +6,12 @@ import posthog from "posthog-js"; · 5 unmodified lines
6import { PostHogProvider } from "posthog-js/react";6import { PostHogProvider } from "posthog-js/react";
7import type { FrontendErrorReporter } from "@executor-js/react/api/error-reporting";7import type { FrontendErrorReporter } from "@executor-js/react/api/error-reporting";
8import { ExecutorProvider } from "@executor-js/react/api/provider";8import { ExecutorProvider } from "@executor-js/react/api/provider";
9import { Button } from "@executor-js/react/components/button";
10import {
11 Dialog,
12 DialogClose,
13 DialogContent,
14 DialogDescription,
15 DialogFooter,
16 DialogHeader,
17 DialogTitle,
18 DialogTrigger,
19} from "@executor-js/react/components/dialog";
20import { Skeleton } from "@executor-js/react/components/skeleton";9import { Skeleton } from "@executor-js/react/components/skeleton";
21import { Toaster } from "@executor-js/react/components/sonner";10import { Toaster } from "@executor-js/react/components/sonner";
22import { ExecutorPluginsProvider } from "@executor-js/sdk/client";11import { ExecutorPluginsProvider } from "@executor-js/sdk/client";
23import { plugins as clientPlugins } from "virtual:executor/plugins-client";12import { plugins as clientPlugins } from "virtual:executor/plugins-client";
24import { AuthProvider, useAuth } from "../web/auth";13import { AuthProvider, useAuth } from "../web/auth";
14import { SupportOptions } from "../web/components/support-options";
25import { LoginPage } from "../web/pages/login";15import { LoginPage } from "../web/pages/login";
26import { OnboardingPage } from "../web/pages/onboarding";16import { OnboardingPage } from "../web/pages/onboarding";
27import { Shell } from "../web/shell";17import { Shell } from "../web/shell";
@@ -185,17 +175,6 @@ function ShellSkeleton() { · 157 unmodified lines
185}175}
186 176
187function ShellErrorFallback() {177function ShellErrorFallback() {
188 const [slackOpen, setSlackOpen] = React.useState(false);
189 const supportLinks = [
190 { label: "Discord", href: "https://discord.gg/eF29HBHwM6", icon: DiscordMark },
191 {
192 label: "GitHub Issues",
193 href: "https://github.com/RhysSullivan/executor/issues",
194 icon: GitHubMark,
195 },
196 { label: "Email", href: "mailto:rhys@executor.sh?subject=Executor%20support", icon: MailMark },
197 ] as const;
198
199 return (178 return (
200 <main className="flex min-h-screen items-center justify-center bg-background px-6 py-10">179 <main className="flex min-h-screen items-center justify-center bg-background px-6 py-10">
201 <section className="w-full max-w-md text-center">180 <section className="w-full max-w-md text-center">
@@ -209,106 +188,14 @@ function ShellErrorFallback() { · 7 unmodified lines
209 <p className="mt-6 text-xs font-medium uppercase tracking-wide text-muted-foreground">188 <p className="mt-6 text-xs font-medium uppercase tracking-wide text-muted-foreground">
210 Get support189 Get support
211 </p>190 </p>
212 <div className="mt-3 flex flex-wrap items-center justify-center gap-2 text-sm">191 <div className="mt-3">
213 <Dialog open={slackOpen} onOpenChange={setSlackOpen}>192 <SupportOptions />
214 <DialogTrigger asChild>
215 <Button type="button" variant="outline" size="sm" className="gap-2">
216 <SlackMark className="size-4" />
217 Slack
218 </Button>
219 </DialogTrigger>
220 <DialogContent>
221 <DialogHeader>
222 <DialogTitle>Slack Connect</DialogTitle>
223 <DialogDescription>
224 Invite <span className="font-medium text-foreground">rhys@executor.sh</span> to
225 Slack Connect.
226 </DialogDescription>
227 </DialogHeader>
228 <DialogFooter>
229 <DialogClose asChild>
230 <Button type="button" variant="outline">
231 Done
232 </Button>
233 </DialogClose>
234 </DialogFooter>
235 </DialogContent>
236 </Dialog>
237 {supportLinks.map((link) => (
238 // oxlint-disable-next-line react/jsx-no-new-function-as-prop -- static support link component choice
239 <a
240 key={link.label}
241 href={link.href}
242 className="inline-flex h-9 items-center gap-2 rounded-md border border-border bg-background px-3 font-medium text-foreground transition-colors hover:bg-muted"
243 >
244 <link.icon className="size-4" />
245 {link.label}
246 </a>
247 ))}
248 </div>193 </div>
249 </section>194 </section>
250 </main>195 </main>
251 );196 );
252}197}
253 198
254function DiscordMark({ className }: { className?: string }) {
255 return (
256 <svg viewBox="0 0 24 24" fill="currentColor" className={className} aria-hidden>
257 <path d="M20.32 4.37A19.8 19.8 0 0 0 15.36 2.8a13.8 13.8 0 0 0-.64 1.32 18.4 18.4 0 0 0-5.44 0 13.8 13.8 0 0 0-.64-1.32 19.7 19.7 0 0 0-4.97 1.57C.53 9.09-.32 13.69.1 18.22a19.9 19.9 0 0 0 6.08 3.03 14.7 14.7 0 0 0 1.3-2.09 12.8 12.8 0 0 1-2.04-.97l.5-.38a14.2 14.2 0 0 0 12.12 0l.5.38c-.65.38-1.33.7-2.04.97.37.74.8 1.44 1.3 2.09a19.9 19.9 0 0 0 6.08-3.03c.5-5.25-.84-9.8-3.58-13.85ZM8.02 15.43c-1.18 0-2.15-1.08-2.15-2.4 0-1.33.95-2.41 2.15-2.41 1.2 0 2.17 1.09 2.15 2.4 0 1.33-.95 2.41-2.15 2.41Zm7.96 0c-1.18 0-2.15-1.08-2.15-2.4 0-1.33.95-2.41 2.15-2.41 1.2 0 2.17 1.09 2.15 2.4 0 1.33-.95 2.41-2.15 2.41Z" />
258 </svg>
259 );
260}
261
262function GitHubMark({ className }: { className?: string }) {
263 return (
264 <svg viewBox="0 0 24 24" fill="currentColor" className={className} aria-hidden>
265 <path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2.15c-3.2.7-3.88-1.36-3.88-1.36-.52-1.33-1.28-1.68-1.28-1.68-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.71 1.26 3.37.96.1-.75.4-1.26.73-1.55-2.55-.29-5.24-1.28-5.24-5.68 0-1.25.45-2.28 1.19-3.08-.12-.29-.52-1.46.11-3.04 0 0 .98-.31 3.17 1.18a10.9 10.9 0 0 1 5.78 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.58.23 2.75.11 3.04.74.8 1.19 1.83 1.19 3.08 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.06.78 2.14v3.16c0 .31.21.67.79.56A11.5 11.5 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5Z" />
266 </svg>
267 );
268}
269
270function MailMark({ className }: { className?: string }) {
271 return (
272 <svg
273 viewBox="0 0 24 24"
274 fill="none"
275 stroke="currentColor"
276 strokeWidth="1.8"
277 className={className}
278 aria-hidden
279 >
280 <path
281 strokeLinecap="round"
282 strokeLinejoin="round"
283 d="M3 7l9 6 9-6M5 19h14a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2Z"
284 />
285 </svg>
286 );
287}
288
289function SlackMark({ className }: { className?: string }) {
290 return (
291 <svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
292 <path
293 d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z"
294 fill="#E01E5A"
295 />
296 <path
297 d="M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.527 2.527 0 0 1 2.521 2.521 2.527 2.527 0 0 1-2.521 2.521H2.522A2.527 2.527 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z"
298 fill="#36C5F0"
299 />
300 <path
301 d="M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.272 0a2.528 2.528 0 0 1-2.521 2.521 2.527 2.527 0 0 1-2.521-2.521V2.522A2.527 2.527 0 0 1 15.163 0a2.528 2.528 0 0 1 2.521 2.522v6.312z"
302 fill="#2EB67D"
303 />
304 <path
305 d="M15.163 18.956a2.528 2.528 0 0 1 2.521 2.522A2.528 2.528 0 0 1 15.163 24a2.527 2.527 0 0 1-2.521-2.522v-2.522h2.521zm0-1.272a2.527 2.527 0 0 1-2.521-2.521 2.527 2.527 0 0 1 2.521-2.521h6.315A2.527 2.527 0 0 1 24 15.163a2.528 2.528 0 0 1-2.522 2.521h-6.315z"
306 fill="#ECB22E"
307 />
308 </svg>
309 );
310}
311
312function AuthGate() {199function AuthGate() {
313 const auth = useAuth();200 const auth = useAuth();
314 201
apps/cloud/src/web/components/support-options.tsx+112−0
@@ -0,0 +1,112 @@
1import { Button } from "@executor-js/react/components/button";
2import {
3 Popover,
4 PopoverContent,
5 PopoverDescription,
6 PopoverHeader,
7 PopoverTitle,
8 PopoverTrigger,
9} from "@executor-js/react/components/popover";
10
11const supportLinks = [
12 { label: "Discord", href: "https://discord.gg/eF29HBHwM6", icon: DiscordMark },
13 {
14 label: "GitHub Issues",
15 href: "https://github.com/RhysSullivan/executor/issues",
16 icon: GitHubMark,
17 },
18 { label: "Email", href: "mailto:rhys@executor.sh?subject=Executor%20support", icon: MailMark },
19] as const;
20
21export function SupportOptions() {
22 return (
23 <div className="flex flex-wrap items-center justify-center gap-2 text-sm">
24 <Popover>
25 <PopoverTrigger asChild>
26 <Button type="button" variant="outline" size="sm" className="gap-2">
27 <SlackMark className="size-4" />
28 Slack
29 </Button>
30 </PopoverTrigger>
31 <PopoverContent className="w-72">
32 <PopoverHeader>
33 <PopoverTitle>Slack Connect</PopoverTitle>
34 <PopoverDescription>
35 Invite <span className="font-medium text-foreground">rhys@executor.sh</span> to Slack
36 Connect.
37 </PopoverDescription>
38 </PopoverHeader>
39 </PopoverContent>
40 </Popover>
41 {supportLinks.map((link) => (
42 // oxlint-disable-next-line react/jsx-no-new-function-as-prop -- static support link component choice
43 <a
44 key={link.label}
45 href={link.href}
46 className="inline-flex h-9 items-center gap-2 rounded-md border border-border bg-background px-3 font-medium text-foreground transition-colors hover:bg-muted"
47 >
48 <link.icon className="size-4" />
49 {link.label}
50 </a>
51 ))}
52 </div>
53 );
54}
55
56function DiscordMark({ className }: { className?: string }) {
57 return (
58 <svg viewBox="0 0 24 24" fill="currentColor" className={className} aria-hidden>
59 <path d="M20.32 4.37A19.8 19.8 0 0 0 15.36 2.8a13.8 13.8 0 0 0-.64 1.32 18.4 18.4 0 0 0-5.44 0 13.8 13.8 0 0 0-.64-1.32 19.7 19.7 0 0 0-4.97 1.57C.53 9.09-.32 13.69.1 18.22a19.9 19.9 0 0 0 6.08 3.03 14.7 14.7 0 0 0 1.3-2.09 12.8 12.8 0 0 1-2.04-.97l.5-.38a14.2 14.2 0 0 0 12.12 0l.5.38c-.65.38-1.33.7-2.04.97.37.74.8 1.44 1.3 2.09a19.9 19.9 0 0 0 6.08-3.03c.5-5.25-.84-9.8-3.58-13.85ZM8.02 15.43c-1.18 0-2.15-1.08-2.15-2.4 0-1.33.95-2.41 2.15-2.41 1.2 0 2.17 1.09 2.15 2.4 0 1.33-.95 2.41-2.15 2.41Zm7.96 0c-1.18 0-2.15-1.08-2.15-2.4 0-1.33.95-2.41 2.15-2.41 1.2 0 2.17 1.09 2.15 2.4 0 1.33-.95 2.41-2.15 2.41Z" />
60 </svg>
61 );
62}
63
64function GitHubMark({ className }: { className?: string }) {
65 return (
66 <svg viewBox="0 0 24 24" fill="currentColor" className={className} aria-hidden>
67 <path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2.15c-3.2.7-3.88-1.36-3.88-1.36-.52-1.33-1.28-1.68-1.28-1.68-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.71 1.26 3.37.96.1-.75.4-1.26.73-1.55-2.55-.29-5.24-1.28-5.24-5.68 0-1.25.45-2.28 1.19-3.08-.12-.29-.52-1.46.11-3.04 0 0 .98-.31 3.17 1.18a10.9 10.9 0 0 1 5.78 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.58.23 2.75.11 3.04.74.8 1.19 1.83 1.19 3.08 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.06.78 2.14v3.16c0 .31.21.67.79.56A11.5 11.5 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5Z" />
68 </svg>
69 );
70}
71
72function MailMark({ className }: { className?: string }) {
73 return (
74 <svg
75 viewBox="0 0 24 24"
76 fill="none"
77 stroke="currentColor"
78 strokeWidth="1.8"
79 className={className}
80 aria-hidden
81 >
82 <path
83 strokeLinecap="round"
84 strokeLinejoin="round"
85 d="M3 7l9 6 9-6M5 19h14a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2Z"
86 />
87 </svg>
88 );
89}
90
91function SlackMark({ className }: { className?: string }) {
92 return (
93 <svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
94 <path
95 d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z"
96 fill="#E01E5A"
97 />
98 <path
99 d="M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.527 2.527 0 0 1 2.521 2.521 2.527 2.527 0 0 1-2.521 2.521H2.522A2.527 2.527 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z"
100 fill="#36C5F0"
101 />
102 <path
103 d="M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.272 0a2.528 2.528 0 0 1-2.521 2.521 2.527 2.527 0 0 1-2.521-2.521V2.522A2.527 2.527 0 0 1 15.163 0a2.528 2.528 0 0 1 2.521 2.522v6.312z"
104 fill="#2EB67D"
105 />
106 <path
107 d="M15.163 18.956a2.528 2.528 0 0 1 2.521 2.522A2.528 2.528 0 0 1 15.163 24a2.527 2.527 0 0 1-2.521-2.522v-2.522h2.521zm0-1.272a2.527 2.527 0 0 1-2.521-2.521 2.527 2.527 0 0 1 2.521-2.521h6.315A2.527 2.527 0 0 1 24 15.163a2.528 2.528 0 0 1-2.522 2.521h-6.315z"
108 fill="#ECB22E"
109 />
110 </svg>
111 );
112}
apps/cloud/src/web/shell.tsx+65−11
@@ -16,6 +16,7 @@ import { · 15 unmodified lines
16 DialogHeader,16 DialogHeader,
17 DialogTitle,17 DialogTitle,
18} from "@executor-js/react/components/dialog";18} from "@executor-js/react/components/dialog";
19import { SupportOptions } from "./components/support-options";
19import {20import {
20 DropdownMenu,21 DropdownMenu,
21 DropdownMenuContent,22 DropdownMenuContent,
@@ -37,6 +38,19 @@ import { · 15 unmodified lines
37 useCreateOrganizationForm,38 useCreateOrganizationForm,
38} from "./components/create-organization-form";39} from "./components/create-organization-form";
39 40
41// ── Brand ────────────────────────────────────────────────────────────────
42
43function Brand(props: { onNavigate?: () => void }) {
44 return (
45 <Link to="/" onClick={props.onNavigate} className="flex items-center gap-1.5">
46 <span className="font-display text-base tracking-tight text-foreground">executor</span>
47 <span className="rounded-sm bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-primary">
48 Beta
49 </span>
50 </Link>
51 );
52}
53
40// ── NavItem ──────────────────────────────────────────────────────────────54// ── NavItem ──────────────────────────────────────────────────────────────
41 55
42function NavItem(props: { to: string; label: string; active: boolean; onNavigate?: () => void }) {56function NavItem(props: { to: string; label: string; active: boolean; onNavigate?: () => void }) {
@@ -351,6 +365,50 @@ function UserFooter() { · 308 unmodified lines
351 );365 );
352}366}
353 367
368// ── SupportButton ────────────────────────────────────────────────────────
369
370function HelpIcon({ className }: { className?: string }) {
371 return (
372 <svg viewBox="0 0 16 16" fill="none" className={className} aria-hidden>
373 <circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeWidth="1.3" />
374 <path
375 d="M6.25 6.25c.25-1 1-1.5 1.85-1.5 1 0 1.9.7 1.9 1.7 0 .8-.5 1.2-1.1 1.6-.55.4-.9.7-.9 1.45M8 11.25v.05"
376 stroke="currentColor"
377 strokeWidth="1.3"
378 strokeLinecap="round"
379 />
380 </svg>
381 );
382}
383
384function SupportButton() {
385 const [open, setOpen] = useState(false);
386 return (
387 <Dialog open={open} onOpenChange={setOpen}>
388 <Button
389 type="button"
390 variant="ghost"
391 onClick={() => setOpen(true)}
392 className="flex h-auto w-full items-center justify-start gap-2.5 rounded-md px-2.5 py-1.5 text-sm font-normal text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground"
393 >
394 <HelpIcon className="size-3.5 text-muted-foreground" />
395 Get support
396 </Button>
397 <DialogContent className="sm:max-w-[440px]">
398 <DialogHeader>
399 <DialogTitle className="font-display text-xl">Get support</DialogTitle>
400 <DialogDescription className="text-sm leading-relaxed">
401 Reach out through any of the channels below.
402 </DialogDescription>
403 </DialogHeader>
404 <div className="py-2">
405 <SupportOptions />
406 </div>
407 </DialogContent>
408 </Dialog>
409 );
410}
411
354// ── SidebarContent ───────────────────────────────────────────────────────412// ── SidebarContent ───────────────────────────────────────────────────────
355 413
356function SidebarContent(props: { pathname: string; onNavigate?: () => void; showBrand?: boolean }) {414function SidebarContent(props: { pathname: string; onNavigate?: () => void; showBrand?: boolean }) {
@@ -365,9 +423,7 @@ function SidebarContent(props: { pathname: string; onNavigate?: () => void; show · 8 unmodified lines
365 <>423 <>
366 {props.showBrand !== false && (424 {props.showBrand !== false && (
367 <div className="flex h-12 shrink-0 items-center border-b border-sidebar-border px-4">425 <div className="flex h-12 shrink-0 items-center border-b border-sidebar-border px-4">
368 <Link to="/" className="flex items-center gap-1.5">426 <Brand onNavigate={props.onNavigate} />
369 <span className="font-display text-base tracking-tight text-foreground">executor</span>
370 </Link>
371 </div>427 </div>
372 )}428 )}
373 429
@@ -396,6 +452,10 @@ function SidebarContent(props: { pathname: string; onNavigate?: () => void; show · 22 unmodified lines
396 <SourceList pathname={props.pathname} onNavigate={props.onNavigate} />452 <SourceList pathname={props.pathname} onNavigate={props.onNavigate} />
397 </nav>453 </nav>
398 454
455 <div className="shrink-0 px-2 pb-2">
456 <SupportButton />
457 </div>
458
399 <UserFooter />459 <UserFooter />
400 </>460 </>
401 );461 );
@@ -443,11 +503,7 @@ export function Shell() { · 41 unmodified lines
443 />503 />
444 <div className="relative flex h-full w-[84vw] max-w-xs flex-col border-r border-sidebar-border bg-sidebar shadow-2xl">504 <div className="relative flex h-full w-[84vw] max-w-xs flex-col border-r border-sidebar-border bg-sidebar shadow-2xl">
445 <div className="flex h-12 shrink-0 items-center justify-between border-b border-sidebar-border px-4">505 <div className="flex h-12 shrink-0 items-center justify-between border-b border-sidebar-border px-4">
446 <Link to="/" className="flex items-center gap-1.5">506 <Brand onNavigate={() => setMobileSidebarOpen(false)} />
447 <span className="font-display text-base tracking-tight text-foreground">
448 executor
449 </span>
450 </Link>
451 <Button507 <Button
452 variant="ghost"508 variant="ghost"
453 size="icon-sm"509 size="icon-sm"
@@ -496,9 +552,7 @@ export function Shell() { · 42 unmodified lines
496 />552 />
497 </svg>553 </svg>
498 </Button>554 </Button>
499 <Link to="/" className="flex items-center gap-1.5">555 <Brand />
500 <span className="font-display text-base tracking-tight text-foreground">executor</span>
501 </Link>
502 <div className="w-8 shrink-0" />556 <div className="w-8 shrink-0" />
503 </div>557 </div>
504 558