diff --git a/apps/desktop2/src/components/main/body/calendars.tsx b/apps/desktop2/src/components/main/body/calendars.tsx
new file mode 100644
index 0000000000..6f74349158
--- /dev/null
+++ b/apps/desktop2/src/components/main/body/calendars.tsx
@@ -0,0 +1,144 @@
+import { clsx } from "clsx";
+import { addMonths, eachDayOfInterval, endOfMonth, format, getDay, isSameMonth, startOfMonth } from "date-fns";
+import { CalendarIcon, FileTextIcon } from "lucide-react";
+
+import { CalendarStructure } from "@hypr/ui/components/block/calendar-structure";
+import * as persisted from "../../../store/tinybase/persisted";
+import { type Tab, useTabs } from "../../../store/zustand/tabs";
+import { type TabItem, TabItemBase } from "./shared";
+
+export const TabItemCalendar: TabItem = ({ tab, handleClose, handleSelect }) => {
+ return (
+ }
+ title={"Calendar"}
+ active={tab.active}
+ handleClose={() => handleClose(tab)}
+ handleSelect={() => handleSelect(tab)}
+ />
+ );
+};
+
+export function TabContentCalendar({ tab }: { tab: Tab }) {
+ if (tab.type !== "calendars") {
+ return null;
+ }
+
+ const { openCurrent } = useTabs();
+ const monthStart = startOfMonth(tab.month);
+ const monthEnd = endOfMonth(tab.month);
+ const days = eachDayOfInterval({ start: monthStart, end: monthEnd }).map((day) => format(day, "yyyy-MM-dd"));
+ const startDayOfWeek = getDay(monthStart);
+ const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+
+ const handlePreviousMonth = () => {
+ openCurrent({ ...tab, month: addMonths(tab.month, -1) });
+ };
+
+ const handleNextMonth = () => {
+ openCurrent({ ...tab, month: addMonths(tab.month, 1) });
+ };
+
+ const handleToday = () => {
+ openCurrent({ ...tab, month: new Date() });
+ };
+
+ return (
+
+ {days.map((day) => (
+
+ ))}
+
+ );
+}
+
+function TabContentCalendarDay({ day, isCurrentMonth }: { day: string; isCurrentMonth: boolean }) {
+ const eventIds = persisted.UI.useSliceRowIds(
+ persisted.INDEXES.eventsByDate,
+ day,
+ persisted.STORE_ID,
+ );
+
+ const sessionIds = persisted.UI.useSliceRowIds(
+ persisted.INDEXES.sessionByDateWithoutEvent,
+ day,
+ persisted.STORE_ID,
+ );
+
+ const dayNumber = format(new Date(day), "d");
+ const isToday = format(new Date(), "yyyy-MM-dd") === day;
+ const dayOfWeek = getDay(new Date(day));
+ const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
+
+ return (
+
+
+
+
+
+ {eventIds.map((eventId) => )}
+ {sessionIds.map((sessionId) => )}
+
+
+
+ );
+}
+
+function TabContentCalendarDayEvents({ eventId }: { eventId: string }) {
+ const event = persisted.UI.useRow("events", eventId, persisted.STORE_ID);
+
+ return (
+
+ );
+}
+
+function TabContentCalendarDaySessions({ sessionId }: { sessionId: string }) {
+ const session = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID);
+ return (
+
+
+
+ {session.title}
+
+
+ );
+}
diff --git a/apps/desktop2/src/components/main/body/events.tsx b/apps/desktop2/src/components/main/body/events.tsx
new file mode 100644
index 0000000000..e4178985c2
--- /dev/null
+++ b/apps/desktop2/src/components/main/body/events.tsx
@@ -0,0 +1,26 @@
+import { CalendarIcon } from "lucide-react";
+
+import * as persisted from "../../../store/tinybase/persisted";
+import { rowIdfromTab, type Tab } from "../../../store/zustand/tabs";
+import { type TabItem, TabItemBase } from "./shared";
+
+export const TabItemEvent: TabItem = ({ tab, handleClose, handleSelect }) => {
+ const title = persisted.UI.useCell("events", rowIdfromTab(tab), "title", persisted.STORE_ID);
+
+ return (
+ }
+ title={title ?? ""}
+ active={tab.active}
+ handleClose={() => handleClose(tab)}
+ handleSelect={() => handleSelect(tab)}
+ />
+ );
+};
+
+export function TabContentEvent({ tab }: { tab: Tab }) {
+ const id = rowIdfromTab(tab);
+ const event = persisted.UI.useRow("events", id, persisted.STORE_ID);
+
+ return {JSON.stringify(event, null, 2)};
+}
diff --git a/apps/desktop2/src/components/main/body/folders.tsx b/apps/desktop2/src/components/main/body/folders.tsx
new file mode 100644
index 0000000000..4e14478a2f
--- /dev/null
+++ b/apps/desktop2/src/components/main/body/folders.tsx
@@ -0,0 +1,215 @@
+import { FolderIcon, StickyNoteIcon } from "lucide-react";
+
+import * as persisted from "../../../store/tinybase/persisted";
+import { type Tab } from "../../../store/zustand/tabs";
+import { useTabs } from "../../../store/zustand/tabs";
+import { type TabItem, TabItemBase } from "./shared";
+
+export const TabItemFolder: TabItem = ({ tab, handleClose, handleSelect }) => {
+ if (tab.type === "folders" && tab.id === null) {
+ return ;
+ }
+
+ if (tab.type === "folders" && tab.id !== null) {
+ return ;
+ }
+
+ return null;
+};
+
+const TabItemFolderAll: TabItem = ({ tab, handleClose, handleSelect }) => {
+ return (
+ }
+ title={"Folder"}
+ active={tab.active}
+ handleClose={() => handleClose(tab)}
+ handleSelect={() => handleSelect(tab)}
+ />
+ );
+};
+
+const TabItemFolderSpecific: TabItem = ({ tab, handleClose, handleSelect }) => {
+ if (tab.type !== "folders" || tab.id === null) {
+ return null;
+ }
+
+ const folderName = persisted.UI.useCell("folders", tab.id, "name", persisted.STORE_ID);
+
+ return (
+ }
+ title={folderName ?? ""}
+ active={tab.active}
+ handleClose={() => handleClose(tab)}
+ handleSelect={() => handleSelect(tab)}
+ />
+ );
+};
+
+export function TabContentFolder({ tab }: { tab: Tab }) {
+ if (tab.type !== "folders") {
+ return null;
+ }
+
+ // If tab.id is null, show top-level folders
+ if (tab.id === null) {
+ return ;
+ }
+
+ // If tab.id is a folder, show that folder's contents
+ return ;
+}
+
+function TabContentFolderTopLevel() {
+ const topLevelFolderIds = persisted.UI.useSliceRowIds(
+ persisted.INDEXES.foldersByParent,
+ "",
+ persisted.STORE_ID,
+ );
+
+ return (
+
+
All Folders
+
+ {topLevelFolderIds?.map((folderId) => )}
+
+
+ );
+}
+
+function FolderCard({ folderId }: { folderId: string }) {
+ const folder = persisted.UI.useRow("folders", folderId, persisted.STORE_ID);
+ const { openCurrent } = useTabs();
+
+ // Count children
+ const childFolderIds = persisted.UI.useSliceRowIds(
+ persisted.INDEXES.foldersByParent,
+ folderId,
+ persisted.STORE_ID,
+ );
+
+ const sessionIds = persisted.UI.useSliceRowIds(
+ persisted.INDEXES.sessionsByFolder,
+ folderId,
+ persisted.STORE_ID,
+ );
+
+ const childCount = (childFolderIds?.length ?? 0) + (sessionIds?.length ?? 0);
+
+ return (
+ openCurrent({ type: "folders", id: folderId, active: true })}
+ >
+
+ {folder.name}
+ {childCount > 0 && {childCount} items}
+
+ );
+}
+
+function TabContentFolderSpecific({ folderId }: { folderId: string }) {
+ const childFolderIds = persisted.UI.useSliceRowIds(
+ persisted.INDEXES.foldersByParent,
+ folderId,
+ persisted.STORE_ID,
+ );
+
+ const sessionIds = persisted.UI.useSliceRowIds(
+ persisted.INDEXES.sessionsByFolder,
+ folderId,
+ persisted.STORE_ID,
+ );
+
+ return (
+
+
+
+ {(childFolderIds?.length ?? 0) > 0 && (
+
+
Folders
+
+ {childFolderIds!.map((childId) => )}
+
+
+ )}
+
+ {(sessionIds?.length ?? 0) > 0 && (
+
+
Notes
+
+ {sessionIds!.map((sessionId) => )}
+
+
+ )}
+
+ {(childFolderIds?.length ?? 0) === 0 && (sessionIds?.length ?? 0) === 0 && (
+
+ This folder is empty
+
+ )}
+
+ );
+}
+
+function TabContentFolderBreadcrumb({ folderId }: { folderId: string }) {
+ const { openCurrent } = useTabs();
+
+ const folderIds = persisted.UI.useLinkedRowIds(
+ "folderToParentFolder",
+ folderId,
+ persisted.STORE_ID,
+ );
+
+ if (!folderIds || folderIds.length === 0) {
+ return null;
+ }
+
+ const folderChain = [...folderIds].reverse();
+
+ return (
+
+
+ {folderChain.map((id) => {
+ const isLast = id === folderId;
+ return (
+
+ /
+
+
+ );
+ })}
+
+ );
+}
+
+function TabContentFolderBreadcrumbItem({ folderId }: { folderId: string }) {
+ const folderName = persisted.UI.useCell("folders", folderId, "name", persisted.STORE_ID);
+ return {folderName};
+}
+
+function FolderSessionItem({ sessionId }: { sessionId: string }) {
+ const session = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID);
+ const { openCurrent } = useTabs();
+
+ return (
+ openCurrent({ type: "sessions", id: sessionId, active: true })}
+ >
+
+ {session.title}
+
+ );
+}
diff --git a/apps/desktop2/src/components/main/body/humans.tsx b/apps/desktop2/src/components/main/body/humans.tsx
new file mode 100644
index 0000000000..7979af2c31
--- /dev/null
+++ b/apps/desktop2/src/components/main/body/humans.tsx
@@ -0,0 +1,31 @@
+import { UserIcon } from "lucide-react";
+
+import * as persisted from "../../../store/tinybase/persisted";
+import { type Tab } from "../../../store/zustand/tabs";
+import { type TabItem, TabItemBase } from "./shared";
+
+export const TabItemHuman: TabItem = ({ tab, handleClose, handleSelect }) => {
+ if (tab.type !== "humans") {
+ throw new Error("non_human_tab");
+ }
+
+ const title = persisted.UI.useCell("humans", tab.id, "name", persisted.STORE_ID);
+
+ return (
+ }
+ title={title ?? "Human"}
+ active={tab.active}
+ handleClose={() => handleClose(tab)}
+ handleSelect={() => handleSelect(tab)}
+ />
+ );
+};
+
+export function TabContentHuman({ tab }: { tab: Tab }) {
+ if (tab.type !== "humans") {
+ throw new Error("non_human_tab");
+ }
+
+ return Human
;
+}
diff --git a/apps/desktop2/src/components/main/body/index.tsx b/apps/desktop2/src/components/main/body/index.tsx
new file mode 100644
index 0000000000..f11d0a8567
--- /dev/null
+++ b/apps/desktop2/src/components/main/body/index.tsx
@@ -0,0 +1,109 @@
+import clsx from "clsx";
+import { Reorder } from "motion/react";
+
+import { type Tab, uniqueIdfromTab, useTabs } from "../../../store/zustand/tabs";
+
+import { TabContentCalendar, TabItemCalendar } from "./calendars";
+import { TabContentEvent, TabItemEvent } from "./events";
+import { TabContentFolder, TabItemFolder } from "./folders";
+import { TabContentHuman, TabItemHuman } from "./humans";
+import { TabContentNote, TabItemNote } from "./sessions";
+
+export function MainContent() {
+ const { tabs, currentTab } = useTabs();
+
+ if (!currentTab) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ );
+}
+
+function TabsHeader({ tabs }: { tabs: Tab[] }) {
+ const { select, close, reorder } = useTabs();
+
+ return (
+
+
+ {tabs.map((tab) => (
+
+
+
+ ))}
+
+
+ );
+}
+
+function TabItem(
+ { tab, handleClose, handleSelect }: { tab: Tab; handleClose: (tab: Tab) => void; handleSelect: (tab: Tab) => void },
+) {
+ if (tab.type === "sessions") {
+ return ;
+ }
+
+ if (tab.type === "events") {
+ return ;
+ }
+
+ if (tab.type === "calendars") {
+ return ;
+ }
+ if (tab.type === "folders") {
+ return ;
+ }
+
+ if (tab.type === "humans") {
+ return ;
+ }
+
+ return null;
+}
+
+function TabContent({ tab }: { tab: Tab }) {
+ if (tab.type === "sessions") {
+ return ;
+ }
+
+ if (tab.type === "events") {
+ return ;
+ }
+
+ if (tab.type === "calendars") {
+ return ;
+ }
+
+ if (tab.type === "folders") {
+ return ;
+ }
+
+ if (tab.type === "humans") {
+ return ;
+ }
+
+ return null;
+}
diff --git a/apps/desktop2/src/components/main/body/sessions.tsx b/apps/desktop2/src/components/main/body/sessions.tsx
new file mode 100644
index 0000000000..12cbf86ddd
--- /dev/null
+++ b/apps/desktop2/src/components/main/body/sessions.tsx
@@ -0,0 +1,143 @@
+import { StickyNoteIcon } from "lucide-react";
+import { useCallback } from "react";
+
+import NoteEditor from "@hypr/tiptap/editor";
+import { TabHeader } from "@hypr/ui/components/block/tab-header";
+import TitleInput from "@hypr/ui/components/block/title-input";
+import * as persisted from "../../../store/tinybase/persisted";
+import { rowIdfromTab, type Tab, useTabs } from "../../../store/zustand/tabs";
+import { type TabItem, TabItemBase } from "./shared";
+
+export const TabItemNote: TabItem = ({ tab, handleClose, handleSelect }) => {
+ const title = persisted.UI.useCell("sessions", rowIdfromTab(tab), "title", persisted.STORE_ID);
+
+ return (
+ }
+ title={title ?? ""}
+ active={tab.active}
+ handleClose={() => handleClose(tab)}
+ handleSelect={() => handleSelect(tab)}
+ />
+ );
+};
+
+export function TabContentNote({ tab }: { tab: Tab }) {
+ const sessionId = rowIdfromTab(tab);
+ const sessionRow = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID);
+
+ const handleEditTitle = persisted.UI.useSetRowCallback(
+ "sessions",
+ sessionId,
+ (input: string, _store) => ({ ...sessionRow, title: input }),
+ [sessionRow],
+ persisted.STORE_ID,
+ );
+
+ const handleEditRawMd = persisted.UI.useSetRowCallback(
+ "sessions",
+ sessionId,
+ (input: string, _store) => ({ ...sessionRow, raw_md: input }),
+ [sessionRow],
+ persisted.STORE_ID,
+ );
+
+ return (
+
+
+
+ handleEditTitle(e.target.value)}
+ />
+ {}}
+ currentTab="raw"
+ onTabChange={() => {}}
+ isCurrentlyRecording={false}
+ shouldShowTab={true}
+ shouldShowEnhancedTab={false}
+ />
+ handleEditRawMd(e)}
+ mentionConfig={{
+ trigger: "@",
+ handleSearch: async () => {
+ return [];
+ },
+ }}
+ />
+
+ );
+}
+
+function TabContentNoteHeader({ sessionRow }: { sessionRow: ReturnType> }) {
+ return (
+
+
+ {sessionRow.folder_id && (
+
+ )}
+
+
+ {sessionRow.event_id &&
}
+
+ );
+}
+
+function TabContentNoteHeaderFolderChain({ title, folderId }: { title: string; folderId: string }) {
+ const folderIds = persisted.UI.useLinkedRowIds(
+ "folderToParentFolder",
+ folderId,
+ persisted.STORE_ID,
+ );
+
+ if (!folderIds || folderIds.length === 0) {
+ return null;
+ }
+
+ const folderChain = [...folderIds].reverse();
+
+ return (
+
+ {folderChain.map((id, index) => (
+
+ {index > 0 && /}
+
+
+ ))}
+
+ /
+ {title}
+
+
+ );
+}
+
+function TabContentNoteHeaderFolder({ folderId }: { folderId: string }) {
+ const folderName = persisted.UI.useCell("folders", folderId, "name", persisted.STORE_ID);
+ const { openNew } = useTabs();
+ const handleClick = useCallback(() => {
+ openNew({ type: "folders", id: folderId, active: true });
+ }, [openNew, folderId]);
+
+ return (
+
+ );
+}
+
+function TabContentNoteHeaderEvent({ eventId }: { eventId: string }) {
+ const eventRow = persisted.UI.useRow("events", eventId, persisted.STORE_ID);
+ return {eventRow.title}
;
+}
diff --git a/apps/desktop2/src/components/main/body/shared.tsx b/apps/desktop2/src/components/main/body/shared.tsx
new file mode 100644
index 0000000000..ffda0bf59b
--- /dev/null
+++ b/apps/desktop2/src/components/main/body/shared.tsx
@@ -0,0 +1,52 @@
+import { clsx } from "clsx";
+
+import { type Tab } from "../../../store/zustand/tabs";
+
+export type TabItem = (props: {
+ tab: Tab;
+ handleClose: (tab: Tab) => void;
+ handleSelect: (tab: Tab) => void;
+}) => React.ReactNode;
+
+export function TabItemBase(
+ { icon, title, active, handleClose, handleSelect }: {
+ icon: React.ReactNode;
+ title: string;
+ active: boolean;
+ handleClose: () => void;
+ handleSelect: () => void;
+ },
+) {
+ return (
+
+
+
+
+ );
+}
diff --git a/apps/desktop2/src/components/main/left-sidebar.tsx b/apps/desktop2/src/components/main/left-sidebar.tsx
deleted file mode 100644
index 42fb0446d9..0000000000
--- a/apps/desktop2/src/components/main/left-sidebar.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { clsx } from "clsx";
-import { PanelLeftCloseIcon } from "lucide-react";
-import { useCell, useRowIds } from "tinybase/ui-react";
-
-import * as persisted from "../../store/tinybase/persisted";
-
-import { ContextMenuItem } from "@hypr/ui/components/ui/context-menu";
-import { useLeftSidebar } from "@hypr/utils/contexts";
-import { useTabs } from "../../store/zustand/tabs";
-import { type Tab } from "../../store/zustand/tabs";
-import { InteractiveButton } from "../interactive-button";
-
-export function LeftSidebar() {
- const { togglePanel: toggleLeftPanel } = useLeftSidebar();
-
- return (
-
- );
-}
-
-function TimelineView() {
- const allSessionIds = useRowIds("sessions", persisted.STORE_ID);
- const { currentTab } = useTabs();
-
- return (
-
- {allSessionIds?.map((sessionId) => (
-
- ))}
-
- );
-}
-
-function SessionItem({ sessionId, active }: { sessionId: string; active?: boolean }) {
- const title = useCell("sessions", sessionId, "title", persisted.STORE_ID);
- const tab: Tab = { id: sessionId, type: "sessions", active: false };
-
- const { openCurrent, openNew } = useTabs();
-
- const contextMenu = (
- <>
- console.log("Delete session:", sessionId)}>
- Delete
-
- >
- );
-
- return (
- openCurrent(tab)}
- onCmdClick={() => openNew(tab)}
- contextMenu={contextMenu}
- className={clsx([
- "w-full text-left px-2 py-1 hover:bg-blue-50 border-b border-gray-100",
- active && "bg-blue-50",
- ])}
- >
- {title}
-
- );
-}
diff --git a/apps/desktop2/src/components/main/main-area.tsx b/apps/desktop2/src/components/main/main-area.tsx
deleted file mode 100644
index 05f59b6bec..0000000000
--- a/apps/desktop2/src/components/main/main-area.tsx
+++ /dev/null
@@ -1,712 +0,0 @@
-import { useRouteContext } from "@tanstack/react-router";
-import { clsx } from "clsx";
-import { addMonths, eachDayOfInterval, endOfMonth, format, getDay, isSameMonth, startOfMonth } from "date-fns";
-import {
- CalendarIcon,
- CogIcon,
- FileTextIcon,
- FolderIcon,
- PanelLeftOpenIcon,
- PencilIcon,
- StickyNoteIcon,
-} from "lucide-react";
-import { Reorder } from "motion/react";
-import { useCallback } from "react";
-
-import { commands as windowsCommands } from "@hypr/plugin-windows";
-import NoteEditor from "@hypr/tiptap/editor";
-import { CalendarStructure } from "@hypr/ui/components/block/calendar-structure";
-import { TabHeader } from "@hypr/ui/components/block/tab-header";
-import TitleInput from "@hypr/ui/components/block/title-input";
-import { useLeftSidebar } from "@hypr/utils/contexts";
-import * as persisted from "../../store/tinybase/persisted";
-import { useTabs } from "../../store/zustand/tabs";
-import { rowIdfromTab, type Tab, uniqueIdfromTab } from "../../store/zustand/tabs";
-
-export function MainContent() {
- const { tabs, currentTab } = useTabs();
-
- if (!currentTab) {
- return null;
- }
-
- return (
-
-
-
-
- );
-}
-
-export function MainHeader() {
- const { persistedStore, internalStore } = useRouteContext({ from: "__root__" });
- const { isExpanded: isLeftPanelExpanded, togglePanel: toggleLeftPanel } = useLeftSidebar();
- const { openNew } = useTabs();
-
- const handleClickSettings = useCallback(() => {
- windowsCommands.windowShow({ type: "settings" });
- }, []);
-
- const handleClickNewNote = useCallback(() => {
- if (!persistedStore || !internalStore) {
- return;
- }
-
- const sessionId = crypto.randomUUID();
- const user_id = internalStore.getValue("user_id");
-
- persistedStore.setRow("sessions", sessionId, {
- title: "new",
- user_id,
- created_at: new Date().toISOString(),
- });
- openNew({ id: sessionId, type: "sessions", active: true });
- }, [persistedStore, internalStore, openNew]);
-
- return (
-
- {!isLeftPanelExpanded
- && (
-
- )}
-
-
-
-
-
openNew({ type: "folders", id: null, active: true })}
- className="cursor-pointer h-5 w-5 text-muted-foreground hover:text-foreground"
- />
-
- openNew({ type: "calendars", month: new Date(), active: true })}
- className="cursor-pointer h-5 w-5 text-muted-foreground hover:text-foreground"
- />
-
-
- );
-}
-
-function TabsHeader({ tabs }: { tabs: Tab[] }) {
- const { select, close, reorder } = useTabs();
-
- return (
-
-
- {tabs.map((tab) => (
-
-
-
- ))}
-
-
- );
-}
-
-function TabItem(
- { tab, handleClose, handleSelect }: { tab: Tab; handleClose: (tab: Tab) => void; handleSelect: (tab: Tab) => void },
-) {
- if (tab.type === "sessions") {
- return ;
- }
-
- if (tab.type === "events") {
- return ;
- }
-
- if (tab.type === "calendars") {
- return ;
- }
- if (tab.type === "folders") {
- return ;
- }
-
- return null;
-}
-
-const TabItemNote: TabItem = ({ tab, handleClose, handleSelect }) => {
- const title = persisted.UI.useCell("sessions", rowIdfromTab(tab), "title", persisted.STORE_ID);
-
- return (
- }
- title={title ?? ""}
- active={tab.active}
- handleClose={() => handleClose(tab)}
- handleSelect={() => handleSelect(tab)}
- />
- );
-};
-
-const TabItemEvent: TabItem = ({ tab, handleClose, handleSelect }) => {
- const title = persisted.UI.useCell("events", rowIdfromTab(tab), "title", persisted.STORE_ID);
-
- return (
- }
- title={title ?? ""}
- active={tab.active}
- handleClose={() => handleClose(tab)}
- handleSelect={() => handleSelect(tab)}
- />
- );
-};
-
-const TabItemCalendar: TabItem = ({ tab, handleClose, handleSelect }) => {
- return (
- }
- title={"Calendar"}
- active={tab.active}
- handleClose={() => handleClose(tab)}
- handleSelect={() => handleSelect(tab)}
- />
- );
-};
-
-const TabItemFolder: TabItem = ({ tab, handleClose, handleSelect }) => {
- if (tab.type === "folders" && tab.id === null) {
- return ;
- }
-
- if (tab.type === "folders" && tab.id !== null) {
- return ;
- }
-
- return null;
-};
-
-const TabItemFolderAll: TabItem = ({ tab, handleClose, handleSelect }) => {
- return (
- }
- title={"Folder"}
- active={tab.active}
- handleClose={() => handleClose(tab)}
- handleSelect={() => handleSelect(tab)}
- />
- );
-};
-
-const TabItemFolderSpecific: TabItem = ({ tab, handleClose, handleSelect }) => {
- if (tab.type !== "folders" || tab.id === null) {
- return null;
- }
-
- const folderName = persisted.UI.useCell("folders", tab.id, "name", persisted.STORE_ID);
-
- return (
- }
- title={folderName ?? ""}
- active={tab.active}
- handleClose={() => handleClose(tab)}
- handleSelect={() => handleSelect(tab)}
- />
- );
-};
-
-type TabItem = (props: {
- tab: Tab;
- handleClose: (tab: Tab) => void;
- handleSelect: (tab: Tab) => void;
-}) => React.ReactNode;
-
-function TabItemBase(
- { icon, title, active, handleClose, handleSelect }: {
- icon: React.ReactNode;
- title: string;
- active: boolean;
- handleClose: () => void;
- handleSelect: () => void;
- },
-) {
- return (
-
-
-
-
- );
-}
-
-function TabContent({ tab }: { tab: Tab }) {
- if (tab.type === "sessions") {
- return ;
- }
-
- if (tab.type === "events") {
- return ;
- }
-
- if (tab.type === "calendars") {
- return ;
- }
-
- if (tab.type === "folders") {
- return ;
- }
-
- return null;
-}
-
-function TabContentNote({ tab }: { tab: Tab }) {
- const sessionId = rowIdfromTab(tab);
- const sessionRow = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID);
-
- const handleEditTitle = persisted.UI.useSetRowCallback(
- "sessions",
- sessionId,
- (input: string, _store) => ({ ...sessionRow, title: input }),
- [sessionRow],
- persisted.STORE_ID,
- );
-
- const handleEditRawMd = persisted.UI.useSetRowCallback(
- "sessions",
- sessionId,
- (input: string, _store) => ({ ...sessionRow, raw_md: input }),
- [sessionRow],
- persisted.STORE_ID,
- );
-
- return (
-
-
- handleEditTitle(e.target.value)}
- />
- {}}
- currentTab="raw"
- onTabChange={() => {}}
- isCurrentlyRecording={false}
- shouldShowTab={true}
- shouldShowEnhancedTab={false}
- />
- handleEditRawMd(e)}
- mentionConfig={{
- trigger: "@",
- handleSearch: async () => {
- return [];
- },
- }}
- />
-
- );
-}
-
-function TabContentNoteHeader({ sessionRow }: { sessionRow: ReturnType> }) {
- return (
-
-
- {sessionRow.folder_id && (
-
- )}
-
-
- {sessionRow.event_id &&
}
-
- );
-}
-
-function TabContentNoteHeaderFolderChain({ title, folderId }: { title: string; folderId: string }) {
- const folderIds = persisted.UI.useLinkedRowIds(
- "folderToParentFolder",
- folderId,
- persisted.STORE_ID,
- );
-
- if (!folderIds || folderIds.length === 0) {
- return null;
- }
-
- const folderChain = [...folderIds].reverse();
-
- return (
-
- {folderChain.map((id, index) => (
-
- {index > 0 && /}
-
-
- ))}
-
- /
- {title}
-
-
- );
-}
-
-function TabContentNoteHeaderFolder({ folderId }: { folderId: string }) {
- const folderName = persisted.UI.useCell("folders", folderId, "name", persisted.STORE_ID);
- return {folderName};
-}
-
-function TabContentNoteHeaderEvent({ eventId }: { eventId: string }) {
- const eventRow = persisted.UI.useRow("events", eventId, persisted.STORE_ID);
- return {eventRow.title}
;
-}
-
-function TabContentEvent({ tab }: { tab: Tab }) {
- const id = rowIdfromTab(tab);
- const event = persisted.UI.useRow("events", id, persisted.STORE_ID);
-
- return {JSON.stringify(event, null, 2)};
-}
-
-function TabContentCalendar({ tab }: { tab: Tab }) {
- if (tab.type !== "calendars") {
- return null;
- }
-
- const { openCurrent } = useTabs();
- const monthStart = startOfMonth(tab.month);
- const monthEnd = endOfMonth(tab.month);
- const days = eachDayOfInterval({ start: monthStart, end: monthEnd }).map((day) => format(day, "yyyy-MM-dd"));
- const startDayOfWeek = getDay(monthStart);
- const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
-
- const handlePreviousMonth = () => {
- openCurrent({ ...tab, month: addMonths(tab.month, -1) });
- };
-
- const handleNextMonth = () => {
- openCurrent({ ...tab, month: addMonths(tab.month, 1) });
- };
-
- const handleToday = () => {
- openCurrent({ ...tab, month: new Date() });
- };
-
- return (
-
- {days.map((day) => (
-
- ))}
-
- );
-}
-
-function TabContentCalendarDay({ day, isCurrentMonth }: { day: string; isCurrentMonth: boolean }) {
- const eventIds = persisted.UI.useSliceRowIds(
- persisted.INDEXES.eventsByDate,
- day,
- persisted.STORE_ID,
- );
-
- const sessionIds = persisted.UI.useSliceRowIds(
- persisted.INDEXES.sessionByDateWithoutEvent,
- day,
- persisted.STORE_ID,
- );
-
- const dayNumber = format(new Date(day), "d");
- const isToday = format(new Date(), "yyyy-MM-dd") === day;
- const dayOfWeek = getDay(new Date(day));
- const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
-
- return (
-
-
-
-
-
- {eventIds.map((eventId) => )}
- {sessionIds.map((sessionId) => )}
-
-
-
- );
-}
-
-function TabContentCalendarDayEvents({ eventId }: { eventId: string }) {
- const event = persisted.UI.useRow("events", eventId, persisted.STORE_ID);
-
- return (
-
- );
-}
-
-function TabContentCalendarDaySessions({ sessionId }: { sessionId: string }) {
- const session = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID);
- return (
-
-
-
- {session.title}
-
-
- );
-}
-
-function TabContentFolder({ tab }: { tab: Tab }) {
- if (tab.type !== "folders") {
- return null;
- }
-
- // If tab.id is null, show top-level folders
- if (tab.id === null) {
- return ;
- }
-
- // If tab.id is a folder, show that folder's contents
- return ;
-}
-
-function TabContentFolderTopLevel() {
- const topLevelFolderIds = persisted.UI.useSliceRowIds(
- persisted.INDEXES.foldersByParent,
- "",
- persisted.STORE_ID,
- );
-
- return (
-
-
All Folders
-
- {topLevelFolderIds?.map((folderId) => )}
-
-
- );
-}
-
-function FolderCard({ folderId }: { folderId: string }) {
- const folder = persisted.UI.useRow("folders", folderId, persisted.STORE_ID);
- const { openCurrent } = useTabs();
-
- // Count children
- const childFolderIds = persisted.UI.useSliceRowIds(
- persisted.INDEXES.foldersByParent,
- folderId,
- persisted.STORE_ID,
- );
-
- const sessionIds = persisted.UI.useSliceRowIds(
- persisted.INDEXES.sessionsByFolder,
- folderId,
- persisted.STORE_ID,
- );
-
- const childCount = (childFolderIds?.length ?? 0) + (sessionIds?.length ?? 0);
-
- return (
- openCurrent({ type: "folders", id: folderId, active: true })}
- >
-
- {folder.name}
- {childCount > 0 && {childCount} items}
-
- );
-}
-
-function TabContentFolderSpecific({ folderId }: { folderId: string }) {
- const childFolderIds = persisted.UI.useSliceRowIds(
- persisted.INDEXES.foldersByParent,
- folderId,
- persisted.STORE_ID,
- );
-
- const sessionIds = persisted.UI.useSliceRowIds(
- persisted.INDEXES.sessionsByFolder,
- folderId,
- persisted.STORE_ID,
- );
-
- return (
-
-
-
- {(childFolderIds?.length ?? 0) > 0 && (
-
-
Folders
-
- {childFolderIds!.map((childId) => )}
-
-
- )}
-
- {(sessionIds?.length ?? 0) > 0 && (
-
-
Notes
-
- {sessionIds!.map((sessionId) => )}
-
-
- )}
-
- {(childFolderIds?.length ?? 0) === 0 && (sessionIds?.length ?? 0) === 0 && (
-
- This folder is empty
-
- )}
-
- );
-}
-
-function TabContentFolderBreadcrumb({ folderId }: { folderId: string }) {
- const { openCurrent } = useTabs();
-
- const folderIds = persisted.UI.useLinkedRowIds(
- "folderToParentFolder",
- folderId,
- persisted.STORE_ID,
- );
-
- if (!folderIds || folderIds.length === 0) {
- return null;
- }
-
- const folderChain = [...folderIds].reverse();
-
- return (
-
-
- {folderChain.map((id) => {
- const isLast = id === folderId;
- return (
-
- /
-
-
- );
- })}
-
- );
-}
-
-function TabContentFolderBreadcrumbItem({ folderId }: { folderId: string }) {
- const folderName = persisted.UI.useCell("folders", folderId, "name", persisted.STORE_ID);
- return {folderName};
-}
-
-function FolderSessionItem({ sessionId }: { sessionId: string }) {
- const session = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID);
- const { openCurrent } = useTabs();
-
- return (
- openCurrent({ type: "sessions", id: sessionId, active: true })}
- >
-
- {session.title}
-
- );
-}
diff --git a/apps/desktop2/src/components/main/sidebar/index.tsx b/apps/desktop2/src/components/main/sidebar/index.tsx
new file mode 100644
index 0000000000..9679031f7e
--- /dev/null
+++ b/apps/desktop2/src/components/main/sidebar/index.tsx
@@ -0,0 +1,32 @@
+import { clsx } from "clsx";
+import { PanelLeftCloseIcon } from "lucide-react";
+
+import { useLeftSidebar } from "@hypr/utils/contexts";
+import { ProfileSection } from "./profile";
+import { TimelineView } from "./timeline";
+
+export function LeftSidebar() {
+ const { togglePanel: toggleLeftPanel } = useLeftSidebar();
+
+ return (
+
+ );
+}
diff --git a/apps/desktop2/src/components/main/sidebar/profile.tsx b/apps/desktop2/src/components/main/sidebar/profile.tsx
new file mode 100644
index 0000000000..28bd8f222b
--- /dev/null
+++ b/apps/desktop2/src/components/main/sidebar/profile.tsx
@@ -0,0 +1,97 @@
+import { clsx } from "clsx";
+import { Bell, Calendar, ChevronUpIcon, FolderOpen, RefreshCw, Settings, Users } from "lucide-react";
+import { useCallback, useState } from "react";
+
+import { commands as windowsCommands } from "@hypr/plugin-windows";
+import { useTabs } from "../../../store/zustand/tabs";
+
+export function ProfileSection() {
+ const [isExpanded, setIsExpanded] = useState(false);
+ const { openNew } = useTabs();
+
+ const handleClickSettings = useCallback(() => {
+ windowsCommands.windowShow({ type: "settings" });
+ }, []);
+
+ const handleClickFolders = useCallback(() => {
+ openNew({ type: "folders", id: null, active: true });
+ }, [openNew]);
+
+ const handleClickCalendar = useCallback(() => {
+ openNew({ type: "calendars", month: new Date(), active: true });
+ }, [openNew]);
+
+ const handleClickNotifications = useCallback(() => {
+ console.log("Notifications");
+ }, []);
+
+ const handleClickCheckUpdates = useCallback(() => {
+ console.log("Check for updates");
+ }, []);
+
+ const handleClickContacts = useCallback(() => {
+ console.log("Contacts");
+ }, []);
+
+ const menuItems = [
+ { icon: Bell, label: "Notifications", badge: 10, onClick: handleClickNotifications },
+ { icon: RefreshCw, label: "Check for updates", onClick: handleClickCheckUpdates },
+ { icon: FolderOpen, label: "Folders", onClick: handleClickFolders },
+ { icon: Users, label: "Contacts", onClick: handleClickContacts },
+ { icon: Calendar, label: "Calendar", onClick: handleClickCalendar },
+ { icon: Settings, label: "Settings", onClick: handleClickSettings },
+ ];
+
+ return (
+
+
+
+ {menuItems.map((item) => (
+
+ ))}
+
+
+
+
+
setIsExpanded(!isExpanded)}
+ >
+
+

+
+
+
+
+
+
+ );
+}
diff --git a/apps/desktop2/src/components/main/sidebar/timeline.tsx b/apps/desktop2/src/components/main/sidebar/timeline.tsx
new file mode 100644
index 0000000000..6d51f153d6
--- /dev/null
+++ b/apps/desktop2/src/components/main/sidebar/timeline.tsx
@@ -0,0 +1,52 @@
+import { clsx } from "clsx";
+
+import { ContextMenuItem } from "@hypr/ui/components/ui/context-menu";
+import * as persisted from "../../../store/tinybase/persisted";
+import { Tab, useTabs } from "../../../store/zustand/tabs";
+import { InteractiveButton } from "../../interactive-button";
+
+export function TimelineView() {
+ const allSessionIds = persisted.UI.useRowIds("sessions", persisted.STORE_ID);
+ const { currentTab } = useTabs();
+
+ return (
+
+ {allSessionIds?.map((sessionId) => (
+
+ ))}
+
+ );
+}
+
+function SessionItem({ sessionId, active }: { sessionId: string; active?: boolean }) {
+ const title = persisted.UI.useCell("sessions", sessionId, "title", persisted.STORE_ID);
+ const tab: Tab = { id: sessionId, type: "sessions", active: false };
+
+ const { openCurrent, openNew } = useTabs();
+
+ const contextMenu = (
+ <>
+ console.log("Delete session:", sessionId)}>
+ Delete
+
+ >
+ );
+
+ return (
+ openCurrent(tab)}
+ onCmdClick={() => openNew(tab)}
+ contextMenu={contextMenu}
+ className={clsx([
+ "w-full text-left px-2 py-1 hover:bg-blue-50 border-b border-gray-100",
+ active && "bg-blue-50",
+ ])}
+ >
+ {title}
+
+ );
+}
diff --git a/apps/desktop2/src/routes/__root.tsx b/apps/desktop2/src/routes/__root.tsx
index 99832cfc25..0af1c9199b 100644
--- a/apps/desktop2/src/routes/__root.tsx
+++ b/apps/desktop2/src/routes/__root.tsx
@@ -15,7 +15,7 @@ function Component() {
-
+
diff --git a/apps/desktop2/src/routes/app/main/_layout.index.tsx b/apps/desktop2/src/routes/app/main/_layout.index.tsx
index cb026cfb3c..42793059ed 100644
--- a/apps/desktop2/src/routes/app/main/_layout.index.tsx
+++ b/apps/desktop2/src/routes/app/main/_layout.index.tsx
@@ -3,8 +3,8 @@ import { createFileRoute } from "@tanstack/react-router";
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@hypr/ui/components/ui/resizable";
import { useLeftSidebar } from "@hypr/utils/contexts";
import { FloatingChatButton } from "../../../components/floating-chat-button";
-import { LeftSidebar } from "../../../components/main/left-sidebar";
-import { MainContent, MainHeader } from "../../../components/main/main-area";
+import { MainContent } from "../../../components/main/body";
+import { LeftSidebar } from "../../../components/main/sidebar";
export const Route = createFileRoute("/app/main/_layout/")({
component: Component,
@@ -24,10 +24,7 @@ function Component() {
>
)}
-
-
-
-
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 01f3ca9dbd..8a376e3e36 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9827,8 +9827,8 @@ packages:
prosemirror-transform@1.10.4:
resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==}
- prosemirror-view@1.41.2:
- resolution: {integrity: sha512-PGS/jETmh+Qjmre/6vcG7SNHAKiGc4vKOJmHMPRmvcUl7ISuVtrtHmH06UDUwaim4NDJfZfVMl7U7JkMMETa6g==}
+ prosemirror-view@1.41.3:
+ resolution: {integrity: sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==}
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
@@ -16674,9 +16674,9 @@ snapshots:
prosemirror-schema-list: 1.5.1
prosemirror-state: 1.4.3
prosemirror-tables: 1.8.1
- prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.41.2)
+ prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.41.3)
prosemirror-transform: 1.10.4
- prosemirror-view: 1.41.2
+ prosemirror-view: 1.41.3
'@tiptap/react@3.6.5(@floating-ui/dom@1.7.4)(@tiptap/core@3.6.5(@tiptap/pm@3.6.5))(@tiptap/pm@3.6.5)(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
@@ -22604,20 +22604,20 @@ snapshots:
dependencies:
prosemirror-state: 1.4.3
prosemirror-transform: 1.10.4
- prosemirror-view: 1.41.2
+ prosemirror-view: 1.41.3
prosemirror-gapcursor@1.3.2:
dependencies:
prosemirror-keymap: 1.2.3
prosemirror-model: 1.25.3
prosemirror-state: 1.4.3
- prosemirror-view: 1.41.2
+ prosemirror-view: 1.41.3
prosemirror-history@1.4.1:
dependencies:
prosemirror-state: 1.4.3
prosemirror-transform: 1.10.4
- prosemirror-view: 1.41.2
+ prosemirror-view: 1.41.3
rope-sequence: 1.3.4
prosemirror-inputrules@1.5.0:
@@ -22661,7 +22661,7 @@ snapshots:
dependencies:
prosemirror-model: 1.25.3
prosemirror-transform: 1.10.4
- prosemirror-view: 1.41.2
+ prosemirror-view: 1.41.3
prosemirror-tables@1.8.1:
dependencies:
@@ -22669,21 +22669,21 @@ snapshots:
prosemirror-model: 1.25.3
prosemirror-state: 1.4.3
prosemirror-transform: 1.10.4
- prosemirror-view: 1.41.2
+ prosemirror-view: 1.41.3
- prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.41.2):
+ prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.41.3):
dependencies:
'@remirror/core-constants': 3.0.0
escape-string-regexp: 4.0.0
prosemirror-model: 1.25.3
prosemirror-state: 1.4.3
- prosemirror-view: 1.41.2
+ prosemirror-view: 1.41.3
prosemirror-transform@1.10.4:
dependencies:
prosemirror-model: 1.25.3
- prosemirror-view@1.41.2:
+ prosemirror-view@1.41.3:
dependencies:
prosemirror-model: 1.25.3
prosemirror-state: 1.4.3