Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 82 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,99 @@ An AI-native workspace platform that handles auth, deployment,
and real-time collaboration -- so you can focus on building
what actually matters.

## Build With Direction :)
## 🛠️ Development Setup

- **AI agent built in** -- every workspace ships with an intelligent
assistant that understands your domain and takes action through
tools you define
- **Modular by design** -- scheduling, financials, file management,
messaging. drop in what you need, leave out what you don't
- **Deploy anywhere** -- self-host, ship to desktop and mobile,
or deploy to the edge with Cloudflare
- **Enterprise auth** -- SSO, directory sync, and role-based access
control out of the box
Follow these steps to set up your local environment.

## Quick Start
### 1. Initial Setup

Clone the repository and install dependencies:

```bash
git clone https://github.com/High-Performance-Structures/compass.git
cd compass
bun install
cp .env.example .env.local # add your keys
bun run db:generate
```

### 2. Environment Variables

Create `.env.local` and `.dev.vars` in the root directory.

**`.env.local`** (Local Development):
```ini
# Bypass all auth for local development
BYPASS_AUTH=true

# WorkOS (Use placeholder values to trigger mock mode)
WORKOS_API_KEY=placeholder_development_mode
WORKOS_CLIENT_ID=placeholder_development_mode
WORKOS_COOKIE_PASSWORD=your_cookie_password_here
NEXT_PUBLIC_WORKOS_REDIRECT_URI=http://localhost:3000/callback

# AI Agent
OPENROUTER_API_KEY=your_openrouter_key
```

**`.dev.vars`** (Cloudflare Worker Environment):
```ini
# Add any required secret keys here
WORKOS_API_KEY=your_real_key_if_needed
```

### 3. Database Setup

Initialize the local D1 database, run migrations, and seed mock data:

```bash
# 1. Clear any existing local state
rm -rf .wrangler

# 2. Run migrations (schema setup)
bun run db:migrate:local

# 3. Seed data (Users & Projects)
# Finds the local SQLite file and runs the seed scripts
DB_FILE=$(find .wrangler/state/v3/d1 -name "*.sqlite" | head -1) && \
sqlite3 "$DB_FILE" ".read drizzle/seeds/seed-users.sql" && \
sqlite3 "$DB_FILE" ".read drizzle/seeds/seed.sql"

# 4. Insert mock Dev User (if not in seed)
sqlite3 "$DB_FILE" "INSERT OR IGNORE INTO users (id, email, first_name, last_name, display_name, role, is_active, created_at, updated_at) VALUES ('dev-user-1', 'dev@compass.io', 'Dev', 'User', 'Dev User', 'admin', 1, datetime('now'), datetime('now'));"
```

### 4. Running the App

Start the development server:

```bash
bun dev
```

See [docs/](docs/README.md) for detailed setup, environment
variables, and deployment options.
- Open **[http://localhost:3000](http://localhost:3000)**
- You will be automatically redirected to `/dashboard` as the **Dev User**.

---

## 📐 Development Guidelines

### 1. Pulling Changes
Always pull the latest changes before starting work to avoid conflicts:
```bash
git pull origin main
bun install
bun run db:migrate:local
```

### 2. Styling (CSS)
- **Do NOT use hardcoded CSS** (e.g., `style={{ width: '500px' }}`).
- Use **Tailwind CSS classes** (e.g., `w-[500px]` or `w-full max-w-lg`).
- Follow the design system tokens in `tailwind.config.ts`.

### 3. Git Ignore
- Check `.gitignore` before adding new files.
- Never commit `.env` files or local database artifacts (`.wrangler/`).

---

## Tech Stack

Expand Down
File renamed without changes.
File renamed without changes.
110 changes: 54 additions & 56 deletions src/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SiteHeader } from "@/components/site-header"
import { MobileBottomNav } from "@/components/mobile-bottom-nav"
import { CommandMenuProvider } from "@/components/command-menu-provider"
import { SettingsProvider } from "@/components/settings-provider"
import { FeedbackWidget } from "@/components/feedback-widget"

import { PageActionsProvider } from "@/components/page-actions-provider"
import { DashboardContextMenu } from "@/components/dashboard-context-menu"
import { Toaster } from "@/components/ui/sonner"
Expand Down Expand Up @@ -54,61 +54,59 @@ export default async function DashboardLayout({

return (
<ChatProvider>
<VoiceProvider>
<SettingsProvider>
<ProjectListProvider projects={projectList}>
<PageActionsProvider>
<CommandMenuProvider>
<BiometricGuard userId={authUser?.id}>
<DesktopShell>
<FeedbackWidget>
<DashboardContextMenu>
<SidebarProvider
defaultOpen={sidebarOpen}
className="h-screen overflow-hidden"
style={
{
"--sidebar-width": "calc(var(--spacing) * 72)",
} as React.CSSProperties
}
>
<AppSidebar
variant="inset"
projects={projectList}
dashboards={dashboardList}
user={user}
activeOrgId={activeOrgId}
activeOrgName={activeOrgName}
/>
<SidebarInset className="overflow-hidden">
<DesktopOfflineBanner />
<OfflineBanner />
<DemoBanner isDemo={isDemo} />
<SiteHeader user={user} />
<div className="flex min-h-0 flex-1 overflow-hidden">
<MainContent>
{children}
</MainContent>
<ChatPanelShell />
</div>
</SidebarInset>
<MobileBottomNav />
<NativeShell />
<PushNotificationRegistrar />
<p className="pointer-events-none fixed bottom-3 left-0 right-0 hidden text-center text-xs text-muted-foreground/60 md:block">
Pre-alpha build
</p>
<Toaster position="bottom-right" />
</SidebarProvider>
</DashboardContextMenu>
</FeedbackWidget>
</DesktopShell>
</BiometricGuard>
</CommandMenuProvider>
</PageActionsProvider>
</ProjectListProvider>
</SettingsProvider>
</VoiceProvider>
<VoiceProvider>
<SettingsProvider>
<ProjectListProvider projects={projectList}>
<PageActionsProvider>
<CommandMenuProvider>
<BiometricGuard userId={authUser?.id}>
<DesktopShell>
<DashboardContextMenu>
<SidebarProvider
defaultOpen={sidebarOpen}
className="h-screen overflow-hidden"
style={
{
"--sidebar-width": "calc(var(--spacing) * 72)",
} as React.CSSProperties
}
>
<AppSidebar
variant="inset"
projects={projectList}
dashboards={dashboardList}
user={user}
activeOrgId={activeOrgId}
activeOrgName={activeOrgName}
/>
<SidebarInset className="overflow-hidden">
<DesktopOfflineBanner />
<OfflineBanner />
<DemoBanner isDemo={isDemo} />
<SiteHeader user={user} />
<div className="flex min-h-0 flex-1 overflow-hidden">
<MainContent>
{children}
</MainContent>
<ChatPanelShell />
</div>
</SidebarInset>
<MobileBottomNav />
<NativeShell />
<PushNotificationRegistrar />
<p className="pointer-events-none fixed bottom-3 left-0 right-0 hidden text-center text-xs text-muted-foreground/60 md:block">
Pre-alpha build
</p>
<Toaster position="bottom-right" />
</SidebarProvider>
</DashboardContextMenu>
</DesktopShell>
</BiometricGuard>
</CommandMenuProvider>
</PageActionsProvider>
</ProjectListProvider>
</SettingsProvider>
</VoiceProvider>
</ChatProvider>
)
}
91 changes: 70 additions & 21 deletions src/components/agent/chat-panel-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useState, useEffect, useRef, useCallback } from "react"
import { usePathname } from "next/navigation"
import { MessageSquare } from "lucide-react"
import { XIcon, ChevronLeft } from "lucide-react"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import {
Expand Down Expand Up @@ -131,7 +131,7 @@ export function ChatPanelShell() {
// container width/style for panel mode
const panelStyle =
!isDashboard && isOpen
? { width: panelWidth }
? ({ "--panel-width": `${panelWidth}px` } as React.CSSProperties)
: undefined

const keyboardStyle =
Expand All @@ -146,21 +146,41 @@ export function ChatPanelShell() {
"flex flex-col",
"transition-[flex,width,border-color,box-shadow,opacity,transform] duration-300 ease-in-out",
isDashboard
? "flex-1 bg-background"
? "flex-1 bg-background pb-[calc(3.5rem+env(safe-area-inset-bottom))] md:pb-0"
: [
"bg-background dark:bg-[oklch(0.255_0_0)]",
"fixed inset-0 z-50",
"md:relative md:inset-auto md:z-auto",
"md:shrink-0 md:overflow-hidden",
"md:rounded-xl md:border md:border-border md:shadow-lg md:my-2 md:mr-2",
isResizing && "transition-none",
isOpen
? "translate-x-0 md:opacity-100"
: "translate-x-full md:translate-x-0 md:w-0 md:border-transparent md:shadow-none md:opacity-0",
]
"bg-background dark:bg-[oklch(0.255_0_0)]",
"fixed inset-0 z-[60]",
"pb-[env(safe-area-inset-bottom)]",
"w-full md:w-[var(--panel-width)]", // Use CSS var for responsive width
"md:relative md:inset-auto md:z-auto md:pb-0",
"md:shrink-0 md:overflow-hidden",
"md:rounded-xl md:border md:border-border md:shadow-lg md:my-2 md:mr-2",
isResizing && "transition-none",
isOpen
? "translate-x-0 md:opacity-100"
: "translate-x-full md:translate-x-0 md:w-0 md:border-transparent md:shadow-none md:opacity-0",
]
)}
style={{ ...panelStyle, ...keyboardStyle }}
>
{/* Header with Back/Close Button */}
{!isDashboard && isOpen && (
<div className="flex items-center p-2 border-b shrink-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<Button variant="ghost" size="sm" onClick={close} className="gap-1 text-muted-foreground hover:text-foreground">
{/* Mobile Back */}
<span className="flex items-center gap-1 md:hidden">
<ChevronLeft className="h-4 w-4" />
Back
</span>
{/* Desktop Close */}
<span className="hidden md:flex items-center gap-1">
<XIcon className="h-4 w-4" />
Close
</span>
</Button>
</div>
)}

{/* Desktop resize handle (panel mode only) */}
{!isDashboard && (
<div
Expand All @@ -169,9 +189,13 @@ export function ChatPanelShell() {
/>
)}

<ChatView
variant={isDashboard ? "page" : "panel"}
/>
{isDashboard ? (
<ChatView variant="page" />
) : (
<div className="flex-1 min-h-0 relative">
<ChatView variant="panel" />
</div>
)}
</div>

{/* Mobile backdrop (panel mode only) */}
Expand All @@ -184,14 +208,39 @@ export function ChatPanelShell() {
)}

{/* Mobile FAB (panel mode only) */}
{!isDashboard && !isOpen && (
{/* Chat Toggle FAB (visible on specific pages) */}
{!isDashboard && (
<Button
size="icon"
className="fixed bottom-4 right-4 z-50 h-12 w-12 rounded-full shadow-lg md:hidden"
className={cn(
"fixed right-4 z-50 h-12 rounded-full shadow-lg transition-transform",
"w-12 p-0 md:w-auto md:px-4", // Adaptive width/shape
"bottom-[4.5rem] md:bottom-4", // Positioning
isOpen && "hidden" // Hide when open
)}
onClick={toggle}
aria-label="Open chat"
aria-label={isOpen ? "Close chat" : "Open chat"}
>
<MessageSquare className="h-5 w-5" />
{isOpen ? (
<XIcon className="h-5 w-5" />
) : (
<>
<span
className={cn(
"!size-6 block bg-current",
!isOpen && "animate-[spin_5s_ease-in-out_infinite_alternate]"
)}
style={{
maskImage: "url(/logo-black.png)",
maskSize: "contain",
maskRepeat: "no-repeat",
WebkitMaskImage: "url(/logo-black.png)",
WebkitMaskSize: "contain",
WebkitMaskRepeat: "no-repeat",
}}
/>
<span className="hidden md:ml-2 md:block font-semibold">Compass</span>
</>
)}
</Button>
)}
</>
Expand Down
Loading
Loading