diff --git a/README.md b/README.md index 482ea95..434152c 100755 --- a/README.md +++ b/README.md @@ -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 diff --git a/drizzle/seed-users.sql b/drizzle/seeds/seed-users.sql similarity index 100% rename from drizzle/seed-users.sql rename to drizzle/seeds/seed-users.sql diff --git a/drizzle/seed.sql b/drizzle/seeds/seed.sql similarity index 100% rename from drizzle/seed.sql rename to drizzle/seeds/seed.sql diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index 63a407a..e435e8c 100755 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -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" @@ -54,61 +54,59 @@ export default async function DashboardLayout({ return ( - - - - - - - - - - - - - - - - -
- - {children} - - -
-
- - - -

- Pre-alpha build -

- -
-
-
-
-
-
-
-
-
-
+ + + + + + + + + + + + + + + +
+ + {children} + + +
+
+ + + +

+ Pre-alpha build +

+ +
+
+
+
+
+
+
+
+
) } diff --git a/src/components/agent/chat-panel-shell.tsx b/src/components/agent/chat-panel-shell.tsx index 07539a7..0a36806 100755 --- a/src/components/agent/chat-panel-shell.tsx +++ b/src/components/agent/chat-panel-shell.tsx @@ -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 { @@ -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 = @@ -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 && ( +
+ +
+ )} + {/* Desktop resize handle (panel mode only) */} {!isDashboard && (
)} - + {isDashboard ? ( + + ) : ( +
+ +
+ )}
{/* Mobile backdrop (panel mode only) */} @@ -184,14 +208,39 @@ export function ChatPanelShell() { )} {/* Mobile FAB (panel mode only) */} - {!isDashboard && !isOpen && ( + {/* Chat Toggle FAB (visible on specific pages) */} + {!isDashboard && ( )} diff --git a/src/components/nav-user.tsx b/src/components/nav-user.tsx index dfaf34a..681e983 100755 --- a/src/components/nav-user.tsx +++ b/src/components/nav-user.tsx @@ -92,51 +92,54 @@ export function NavUser({ - - {user.avatar && ( - - )} - - {initials} - - - - {user.name} - - {/* Voice controls -- replace the old dots icon */} -
- { stopEvent(e); toggleMute() }} - icon={isMuted ? IconMicrophoneOff : IconMicrophone} - label={isMuted ? "Unmute" : "Mute"} - dimmed={isMuted} - devices={inputDevices} - selectedDeviceId={inputDeviceId} - onSelectDevice={setInputDevice} - deviceLabel="Input Device" - /> - { stopEvent(e); toggleDeafen() }} - icon={isDeafened ? IconHeadphonesOff : IconHeadphones} - label={isDeafened ? "Undeafen" : "Deafen"} - dimmed={isDeafened} - devices={outputDevices} - selectedDeviceId={outputDeviceId} - onSelectDevice={setOutputDevice} - deviceLabel="Output Device" - /> - - - +
+ + {user.avatar && ( + + )} + + {initials} + + + + {user.name} + + {/* Voice controls -- replace the old dots icon */} +
+ { stopEvent(e); toggleMute() }} + icon={isMuted ? IconMicrophoneOff : IconMicrophone} + label={isMuted ? "Unmute" : "Mute"} + dimmed={isMuted} + devices={inputDevices} + selectedDeviceId={inputDeviceId} + onSelectDevice={setInputDevice} + deviceLabel="Input Device" + /> + { stopEvent(e); toggleDeafen() }} + icon={isDeafened ? IconHeadphonesOff : IconHeadphones} + label={isDeafened ? "Undeafen" : "Deafen"} + dimmed={isDeafened} + devices={outputDevices} + selectedDeviceId={outputDeviceId} + onSelectDevice={setOutputDevice} + deviceLabel="Output Device" + /> + + + +
diff --git a/src/components/org-switcher.tsx b/src/components/org-switcher.tsx index ce2cc90..d884f27 100644 --- a/src/components/org-switcher.tsx +++ b/src/components/org-switcher.tsx @@ -80,80 +80,82 @@ export function OrgSwitcher({ - - - - - - - - + - {orgs.map((org, i) => { - const isActive = org.id === activeOrgId - const OrgIcon = - org.type === "personal" ? IconUser : IconBuilding + + + + + + + + {orgs.map((org, i) => { + const isActive = org.id === activeOrgId + const OrgIcon = + org.type === "personal" ? IconUser : IconBuilding - return ( - - {i > 0 && } - void handleOrgSwitch(org.id)} - disabled={isLoading} - className="gap-2 px-2 py-1.5" - > - - - {org.name} - - {isActive && ( - - )} - - - ) - })} - - + return ( + + {i > 0 && } + void handleOrgSwitch(org.id)} + disabled={isLoading} + className="gap-2 px-2 py-1.5" + > + + + {org.name} + + {isActive && ( + + )} + + + ) + })} + + +
diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index 566d5f5..bdcabb2 100755 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -5,7 +5,6 @@ import { useTheme } from "next-themes" import { IconLogout, IconMenu2, - IconMessageCircle, IconMoon, IconSearch, IconSparkles, @@ -28,7 +27,9 @@ import { import { SidebarTrigger, useSidebar } from "@/components/ui/sidebar" import { NotificationsPopover } from "@/components/notifications-popover" import { useCommandMenu } from "@/components/command-menu-provider" -import { useAgentOptional } from "@/components/agent/chat-provider" +import { useAgentOptional, useChatState, useRenderState } from "@/components/agent/chat-provider" +import { usePathname } from "next/navigation" +import { ChevronLeft } from "lucide-react" import { AccountModal } from "@/components/account-modal" import { getInitials } from "@/lib/utils" import type { SidebarUser } from "@/lib/auth" @@ -45,6 +46,14 @@ export function SiteHeader({ const agentContext = useAgentOptional() const [accountOpen, setAccountOpen] = React.useState(false) const { toggleSidebar } = useSidebar() + const pathname = usePathname() + const agentState = useChatState() + const renderState = useRenderState() + + // Determine if we are on the main dashboard chat view with active messages + // In this state, we replace the sidebar toggle with a "Back" button to reset the chat + const hasRenderedUI = !!renderState.spec?.root || renderState.isRendering + const isDashboardChatActive = pathname === "/dashboard" && !hasRenderedUI && agentState.messages.length > 0 const initials = user ? getInitials(user.name) : "?" @@ -61,11 +70,19 @@ export function SiteHeader({ type="button" className="flex size-10 shrink-0 items-center justify-center rounded-full -ml-0.5 hover:bg-background/60" onClick={() => { - toggleSidebar() + if (isDashboardChatActive) { + agentState.newChat() + } else { + toggleSidebar() + } }} - aria-label="Open menu" + aria-label={isDashboardChatActive ? "Back to new chat" : "Open menu"} > - + {isDashboardChatActive ? ( + + ) : ( + + )}