Remove OS titlebar and add custom window chrome (#29)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Wes
2026-03-11 10:01:37 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 4ecb5d3be2
commit 57cd81fd8b
9 changed files with 82 additions and 29 deletions
+9 -1
View File
@@ -3,5 +3,13 @@
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": ["core:default", "opener:default", "websocket:default"]
"permissions": [
"core:default",
"core:window:allow-start-dragging",
"core:window:allow-show",
"core:window:allow-close",
"opener:default",
"websocket:default",
"window-state:default"
]
}
+1 -1
View File
@@ -609,7 +609,7 @@ pub fn run() {
.plugin(
tauri_plugin_window_state::Builder::default()
.with_state_flags(
StateFlags::SIZE | StateFlags::POSITION | StateFlags::MAXIMIZED,
StateFlags::all() & !StateFlags::VISIBLE,
)
.build(),
)
+5 -1
View File
@@ -14,7 +14,11 @@
{
"title": "",
"width": 800,
"height": 600
"height": 600,
"visible": false,
"titleBarStyle": "Overlay",
"hiddenTitle": true,
"trafficLightPosition": { "x": 12, "y": 22 }
}
],
"security": {
+7
View File
@@ -1,5 +1,12 @@
import { getCurrentWindow } from "@tauri-apps/api/window";
import { useEffect } from "react";
import { AppShell } from "@/app/AppShell";
export function App() {
useEffect(() => {
getCurrentWindow().show();
}, []);
return <AppShell />;
}
+19 -2
View File
@@ -1,3 +1,4 @@
import { getCurrentWindow } from "@tauri-apps/api/window";
import * as React from "react";
import { Settings2 } from "lucide-react";
@@ -26,7 +27,11 @@ import { getEventById } from "@/shared/api/tauri";
import { useIdentityQuery } from "@/shared/api/hooks";
import type { RelayEvent, SearchHit } from "@/shared/api/types";
import { Button } from "@/shared/ui/button";
import { SidebarInset, SidebarProvider } from "@/shared/ui/sidebar";
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/shared/ui/sidebar";
type AppView = "home" | "channel";
@@ -168,6 +173,7 @@ export function AppShell() {
return (
<SidebarProvider className="h-dvh overflow-hidden overscroll-none">
<SidebarTrigger className="fixed left-[80px] top-[9px] z-50 h-6 w-6" />
<AppSidebar
channels={channels}
errorMessage={
@@ -210,9 +216,20 @@ export function AppShell() {
/>
<SidebarInset
className="min-h-0 min-w-0 overflow-hidden"
className="relative min-h-0 min-w-0 overflow-hidden pt-7"
key={contentPaneKey}
>
{/* Drag strip covering the traffic-light inset area */}
<div
className="absolute inset-x-0 top-0 flex h-7 items-center px-2"
onPointerDown={(e) => {
if (e.button !== 0) return;
const target = e.target as HTMLElement;
if (target.closest('button, a, input, [role="button"]')) return;
e.preventDefault();
getCurrentWindow().startDragging();
}}
/>
{selectedView === "home" ? (
<ChatHeader
description="Personalized feed for mentions, reminders, channel activity, and agent work."
+10 -3
View File
@@ -1,8 +1,8 @@
import { getCurrentWindow } from "@tauri-apps/api/window";
import { CircleDot, FileText, Hash, Home } from "lucide-react";
import type * as React from "react";
import type { ChannelType } from "@/shared/api/types";
import { SidebarTrigger } from "@/shared/ui/sidebar";
type ChatHeaderProps = {
actions?: React.ReactNode;
@@ -34,6 +34,14 @@ function ChannelIcon({
return <Hash className="h-5 w-5 text-primary" />;
}
function handlePointerDown(e: React.PointerEvent) {
if (e.button !== 0) return;
const target = e.target as HTMLElement;
if (target.closest('button, a, input, [role="button"]')) return;
e.preventDefault();
getCurrentWindow().startDragging();
}
export function ChatHeader({
actions,
title,
@@ -45,9 +53,8 @@ export function ChatHeader({
<header
className="flex min-w-0 items-center gap-3 border-b border-border/80 bg-background px-4 py-3 sm:px-6"
data-testid="chat-header"
onPointerDown={handlePointerDown}
>
<SidebarTrigger />
<div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-2">
<ChannelIcon channelType={channelType} mode={mode} />
+14 -14
View File
@@ -144,11 +144,11 @@ function FeedSection({
onOpenChannel,
}: FeedSectionProps) {
return (
<section className="rounded-[1.75rem] border border-border/80 bg-card/80 p-5 shadow-sm">
<section className="rounded-xl border border-border/80 bg-card/80 p-5 shadow-sm">
<div className="flex items-start justify-between gap-4">
<div className="min-w-0">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-primary/10 text-primary">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
<Icon className="h-4 w-4" />
</div>
<div>
@@ -166,7 +166,7 @@ function FeedSection({
<div className="mt-5 space-y-3">
{items.length === 0 ? (
<div className="rounded-3xl border border-dashed border-border/80 bg-background/60 px-5 py-7 text-center">
<div className="rounded-lg border border-dashed border-border/80 bg-background/60 px-5 py-7 text-center">
<p className="text-sm font-semibold tracking-tight">{emptyTitle}</p>
<p className="mt-2 text-sm text-muted-foreground">
{emptyDescription}
@@ -181,11 +181,11 @@ function FeedSection({
return (
<article
className="rounded-3xl border border-border/70 bg-background/70 p-4 shadow-sm"
className="rounded-lg border border-border/70 bg-background/70 p-4 shadow-sm"
key={item.id}
>
<div className="flex gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-secondary/70 text-secondary-foreground">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-secondary/70 text-secondary-foreground">
<Icon className="h-4 w-4" />
</div>
@@ -271,7 +271,7 @@ function SummaryCard({
return (
<div
className={cn(
"rounded-3xl border p-4 shadow-sm",
"rounded-lg border p-4 shadow-sm",
tone === "urgent"
? "border-primary/20 bg-primary/10"
: "border-border/80 bg-background/70",
@@ -280,7 +280,7 @@ function SummaryCard({
<div className="flex items-center gap-3">
<div
className={cn(
"flex h-10 w-10 items-center justify-center rounded-2xl",
"flex h-10 w-10 items-center justify-center rounded-lg",
tone === "urgent"
? "bg-primary text-primary-foreground"
: "bg-secondary text-secondary-foreground",
@@ -303,13 +303,13 @@ function HomeLoadingState() {
return (
<div className="flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-4 py-4 sm:px-6">
<div className="mx-auto flex w-full max-w-6xl flex-col gap-6">
<div className="rounded-[1.75rem] border border-border/80 bg-card/80 p-5 shadow-sm">
<div className="rounded-xl border border-border/80 bg-card/80 p-5 shadow-sm">
<Skeleton className="h-6 w-44" />
<Skeleton className="mt-3 h-4 w-full max-w-xl" />
<div className="mt-5 grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
{["first", "second", "third", "fourth"].map((item) => (
<Skeleton className="h-24 rounded-3xl" key={item} />
<Skeleton className="h-24 rounded-lg" key={item} />
))}
</div>
</div>
@@ -317,14 +317,14 @@ function HomeLoadingState() {
<div className="grid gap-4 xl:grid-cols-2">
{["mentions", "actions", "activity", "agents"].map((section) => (
<div
className="rounded-[1.75rem] border border-border/80 bg-card/80 p-5 shadow-sm"
className="rounded-xl border border-border/80 bg-card/80 p-5 shadow-sm"
key={section}
>
<Skeleton className="h-6 w-32" />
<Skeleton className="mt-3 h-4 w-full max-w-xs" />
<div className="mt-5 space-y-3">
{["a", "b", "c"].map((row) => (
<Skeleton className="h-28 rounded-3xl" key={row} />
<Skeleton className="h-28 rounded-lg" key={row} />
))}
</div>
</div>
@@ -364,7 +364,7 @@ export function HomeView({
return (
<div className="flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-4 py-4 sm:px-6">
<div className="mx-auto flex w-full max-w-3xl flex-col gap-4">
<div className="rounded-[1.75rem] border border-destructive/30 bg-destructive/5 px-6 py-8 shadow-sm">
<div className="rounded-xl border border-destructive/30 bg-destructive/5 px-6 py-8 shadow-sm">
<p className="text-base font-semibold tracking-tight">
Home feed unavailable
</p>
@@ -386,11 +386,11 @@ export function HomeView({
return (
<div className="flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-4 py-4 sm:px-6">
<div className="mx-auto flex w-full max-w-6xl flex-col gap-6">
<section className="rounded-[1.75rem] border border-border/80 bg-card/80 p-5 shadow-sm">
<section className="rounded-xl border border-border/80 bg-card/80 p-5 shadow-sm">
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
<div className="min-w-0">
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-[1.25rem] bg-primary text-primary-foreground shadow-sm">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary text-primary-foreground shadow-sm">
<Sparkles className="h-5 w-5" />
</div>
<div>
+14 -4
View File
@@ -1,3 +1,4 @@
import { getCurrentWindow } from "@tauri-apps/api/window";
import {
CircleDot,
FileText,
@@ -294,13 +295,24 @@ export function AppSidebar({
}
}
function handleDragPointerDown(e: React.PointerEvent) {
if (e.button !== 0) return;
const target = e.target as HTMLElement;
if (target.closest('button, a, input, [role="button"]')) return;
e.preventDefault();
getCurrentWindow().startDragging();
}
return (
<Sidebar
collapsible="offcanvas"
data-testid="app-sidebar"
variant="sidebar"
>
<SidebarHeader className="gap-3">
<SidebarHeader
className="gap-3 pt-7"
onPointerDown={handleDragPointerDown}
>
<div className="flex items-center gap-3 rounded-xl bg-sidebar-accent/80 px-3 py-3">
<div className="flex h-6 w-6 items-center justify-center rounded-xl text-lg">
<span aria-hidden="true">🌱</span>
@@ -324,9 +336,7 @@ export function AppSidebar({
<Search className="h-4 w-4" />
Search messages
</span>
<span className="rounded-md border border-sidebar-border/80 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-sidebar-foreground/60">
Cmd K
</span>
<span className="text-xs text-sidebar-foreground/50">&#x2318;K</span>
</Button>
</SidebarHeader>
+3 -3
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { PanelLeft } from "lucide-react";
import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
import { cn } from "@/shared/lib/cn";
import { useIsMobile } from "@/shared/hooks/use-mobile";
@@ -271,7 +271,7 @@ const SidebarTrigger = React.forwardRef<
React.ElementRef<typeof Button>,
React.ComponentProps<typeof Button>
>(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar();
const { toggleSidebar, open } = useSidebar();
return (
<Button
@@ -286,7 +286,7 @@ const SidebarTrigger = React.forwardRef<
}}
{...props}
>
<PanelLeft />
{open ? <PanelLeftClose /> : <PanelLeftOpen />}
<span className="sr-only">Toggle Sidebar</span>
</Button>
);