From 8551c4489483ae100d31d6e9610b567ab047a5be Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 19 Sep 2026 13:55:42 +0100 Subject: [PATCH 1/2] Redesign as modern SaaS app, drop window-manager UI Replace draggable floating windows with a sidebar + pin-grid layout (Linear/Notion-style), consolidate three overlapping AI modals into one command palette, and give pins real iframe-based tab browsing instead of static tab metadata. --- .gitignore | 18 +- Affirmations.tsx | 45 -- App.tsx | 956 +++++++++++++------------------------------ ColorStylePicker.tsx | 87 ---- CommandPalette.tsx | 97 +++++ Darkmodetoggle.tsx | 6 - Desktop.tsx | 249 ----------- DesktopFolder.tsx | 114 ------ EchoAssistant.tsx | 256 ------------ FocusTaskBoard.tsx | 104 ----- GhostSearch.tsx | 112 ----- Onboarding.tsx | 135 ++---- PinCard.tsx | 96 +++++ PinDetail.tsx | 185 +++++++++ PreferencesModal.tsx | 434 ++++---------------- Sidebar.tsx | 135 ++++++ SmartAssistant.tsx | 124 ------ StatusBar.tsx | 469 --------------------- ThemeToggle.tsx | 37 -- TopBar.tsx | 75 ++++ Window.tsx | 528 ------------------------ WorkspaceManager.tsx | 97 ----- index.html | 280 +++++-------- types.ts | 75 +--- 24 files changed, 1109 insertions(+), 3605 deletions(-) delete mode 100644 Affirmations.tsx delete mode 100644 ColorStylePicker.tsx create mode 100644 CommandPalette.tsx delete mode 100644 Darkmodetoggle.tsx delete mode 100644 Desktop.tsx delete mode 100644 DesktopFolder.tsx delete mode 100644 EchoAssistant.tsx delete mode 100644 FocusTaskBoard.tsx delete mode 100644 GhostSearch.tsx create mode 100644 PinCard.tsx create mode 100644 PinDetail.tsx create mode 100644 Sidebar.tsx delete mode 100644 SmartAssistant.tsx delete mode 100644 StatusBar.tsx delete mode 100644 ThemeToggle.tsx create mode 100644 TopBar.tsx delete mode 100644 Window.tsx delete mode 100644 WorkspaceManager.tsx diff --git a/.gitignore b/.gitignore index 8b5e6ce..b328fd8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,8 @@ -# node_modules/ -# dist/ -# .env -# .env.local -# .DS_Store -# *.log -# coverage/ -# .next/ -# -------------------------- -# +node_modules/ +dist/ +.env +.env.local +.DS_Store +*.log +coverage/ +.next/ diff --git a/Affirmations.tsx b/Affirmations.tsx deleted file mode 100644 index 2863142..0000000 --- a/Affirmations.tsx +++ /dev/null @@ -1,45 +0,0 @@ - -import React, { useState, useEffect } from 'react'; - -const MESSAGES = [ - "You are a masterpiece.", - "Bring your magic.", - "Imagine without fear.", - "Write your story.", - "It's all within you.", - "Reinvent yourself.", - "No limits.", - "Become you.", - "You are enough.", - "Dream big." -]; - -const Affirmations: React.FC = () => { - const [index, setIndex] = useState(0); - const [fade, setFade] = useState(true); - - useEffect(() => { - const interval = setInterval(() => { - setFade(false); - setTimeout(() => { - setIndex((prev) => (prev + 1) % MESSAGES.length); - setFade(true); - }, 1000); - }, 15000); - return () => clearInterval(interval); - }, []); - - return ( -
-
-

- {MESSAGES[index]} -

-
-
- ); -}; - -export default Affirmations; diff --git a/App.tsx b/App.tsx index a2fbe3b..beb1704 100644 --- a/App.tsx +++ b/App.tsx @@ -1,377 +1,267 @@ -import React, { - useState, - useEffect, - useCallback, - useMemo, - useRef, -} from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { - IndicatorStyle, - WindowState, - Workspace, - UserPreferences, - TabData, - ColorTheme, - FocusTimerState, + Pin, TodoItem, Priority, - SoundscapeType, + UserPreferences, + FocusTimerState, } from "./types"; -import StatusBar from "./StatusBar"; -import Desktop from "./Desktop"; -import Onboarding from "./Onboarding"; +import Sidebar from "./Sidebar"; +import TopBar from "./TopBar"; +import PinCard from "./PinCard"; +import PinDetail from "./PinDetail"; +import CommandPalette from "./CommandPalette"; import PreferencesModal from "./PreferencesModal"; -import WorkspaceManager from "./WorkspaceManager"; -import SmartAssistant from "./SmartAssistant"; -import GhostSearch from "./GhostSearch"; -import EchoAssistant from "./EchoAssistant"; +import Onboarding from "./Onboarding"; import { GoogleGenAI, Type } from "@google/genai"; import { soundscapeEngine } from "./soundscapeEngine"; - -const THEME_ACCENTS: Record = { - nebula: "#3b82f6", - sunrise: "#f97316", - ocean: "#06b6d4", - emerald: "#10b981", -}; +import { Plus } from "lucide-react"; const DEFAULT_PREFERENCES: UserPreferences = { - launchAtLogin: false, - showMenuBarIcon: true, - indicatorStyle: IndicatorStyle.COLORED_GLOW, - accentColor: "#3b82f6", + accentColor: "#6366f1", theme: "dark", - minOpacity: 0.4, - zenMode: false, - ghostingEnabled: true, - colorTheme: "nebula", soundscape: "none", - snapZonesEnabled: true, - shortcuts: { - pin: "p", - transparency: "t", - focus: "f", - workspace: "w", - zen: "z", - search: "k", - }, }; -const INITIAL_WINDOWS: WindowState[] = [ +const INITIAL_PINS: Pin[] = [ { - id: "w1", + id: "p1", title: "Research Project", - type: "browser", - x: 350, - y: 100, - width: 600, - height: 450, + type: "tabs", + content: "Deep dive into the architecture of modern LLMs.", + category: "AI Research", isPinned: true, - opacity: 1, + isArchived: false, focusCount: 8, - content: "Deep dive into the architecture of modern LLMs.", + createdAt: Date.now(), tabs: [ { id: "t1", - title: "Gemini Technical Paper", - url: "deepmind.google/gemini", + title: "Example Domain", + url: "https://example.com", isActive: true, - category: "AI Research", - lastAccessed: Date.now(), }, { id: "t2", - title: "React Performance Tips", - url: "react.dev/learn", + title: "First Website Ever", + url: "https://info.cern.ch", isActive: false, - category: "Dev Docs", - lastAccessed: Date.now() - 10000, }, { id: "t3", - title: "Window Management UX", - url: "nngroup.com", + title: "W3C", + url: "https://www.w3.org", isActive: false, - category: "Design", - lastAccessed: Date.now() - 20000, }, ], }, { - id: "w2", + id: "p2", title: "Focus Space", - type: "notes", - x: 800, - y: 400, - width: 300, - height: 300, - isPinned: false, - opacity: 1, - focusCount: 3, + type: "note", content: "Goal: Finish the multi-tab architecture by end of day.", - }, - { - id: "w3", - title: "Creative Canvas", - type: "canvas", - x: 100, - y: 180, - width: 320, - height: 380, isPinned: false, - opacity: 1, - isClosed: true, - focusCount: 1, - content: "Generative mood board.", - canvasData: { - prompt: - "A futuristic digital workspace with floating holographic screens in Nebula colors", - imageUrl: "", - isGenerating: false, - }, + isArchived: false, + focusCount: 3, + createdAt: Date.now(), }, ]; +type View = "today" | "pins" | "vault"; + const App: React.FC = () => { const [isOnboarding, setIsOnboarding] = useState(true); const [preferences, setPreferences] = useState(DEFAULT_PREFERENCES); - const [windows, setWindows] = useState(INITIAL_WINDOWS); + const [pins, setPins] = useState(INITIAL_PINS); const [todos, setTodos] = useState([]); - const [archivedTabs, setArchivedTabs] = useState([]); - const [activeWindowId, setActiveWindowId] = useState(null); - const [isPreferencesOpen, setIsPreferencesOpen] = useState(false); - const [isWorkspaceManagerOpen, setIsWorkspaceManagerOpen] = useState(false); - const [isAssistantOpen, setIsAssistantOpen] = useState(false); - const [isEchoOpen, setIsEchoOpen] = useState(false); + const [view, setView] = useState("today"); + const [openPinId, setOpenPinId] = useState(null); const [isSearchOpen, setIsSearchOpen] = useState(false); - const [workspaces, setWorkspaces] = useState([]); - const [isMobile, setIsMobile] = useState(false); - const [globalWallpaper, setGlobalWallpaper] = useState(null); + const [isPrefsOpen, setIsPrefsOpen] = useState(false); + const [isStacking, setIsStacking] = useState(false); const [focusTimer, setFocusTimer] = useState({ isActive: false, timeLeft: 25 * 60, + duration: 25 * 60, mode: "focus", }); - const [soundscapeVolume, setSoundscapeVolume] = useState(0.5); - - // Soundscape audio controller useEffect(() => { - if (preferences.soundscape && preferences.soundscape !== "none") { + if (preferences.soundscape !== "none") { soundscapeEngine.start(preferences.soundscape); } else { soundscapeEngine.stop(); } - return () => { - soundscapeEngine.stop(); - }; + return () => soundscapeEngine.stop(); }, [preferences.soundscape]); - // Sync volume changes useEffect(() => { - soundscapeEngine.setVolume(soundscapeVolume); - }, [soundscapeVolume]); - - // Timer Tick - useEffect(() => { - let interval: any; - if (focusTimer.isActive && focusTimer.timeLeft > 0) { - interval = setInterval(() => { - setFocusTimer((prev) => ({ ...prev, timeLeft: prev.timeLeft - 1 })); - }, 1000); - } else if (focusTimer.timeLeft === 0) { - setFocusTimer((prev) => ({ ...prev, isActive: false })); - } - return () => clearInterval(interval); + if (!focusTimer.isActive || focusTimer.timeLeft <= 0) return; + const id = setInterval(() => { + setFocusTimer((prev) => + prev.timeLeft <= 1 + ? { ...prev, timeLeft: 0, isActive: false } + : { ...prev, timeLeft: prev.timeLeft - 1 }, + ); + }, 1000); + return () => clearInterval(id); }, [focusTimer.isActive, focusTimer.timeLeft]); - // Handle Responsiveness useEffect(() => { - const checkMobile = () => setIsMobile(window.innerWidth < 768); - checkMobile(); - window.addEventListener("resize", checkMobile); - return () => window.removeEventListener("resize", checkMobile); + const handler = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { + e.preventDefault(); + setIsSearchOpen(true); + } + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); }, []); const toggleFocusTimer = () => setFocusTimer((prev) => ({ ...prev, isActive: !prev.isActive })); - const updateWindowPosition = useCallback( - (id: string, x: number, y: number) => { - if (isMobile) return; - setWindows((prev) => - prev.map((w) => { - if (w.id !== id) return w; - if (w.preSnapDimensions) { - const pre = w.preSnapDimensions; - return { - ...w, - x: x + (w.width - pre.width) / 2, - y, - width: pre.width, - height: pre.height, - preSnapDimensions: undefined, - }; - } - return { ...w, x, y }; - }), - ); - }, - [isMobile], - ); - - const handleSnapWindow = useCallback( - (id: string, snapType: "left" | "right" | "top" | "bottom" | "none") => { - if (isMobile) return; - setWindows((prev) => - prev.map((w) => { - if (w.id !== id) return w; + const togglePin = useCallback((id: string) => { + setPins((prev) => + prev.map((p) => (p.id === id ? { ...p, isPinned: !p.isPinned } : p)), + ); + }, []); - const width = window.innerWidth; - const height = window.innerHeight; - const statusBarHeight = 32; // pt-8 is 32px - const desktopHeight = height - statusBarHeight; + const archivePin = useCallback((id: string) => { + setPins((prev) => + prev.map((p) => (p.id === id ? { ...p, isArchived: true } : p)), + ); + }, []); - if (snapType === "none") return w; + const restorePin = useCallback((id: string) => { + setPins((prev) => + prev.map((p) => (p.id === id ? { ...p, isArchived: false } : p)), + ); + }, []); - const preSnapDimensions = w.preSnapDimensions || { - x: w.x, - y: w.y, - width: w.width, - height: w.height, - }; + const openPin = useCallback((id: string) => { + setOpenPinId(id); + setPins((prev) => + prev.map((p) => + p.id === id ? { ...p, focusCount: p.focusCount + 1 } : p, + ), + ); + }, []); - if (snapType === "left") { - return { - ...w, - x: 0, - y: statusBarHeight, - width: width / 2, - height: desktopHeight, - isPinned: true, - preSnapDimensions, - }; - } else if (snapType === "right") { - return { - ...w, - x: width / 2, - y: statusBarHeight, - width: width / 2, - height: desktopHeight, - isPinned: true, - preSnapDimensions, - }; - } else if (snapType === "top") { - return { - ...w, - x: 0, - y: statusBarHeight, - width: width, - height: desktopHeight, - isPinned: true, - preSnapDimensions, - }; - } else if (snapType === "bottom") { - return { - ...w, - x: 0, - y: statusBarHeight + desktopHeight / 2, - width: width, - height: desktopHeight / 2, - isPinned: true, - preSnapDimensions, - }; - } - return w; - }), - ); - }, - [isMobile], - ); + const updatePinContent = useCallback((id: string, content: string) => { + setPins((prev) => prev.map((p) => (p.id === id ? { ...p, content } : p))); + }, []); - const togglePin = useCallback((id: string) => { - setWindows((prev) => - prev.map((w) => (w.id === id ? { ...w, isPinned: !w.isPinned } : w)), + const switchTab = useCallback((pinId: string, tabId: string) => { + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId || !p.tabs) return p; + return { + ...p, + tabs: p.tabs.map((t) => ({ ...t, isActive: t.id === tabId })), + }; + }), ); }, []); - const handleGenerateCanvas = useCallback( - async (id: string, prompt: string) => { - setWindows((prev) => - prev.map((w) => - w.id === id - ? { - ...w, - canvasData: { ...w.canvasData, isGenerating: true, prompt }, - } - : w, - ), - ); + const navigateTab = useCallback((pinId: string, tabId: string, url: string) => { + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId || !p.tabs) return p; + return { + ...p, + tabs: p.tabs.map((t) => + t.id === tabId + ? { ...t, url, title: t.title || url, lastAccessed: Date.now() } + : t, + ), + }; + }), + ); + }, []); - try { - const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); - const response = await ai.models.generateContent({ - model: "gemini-2.5-flash-image", - contents: { parts: [{ text: prompt }] }, - config: { imageConfig: { aspectRatio: "1:1" } }, - }); + const addTab = useCallback((pinId: string, url: string) => { + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId) return p; + const newTab = { + id: `tab-${Date.now()}`, + title: url.replace(/^https?:\/\//, ""), + url, + isActive: true, + lastAccessed: Date.now(), + }; + return { + ...p, + tabs: [...(p.tabs || []).map((t) => ({ ...t, isActive: false })), newTab], + }; + }), + ); + }, []); - let imageUrl = ""; - for (const part of response.candidates?.[0]?.content?.parts || []) { - if (part.inlineData) { - imageUrl = `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`; - break; - } + const closeTab = useCallback((pinId: string, tabId: string) => { + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId || !p.tabs) return p; + const filtered = p.tabs.filter((t) => t.id !== tabId); + if (filtered.length > 0 && !filtered.some((t) => t.isActive)) { + filtered[0].isActive = true; } + return { ...p, tabs: filtered }; + }), + ); + }, []); - setWindows((prev) => - prev.map((w) => - w.id === id - ? { - ...w, - canvasData: { - ...w.canvasData, - isGenerating: false, - imageUrl, - prompt, - }, - } - : w, - ), - ); - } catch (e) { - console.error("Canvas Generation Failed:", e); - setWindows((prev) => - prev.map((w) => - w.id === id - ? { ...w, canvasData: { ...w.canvasData, isGenerating: false } } - : w, - ), - ); - } - }, - [], - ); + const addIntention = useCallback((text: string, priority: Priority = "medium") => { + setTodos((prev) => [ + { id: `t-${Date.now()}`, text, priority, completed: false, createdAt: Date.now() }, + ...prev, + ]); + }, []); - const handleApplyWallpaper = useCallback((imageUrl: string) => { - setGlobalWallpaper(imageUrl); + const addNotePin = useCallback(() => { + const newPin: Pin = { + id: `p-${Date.now()}`, + title: "New Note", + type: "note", + content: "", + isPinned: false, + isArchived: false, + focusCount: 0, + createdAt: Date.now(), + }; + setPins((prev) => [newPin, ...prev]); + setOpenPinId(newPin.id); }, []); - const handleSmartStack = useCallback( - async (windowId: string) => { - const targetWindow = windows.find((w) => w.id === windowId); - if (!targetWindow || !targetWindow.tabs || targetWindow.tabs.length < 2) - return; + const addBrowserPin = useCallback(() => { + const newPin: Pin = { + id: `p-${Date.now()}`, + title: "New Tab Group", + type: "tabs", + content: "", + isPinned: false, + isArchived: false, + focusCount: 0, + createdAt: Date.now(), + tabs: [ + { id: `tab-${Date.now()}`, title: "New Tab", url: "", isActive: true }, + ], + }; + setPins((prev) => [newPin, ...prev]); + setOpenPinId(newPin.id); + }, []); + const handleSmartStack = useCallback( + async (pinId: string) => { + const pin = pins.find((p) => p.id === pinId); + if (!pin?.tabs || pin.tabs.length < 2) return; + setIsStacking(true); try { const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); - const tabData = targetWindow.tabs.map((t) => ({ - id: t.id, - title: t.title, - })); - + const tabData = pin.tabs.map((t) => ({ id: t.id, title: t.title })); const response = await ai.models.generateContent({ model: "gemini-3-flash-preview", contents: `Categorize these browser tabs into logical groups. Return a JSON array of objects with "id" and "category" (max 2 words). Tabs: ${JSON.stringify(tabData)}`, @@ -390,15 +280,13 @@ const App: React.FC = () => { }, }, }); - const categories = JSON.parse(response.text || "[]"); - - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId || !w.tabs) return w; + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId || !p.tabs) return p; return { - ...w, - tabs: w.tabs.map((t) => { + ...p, + tabs: p.tabs.map((t) => { const cat = categories.find((c: any) => c.id === t.id); return cat ? { ...t, category: cat.category } : t; }), @@ -406,406 +294,150 @@ const App: React.FC = () => { }), ); } catch (e) { - console.error("Smart Stack Failed:", e); - } - }, - [windows], - ); - - const handleSwitchTab = useCallback((windowId: string, tabId: string) => { - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId || !w.tabs) return w; - return { - ...w, - tabs: w.tabs.map((t) => ({ - ...t, - isActive: t.id === tabId, - lastAccessed: t.id === tabId ? Date.now() : t.lastAccessed, - })), - }; - }), - ); - }, []); - - const handleUpdateTabCategory = useCallback( - (windowId: string, tabId: string, category: string) => { - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId || !w.tabs) return w; - return { - ...w, - tabs: w.tabs.map((t) => (t.id === tabId ? { ...t, category } : t)), - }; - }), - ); - }, - [], - ); - - const handleAddTab = useCallback((windowId: string) => { - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId) return w; - const newTab: TabData = { - id: `tab-${Date.now()}`, - title: "New Surface", - url: "pinpoint.internal/new", - isActive: true, - lastAccessed: Date.now(), - }; - return { - ...w, - tabs: [ - ...(w.tabs || []).map((t) => ({ ...t, isActive: false })), - newTab, - ], - }; - }), - ); - }, []); - - const handleCloseTab = useCallback((windowId: string, tabId: string) => { - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId || !w.tabs) return w; - const tabToArchive = w.tabs.find((t) => t.id === tabId); - if (tabToArchive) { - setArchivedTabs((prevArchive) => [ - tabToArchive, - ...prevArchive.slice(0, 19), - ]); - } - const filteredTabs = w.tabs.filter((t) => t.id !== tabId); - if (filteredTabs.length > 0 && !filteredTabs.some((t) => t.isActive)) { - filteredTabs[0].isActive = true; - } - return { ...w, tabs: filteredTabs }; - }), - ); - }, []); - - const handleCloseWindow = useCallback((id: string) => { - setWindows((prev) => - prev.map((w) => (w.id === id ? { ...w, isClosed: true } : w)), - ); - }, []); - - const handleRestoreWindow = useCallback((id: string) => { - setWindows((prev) => - prev.map((w) => (w.id === id ? { ...w, isClosed: false } : w)), - ); - }, []); - - const handleToggleCollapseWindow = useCallback((id: string) => { - setWindows((prev) => - prev.map((w) => - w.id === id ? { ...w, isCollapsed: !w.isCollapsed } : w, - ), - ); - }, []); - - const handleSetActiveWindowId = useCallback((id: string | null) => { - setActiveWindowId(id); - if (id) { - setWindows((prev) => - prev.map((w) => - w.id === id ? { ...w, focusCount: (w.focusCount || 0) + 1 } : w, - ), - ); - } - }, []); - - const handleDesktopTidy = useCallback(() => { - setWindows((prev) => { - const visibleWindows = prev.filter((w) => !w.isClosed); - if (visibleWindows.length === 0) return prev; - - // Sort by focusCount (usage frequency) descending - const sorted = [...visibleWindows].sort((a, b) => { - const countA = a.focusCount || 0; - const countB = b.focusCount || 0; - if (countB !== countA) return countB - countA; - return a.id.localeCompare(b.id); - }); - - const screenWidth = window.innerWidth || 1200; - const screenHeight = window.innerHeight || 800; - - const centerWidth = Math.max(500, Math.min(680, screenWidth * 0.55)); - const centerHeight = Math.max(380, Math.min(500, screenHeight * 0.6)); - const centerX = (screenWidth - centerWidth) / 2; - const centerY = (screenHeight - centerHeight) / 2 + 10; - - const mostActive = sorted[0]; - const others = sorted.slice(1); - - return prev.map((w) => { - if (w.isClosed) return w; - - if (w.id === mostActive.id) { - return { - ...w, - x: centerX, - y: centerY, - width: centerWidth, - height: centerHeight, - isPinned: true, - isCollapsed: false, - }; - } - - const index = others.findIndex((o) => o.id === w.id); - if (index === -1) return w; - - const isLeft = index % 2 === 0; - const sideIndex = Math.floor(index / 2); - const totalOnSide = Math.ceil(others.length / 2); - - const sideWidth = Math.max( - 280, - Math.min(320, (screenWidth - centerWidth) / 2 - 30), - ); - const sideX = isLeft - ? Math.max(20, (centerX - sideWidth) / 2) - : Math.min( - screenWidth - sideWidth - 20, - centerX + - centerWidth + - (screenWidth - (centerX + centerWidth) - sideWidth) / 2, - ); - - const availableHeight = screenHeight - 120; - const sideHeight = Math.min(260, availableHeight / totalOnSide - 20); - const startY = 70; - const sideY = startY + sideIndex * (sideHeight + 15); - - return { - ...w, - x: sideX, - y: sideY, - width: sideWidth, - height: sideHeight, - isPinned: false, - isCollapsed: false, - }; - }); - }); - - setWindows((prev) => { - const visibleWindows = prev.filter((w) => !w.isClosed); - if (visibleWindows.length > 0) { - const sorted = [...visibleWindows].sort((a, b) => { - const countA = a.focusCount || 0; - const countB = b.focusCount || 0; - if (countB !== countA) return countB - countA; - return a.id.localeCompare(b.id); - }); - setActiveWindowId(sorted[0].id); + console.error("Smart Stack failed:", e); + } finally { + setIsStacking(false); } - return prev; - }); - }, []); - - const handleRestoreTab = useCallback( - (tab: TabData) => { - // Restore to currently active window or first browser window - setWindows((prev) => { - let targetId = - activeWindowId || prev.find((w) => w.type === "browser")?.id; - if (!targetId) return prev; - - return prev.map((w) => { - if (w.id !== targetId) return w; - return { - ...w, - tabs: [ - ...(w.tabs || []).map((t) => ({ ...t, isActive: false })), - { ...tab, isActive: true, lastAccessed: Date.now() }, - ], - }; - }); - }); - setArchivedTabs((prev) => prev.filter((t) => t.id !== tab.id)); - }, - [activeWindowId], - ); - - const addIntention = useCallback( - (text: string, priority: Priority = "medium") => { - setTodos((prev) => [ - { - id: `t-${Date.now()}`, - text, - priority, - completed: false, - createdAt: Date.now(), - }, - ...prev, - ]); }, - [], + [pins], ); if (isOnboarding) { - return ( - setIsOnboarding(false)} - preferences={preferences} - setPreferences={setPreferences} - /> - ); + return setIsOnboarding(false)} />; } + const visiblePins = + view === "vault" + ? pins.filter((p) => p.isArchived) + : view === "pins" + ? pins.filter((p) => !p.isArchived) + : pins.filter((p) => !p.isArchived && p.isPinned); + + const openPinObj = pins.find((p) => p.id === openPinId) || null; + return (
- w.isPinned).length} - focusMode={preferences.zenMode} - currentTheme={preferences.colorTheme} - theme={preferences.theme} - onToggleTheme={() => - setPreferences((p) => ({ - ...p, - theme: p.theme === "dark" ? "light" : "dark", - })) - } - accentColor={preferences.accentColor} - onAccentChange={(color) => - setPreferences((p) => ({ ...p, accentColor: color })) - } - focusTimer={focusTimer} - archivedTabs={archivedTabs} - onRestoreTab={handleRestoreTab} - onToggleFocusTimer={toggleFocusTimer} - onThemeChange={(theme) => - setPreferences((p) => ({ ...p, colorTheme: theme })) - } - onOpenPrefs={() => setIsPreferencesOpen(true)} - onOpenWorkspaces={() => setIsWorkspaceManagerOpen(true)} - onOpenAssistant={() => setIsAssistantOpen(true)} - onOpenSearch={() => setIsSearchOpen(true)} - onOpenEcho={() => setIsEchoOpen(true)} - isMobile={isMobile} - soundscape={preferences.soundscape} - onSoundscapeChange={(sound) => - setPreferences((p) => ({ ...p, soundscape: sound })) - } - soundscapeVolume={soundscapeVolume} - onSoundscapeVolumeChange={(vol) => setSoundscapeVolume(vol)} - closedWindows={windows.filter((w) => w.isClosed)} - onRestoreWindow={handleRestoreWindow} - /> - - - setWindows((prev) => - prev.map((w) => (w.id === id ? { ...w, opacity } : w)), - ) - } - onDetachTab={handleCloseTab} - onSwitchTab={handleSwitchTab} - onUpdateTabCategory={handleUpdateTabCategory} - onAddTab={handleAddTab} - onCloseTab={handleCloseTab} - onGenerateGlance={() => {}} - onGenerateCanvas={handleGenerateCanvas} - onApplyWallpaper={handleApplyWallpaper} - onSmartStack={handleSmartStack} + onAddTodo={addIntention} onToggleTodo={(id) => setTodos((prev) => - prev.map((t) => - t.id === id ? { ...t, completed: !t.completed } : t, - ), + prev.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t)), ) } - onAddTodo={addIntention} - onDeleteTodo={(id) => - setTodos((prev) => prev.filter((t) => t.id !== id)) - } - onGroupWindows={() => {}} - onRemoveFromFolder={() => {}} - isMobile={isMobile} - globalWallpaper={globalWallpaper} - onCloseWindow={handleCloseWindow} - onSnapWindow={handleSnapWindow} - onToggleCollapseWindow={handleToggleCollapseWindow} + onDeleteTodo={(id) => setTodos((prev) => prev.filter((t) => t.id !== id))} /> - {isEchoOpen && ( - setIsEchoOpen(false)} - commands={{ - setTimer: (mins) => - setFocusTimer({ - mode: "focus", - timeLeft: mins * 60, - isActive: true, - }), - setTheme: (theme) => - setPreferences((p) => ({ - ...p, - colorTheme: theme as ColorTheme, - })), - pinWindow: (id) => togglePin(id), - addIntention: (text) => addIntention(text), - toggleZen: () => - setPreferences((p) => ({ ...p, zenMode: !p.zenMode })), - desktopTidy: handleDesktopTidy, - }} +
+ + setPreferences((p) => ({ + ...p, + theme: p.theme === "dark" ? "light" : "dark", + })) + } + onOpenSearch={() => setIsSearchOpen(true)} + onOpenPrefs={() => setIsPrefsOpen(true)} + focusTimer={focusTimer} + onToggleFocusTimer={toggleFocusTimer} /> - )} - {isAssistantOpen && ( - {}} - onCategorize={() => {}} - onSyncTabs={() => {}} - onCleanDesktop={handleDesktopTidy} - onClose={() => setIsAssistantOpen(false)} - isMobile={isMobile} +
+
+

+ {view === "today" ? "Pinned for today" : view} +

+ {view !== "vault" && ( +
+ + +
+ )} +
+ + {visiblePins.length === 0 ? ( +

+ {view === "vault" ? "Vault is empty." : "Nothing here yet."} +

+ ) : ( +
+ {visiblePins.map((pin) => + view === "vault" ? ( +
+

{pin.title}

+ +
+ ) : ( + + ), + )} +
+ )} +
+
+ + {openPinObj && ( + setOpenPinId(null)} + onUpdateContent={updatePinContent} + onSmartStack={handleSmartStack} + isStacking={isStacking} + onSwitchTab={switchTab} + onAddTab={addTab} + onCloseTab={closeTab} + onNavigateTab={navigateTab} /> )} - {isSearchOpen && ( - { - handleSetActiveWindowId(id); + openPin(id); setIsSearchOpen(false); }} onClose={() => setIsSearchOpen(false)} /> )} - {isPreferencesOpen && ( + {isPrefsOpen && ( setIsPreferencesOpen(false)} - isMobile={isMobile} - /> - )} - {isWorkspaceManagerOpen && ( - {}} - onRestore={() => {}} - onClose={() => setIsWorkspaceManagerOpen(false)} + onClose={() => setIsPrefsOpen(false)} /> )}
diff --git a/ColorStylePicker.tsx b/ColorStylePicker.tsx deleted file mode 100644 index f20cd09..0000000 --- a/ColorStylePicker.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useState } from "react"; -import { ACCENT_PRESETS } from "./types"; -import { Palette } from "lucide-react"; - -interface Props { - accentColor: string; - onChange: (color: string) => void; - isMobile?: boolean; -} - -const ColorStylePicker: React.FC = ({ - accentColor, - onChange, - isMobile, -}) => { - const [isOpen, setIsOpen] = useState(false); - - return ( -
- - - {isOpen && ( -
-
-

- Accent Color -

- -
- -
- {ACCENT_PRESETS.map((preset) => ( - - ))} -
- - onChange(e.target.value)} - className="mt-3 w-full h-8 rounded-lg bg-transparent border border-white/10 cursor-pointer" - title="Custom color" - /> -
- )} -
- ); -}; - -export default ColorStylePicker; diff --git a/CommandPalette.tsx b/CommandPalette.tsx new file mode 100644 index 0000000..8f0e4a8 --- /dev/null +++ b/CommandPalette.tsx @@ -0,0 +1,97 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { Pin } from "./types"; +import { Search, Link2, StickyNote, Layers } from "lucide-react"; + +interface Props { + pins: Pin[]; + onSelect: (id: string) => void; + onClose: () => void; +} + +const TYPE_ICON: Record> = { + link: Link2, + note: StickyNote, + tabs: Layers, +}; + +const CommandPalette: React.FC = ({ pins, onSelect, onClose }) => { + const [query, setQuery] = useState(""); + const inputRef = useRef(null); + + useEffect(() => { + inputRef.current?.focus(); + const handler = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, [onClose]); + + const results = useMemo(() => { + const q = query.trim().toLowerCase(); + const visible = pins.filter((p) => !p.isArchived); + if (!q) return visible.slice(0, 8); + return visible.filter( + (p) => + p.title.toLowerCase().includes(q) || + p.content.toLowerCase().includes(q) || + p.category?.toLowerCase().includes(q) || + p.tabs?.some((t) => t.title.toLowerCase().includes(q)), + ); + }, [pins, query]); + + return ( +
+
e.stopPropagation()} + className="w-full max-w-lg bg-surface border border-app rounded-xl shadow-xl overflow-hidden" + > +
+ + setQuery(e.target.value)} + placeholder="Search pins..." + className="flex-1 bg-transparent outline-none text-sm placeholder:text-app-tertiary" + /> + + esc + +
+
+ {results.length === 0 && ( +

+ No results +

+ )} + {results.map((p) => { + const Icon = TYPE_ICON[p.type]; + return ( + + ); + })} +
+
+
+ ); +}; + +export default CommandPalette; diff --git a/Darkmodetoggle.tsx b/Darkmodetoggle.tsx deleted file mode 100644 index 926ba29..0000000 --- a/Darkmodetoggle.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { useDarkMode, DarkModeToggle } from "./useDarkMode"; - -function Navbar() { - const { isDark, toggle } = useDarkMode(); - return ; -} diff --git a/Desktop.tsx b/Desktop.tsx deleted file mode 100644 index 599bfb0..0000000 --- a/Desktop.tsx +++ /dev/null @@ -1,249 +0,0 @@ - -import React, { useMemo, useState } from 'react'; -import { WindowState, UserPreferences, ColorTheme, TodoItem, Priority } from './types'; -import Window from './Window'; -import Affirmations from './Affirmations'; -import FocusTaskBoard from './FocusTaskBoard'; -import DesktopFolder from './DesktopFolder'; - -interface Props { - windows: WindowState[]; - todos: TodoItem[]; - activeWindowId: string | null; - focusMode: boolean; - preferences: UserPreferences; - focusTimerActive: boolean; - setActiveWindowId: (id: string | null) => void; - updateWindowPosition: (id: string, x: number, y: number) => void; - togglePin: (id: string) => void; - updateWindowOpacity: (id: string, opacity: number) => void; - onDetachTab: (windowId: string, tabId: string) => void; - onSwitchTab?: (windowId: string, tabId: string) => void; - onUpdateTabCategory?: (windowId: string, tabId: string, category: string) => void; - onAddTab?: (windowId: string) => void; - onCloseTab?: (windowId: string, tabId: string) => void; - onGenerateGlance: (id: string) => void; - onGenerateCanvas?: (id: string, prompt: string) => void; - onApplyWallpaper?: (imageUrl: string) => void; - onSmartStack?: (id: string) => void; - onToggleTodo: (id: string) => void; - onAddTodo: (text: string, priority: Priority) => void; - onDeleteTodo: (id: string) => void; - onGroupWindows: (sourceId: string, targetId: string) => void; - onRemoveFromFolder: (id: string) => void; - isMobile: boolean; - globalWallpaper: string | null; - onCloseWindow?: (id: string) => void; - onSnapWindow?: (id: string, snapType: 'left' | 'right' | 'top' | 'bottom' | 'none') => void; - onToggleCollapseWindow?: (id: string) => void; -} - -const Desktop: React.FC = ({ - windows, - todos, - activeWindowId, - focusMode, - preferences, - focusTimerActive, - setActiveWindowId, - updateWindowPosition, - togglePin, - updateWindowOpacity, - onDetachTab, - onSwitchTab, - onUpdateTabCategory, - onAddTab, - onCloseTab, - onGenerateGlance, - onGenerateCanvas, - onApplyWallpaper, - onSmartStack, - onToggleTodo, - onAddTodo, - onDeleteTodo, - onGroupWindows, - onRemoveFromFolder, - isMobile, - globalWallpaper, - onCloseWindow, - onSnapWindow, - onToggleCollapseWindow -}) => { - const [draggedWindowId, setDraggedWindowId] = useState(null); - - const desktopItems = useMemo(() => { - return windows.filter(w => !w.parentId && !w.isClosed); - }, [windows]); - - const activeDraggedWindow = useMemo(() => { - return windows.find(w => w.id === draggedWindowId); - }, [windows, draggedWindowId]); - - const activeSnapZone = useMemo(() => { - if (!activeDraggedWindow || isMobile || preferences.snapZonesEnabled === false) return 'none'; - - const threshold = 60; - const width = window.innerWidth; - const height = window.innerHeight; - - if (activeDraggedWindow.x < threshold) { - return 'left'; - } - if (activeDraggedWindow.x + activeDraggedWindow.width > width - threshold) { - return 'right'; - } - if (activeDraggedWindow.y < threshold + 32) { - return 'top'; - } - if (activeDraggedWindow.y + activeDraggedWindow.height > height - threshold) { - return 'bottom'; - } - return 'none'; - }, [activeDraggedWindow, isMobile]); - - const handleDragEnd = (id: string) => { - if (activeSnapZone !== 'none' && onSnapWindow) { - onSnapWindow(id, activeSnapZone); - } - setDraggedWindowId(null); - }; - - const sortedItems = [...desktopItems].sort((a, b) => { - if (a.isPinned && !b.isPinned) return 1; - if (!a.isPinned && b.isPinned) return -1; - if (a.id === activeWindowId) return 1; - if (b.id === activeWindowId) return -1; - return 0; - }); - - const themeColors = useMemo(() => { - const t = preferences.colorTheme; - const colors: Record = { - nebula: ['#3b82f6', '#9333ea', '#6366f1', '#ec4899'], - sunrise: ['#f97316', '#ef4444', '#f59e0b', '#db2777'], - ocean: ['#06b6d4', '#0891b2', '#10b981', '#3b82f6'], - emerald: ['#10b981', '#059669', '#84cc16', '#06b6d4'], - }; - return colors[t]; - }, [preferences.colorTheme]); - - return ( -
setActiveWindowId(null)} - style={{ - '--accent-1': themeColors[0], - '--accent-2': themeColors[1], - '--accent-3': themeColors[2], - } as React.CSSProperties} - > - {/* Dynamic Background Layer */} - {globalWallpaper ? ( -
- ) : ( - <> -
-
- - )} - - {!isMobile && } - -
- {!focusMode && ( -
- -
- )} - -
- {sortedItems.map(item => ( - item.type === 'folder' ? ( - w.parentId === item.id)} - isActive={activeWindowId === item.id} - onActivate={() => setActiveWindowId(item.id)} - onMove={updateWindowPosition} - onOpenChild={(id) => { - onRemoveFromFolder(id); - setActiveWindowId(id); - }} - /> - ) : ( - setActiveWindowId(item.id)} - onMove={(x, y) => updateWindowPosition(item.id, x, y)} - onTogglePin={(timer) => togglePin(item.id)} - onUpdateOpacity={(o) => updateWindowOpacity(item.id, o)} - onDetachTab={(tabId) => onDetachTab(item.id, tabId)} - onSwitchTab={(tabId) => onSwitchTab?.(item.id, tabId)} - onUpdateTabCategory={(tabId, cat) => onUpdateTabCategory?.(item.id, tabId, cat)} - onAddTab={() => onAddTab?.(item.id)} - onCloseTab={(tabId) => onCloseTab?.(item.id, tabId)} - onGenerateGlance={() => onGenerateGlance(item.id)} - onGenerateCanvas={onGenerateCanvas} - onApplyWallpaper={onApplyWallpaper} - onSmartStack={onSmartStack} - onDroppedOn={(targetId) => onGroupWindows(item.id, targetId)} - isMobile={isMobile} - onClose={() => onCloseWindow?.(item.id)} - onToggleCollapse={() => onToggleCollapseWindow?.(item.id)} - onDragStart={() => setDraggedWindowId(item.id)} - onDragEnd={() => handleDragEnd(item.id)} - /> - ) - ))} -
-
- - {/* Snap Zone Visual Indicators */} - {draggedWindowId && activeSnapZone !== 'none' && ( -
-
-
-
- - {activeSnapZone === 'left' ? '⬅️' : activeSnapZone === 'right' ? '➡️' : activeSnapZone === 'top' ? '🔼' : '🔽'} - - - {activeSnapZone === 'top' ? 'Maximize' : `Snap ${activeSnapZone}`} - -
-
-
- )} -
- ); -}; - -export default Desktop; diff --git a/DesktopFolder.tsx b/DesktopFolder.tsx deleted file mode 100644 index 86a108c..0000000 --- a/DesktopFolder.tsx +++ /dev/null @@ -1,114 +0,0 @@ - -import React, { useState, useEffect } from 'react'; -import { WindowState } from './types'; - -interface Props { - folder: WindowState; - childrenWindows: WindowState[]; - isActive: boolean; - onActivate: () => void; - onMove: (id: string, x: number, y: number) => void; - onOpenChild: (id: string) => void; -} - -const DesktopFolder: React.FC = ({ folder, childrenWindows, isActive, onActivate, onMove, onOpenChild }) => { - const [isDragging, setIsDragging] = useState(false); - const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); - const [isExpanded, setIsExpanded] = useState(false); - - const handleMouseDown = (e: React.MouseEvent) => { - e.stopPropagation(); - onActivate(); - setIsDragging(true); - setDragOffset({ - x: e.clientX - folder.x, - y: e.clientY - folder.y, - }); - }; - - useEffect(() => { - const handleMouseMove = (e: MouseEvent) => { - if (isDragging) onMove(folder.id, e.clientX - dragOffset.x, e.clientY - dragOffset.y); - }; - const handleMouseUp = () => setIsDragging(false); - if (isDragging) { - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - } - return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - }, [isDragging, dragOffset, onMove, folder.id]); - - return ( -
setIsExpanded(!isExpanded)} - > - {/* Folder Icon Stack Preview */} -
-
-
-
- 📁 -
-
- - - {folder.title} - - -
- {childrenWindows.length} Items -
- - {/* Expanded Grid View */} - {isExpanded && ( -
{ e.stopPropagation(); setIsExpanded(false); }} - > -
e.stopPropagation()} - > - {childrenWindows.map(win => ( -
onOpenChild(win.id)} - className="glass rounded-3xl p-6 aspect-square flex flex-col items-center justify-center space-y-4 hover:bg-white/10 transition-all cursor-pointer group/item hover:scale-105 active:scale-95 shadow-2xl" - > -
- {win.type === 'browser' ? '🌐' : win.type === 'code' ? '💻' : '📄'} -
-
-

{win.title}

-

{win.type}

-
-
- ))} -
- -
- )} -
- ); -}; - -export default DesktopFolder; diff --git a/EchoAssistant.tsx b/EchoAssistant.tsx deleted file mode 100644 index 7a6c8a7..0000000 --- a/EchoAssistant.tsx +++ /dev/null @@ -1,256 +0,0 @@ - -import React, { useState, useEffect, useRef } from 'react'; -import { GoogleGenAI, LiveServerMessage, Modality, Type, FunctionDeclaration } from '@google/genai'; -import { WindowState } from './types'; - -interface Props { - windows: WindowState[]; - onClose: () => void; - commands: { - setTimer: (mins: number) => void; - setTheme: (theme: string) => void; - pinWindow: (id: string) => void; - addIntention: (text: string) => void; - toggleZen: () => void; - desktopTidy: () => void; - }; -} - -const EchoAssistant: React.FC = ({ windows, onClose, commands }) => { - const [isActive, setIsActive] = useState(false); - const [transcription, setTranscription] = useState([]); - const [audioLevel, setAudioLevel] = useState(0); - const [isConnecting, setIsConnecting] = useState(false); - - const audioCtxRef = useRef(null); - const outputCtxRef = useRef(null); - const nextStartTimeRef = useRef(0); - const sourcesRef = useRef>(new Set()); - const sessionRef = useRef(null); - - // Tools for Voice Control - const controlTools: FunctionDeclaration[] = [ - { - name: 'set_focus_timer', - parameters: { - type: Type.OBJECT, - properties: { minutes: { type: Type.NUMBER, description: 'Minutes for the focus timer' } }, - required: ['minutes'], - } - }, - { - name: 'change_theme', - parameters: { - type: Type.OBJECT, - properties: { theme: { type: Type.STRING, description: 'One of: nebula, sunrise, ocean, emerald' } }, - required: ['theme'], - } - }, - { - name: 'pin_window', - parameters: { - type: Type.OBJECT, - properties: { window_id: { type: Type.STRING, description: 'The ID of the window to pin' } }, - required: ['window_id'], - } - }, - { - name: 'add_intention', - parameters: { - type: Type.OBJECT, - properties: { text: { type: Type.STRING, description: 'Description of the new task' } }, - required: ['text'], - } - }, - { - name: 'toggle_zen_mode', - parameters: { type: Type.OBJECT, properties: {} } - }, - { - name: 'desktop_tidy', - parameters: { type: Type.OBJECT, properties: {} } - } - ]; - - const startSession = async () => { - setIsConnecting(true); - // Create a new GoogleGenAI instance right before making an API call to ensure it always uses the most up-to-date API key - const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); - - audioCtxRef.current = new (window.AudioContext || (window as any).webkitAudioContext)({ sampleRate: 16000 }); - outputCtxRef.current = new (window.AudioContext || (window as any).webkitAudioContext)({ sampleRate: 24000 }); - - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - - const sessionPromise = ai.live.connect({ - model: 'gemini-2.5-flash-native-audio-preview-12-2025', - callbacks: { - onopen: () => { - setIsActive(true); - setIsConnecting(false); - const source = audioCtxRef.current!.createMediaStreamSource(stream); - const processor = audioCtxRef.current!.createScriptProcessor(4096, 1, 1); - - processor.onaudioprocess = (e) => { - const inputData = e.inputBuffer.getChannelData(0); - - // Calculate Audio Level for Visualizer - let sum = 0; - for(let i=0; i session.sendRealtimeInput({ media: pcmBlob })); - }; - - source.connect(processor); - processor.connect(audioCtxRef.current!.destination); - }, - onmessage: async (msg: LiveServerMessage) => { - if (msg.serverContent?.outputTranscription) { - const text = msg.serverContent.outputTranscription.text; - setTranscription(prev => [...prev.slice(-4), `Echo: ${text}`]); - } - - if (msg.toolCall) { - for (const fc of msg.toolCall.functionCalls) { - let result = "ok"; - // Fixed: Added explicit type casting for function arguments to fix TS 'unknown' errors (lines 112-115) - if (fc.name === 'set_focus_timer') commands.setTimer(fc.args.minutes as number); - if (fc.name === 'change_theme') commands.setTheme(fc.args.theme as string); - if (fc.name === 'pin_window') commands.pinWindow(fc.args.window_id as string); - if (fc.name === 'add_intention') commands.addIntention(fc.args.text as string); - if (fc.name === 'toggle_zen_mode') commands.toggleZen(); - if (fc.name === 'desktop_tidy') commands.desktopTidy(); - - sessionPromise.then(s => s.sendToolResponse({ - functionResponses: { id: fc.id, name: fc.name, response: { result } } - })); - } - } - - const audioBase64 = msg.serverContent?.modelTurn?.parts[0]?.inlineData?.data; - if (audioBase64) { - // Use a running timestamp to track the end of the audio playback queue for gapless playback - nextStartTimeRef.current = Math.max(nextStartTimeRef.current, outputCtxRef.current!.currentTime); - const buffer = await decodeAudioData(decode(audioBase64), outputCtxRef.current!, 24000, 1); - const source = outputCtxRef.current!.createBufferSource(); - source.buffer = buffer; - source.connect(outputCtxRef.current!.destination); - source.start(nextStartTimeRef.current); - nextStartTimeRef.current += buffer.duration; - sourcesRef.current.add(source); - } - }, - onerror: (e) => console.error("Echo Error:", e), - onclose: () => { - setIsActive(false); - setIsConnecting(false); - }, - }, - config: { - responseModalities: [Modality.AUDIO], - outputAudioTranscription: {}, - tools: [{ functionDeclarations: controlTools }], - systemInstruction: `You are Echo, the PinPoint Pro workspace controller. - You help users manage their screen using tools. - Current workspace windows: ${windows.map(w => `${w.id}: ${w.title}`).join(', ')}. - Be concise and helpful.` - } - }); - - sessionRef.current = await sessionPromise; - }; - - const stopSession = () => { - sessionRef.current?.close(); - audioCtxRef.current?.close(); - outputCtxRef.current?.close(); - setIsActive(false); - }; - - // Helper Functions - Implementing manual decode/encode as required by guidelines - function decode(b64: string) { - const bin = atob(b64); - const bytes = new Uint8Array(bin.length); - for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); - return bytes; - } - - async function decodeAudioData(data: Uint8Array, ctx: AudioContext, rate: number, chans: number) { - const data16 = new Int16Array(data.buffer); - const count = data16.length / chans; - const buffer = ctx.createBuffer(chans, count, rate); - for (let c = 0; c < chans; c++) { - const cData = buffer.getChannelData(c); - for (let i = 0; i < count; i++) cData[i] = data16[i * chans + c] / 32768.0; - } - return buffer; - } - - function createBlob(data: Float32Array) { - const int16 = new Int16Array(data.length); - for (let i = 0; i < data.length; i++) int16[i] = data[i] * 32768; - return { data: encode(new Uint8Array(int16.buffer)), mimeType: 'audio/pcm;rate=16000' }; - } - - function encode(bytes: Uint8Array) { - let bin = ''; - for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); - return btoa(bin); - } - - return ( -
-
-
-
- Echo Live - {isActive &&
} -
- -
- - {/* Visualizer */} -
- {[...Array(12)].map((_, i) => ( -
- ))} -
- -
- {transcription.map((t, i) => ( -

{t}

- ))} - {transcription.length === 0 && !isConnecting && ( -

"Echo, pin my research window..."

- )} - {isConnecting &&

Initializing Neural Link...

} -
- - - -

- {isActive ? 'Echo is listening...' : 'Tap to start voice control'} -

-
-
- ); -}; - -export default EchoAssistant; diff --git a/FocusTaskBoard.tsx b/FocusTaskBoard.tsx deleted file mode 100644 index c242a8b..0000000 --- a/FocusTaskBoard.tsx +++ /dev/null @@ -1,104 +0,0 @@ - -import React, { useState } from 'react'; -import { TodoItem, Priority } from './types'; - -interface Props { - todos: TodoItem[]; - onToggleTodo: (id: string) => void; - onAddTodo: (text: string, priority: Priority) => void; - onDeleteTodo: (id: string) => void; - isMobile?: boolean; -} - -const PRIORITY_COLORS: Record = { - critical: '#ef4444', - high: '#f97316', - medium: '#3b82f6', - low: '#64748b', -}; - -const FocusTaskBoard: React.FC = ({ todos, onToggleTodo, onAddTodo, onDeleteTodo, isMobile }) => { - const [newText, setNewText] = useState(''); - const [newPriority, setNewPriority] = useState('medium'); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (newText.trim()) { - onAddTodo(newText, newPriority); - setNewText(''); - } - }; - - const sortedTodos = [...todos].sort((a, b) => { - const weights: Record = { critical: 4, high: 3, medium: 2, low: 1 }; - if (a.completed !== b.completed) return a.completed ? 1 : -1; - return weights[b.priority] - weights[a.priority]; - }); - - return ( -
-
-

Intentions

-
-
- -
- setNewText(e.target.value)} - placeholder="Add intention..." - className="w-full bg-transparent border-b border-white/10 py-2 text-xs focus:outline-none focus:border-blue-500/50 transition-all placeholder:text-slate-600 font-medium" - /> -
- {(['critical', 'high', 'medium', 'low'] as Priority[]).map((p) => ( -
-
- -
- {sortedTodos.map(todo => { - const isCritical = todo.priority === 'critical' && !todo.completed; - - return ( -
onToggleTodo(todo.id)} - className={`relative group cursor-pointer transition-all duration-500 p-2 rounded-lg hover:bg-white/5 ${todo.completed ? 'opacity-20' : 'opacity-100'}`} - > -
-
-
-

- {todo.text} -

-
- -
-
- ); - })} - {sortedTodos.length === 0 && ( -

No tasks active.

- )} -
-
- ); -}; - -export default FocusTaskBoard; diff --git a/GhostSearch.tsx b/GhostSearch.tsx deleted file mode 100644 index 48798f2..0000000 --- a/GhostSearch.tsx +++ /dev/null @@ -1,112 +0,0 @@ - -import React, { useState, useEffect, useRef } from 'react'; -import { WindowState } from './types'; -import { GoogleGenAI } from "@google/genai"; - -interface Props { - windows: WindowState[]; - onSelect: (windowId: string) => void; - onClose: () => void; -} - -const GhostSearch: React.FC = ({ windows, onSelect, onClose }) => { - const [query, setQuery] = useState(''); - const [results, setResults] = useState<{ id: string, title: string, relevance: string }[]>([]); - const [loading, setLoading] = useState(false); - const inputRef = useRef(null); - - useEffect(() => { - inputRef.current?.focus(); - }, []); - - const handleSearch = async (e: React.FormEvent) => { - e.preventDefault(); - if (!query.trim()) return; - - setLoading(true); - try { - // Corrected GoogleGenAI initialization to follow guidelines - const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); - const context = windows.map(w => `ID: ${w.id}, Title: ${w.title}, Content: ${w.content}`).join('\n---\n'); - - const response = await ai.models.generateContent({ - model: "gemini-3-flash-preview", - contents: `I have several open windows with this content: - ${context} - - The user is searching for: "${query}" - - Analyze which windows are most relevant. Return a valid JSON array of objects with "id" and "relevance" (a 5-word summary of why it's relevant). Only return the JSON.`, - config: { responseMimeType: "application/json" } - }); - - const parsed = JSON.parse(response.text || '[]'); - const resultsWithTitles = parsed.map((res: any) => ({ - ...res, - title: windows.find(w => w.id === res.id)?.title || 'Unknown Window' - })); - setResults(resultsWithTitles); - } catch (e) { - console.error("Search failed:", e); - } finally { - setLoading(false); - } - }; - - return ( -
-
e.stopPropagation()} - > -
-
- 🔍 - setQuery(e.target.value)} - placeholder="Search across your workspace..." - className="flex-1 bg-transparent border-none outline-none text-lg font-medium placeholder:text-slate-600" - /> - {loading &&
} -
- - -
- {results.length > 0 ? ( - results.map(res => ( - - )) - ) : query && !loading ? ( -
- Press Enter to ask AI to find it... -
- ) : ( -
- Ghost Search active -
- )} -
- -
- Powered by Gemini Contextual Engine - ESC to cancel -
-
-
- ); -}; - -export default GhostSearch; diff --git a/Onboarding.tsx b/Onboarding.tsx index 007845a..5b1f692 100644 --- a/Onboarding.tsx +++ b/Onboarding.tsx @@ -1,119 +1,34 @@ - -import React, { useState } from 'react'; -import { IndicatorStyle, UserPreferences } from './types'; -import { Pin } from 'lucide-react'; +import React from "react"; +import { Pin } from "lucide-react"; interface Props { onComplete: () => void; - preferences: UserPreferences; - setPreferences: (p: UserPreferences) => void; } -const Onboarding: React.FC = ({ onComplete, preferences, setPreferences }) => { - const [step, setStep] = useState(1); - - const next = () => setStep(s => s + 1); - - const renderStep = () => { - switch (step) { - case 1: - return ( -
-
- Open Source -
- v1.0.0-beta -
-
- -
-

Welcome to PinPoint Pro

-

Pin anything, stay focused. Elevate your productivity with context-aware window management.

- -
- ); - case 2: - return ( -
-

Quick Setup

-
-
-
-

Use suggested shortcuts

-

Optimized for Apple keyboards and macOS power users.

- -
-
- Pin Surface -
- - + - P -
-
-
- Ghost Search -
- - + - K -
-
-
-
-
-

Customize my shortcuts

-

Fine-tune keys to match your existing muscle memory.

- -
- - - - Key -
-
-
-
- ); - case 3: - return ( -
-

You're all set!

-

- PinPoint is now your new digital surface. Drag windows into folders to organize, or use the AI Assistant to clean up. -

-
- -

Proudly Open Source • MIT License

-
-
- ); - default: - return null; - } - }; - +const Onboarding: React.FC = ({ onComplete }) => { return ( -
-
-
- {renderStep()} +
+
+
+ +
+ + Open Source · v1.0.0 + +

+ Welcome to PinPoint Pro +

+

+ Pin what matters, set your intentions, and stay focused — without + the clutter. +

+ +
); }; diff --git a/PinCard.tsx b/PinCard.tsx new file mode 100644 index 0000000..25bb04e --- /dev/null +++ b/PinCard.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import { Pin } from "./types"; +import { Link2, StickyNote, Layers, Pin as PinIcon, Archive } from "lucide-react"; + +interface Props { + pin: Pin; + onOpen: (id: string) => void; + onTogglePin: (id: string) => void; + onArchive: (id: string) => void; +} + +const TYPE_ICON: Record> = { + link: Link2, + note: StickyNote, + tabs: Layers, +}; + +const PinCard: React.FC = ({ pin, onOpen, onTogglePin, onArchive }) => { + const Icon = TYPE_ICON[pin.type]; + + return ( +
onOpen(pin.id)} + className="group relative flex flex-col gap-3 p-4 rounded-xl border border-app bg-surface hover:border-app-strong hover:shadow-sm transition-all cursor-pointer" + > +
+
+ +
+
+ + +
+
+ +
+

+ {pin.title} +

+

+ {pin.type === "tabs" + ? `${pin.tabs?.length || 0} tabs` + : pin.content} +

+
+ + {pin.type === "tabs" && pin.tabs && pin.tabs.length > 0 && ( +
+ {pin.tabs.slice(0, 3).map((t) => ( + + {t.title} + + ))} +
+ )} + +
+ {pin.category ? ( + + {pin.category} + + ) : ( + + )} + {pin.focusCount > 0 && ( + + {pin.focusCount} focus{pin.focusCount === 1 ? "" : "es"} + + )} +
+
+ ); +}; + +export default PinCard; diff --git a/PinDetail.tsx b/PinDetail.tsx new file mode 100644 index 0000000..39de37b --- /dev/null +++ b/PinDetail.tsx @@ -0,0 +1,185 @@ +import React, { useState } from "react"; +import { Pin } from "./types"; +import { X, Sparkles, Plus, ArrowLeft, ArrowRight, RotateCw } from "lucide-react"; + +interface Props { + pin: Pin; + onClose: () => void; + onUpdateContent: (id: string, content: string) => void; + onSmartStack: (id: string) => void; + isStacking: boolean; + onSwitchTab: (pinId: string, tabId: string) => void; + onAddTab: (pinId: string, url: string) => void; + onCloseTab: (pinId: string, tabId: string) => void; + onNavigateTab: (pinId: string, tabId: string, url: string) => void; +} + +const normalizeUrl = (raw: string) => { + const trimmed = raw.trim(); + if (!trimmed) return ""; + if (/^https?:\/\//i.test(trimmed)) return trimmed; + return `https://${trimmed}`; +}; + +const PinDetail: React.FC = ({ + pin, + onClose, + onUpdateContent, + onSmartStack, + isStacking, + onSwitchTab, + onAddTab, + onCloseTab, + onNavigateTab, +}) => { + const activeTab = pin.tabs?.find((t) => t.isActive) || pin.tabs?.[0]; + const [addressValue, setAddressValue] = useState(activeTab?.url || ""); + const [newTabUrl, setNewTabUrl] = useState(""); + const [reloadKey, setReloadKey] = useState(0); + + React.useEffect(() => { + setAddressValue(activeTab?.url || ""); + }, [activeTab?.id, activeTab?.url]); + + const isBrowser = pin.type === "tabs"; + + const submitAddress = (e: React.FormEvent) => { + e.preventDefault(); + if (!activeTab) return; + onNavigateTab(pin.id, activeTab.id, normalizeUrl(addressValue)); + }; + + const submitNewTab = (e: React.FormEvent) => { + e.preventDefault(); + if (!newTabUrl.trim()) return; + onAddTab(pin.id, normalizeUrl(newTabUrl)); + setNewTabUrl(""); + }; + + return ( +
+
e.stopPropagation()} + className={`w-full flex flex-col bg-surface border border-app rounded-2xl shadow-xl overflow-hidden ${ + isBrowser ? "max-w-4xl h-[85vh]" : "max-w-lg max-h-[80vh]" + }`} + > +
+

+ {pin.title} +

+
+ {isBrowser && ( + + )} + +
+
+ + {isBrowser && pin.tabs ? ( + <> +
+ {pin.tabs.map((t) => ( +
onSwitchTab(pin.id, t.id)} + className={`group flex items-center gap-2 px-3 py-2 rounded-t-lg text-xs cursor-pointer max-w-[160px] shrink-0 ${ + t.isActive + ? "bg-surface text-app-primary font-medium" + : "text-app-tertiary hover:bg-app-muted-hover" + }`} + > + {t.title || t.url || "New Tab"} + +
+ ))} +
+ setNewTabUrl(e.target.value)} + placeholder="+ new tab url" + className="text-xs bg-transparent outline-none placeholder:text-app-tertiary w-28 py-2" + /> +
+
+ +
+ + + + setAddressValue(e.target.value)} + placeholder="Enter a URL..." + className="flex-1 text-xs bg-app-muted rounded-md px-2.5 py-1.5 outline-none" + /> + + +
+ {activeTab?.url ? ( +