@saadnvd1/agent-os 0.3.799 → 0.3.807
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/api/chat-views/[sessionId]/route.ts +19 -0
- package/app/api/chat-views/route.test.ts +79 -0
- package/app/api/chat-views/route.ts +8 -0
- package/app/page.tsx +17 -14
- package/components/Chat/ChatPanel.tsx +46 -59
- package/components/Chat/NewDivider.tsx +15 -0
- package/components/Chat/session-link.ts +7 -4
- package/components/Chat/useFind.ts +50 -1
- package/components/Chat/usePosition.ts +363 -0
- package/components/CodeSearchDialog.tsx +90 -0
- package/components/CommandPalette/CommandsBody.tsx +12 -4
- package/components/CommandPalette/useAppCommands.ts +29 -11
- package/components/DevServers/ServerLogsModal.tsx +24 -21
- package/components/Nav/HistoryButtons.tsx +128 -0
- package/components/Pane/MobileTabBar.tsx +3 -0
- package/components/Pane/index.tsx +2 -2
- package/components/SessionList/useSessionSwitching.ts +3 -2
- package/components/Shortcuts/PendingKeys.tsx +19 -0
- package/components/Shortcuts/ShortcutsButton.tsx +22 -0
- package/components/Shortcuts/ShortcutsDialog.tsx +122 -0
- package/components/SidebarFooter.tsx +2 -0
- package/components/Terminal/hooks/useTerminalConnection.ts +4 -3
- package/components/views/DesktopView.tsx +7 -9
- package/components/views/MobileView.tsx +2 -8
- package/components/views/lazy.ts +2 -2
- package/content/tips.json +146 -61
- package/content/whats-new.json +32 -0
- package/data/chat-views/index.ts +2 -0
- package/data/chat-views/keys.ts +3 -0
- package/data/chat-views/queries.ts +34 -0
- package/data/sessions/pin.ts +0 -1
- package/hooks/useChatViewsSync.ts +12 -0
- package/hooks/useNavActions.ts +104 -0
- package/hooks/useNavKeys.ts +105 -0
- package/hooks/useSessionUrl.ts +23 -5
- package/lib/bus/index.ts +4 -2
- package/lib/bus/role-alias.ts +4 -2
- package/lib/bus/targets.ts +2 -1
- package/lib/chat/escape.ts +8 -4
- package/lib/chat-views/body.ts +41 -0
- package/lib/chat-views/store.ts +67 -0
- package/lib/chat-views/types.ts +20 -0
- package/lib/db/migrations/20261031T1500_chat_views.ts +30 -0
- package/lib/keybindings/focus.ts +17 -0
- package/lib/keybindings/keymap.ts +120 -0
- package/lib/keybindings/sequence.ts +64 -0
- package/lib/live/service.ts +1 -1
- package/lib/nav/history-mirror.ts +59 -0
- package/lib/nav/history-writer.ts +109 -0
- package/lib/nav/position.ts +67 -0
- package/lib/nav/recent.ts +34 -0
- package/lib/orchestrator/armed.ts +5 -0
- package/lib/palette/sections.ts +12 -6
- package/lib/session-url.ts +11 -5
- package/package.json +1 -1
- package/public/tips/chat-access-picker-dark.webp +0 -0
- package/public/tips/chat-access-picker.webp +0 -0
- package/public/tips/chat-attach-files-poster.webp +0 -0
- package/public/tips/chat-attach-files.mp4 +0 -0
- package/public/tips/chat-bookmark-dark.webp +0 -0
- package/public/tips/chat-bookmark.webp +0 -0
- package/public/tips/chat-plan-mode-dark.webp +0 -0
- package/public/tips/chat-plan-mode.webp +0 -0
- package/public/tips/chat-search-poster.webp +0 -0
- package/public/tips/chat-search.mp4 +0 -0
- package/public/tips/chat-tab-suggestion-poster.webp +0 -0
- package/public/tips/chat-tab-suggestion.mp4 +0 -0
- package/public/tips/chat-terminal-handoff-poster.webp +0 -0
- package/public/tips/chat-terminal-handoff.mp4 +0 -0
- package/public/tips/chat-undo-dark.webp +0 -0
- package/public/tips/chat-undo.webp +0 -0
- package/public/tips/export-ai-dark.webp +0 -0
- package/public/tips/export-ai.webp +0 -0
- package/public/tips/net-messages-dark.webp +0 -0
- package/public/tips/net-messages.webp +0 -0
- package/public/tips/recipe-save-dark.webp +0 -0
- package/public/tips/recipe-save.webp +0 -0
- package/public/tips/schedule-first-dark.webp +0 -0
- package/public/tips/schedule-first.webp +0 -0
- package/public/tips/share-check-dark.webp +0 -0
- package/public/tips/share-check.webp +0 -0
- package/public/tips/sidebar-draft-poster.webp +0 -0
- package/public/tips/sidebar-draft.mp4 +0 -0
- package/public/tips/sidebar-groups-dark.webp +0 -0
- package/public/tips/sidebar-groups.webp +0 -0
- package/public/tips/sidebar-load-dark.webp +0 -0
- package/public/tips/sidebar-load.webp +0 -0
- package/public/tips/sidebar-snooze-dark.webp +0 -0
- package/public/tips/sidebar-snooze.webp +0 -0
- package/public/tips/tasks-plan-pieces-dark.webp +0 -0
- package/public/tips/tasks-plan-pieces.webp +0 -0
- package/public/tips/tasks-sign-off-dark.webp +0 -0
- package/public/tips/tasks-sign-off.webp +0 -0
- package/public/tips/tasks-stacks-dark.webp +0 -0
- package/public/tips/tasks-stacks.webp +0 -0
- package/scripts/check +2 -2
- package/stores/chatJump.ts +2 -6
- package/stores/chatPositions.ts +65 -0
- package/stores/navHistory.ts +98 -0
- package/stores/shortcutsUi.ts +20 -0
- package/components/QuickSwitcher.tsx +0 -286
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useSnapshot } from "valtio";
|
|
5
|
+
import { ArrowLeft, ArrowRight } from "lucide-react";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import {
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuLabel,
|
|
12
|
+
DropdownMenuSeparator,
|
|
13
|
+
DropdownMenuTrigger,
|
|
14
|
+
} from "@/components/ui/dropdown-menu";
|
|
15
|
+
import {
|
|
16
|
+
Tooltip,
|
|
17
|
+
TooltipContent,
|
|
18
|
+
TooltipTrigger,
|
|
19
|
+
} from "@/components/ui/tooltip";
|
|
20
|
+
import { useLongPress } from "@/components/Chat/useLongPress";
|
|
21
|
+
import { earlier, later, type MirrorEntry } from "@/lib/nav/history-mirror";
|
|
22
|
+
import { isMac } from "@/lib/keybindings/chord";
|
|
23
|
+
import { navHistory, navHistoryActions } from "@/stores/navHistory";
|
|
24
|
+
|
|
25
|
+
const KEYS_BACK = () => (isMac() ? "⌘[" : "Alt+←");
|
|
26
|
+
const KEYS_FORWARD = () => (isMac() ? "⌘]" : "Alt+→");
|
|
27
|
+
|
|
28
|
+
function useReach() {
|
|
29
|
+
const snap = useSnapshot(navHistory);
|
|
30
|
+
const m = { entries: [...snap.entries], current: snap.current };
|
|
31
|
+
return { back: earlier(m), forward: later(m) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Entries({ entries }: { entries: MirrorEntry[] }) {
|
|
35
|
+
return entries.slice(0, 15).map((e) => (
|
|
36
|
+
<DropdownMenuItem
|
|
37
|
+
key={e.index}
|
|
38
|
+
className="min-h-11 md:min-h-0"
|
|
39
|
+
onSelect={() => navHistoryActions.goTo(e.index)}
|
|
40
|
+
>
|
|
41
|
+
<span className="truncate">{e.name || "Chat"}</span>
|
|
42
|
+
</DropdownMenuItem>
|
|
43
|
+
));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One history button: tap goes a step; hold (touch) or right-click opens the list of chats it would pass. */
|
|
47
|
+
function HistoryButton({
|
|
48
|
+
dir,
|
|
49
|
+
entries,
|
|
50
|
+
className,
|
|
51
|
+
}: {
|
|
52
|
+
dir: "back" | "forward";
|
|
53
|
+
entries: MirrorEntry[];
|
|
54
|
+
className?: string;
|
|
55
|
+
}) {
|
|
56
|
+
const [open, setOpen] = useState(false);
|
|
57
|
+
const hold = useLongPress(entries.length > 0 ? () => setOpen(true) : null);
|
|
58
|
+
const Icon = dir === "back" ? ArrowLeft : ArrowRight;
|
|
59
|
+
const label = dir === "back" ? "Back" : "Forward";
|
|
60
|
+
const hint = dir === "back" ? KEYS_BACK() : KEYS_FORWARD();
|
|
61
|
+
const go = () =>
|
|
62
|
+
dir === "back" ? navHistoryActions.back() : navHistoryActions.forward();
|
|
63
|
+
const button = (
|
|
64
|
+
<Button
|
|
65
|
+
variant="ghost"
|
|
66
|
+
size="icon-sm"
|
|
67
|
+
aria-label={label}
|
|
68
|
+
disabled={entries.length === 0}
|
|
69
|
+
className={className}
|
|
70
|
+
onClick={(e) => {
|
|
71
|
+
e.stopPropagation();
|
|
72
|
+
go();
|
|
73
|
+
}}
|
|
74
|
+
onContextMenu={(e) => {
|
|
75
|
+
if (entries.length === 0) return;
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
setOpen(true);
|
|
78
|
+
}}
|
|
79
|
+
{...hold}
|
|
80
|
+
>
|
|
81
|
+
<Icon className="h-4 w-4" />
|
|
82
|
+
</Button>
|
|
83
|
+
);
|
|
84
|
+
return (
|
|
85
|
+
<DropdownMenu open={open} onOpenChange={setOpen} modal={false}>
|
|
86
|
+
<Tooltip>
|
|
87
|
+
<TooltipTrigger asChild>
|
|
88
|
+
<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>
|
|
89
|
+
</TooltipTrigger>
|
|
90
|
+
<TooltipContent>{`${label} (${hint})`}</TooltipContent>
|
|
91
|
+
</Tooltip>
|
|
92
|
+
<DropdownMenuContent align="start" className="max-w-72">
|
|
93
|
+
<DropdownMenuLabel>
|
|
94
|
+
{dir === "back" ? "Earlier" : "Later"}
|
|
95
|
+
</DropdownMenuLabel>
|
|
96
|
+
<DropdownMenuSeparator />
|
|
97
|
+
<Entries entries={entries} />
|
|
98
|
+
</DropdownMenuContent>
|
|
99
|
+
</DropdownMenu>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Desktop: back and forward through the chats and bookmarks visited. */
|
|
104
|
+
export function HistoryButtons() {
|
|
105
|
+
const { back, forward } = useReach();
|
|
106
|
+
return (
|
|
107
|
+
<>
|
|
108
|
+
<HistoryButton dir="back" entries={back} className="h-7 w-7 shrink-0" />
|
|
109
|
+
<HistoryButton
|
|
110
|
+
dir="forward"
|
|
111
|
+
entries={forward}
|
|
112
|
+
className="h-7 w-7 shrink-0"
|
|
113
|
+
/>
|
|
114
|
+
</>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Phone: the back button beside the menu; hold it for the list. */
|
|
119
|
+
export function PhoneBackButton() {
|
|
120
|
+
const { back } = useReach();
|
|
121
|
+
return (
|
|
122
|
+
<HistoryButton
|
|
123
|
+
dir="back"
|
|
124
|
+
entries={back}
|
|
125
|
+
className="relative h-8 w-8 shrink-0 after:absolute after:-inset-1.5 after:content-['']"
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -44,6 +44,7 @@ import { useCanAddAnalyst } from "@/components/Analyst/pair";
|
|
|
44
44
|
import { MoveMenuItems } from "@/components/Tasks/MoveMenuItems";
|
|
45
45
|
import { ChatBookmarksButton } from "@/components/Bookmarks/ChatBookmarksButton";
|
|
46
46
|
import type { ViewMode } from "@/contexts/PaneContext";
|
|
47
|
+
import { PhoneBackButton } from "@/components/Nav/HistoryButtons";
|
|
47
48
|
import { MobileViewPicker } from "./MobileViewPicker";
|
|
48
49
|
|
|
49
50
|
interface MobileTabBarProps {
|
|
@@ -169,6 +170,8 @@ export function MobileTabBar({
|
|
|
169
170
|
</Button>
|
|
170
171
|
)}
|
|
171
172
|
|
|
173
|
+
<PhoneBackButton />
|
|
174
|
+
|
|
172
175
|
{/* Session/Tab navigation */}
|
|
173
176
|
<div className="flex min-w-0 flex-1 items-center gap-1">
|
|
174
177
|
<button
|
|
@@ -255,7 +255,7 @@ export const Pane = memo(function Pane({
|
|
|
255
255
|
fileOpenActions.clearRequest();
|
|
256
256
|
// TODO: Scroll to line (requires FileEditor enhancement)
|
|
257
257
|
}
|
|
258
|
-
}, [fileOpenRequest, isFocused, session, fileEditor]);
|
|
258
|
+
}, [fileOpenRequest, isFocused, session, fileEditor, setViewMode]);
|
|
259
259
|
|
|
260
260
|
// An artifact's file, opened in the Files view.
|
|
261
261
|
const openInFiles = useCallback(
|
|
@@ -291,7 +291,7 @@ export const Pane = memo(function Pane({
|
|
|
291
291
|
|
|
292
292
|
// Create onConnected callback for a specific tab
|
|
293
293
|
const getTerminalConnectedHandler = useCallback(
|
|
294
|
-
(tab:
|
|
294
|
+
(tab: TabData) => () => {
|
|
295
295
|
console.log(
|
|
296
296
|
`[AgentOS] Terminal connected for pane: ${paneId}, tab: ${tab.id}`
|
|
297
297
|
);
|
|
@@ -4,11 +4,12 @@ import { MessageSquare } from "lucide-react";
|
|
|
4
4
|
import { useKeybindings } from "@/hooks/useKeybindings";
|
|
5
5
|
import { usePaletteCommands } from "@/hooks/usePaletteCommands";
|
|
6
6
|
import { chordHint } from "@/lib/keybindings/chord";
|
|
7
|
+
import { keysOf } from "@/lib/keybindings/keymap";
|
|
7
8
|
import { fieldOwnsKey } from "@/lib/keybindings/focus";
|
|
8
9
|
import { neighbourSession } from "@/lib/sidebar/visible-order";
|
|
9
10
|
|
|
10
|
-
const PREVIOUS = "
|
|
11
|
-
const NEXT = "
|
|
11
|
+
const PREVIOUS = keysOf("session.previous");
|
|
12
|
+
const NEXT = keysOf("session.next");
|
|
12
13
|
|
|
13
14
|
// ⌘↑ / ⌘↓ (Ctrl off a Mac) select the row above or below the active one, as
|
|
14
15
|
// a click would, in the order the sidebar shows. Nothing happens at either end.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useSnapshot } from "valtio";
|
|
4
|
+
import { shortcutsUi } from "@/stores/shortcutsUi";
|
|
5
|
+
|
|
6
|
+
/** The first key of a sequence, shown until the second comes or time runs out. */
|
|
7
|
+
export function PendingKeys() {
|
|
8
|
+
const { pending } = useSnapshot(shortcutsUi);
|
|
9
|
+
if (!pending) return null;
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
role="status"
|
|
13
|
+
className="bg-popover text-popover-foreground border-foreground/10 pointer-events-none fixed bottom-4 left-1/2 z-50 -translate-x-1/2 rounded-md border px-3 py-1.5 text-sm shadow-md"
|
|
14
|
+
>
|
|
15
|
+
<kbd className="font-mono font-medium uppercase">{pending}</kbd>
|
|
16
|
+
<span className="text-muted-foreground ml-2">then…</span>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Keyboard } from "lucide-react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { shortcutsUiActions } from "@/stores/shortcutsUi";
|
|
6
|
+
|
|
7
|
+
/** A footer icon that opens the shortcuts panel; on a phone it is the only way in. */
|
|
8
|
+
export function ShortcutsButton() {
|
|
9
|
+
return (
|
|
10
|
+
<Button
|
|
11
|
+
type="button"
|
|
12
|
+
variant="ghost"
|
|
13
|
+
size="icon"
|
|
14
|
+
title="Keyboard shortcuts (?)"
|
|
15
|
+
aria-label="Keyboard shortcuts"
|
|
16
|
+
onClick={() => shortcutsUiActions.open()}
|
|
17
|
+
className="text-muted-foreground relative h-8 w-8 after:absolute after:-inset-1.5 after:content-[''] md:after:hidden [@media(hover:none)]:md:h-11 [@media(hover:none)]:md:w-11"
|
|
18
|
+
>
|
|
19
|
+
<Keyboard className="h-4 w-4" />
|
|
20
|
+
</Button>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useSnapshot } from "valtio";
|
|
5
|
+
import { Search } from "lucide-react";
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
} from "@/components/ui/dialog";
|
|
12
|
+
import { Switch } from "@/components/ui/switch";
|
|
13
|
+
import { useSingleKeys } from "@/hooks/useNavKeys";
|
|
14
|
+
import { chordHint, isMac } from "@/lib/keybindings/chord";
|
|
15
|
+
import {
|
|
16
|
+
isSequence,
|
|
17
|
+
searchKeymap,
|
|
18
|
+
type KeyEntry,
|
|
19
|
+
type KeyGroup,
|
|
20
|
+
} from "@/lib/keybindings/keymap";
|
|
21
|
+
import { shortcutsUi, shortcutsUiActions } from "@/stores/shortcutsUi";
|
|
22
|
+
|
|
23
|
+
const GROUPS: KeyGroup[] = ["Go to", "Chats", "General", "Browser"];
|
|
24
|
+
|
|
25
|
+
// The browser's own keys for back and forward, per platform.
|
|
26
|
+
function hint(k: KeyEntry): string[] {
|
|
27
|
+
if (isSequence(k.keys)) return k.keys.split(" ").map((x) => x.toUpperCase());
|
|
28
|
+
if (k.browser) {
|
|
29
|
+
if (!isMac()) return [k.id === "history.back" ? "Alt+←" : "Alt+→"];
|
|
30
|
+
const arrow = k.keys.replace("[", "left").replace("]", "right");
|
|
31
|
+
return [chordHint(k.keys, true), chordHint(arrow, true)];
|
|
32
|
+
}
|
|
33
|
+
return [chordHint(k.keys)];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function Row({ k }: { k: KeyEntry }) {
|
|
37
|
+
const parts = hint(k);
|
|
38
|
+
return (
|
|
39
|
+
<li className="flex min-h-11 items-center justify-between gap-3 py-1 md:min-h-8">
|
|
40
|
+
<span className="min-w-0 text-sm">{k.label}</span>
|
|
41
|
+
<span className="flex shrink-0 items-center gap-1">
|
|
42
|
+
{isSequence(k.keys) ? (
|
|
43
|
+
parts.map((p, i) => <Kbd key={i}>{p}</Kbd>)
|
|
44
|
+
) : (
|
|
45
|
+
<Kbd>{parts.join(" · ")}</Kbd>
|
|
46
|
+
)}
|
|
47
|
+
</span>
|
|
48
|
+
</li>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const Kbd = ({ children }: { children: React.ReactNode }) => (
|
|
53
|
+
<kbd className="bg-muted text-muted-foreground min-w-6 rounded border px-1.5 py-0.5 text-center font-mono text-xs">
|
|
54
|
+
{children}
|
|
55
|
+
</kbd>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/** Every shortcut, searchable, with the switch that turns the bare-key ones off. */
|
|
59
|
+
export function ShortcutsDialog() {
|
|
60
|
+
const { open } = useSnapshot(shortcutsUi);
|
|
61
|
+
const [query, setQuery] = useState("");
|
|
62
|
+
const [singles, setSingles] = useSingleKeys();
|
|
63
|
+
const found = searchKeymap(query);
|
|
64
|
+
return (
|
|
65
|
+
<Dialog
|
|
66
|
+
open={open}
|
|
67
|
+
onOpenChange={(o) => {
|
|
68
|
+
shortcutsUiActions.setOpen(o);
|
|
69
|
+
if (!o) setQuery("");
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<DialogContent className="flex max-h-[min(80dvh,640px)] flex-col gap-3 sm:max-w-lg">
|
|
73
|
+
<DialogTitle>Keyboard shortcuts</DialogTitle>
|
|
74
|
+
<DialogDescription className="sr-only">
|
|
75
|
+
Search the shortcuts and turn the single-key ones off
|
|
76
|
+
</DialogDescription>
|
|
77
|
+
<label className="border-foreground/10 flex items-center gap-2 rounded-md border px-3">
|
|
78
|
+
<Search className="text-muted-foreground h-4 w-4 shrink-0" />
|
|
79
|
+
<input
|
|
80
|
+
autoFocus
|
|
81
|
+
value={query}
|
|
82
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
83
|
+
placeholder="Search shortcuts…"
|
|
84
|
+
aria-label="Search shortcuts"
|
|
85
|
+
className="placeholder:text-muted-foreground h-11 min-w-0 flex-1 bg-transparent text-base outline-none sm:text-sm"
|
|
86
|
+
/>
|
|
87
|
+
</label>
|
|
88
|
+
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
|
89
|
+
{found.length === 0 && (
|
|
90
|
+
<p className="text-muted-foreground py-6 text-center text-sm">
|
|
91
|
+
No shortcut matches.
|
|
92
|
+
</p>
|
|
93
|
+
)}
|
|
94
|
+
{GROUPS.map((g) => {
|
|
95
|
+
const rows = found.filter((k) => k.group === g);
|
|
96
|
+
if (!rows.length) return null;
|
|
97
|
+
return (
|
|
98
|
+
<section key={g} className="mb-3">
|
|
99
|
+
<h3 className="label-mono text-muted-foreground mb-1">{g}</h3>
|
|
100
|
+
<ul>
|
|
101
|
+
{rows.map((k) => (
|
|
102
|
+
<Row key={k.id} k={k} />
|
|
103
|
+
))}
|
|
104
|
+
</ul>
|
|
105
|
+
</section>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
108
|
+
</div>
|
|
109
|
+
<label className="border-foreground/10 flex min-h-11 items-center justify-between gap-3 border-t pt-3 text-sm">
|
|
110
|
+
<span>
|
|
111
|
+
Single-key shortcuts
|
|
112
|
+
<span className="text-muted-foreground block text-xs">
|
|
113
|
+
Letters like C and G I, only when you are not typing. This browser
|
|
114
|
+
only.
|
|
115
|
+
</span>
|
|
116
|
+
</span>
|
|
117
|
+
<Switch checked={singles} onCheckedChange={setSingles} />
|
|
118
|
+
</label>
|
|
119
|
+
</DialogContent>
|
|
120
|
+
</Dialog>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TipButton } from "@/components/Tips";
|
|
2
2
|
import { WhatsNewButton } from "@/components/WhatsNew";
|
|
3
3
|
import { ThemeToggle } from "@/components/ThemeToggle";
|
|
4
|
+
import { ShortcutsButton } from "@/components/Shortcuts/ShortcutsButton";
|
|
4
5
|
import {
|
|
5
6
|
Tooltip,
|
|
6
7
|
TooltipContent,
|
|
@@ -13,6 +14,7 @@ export function SidebarFooter() {
|
|
|
13
14
|
<div className="flex items-center justify-between">
|
|
14
15
|
<span className="label-mono text-muted-foreground">Theme</span>
|
|
15
16
|
<div className="flex items-center">
|
|
17
|
+
<ShortcutsButton />
|
|
16
18
|
<TipButton />
|
|
17
19
|
<WhatsNewButton />
|
|
18
20
|
<ThemeToggle />
|
|
@@ -137,7 +137,8 @@ export function useTerminalConnection({
|
|
|
137
137
|
|
|
138
138
|
// Main setup effect
|
|
139
139
|
useEffect(() => {
|
|
140
|
-
|
|
140
|
+
const container = terminalRef.current;
|
|
141
|
+
if (!container) return;
|
|
141
142
|
|
|
142
143
|
let cancelled = false;
|
|
143
144
|
// Reset intentional close flag (may be true from previous cleanup)
|
|
@@ -220,9 +221,9 @@ export function useTerminalConnection({
|
|
|
220
221
|
|
|
221
222
|
// Save scroll state before unmount
|
|
222
223
|
const term = xtermRef.current;
|
|
223
|
-
if (term && callbacksRef.current.onBeforeUnmount
|
|
224
|
+
if (term && callbacksRef.current.onBeforeUnmount) {
|
|
224
225
|
const buffer = term.buffer.active;
|
|
225
|
-
const viewport =
|
|
226
|
+
const viewport = container.querySelector(
|
|
226
227
|
".xterm-viewport"
|
|
227
228
|
) as HTMLElement;
|
|
228
229
|
callbacksRef.current.onBeforeUnmount({
|
|
@@ -6,8 +6,9 @@ import { UpdateBanner } from "@/components/Updates";
|
|
|
6
6
|
import { PaneLayout } from "@/components/PaneLayout";
|
|
7
7
|
import { PaneBarSlotsProvider } from "@/components/Pane/PaneBarSlots";
|
|
8
8
|
import type { ViewProps } from "./types";
|
|
9
|
-
import {
|
|
9
|
+
import { CodeSearchDialog, StartServerDialog } from "./lazy";
|
|
10
10
|
import { fileOpenActions } from "@/stores/fileOpen";
|
|
11
|
+
import { HistoryButtons } from "@/components/Nav/HistoryButtons";
|
|
11
12
|
import { useSidebarPinned } from "@/hooks/useSidebarPinned";
|
|
12
13
|
|
|
13
14
|
export function DesktopView({
|
|
@@ -57,7 +58,10 @@ export function DesktopView({
|
|
|
57
58
|
<div className="min-h-0 flex-1">
|
|
58
59
|
<PaneBarSlotsProvider
|
|
59
60
|
leading={
|
|
60
|
-
|
|
61
|
+
<>
|
|
62
|
+
<SidebarToggle isPinned={isPinned} togglePin={togglePin} />
|
|
63
|
+
<HistoryButtons />
|
|
64
|
+
</>
|
|
61
65
|
}
|
|
62
66
|
trailing={<DesktopAppActions showNew={!isPinned} />}
|
|
63
67
|
>
|
|
@@ -67,16 +71,10 @@ export function DesktopView({
|
|
|
67
71
|
</div>
|
|
68
72
|
|
|
69
73
|
{/* Dialogs */}
|
|
70
|
-
<
|
|
71
|
-
sessions={sessions}
|
|
74
|
+
<CodeSearchDialog
|
|
72
75
|
open={showQuickSwitcher}
|
|
73
76
|
onOpenChange={setShowQuickSwitcher}
|
|
74
|
-
currentSessionId={focusedActiveTab?.sessionId ?? undefined}
|
|
75
77
|
activeSessionWorkingDir={activeSession?.working_directory ?? undefined}
|
|
76
|
-
onSelectSession={(sessionId) => {
|
|
77
|
-
const session = sessions.find((s) => s.id === sessionId);
|
|
78
|
-
if (session) selectSession(session);
|
|
79
|
-
}}
|
|
80
78
|
onSelectFile={(file, line) => {
|
|
81
79
|
// Convert relative path to absolute by prepending working directory
|
|
82
80
|
const absolutePath = activeSession?.working_directory
|
|
@@ -6,7 +6,7 @@ import { PaneLayout } from "@/components/PaneLayout";
|
|
|
6
6
|
import { SwipeSidebar } from "@/components/mobile/SwipeSidebar";
|
|
7
7
|
import { UpdateBanner } from "@/components/Updates";
|
|
8
8
|
import type { ViewProps } from "./types";
|
|
9
|
-
import {
|
|
9
|
+
import { CodeSearchDialog, StartServerDialog } from "./lazy";
|
|
10
10
|
import { fileOpenActions } from "@/stores/fileOpen";
|
|
11
11
|
|
|
12
12
|
export function MobileView({
|
|
@@ -66,16 +66,10 @@ export function MobileView({
|
|
|
66
66
|
</div>
|
|
67
67
|
|
|
68
68
|
{/* Dialogs */}
|
|
69
|
-
<
|
|
70
|
-
sessions={sessions}
|
|
69
|
+
<CodeSearchDialog
|
|
71
70
|
open={showQuickSwitcher}
|
|
72
71
|
onOpenChange={setShowQuickSwitcher}
|
|
73
|
-
currentSessionId={focusedActiveTab?.sessionId ?? undefined}
|
|
74
72
|
activeSessionWorkingDir={activeSession?.working_directory ?? undefined}
|
|
75
|
-
onSelectSession={(sessionId) => {
|
|
76
|
-
const session = sessions.find((s) => s.id === sessionId);
|
|
77
|
-
if (session) attachToSession(session);
|
|
78
|
-
}}
|
|
79
73
|
onSelectFile={(file, line) => {
|
|
80
74
|
// Convert relative path to absolute by prepending working directory
|
|
81
75
|
const absolutePath = activeSession?.working_directory
|
package/components/views/lazy.ts
CHANGED
|
@@ -2,8 +2,8 @@ import dynamic from "next/dynamic";
|
|
|
2
2
|
|
|
3
3
|
// Dialogs both views hold closed until asked for: their code loads after the
|
|
4
4
|
// first paint rather than with it.
|
|
5
|
-
export const
|
|
6
|
-
() => import("@/components/
|
|
5
|
+
export const CodeSearchDialog = dynamic(
|
|
6
|
+
() => import("@/components/CodeSearchDialog").then((m) => m.CodeSearchDialog),
|
|
7
7
|
{ ssr: false }
|
|
8
8
|
);
|
|
9
9
|
export const StartServerDialog = dynamic(
|