Skip to content

Commit 7314dc7

Browse files
Open notes in standalone windows
Adds a note-specific native window route and wires sidebar double-click to open the existing note view in that window. Includes regression coverage for the sidebar double-click path.
1 parent 2b9d773 commit 7314dc7

10 files changed

Lines changed: 307 additions & 25 deletions

File tree

apps/desktop/src/routeTree.gen.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Route as AppIndexRouteImport } from './routes/app/index'
1414
import { Route as AppOnboardingRouteImport } from './routes/app/onboarding'
1515
import { Route as AppInstructionRouteImport } from './routes/app/instruction'
1616
import { Route as AppComposerRouteImport } from './routes/app/composer'
17+
import { Route as AppNoteSessionIdRouteImport } from './routes/app/note.$sessionId'
1718
import { Route as AppMainLayoutRouteImport } from './routes/app/main/_layout'
1819
import { Route as AppMainLayoutIndexRouteImport } from './routes/app/main/_layout.index'
1920

@@ -42,6 +43,11 @@ const AppComposerRoute = AppComposerRouteImport.update({
4243
path: '/composer',
4344
getParentRoute: () => AppRouteRoute,
4445
} as any)
46+
const AppNoteSessionIdRoute = AppNoteSessionIdRouteImport.update({
47+
id: '/note/$sessionId',
48+
path: '/note/$sessionId',
49+
getParentRoute: () => AppRouteRoute,
50+
} as any)
4551
const AppMainLayoutRoute = AppMainLayoutRouteImport.update({
4652
id: '/main/_layout',
4753
path: '/main',
@@ -60,13 +66,15 @@ export interface FileRoutesByFullPath {
6066
'/app/onboarding': typeof AppOnboardingRoute
6167
'/app/': typeof AppIndexRoute
6268
'/app/main': typeof AppMainLayoutRouteWithChildren
69+
'/app/note/$sessionId': typeof AppNoteSessionIdRoute
6370
'/app/main/': typeof AppMainLayoutIndexRoute
6471
}
6572
export interface FileRoutesByTo {
6673
'/app/composer': typeof AppComposerRoute
6774
'/app/instruction': typeof AppInstructionRoute
6875
'/app/onboarding': typeof AppOnboardingRoute
6976
'/app': typeof AppIndexRoute
77+
'/app/note/$sessionId': typeof AppNoteSessionIdRoute
7078
'/app/main': typeof AppMainLayoutIndexRoute
7179
}
7280
export interface FileRoutesById {
@@ -77,6 +85,7 @@ export interface FileRoutesById {
7785
'/app/onboarding': typeof AppOnboardingRoute
7886
'/app/': typeof AppIndexRoute
7987
'/app/main/_layout': typeof AppMainLayoutRouteWithChildren
88+
'/app/note/$sessionId': typeof AppNoteSessionIdRoute
8089
'/app/main/_layout/': typeof AppMainLayoutIndexRoute
8190
}
8291
export interface FileRouteTypes {
@@ -88,13 +97,15 @@ export interface FileRouteTypes {
8897
| '/app/onboarding'
8998
| '/app/'
9099
| '/app/main'
100+
| '/app/note/$sessionId'
91101
| '/app/main/'
92102
fileRoutesByTo: FileRoutesByTo
93103
to:
94104
| '/app/composer'
95105
| '/app/instruction'
96106
| '/app/onboarding'
97107
| '/app'
108+
| '/app/note/$sessionId'
98109
| '/app/main'
99110
id:
100111
| '__root__'
@@ -104,6 +115,7 @@ export interface FileRouteTypes {
104115
| '/app/onboarding'
105116
| '/app/'
106117
| '/app/main/_layout'
118+
| '/app/note/$sessionId'
107119
| '/app/main/_layout/'
108120
fileRoutesById: FileRoutesById
109121
}
@@ -148,6 +160,13 @@ declare module '@tanstack/react-router' {
148160
preLoaderRoute: typeof AppComposerRouteImport
149161
parentRoute: typeof AppRouteRoute
150162
}
163+
'/app/note/$sessionId': {
164+
id: '/app/note/$sessionId'
165+
path: '/note/$sessionId'
166+
fullPath: '/app/note/$sessionId'
167+
preLoaderRoute: typeof AppNoteSessionIdRouteImport
168+
parentRoute: typeof AppRouteRoute
169+
}
151170
'/app/main/_layout': {
152171
id: '/app/main/_layout'
153172
path: '/main'
@@ -183,6 +202,7 @@ interface AppRouteRouteChildren {
183202
AppOnboardingRoute: typeof AppOnboardingRoute
184203
AppIndexRoute: typeof AppIndexRoute
185204
AppMainLayoutRoute: typeof AppMainLayoutRouteWithChildren
205+
AppNoteSessionIdRoute: typeof AppNoteSessionIdRoute
186206
}
187207

188208
const AppRouteRouteChildren: AppRouteRouteChildren = {
@@ -191,6 +211,7 @@ const AppRouteRouteChildren: AppRouteRouteChildren = {
191211
AppOnboardingRoute: AppOnboardingRoute,
192212
AppIndexRoute: AppIndexRoute,
193213
AppMainLayoutRoute: AppMainLayoutRouteWithChildren,
214+
AppNoteSessionIdRoute: AppNoteSessionIdRoute,
194215
}
195216

196217
const AppRouteRouteWithChildren = AppRouteRoute._addFileChildren(
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { createFileRoute } from "@tanstack/react-router";
2+
import { useMemo } from "react";
3+
4+
import { ClassicMainLayout } from "~/main/layout";
5+
import { TabContentNote } from "~/session";
6+
import { StandaloneWindowShell } from "~/shared/window-shell";
7+
import type { Tab } from "~/store/zustand/tabs";
8+
9+
export const Route = createFileRoute("/app/note/$sessionId")({
10+
component: Component,
11+
});
12+
13+
function Component() {
14+
const { sessionId } = Route.useParams();
15+
const tab = useMemo(
16+
() =>
17+
({
18+
active: true,
19+
id: sessionId,
20+
pinned: false,
21+
slotId: `note-window-${sessionId}`,
22+
state: { view: null, autoStart: null },
23+
type: "sessions",
24+
}) satisfies Extract<Tab, { type: "sessions" }>,
25+
[sessionId],
26+
);
27+
28+
return (
29+
<ClassicMainLayout includeServices={false}>
30+
<StandaloneWindowShell>
31+
<div className="bg-background h-screen w-screen">
32+
<TabContentNote tab={tab} standaloneWindow />
33+
</div>
34+
</StandaloneWindowShell>
35+
</ClassicMainLayout>
36+
);
37+
}

apps/desktop/src/session/components/outer-header/index.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import { useListener } from "~/stt/contexts";
2020
export function OuterHeader({
2121
sessionId,
2222
currentView,
23+
standaloneWindow = false,
2324
title,
2425
}: {
2526
sessionId: string;
2627
currentView: EditorView;
28+
standaloneWindow?: boolean;
2729
title?: React.ReactNode;
2830
}) {
2931
const { leftsidebar } = useShell();
@@ -48,11 +50,13 @@ export function OuterHeader({
4850
className={cn([
4951
"pointer-events-none absolute inset-y-0 flex items-center",
5052
reserveCollapsedLiveControls ? "right-[153px]" : "right-[70px]",
51-
showSidebarTimelineHeaderGutter
52-
? "left-[104px]"
53-
: showExpandedSidebarTimelineHeader
54-
? "left-0"
55-
: "left-[114px]",
53+
standaloneWindow
54+
? "left-[68px]"
55+
: showSidebarTimelineHeaderGutter
56+
? "left-[104px]"
57+
: showExpandedSidebarTimelineHeader
58+
? "left-0"
59+
: "left-[114px]",
5660
])}
5761
>
5862
<div

apps/desktop/src/session/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ import { useSTTConnection } from "~/stt/useSTTConnection";
2626
import { useUploadFile } from "~/stt/useUploadFile";
2727

2828
export function TabContentNote({
29+
standaloneWindow = false,
2930
tab,
3031
}: {
32+
standaloneWindow?: boolean;
3133
tab: Extract<Tab, { type: "sessions" }>;
3234
}) {
3335
const sessionMode = useListener((state) => state.getSessionMode(tab.id));
@@ -89,6 +91,7 @@ export function TabContentNote({
8991
<AudioPlayer.Provider sessionId={tab.id} url={audioUrl ?? ""}>
9092
<TabContentNoteInner
9193
tab={tab}
94+
standaloneWindow={standaloneWindow}
9295
audioUrlReady={Boolean(audioUrl)}
9396
isAudioUrlLoading={audioUrlQuery.isPending}
9497
/>
@@ -100,10 +103,12 @@ export function TabContentNote({
100103

101104
function TabContentNoteInner({
102105
tab,
106+
standaloneWindow,
103107
audioUrlReady,
104108
isAudioUrlLoading,
105109
}: {
106110
tab: Extract<Tab, { type: "sessions" }>;
111+
standaloneWindow: boolean;
107112
audioUrlReady: boolean;
108113
isAudioUrlLoading: boolean;
109114
}) {
@@ -228,6 +233,7 @@ function TabContentNoteInner({
228233
<OuterHeader
229234
sessionId={tab.id}
230235
currentView={currentView}
236+
standaloneWindow={standaloneWindow}
231237
title={
232238
<TitleInput
233239
ref={titleInputRef}

apps/desktop/src/shared/ui/interactive-button.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
interface InteractiveButtonProps {
1414
children: ReactNode;
1515
onClick?: () => void;
16+
onDoubleClick?: () => void;
1617
onCmdClick?: () => void;
1718
onShiftClick?: () => void;
1819
onMouseDown?: (e: MouseEvent<HTMLElement>) => void;
@@ -27,6 +28,7 @@ interface InteractiveButtonProps {
2728
export function InteractiveButton({
2829
children,
2930
onClick,
31+
onDoubleClick,
3032
onCmdClick,
3133
onShiftClick,
3234
onMouseDown,
@@ -58,11 +60,24 @@ export function InteractiveButton({
5860
[onClick, onCmdClick, onShiftClick, disabled],
5961
);
6062

63+
const handleDoubleClick = useCallback(
64+
(e: MouseEvent<HTMLElement>) => {
65+
if (disabled) {
66+
return;
67+
}
68+
69+
e.preventDefault();
70+
onDoubleClick?.();
71+
},
72+
[onDoubleClick, disabled],
73+
);
74+
6175
const Element = asChild ? "div" : "button";
6276

6377
return (
6478
<Element
6579
onClick={handleClick}
80+
onDoubleClick={onDoubleClick ? handleDoubleClick : undefined}
6681
onDragStart={onDragStart}
6782
onMouseDown={onMouseDown}
6883
onContextMenu={contextMenu ? showMenu : undefined}

0 commit comments

Comments
 (0)