Add Foundry Tauri v2 desktop app with UI polish

- Scaffold Tauri v2 desktop package (foundry/packages/desktop)
- Sidecar build script compiles backend into standalone Bun binary
- Frontend build script packages Vite output for Tauri webview
- macOS glass-effect app icon following Big Sur design standards
- Collapsible sidebars with smooth width transitions
- Inset content framing with borders and nested border-radius (Outer R = Inner R + Padding)
- iMessage-style chat bubble styling with proper corner radii
- Styled composer input with matching border-radius
- Vertical separator between chat and right sidebar
- Website download button component
- Cargo workspace exclude for standalone Tauri build

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nicholas Kissel 2026-03-11 15:11:12 -07:00
parent dbc2ff0682
commit f6656a90af
80 changed files with 6621 additions and 152 deletions

View file

@ -43,15 +43,15 @@ const TranscriptMessageBody = memo(function TranscriptMessageBody({
>
<div
className={css({
maxWidth: "100%",
padding: "12px 16px",
borderTopLeftRadius: "16px",
borderTopRightRadius: "16px",
maxWidth: "80%",
...(isUser
? {
padding: "12px 16px",
backgroundColor: "rgba(255, 255, 255, 0.10)",
color: "#e4e4e7",
borderBottomLeftRadius: "16px",
borderTopLeftRadius: "18px",
borderTopRightRadius: "18px",
borderBottomLeftRadius: "18px",
borderBottomRightRadius: "4px",
}
: {
@ -155,7 +155,7 @@ export const MessageList = memo(function MessageList({
);
const messageContentClass = css({
maxWidth: "80%",
maxWidth: "100%",
display: "flex",
flexDirection: "column",
});
@ -201,7 +201,7 @@ export const MessageList = memo(function MessageList({
<div
ref={scrollRef}
className={css({
padding: "16px 220px 16px 44px",
padding: "16px 20px 16px 20px",
display: "flex",
flexDirection: "column",
flex: 1,

View file

@ -42,7 +42,7 @@ export const PromptComposer = memo(function PromptComposer({
position: "relative",
backgroundColor: "rgba(255, 255, 255, 0.06)",
border: `1px solid ${theme.colors.borderOpaque}`,
borderRadius: "16px",
borderRadius: "12px",
minHeight: `${PROMPT_TEXTAREA_MIN_HEIGHT + 36}px`,
transition: "border-color 200ms ease",
":focus-within": { borderColor: "rgba(255, 255, 255, 0.15)" },
@ -56,7 +56,7 @@ export const PromptComposer = memo(function PromptComposer({
padding: "14px 58px 8px 14px",
background: "transparent",
border: "none",
borderRadius: "16px 16px 0 0",
borderRadius: "12px 12px 0 0",
color: theme.colors.contentPrimary,
fontSize: "13px",
fontFamily: "inherit",
@ -77,7 +77,7 @@ export const PromptComposer = memo(function PromptComposer({
padding: "0",
margin: "0",
border: "none",
borderRadius: "6px",
borderRadius: "10px",
cursor: "pointer",
position: "absolute",
right: "12px",
@ -112,7 +112,7 @@ export const PromptComposer = memo(function PromptComposer({
return (
<div
className={css({
padding: "12px 16px",
padding: "12px 12px",
borderTop: "none",
flexShrink: 0,
display: "flex",

View file

@ -1,7 +1,7 @@
import { memo, useCallback, useMemo, useState, type MouseEvent } from "react";
import { useStyletron } from "baseui";
import { LabelSmall } from "baseui/typography";
import { Archive, ArrowUpFromLine, ChevronRight, FileCode, FilePlus, FileX, FolderOpen, GitPullRequest } from "lucide-react";
import { Archive, ArrowUpFromLine, ChevronRight, FileCode, FilePlus, FileX, FolderOpen, GitPullRequest, PanelRight } from "lucide-react";
import { type ContextMenuItem, ContextMenuOverlay, PanelHeaderBar, SPanel, ScrollBody, useContextMenu } from "./ui";
import { type FileTreeNode, type Task, diffTabId } from "./view-model";
@ -93,6 +93,7 @@ export const RightSidebar = memo(function RightSidebar({
onArchive,
onRevertFile,
onPublishPr,
onToggleSidebar,
}: {
task: Task;
activeTabId: string | null;
@ -100,6 +101,7 @@ export const RightSidebar = memo(function RightSidebar({
onArchive: () => void;
onRevertFile: (path: string) => void;
onPublishPr: () => void;
onToggleSidebar?: () => void;
}) {
const [css, theme] = useStyletron();
const [rightTab, setRightTab] = useState<"changes" | "files">("changes");
@ -171,7 +173,7 @@ export const RightSidebar = memo(function RightSidebar({
})}
>
<GitPullRequest size={12} style={{ flexShrink: 0 }} />
{pullRequestUrl ? "Open PR" : "Publish PR"}
<span className={css({ "@media screen and (max-width: 768px)": { display: "none" } })}>{pullRequestUrl ? "Open PR" : "Publish PR"}</span>
</button>
<button
className={css({
@ -195,7 +197,8 @@ export const RightSidebar = memo(function RightSidebar({
":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)", color: "#ffffff" },
})}
>
<ArrowUpFromLine size={12} style={{ flexShrink: 0 }} /> Push
<ArrowUpFromLine size={12} style={{ flexShrink: 0 }} />{" "}
<span className={css({ "@media screen and (max-width: 768px)": { display: "none" } })}>Push</span>
</button>
<button
onClick={onArchive}
@ -220,13 +223,49 @@ export const RightSidebar = memo(function RightSidebar({
":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)", color: "#ffffff" },
})}
>
<Archive size={12} style={{ flexShrink: 0 }} /> Archive
<Archive size={12} style={{ flexShrink: 0 }} />{" "}
<span className={css({ "@media screen and (max-width: 768px)": { display: "none" } })}>Archive</span>
</button>
</div>
) : null}
{onToggleSidebar ? (
<div
role="button"
tabIndex={0}
onClick={onToggleSidebar}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") onToggleSidebar();
}}
className={css({
width: "26px",
height: "26px",
borderRadius: "6px",
color: "#71717a",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
":hover": { color: "#a1a1aa", backgroundColor: "rgba(255, 255, 255, 0.06)" },
})}
>
<PanelRight size={14} />
</div>
) : null}
</PanelHeaderBar>
<div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", borderTop: "1px solid rgba(255, 255, 255, 0.10)" }}>
<div
style={{
flex: 1,
minHeight: 0,
display: "flex",
flexDirection: "column",
borderTop: "1px solid rgba(255, 255, 255, 0.10)",
borderRight: "1px solid rgba(255, 255, 255, 0.10)",
borderTopRightRadius: "12px",
overflow: "hidden",
}}
>
<div
className={css({
display: "flex",
@ -237,6 +276,7 @@ export const RightSidebar = memo(function RightSidebar({
height: "41px",
minHeight: "41px",
flexShrink: 0,
borderTopRightRadius: "12px",
})}
>
<button

View file

@ -1,7 +1,7 @@
import { memo, useRef, useState } from "react";
import { useStyletron } from "baseui";
import { LabelSmall, LabelXSmall } from "baseui/typography";
import { ChevronDown, ChevronUp, CloudUpload, GitPullRequestDraft, ListChecks, Plus } from "lucide-react";
import { ChevronDown, ChevronUp, CloudUpload, GitPullRequestDraft, ListChecks, PanelLeft, Plus } from "lucide-react";
import { formatRelativeAge, type Task, type ProjectSection } from "./view-model";
import { ContextMenuOverlay, TaskIndicator, PanelHeaderBar, SPanel, ScrollBody, useContextMenu } from "./ui";
@ -34,6 +34,7 @@ export const Sidebar = memo(function Sidebar({
onRenameTask,
onRenameBranch,
onReorderProjects,
onToggleSidebar,
}: {
projects: ProjectSection[];
newTaskRepos: Array<{ id: string; label: string }>;
@ -46,6 +47,7 @@ export const Sidebar = memo(function Sidebar({
onRenameTask: (id: string) => void;
onRenameBranch: (id: string) => void;
onReorderProjects: (fromIndex: number, toIndex: number) => void;
onToggleSidebar?: () => void;
}) {
const [css, theme] = useStyletron();
const contextMenu = useContextMenu();
@ -63,6 +65,45 @@ export const Sidebar = memo(function Sidebar({
display: none !important;
}
`}</style>
{import.meta.env.VITE_DESKTOP ? (
<div
className={css({
height: "38px",
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
paddingRight: "10px",
flexShrink: 0,
position: "relative",
zIndex: 9999,
})}
>
{onToggleSidebar ? (
<div
role="button"
tabIndex={0}
onClick={onToggleSidebar}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") onToggleSidebar();
}}
className={css({
width: "26px",
height: "26px",
borderRadius: "6px",
color: "#71717a",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
":hover": { color: "#a1a1aa", backgroundColor: "rgba(255, 255, 255, 0.06)" },
})}
>
<PanelLeft size={14} />
</div>
) : null}
</div>
) : null}
<PanelHeaderBar $style={{ backgroundColor: "transparent", borderBottom: "none" }}>
<LabelSmall
color={theme.colors.contentPrimary}
@ -71,6 +112,30 @@ export const Sidebar = memo(function Sidebar({
<ListChecks size={14} />
Tasks
</LabelSmall>
{!import.meta.env.VITE_DESKTOP && onToggleSidebar ? (
<div
role="button"
tabIndex={0}
onClick={onToggleSidebar}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") onToggleSidebar();
}}
className={css({
width: "26px",
height: "26px",
borderRadius: "6px",
color: "#71717a",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
":hover": { color: "#a1a1aa", backgroundColor: "rgba(255, 255, 255, 0.06)" },
})}
>
<PanelLeft size={14} />
</div>
) : null}
<div
role="button"
tabIndex={0}

View file

@ -166,7 +166,8 @@ export const TranscriptHeader = memo(function TranscriptHeader({
":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)", color: theme.colors.contentPrimary },
})}
>
<MailOpen size={12} style={{ flexShrink: 0 }} /> {activeTab.unread ? "Mark read" : "Mark unread"}
<MailOpen size={12} style={{ flexShrink: 0 }} />{" "}
<span className={css({ "@media screen and (max-width: 768px)": { display: "none" } })}>{activeTab.unread ? "Mark read" : "Mark unread"}</span>
</button>
) : null}
</PanelHeaderBar>

View file

@ -205,4 +205,6 @@ export const PanelHeaderBar = styled("div", ({ $theme }) => ({
backgroundColor: $theme.colors.backgroundTertiary,
gap: "8px",
flexShrink: 0,
position: "relative" as const,
zIndex: 9999,
}));