@saadnvd1/agent-os 0.3.794 → 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/busy/route.ts +2 -2
- 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/api/tips/route.test.ts +56 -0
- package/app/api/tips/route.ts +45 -0
- package/app/api/updates/health/route.ts +22 -0
- package/app/api/updates/route.test.ts +53 -0
- package/app/api/updates/settings/route.ts +37 -10
- package/app/page.tsx +17 -14
- package/components/Chat/ChatPanel.tsx +74 -59
- package/components/Chat/NewDivider.tsx +15 -0
- package/components/Chat/needs-more-history.test.ts +28 -0
- package/components/Chat/needs-more-history.ts +14 -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/Recipes/TimerFields.tsx +2 -1
- package/components/Schedules/ScheduleForm.tsx +1 -1
- package/components/SessionList/useSessionSwitching.ts +3 -2
- package/components/Settings/SettingsDialog.tsx +1 -1
- 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 +4 -0
- package/components/Terminal/hooks/useTerminalConnection.ts +4 -3
- package/components/Tips/TipButton.tsx +39 -0
- package/components/Tips/TipDialog.tsx +148 -0
- package/components/Tips/TipHost.tsx +14 -0
- package/components/Tips/TipMedia.tsx +71 -0
- package/components/Tips/index.ts +2 -0
- package/components/Updates/AutoUpdates.tsx +141 -0
- package/components/Updates/UpdateBanner.test.ts +39 -1
- package/components/Updates/UpdateBanner.tsx +31 -5
- package/components/Updates/UpdatesSection.tsx +24 -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 +541 -0
- package/content/whats-new.json +56 -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/data/tips/index.ts +49 -0
- package/data/updates/index.ts +5 -0
- package/hooks/useChatViewsSync.ts +12 -0
- package/hooks/useNavActions.ts +104 -0
- package/hooks/useNavKeys.ts +105 -0
- package/hooks/useReloadWhenUpdated.ts +5 -1
- 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/busy.ts +23 -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/db/migrations.ts +21 -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/recipes/timer.ts +3 -1
- package/lib/schedules/cron.ts +14 -5
- package/lib/schedules/store.ts +3 -2
- package/lib/security/demo.ts +2 -0
- package/lib/session-url.ts +11 -5
- package/lib/tasks/finish.ts +5 -5
- package/lib/tips/conditions.ts +38 -0
- package/lib/tips/index.ts +2 -0
- package/lib/tips/load.ts +26 -0
- package/lib/tips/media.ts +101 -0
- package/lib/tips/pick.ts +35 -0
- package/lib/tips/schema.ts +82 -0
- package/lib/tips/state.ts +26 -0
- package/lib/tips/view.ts +60 -0
- package/lib/updates/auto.ts +149 -0
- package/lib/updates/banner.ts +78 -3
- package/lib/updates/check.ts +64 -8
- package/lib/updates/history.ts +99 -0
- package/lib/updates/run.ts +334 -23
- package/lib/updates/settings.ts +66 -0
- package/lib/updates/view.ts +14 -3
- package/package.json +2 -2
- package/public/tips/.gitkeep +0 -0
- 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-slash-menu-poster.webp +0 -0
- package/public/tips/chat-slash-menu.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/autodeploy +4 -0
- package/scripts/autodeploy.test.ts +12 -0
- package/scripts/check +2 -2
- package/scripts/docs-links.test.ts +1 -25
- package/scripts/lib/commands.sh +32 -1
- package/scripts/lib/deps-swap.sh +8 -6
- package/scripts/lib/heading-slugs.ts +26 -0
- package/scripts/lib/update-verify.mjs +90 -0
- package/scripts/redeploy +31 -2
- package/scripts/redeploy.test.ts +94 -3
- package/scripts/tips-lint.test.ts +77 -0
- package/scripts/tips-media.ts +31 -0
- package/scripts/update-e2e.test.ts +215 -0
- package/scripts/update-recover.mjs +130 -0
- package/scripts/update-recover.test.ts +144 -0
- package/scripts/update-run.mjs +182 -40
- package/scripts/update-run.test.ts +208 -2
- package/server.ts +3 -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/stores/tips.ts +14 -0
- package/components/QuickSwitcher.tsx +0 -286
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { proxy } from "valtio";
|
|
2
|
+
|
|
3
|
+
// The shortcuts panel, and the first key of a sequence that is waiting for
|
|
4
|
+
// its second (shown as a chip so the reader sees it was heard).
|
|
5
|
+
export const shortcutsUi = proxy<{ open: boolean; pending: string | null }>({
|
|
6
|
+
open: false,
|
|
7
|
+
pending: null,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const shortcutsUiActions = {
|
|
11
|
+
open: () => {
|
|
12
|
+
shortcutsUi.open = true;
|
|
13
|
+
},
|
|
14
|
+
setOpen: (open: boolean) => {
|
|
15
|
+
shortcutsUi.open = open;
|
|
16
|
+
},
|
|
17
|
+
setPending: (key: string | null) => {
|
|
18
|
+
shortcutsUi.pending = key;
|
|
19
|
+
},
|
|
20
|
+
};
|
package/stores/tips.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { proxy } from "valtio";
|
|
2
|
+
|
|
3
|
+
// Whether the Tip of the Day dialog is open, so the sidebar icon can open
|
|
4
|
+
// the one dialog mounted near the top of the app.
|
|
5
|
+
export const tipsStore = proxy({ open: false });
|
|
6
|
+
|
|
7
|
+
export const tipsActions = {
|
|
8
|
+
open: () => {
|
|
9
|
+
tipsStore.open = true;
|
|
10
|
+
},
|
|
11
|
+
close: () => {
|
|
12
|
+
tipsStore.open = false;
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect, useCallback, useRef } from "react";
|
|
4
|
-
import {
|
|
5
|
-
Dialog,
|
|
6
|
-
DialogContent,
|
|
7
|
-
DialogHeader,
|
|
8
|
-
DialogTitle,
|
|
9
|
-
} from "@/components/ui/dialog";
|
|
10
|
-
import { Input } from "@/components/ui/input";
|
|
11
|
-
import { cn } from "@/lib/utils";
|
|
12
|
-
import { Terminal, GitBranch, Clock, Check } from "lucide-react";
|
|
13
|
-
import type { Session } from "@/lib/db";
|
|
14
|
-
import { CodeSearchResults } from "@/components/CodeSearch/CodeSearchResults";
|
|
15
|
-
import { useRipgrepAvailable } from "@/data/code-search";
|
|
16
|
-
|
|
17
|
-
interface QuickSwitcherProps {
|
|
18
|
-
sessions: Session[];
|
|
19
|
-
open: boolean;
|
|
20
|
-
onOpenChange: (open: boolean) => void;
|
|
21
|
-
onSelectSession: (sessionId: string) => void;
|
|
22
|
-
onSelectFile?: (file: string, line: number) => void;
|
|
23
|
-
currentSessionId?: string;
|
|
24
|
-
activeSessionWorkingDir?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Quick session switcher with search
|
|
29
|
-
* Triggered by Cmd+K or button tap
|
|
30
|
-
*/
|
|
31
|
-
export function QuickSwitcher({
|
|
32
|
-
sessions,
|
|
33
|
-
open,
|
|
34
|
-
onOpenChange,
|
|
35
|
-
onSelectSession,
|
|
36
|
-
onSelectFile,
|
|
37
|
-
currentSessionId,
|
|
38
|
-
activeSessionWorkingDir,
|
|
39
|
-
}: QuickSwitcherProps) {
|
|
40
|
-
const [chosenMode, setMode] = useState<"sessions" | "code">("sessions");
|
|
41
|
-
const [query, setQueryState] = useState("");
|
|
42
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
43
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
44
|
-
|
|
45
|
-
// Check if ripgrep is available
|
|
46
|
-
const { data: ripgrepAvailable } = useRipgrepAvailable();
|
|
47
|
-
|
|
48
|
-
// Code search needs ripgrep
|
|
49
|
-
const mode = ripgrepAvailable === false ? "sessions" : chosenMode;
|
|
50
|
-
|
|
51
|
-
const setQuery = (next: string) => {
|
|
52
|
-
setQueryState(next);
|
|
53
|
-
setSelectedIndex(0);
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// Reset state when dialog opens
|
|
57
|
-
const [wasOpen, setWasOpen] = useState(open);
|
|
58
|
-
if (open !== wasOpen) {
|
|
59
|
-
setWasOpen(open);
|
|
60
|
-
if (open) {
|
|
61
|
-
setMode("sessions");
|
|
62
|
-
setQueryState("");
|
|
63
|
-
setSelectedIndex(0);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Filter sessions based on search query
|
|
68
|
-
const filteredSessions = sessions.filter((session) => {
|
|
69
|
-
if (!query) return true;
|
|
70
|
-
const q = query.toLowerCase();
|
|
71
|
-
return (
|
|
72
|
-
session.name?.toLowerCase().includes(q) ||
|
|
73
|
-
session.working_directory?.toLowerCase().includes(q) ||
|
|
74
|
-
session.agent_type?.toLowerCase().includes(q)
|
|
75
|
-
);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
if (!open) return;
|
|
80
|
-
const t = setTimeout(() => inputRef.current?.focus(), 50);
|
|
81
|
-
return () => clearTimeout(t);
|
|
82
|
-
}, [open]);
|
|
83
|
-
|
|
84
|
-
// Handle keyboard navigation
|
|
85
|
-
const handleKeyDown = useCallback(
|
|
86
|
-
(e: React.KeyboardEvent) => {
|
|
87
|
-
switch (e.key) {
|
|
88
|
-
case "ArrowDown":
|
|
89
|
-
e.preventDefault();
|
|
90
|
-
setSelectedIndex((prev) =>
|
|
91
|
-
Math.min(prev + 1, filteredSessions.length - 1)
|
|
92
|
-
);
|
|
93
|
-
break;
|
|
94
|
-
case "ArrowUp":
|
|
95
|
-
e.preventDefault();
|
|
96
|
-
setSelectedIndex((prev) => Math.max(prev - 1, 0));
|
|
97
|
-
break;
|
|
98
|
-
case "Enter":
|
|
99
|
-
e.preventDefault();
|
|
100
|
-
if (filteredSessions[selectedIndex]) {
|
|
101
|
-
onSelectSession(filteredSessions[selectedIndex].id);
|
|
102
|
-
onOpenChange(false);
|
|
103
|
-
}
|
|
104
|
-
break;
|
|
105
|
-
case "Escape":
|
|
106
|
-
e.preventDefault();
|
|
107
|
-
onOpenChange(false);
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
[filteredSessions, selectedIndex, onSelectSession, onOpenChange]
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
// Format relative time
|
|
115
|
-
const formatTime = (dateStr: string | null) => {
|
|
116
|
-
if (!dateStr) return "";
|
|
117
|
-
const now = new Date();
|
|
118
|
-
const date = new Date(dateStr);
|
|
119
|
-
const diff = now.getTime() - date.getTime();
|
|
120
|
-
const mins = Math.floor(diff / 60000);
|
|
121
|
-
if (mins < 1) return "just now";
|
|
122
|
-
if (mins < 60) return `${mins}m ago`;
|
|
123
|
-
const hours = Math.floor(mins / 60);
|
|
124
|
-
if (hours < 24) return `${hours}h ago`;
|
|
125
|
-
return `${Math.floor(hours / 24)}d ago`;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// Handle file selection from code search
|
|
129
|
-
const handleSelectFile = useCallback(
|
|
130
|
-
(file: string, line: number) => {
|
|
131
|
-
onOpenChange(false);
|
|
132
|
-
onSelectFile?.(file, line);
|
|
133
|
-
},
|
|
134
|
-
[onOpenChange, onSelectFile]
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
139
|
-
<DialogContent
|
|
140
|
-
showCloseButton={false}
|
|
141
|
-
className="gap-0 overflow-hidden p-0 sm:max-w-md"
|
|
142
|
-
>
|
|
143
|
-
<DialogHeader className="sr-only">
|
|
144
|
-
<DialogTitle>Switch Session / Search Code</DialogTitle>
|
|
145
|
-
</DialogHeader>
|
|
146
|
-
|
|
147
|
-
{/* Mode Toggle - only show if ripgrep is available */}
|
|
148
|
-
{ripgrepAvailable === true && (
|
|
149
|
-
<div className="border-border flex gap-2 border-b p-2">
|
|
150
|
-
<button
|
|
151
|
-
onClick={() => setMode("sessions")}
|
|
152
|
-
className={cn(
|
|
153
|
-
"rounded-full px-3 py-1 text-sm transition-colors",
|
|
154
|
-
mode === "sessions"
|
|
155
|
-
? "bg-primary text-primary-foreground"
|
|
156
|
-
: "hover:bg-accent"
|
|
157
|
-
)}
|
|
158
|
-
>
|
|
159
|
-
Sessions
|
|
160
|
-
</button>
|
|
161
|
-
<button
|
|
162
|
-
onClick={() => setMode("code")}
|
|
163
|
-
className={cn(
|
|
164
|
-
"rounded-full px-3 py-1 text-sm transition-colors",
|
|
165
|
-
mode === "code"
|
|
166
|
-
? "bg-primary text-primary-foreground"
|
|
167
|
-
: "hover:bg-accent"
|
|
168
|
-
)}
|
|
169
|
-
>
|
|
170
|
-
Code Search
|
|
171
|
-
</button>
|
|
172
|
-
</div>
|
|
173
|
-
)}
|
|
174
|
-
|
|
175
|
-
{/* Search Input */}
|
|
176
|
-
<div className="border-border border-b p-3">
|
|
177
|
-
<Input
|
|
178
|
-
ref={inputRef}
|
|
179
|
-
placeholder={
|
|
180
|
-
mode === "sessions" || !ripgrepAvailable
|
|
181
|
-
? "Search sessions..."
|
|
182
|
-
: "Search code (min 3 chars)..."
|
|
183
|
-
}
|
|
184
|
-
value={query}
|
|
185
|
-
onChange={(e) => setQuery(e.target.value)}
|
|
186
|
-
onKeyDown={mode === "sessions" ? handleKeyDown : undefined}
|
|
187
|
-
className="h-10"
|
|
188
|
-
/>
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
{/* Content */}
|
|
192
|
-
<div className="max-h-[300px] overflow-y-auto py-2">
|
|
193
|
-
{mode === "sessions" ? (
|
|
194
|
-
filteredSessions.length === 0 ? (
|
|
195
|
-
<div className="text-muted-foreground px-4 py-8 text-center text-sm">
|
|
196
|
-
No sessions found
|
|
197
|
-
</div>
|
|
198
|
-
) : (
|
|
199
|
-
filteredSessions.map((session, index) => {
|
|
200
|
-
const isCurrent = session.id === currentSessionId;
|
|
201
|
-
return (
|
|
202
|
-
<button
|
|
203
|
-
key={session.id}
|
|
204
|
-
onClick={() => {
|
|
205
|
-
onSelectSession(session.id);
|
|
206
|
-
onOpenChange(false);
|
|
207
|
-
}}
|
|
208
|
-
className={cn(
|
|
209
|
-
"flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors",
|
|
210
|
-
index === selectedIndex
|
|
211
|
-
? "bg-accent"
|
|
212
|
-
: "hover:bg-accent/50",
|
|
213
|
-
isCurrent && "bg-primary/10"
|
|
214
|
-
)}
|
|
215
|
-
>
|
|
216
|
-
{/* Icon */}
|
|
217
|
-
<div
|
|
218
|
-
className={cn(
|
|
219
|
-
"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md",
|
|
220
|
-
session.worktree_path
|
|
221
|
-
? "bg-purple-500/20 text-purple-400"
|
|
222
|
-
: "bg-emerald-500/20 text-emerald-400"
|
|
223
|
-
)}
|
|
224
|
-
>
|
|
225
|
-
{session.worktree_path ? (
|
|
226
|
-
<GitBranch className="h-4 w-4" />
|
|
227
|
-
) : (
|
|
228
|
-
<Terminal className="h-4 w-4" />
|
|
229
|
-
)}
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
|
-
{/* Content */}
|
|
233
|
-
<div className="min-w-0 flex-1">
|
|
234
|
-
<div className="flex items-center gap-2">
|
|
235
|
-
<span className="truncate font-medium">
|
|
236
|
-
{session.name || "Unnamed Session"}
|
|
237
|
-
</span>
|
|
238
|
-
{isCurrent && (
|
|
239
|
-
<Check className="text-primary h-3.5 w-3.5 flex-shrink-0" />
|
|
240
|
-
)}
|
|
241
|
-
</div>
|
|
242
|
-
<div className="text-muted-foreground flex items-center gap-2 text-xs">
|
|
243
|
-
<span className="truncate">
|
|
244
|
-
{session.working_directory?.split("/").pop() || "~"}
|
|
245
|
-
</span>
|
|
246
|
-
<span>•</span>
|
|
247
|
-
<span className="capitalize">
|
|
248
|
-
{session.agent_type || "claude"}
|
|
249
|
-
</span>
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
|
|
253
|
-
{/* Time */}
|
|
254
|
-
<div className="text-muted-foreground flex flex-shrink-0 items-center gap-1 text-xs">
|
|
255
|
-
<Clock className="h-3 w-3" />
|
|
256
|
-
<span>{formatTime(session.updated_at)}</span>
|
|
257
|
-
</div>
|
|
258
|
-
</button>
|
|
259
|
-
);
|
|
260
|
-
})
|
|
261
|
-
)
|
|
262
|
-
) : (
|
|
263
|
-
<CodeSearchResults
|
|
264
|
-
workingDirectory={activeSessionWorkingDir || "~"}
|
|
265
|
-
query={query}
|
|
266
|
-
onSelectFile={handleSelectFile}
|
|
267
|
-
/>
|
|
268
|
-
)}
|
|
269
|
-
</div>
|
|
270
|
-
|
|
271
|
-
{/* Footer Hint */}
|
|
272
|
-
<div className="border-border text-muted-foreground flex items-center gap-4 border-t px-4 py-2 text-xs">
|
|
273
|
-
<span>
|
|
274
|
-
<kbd className="bg-muted rounded px-1.5 py-0.5">↑↓</kbd> navigate
|
|
275
|
-
</span>
|
|
276
|
-
<span>
|
|
277
|
-
<kbd className="bg-muted rounded px-1.5 py-0.5">↵</kbd> select
|
|
278
|
-
</span>
|
|
279
|
-
<span>
|
|
280
|
-
<kbd className="bg-muted rounded px-1.5 py-0.5">esc</kbd> close
|
|
281
|
-
</span>
|
|
282
|
-
</div>
|
|
283
|
-
</DialogContent>
|
|
284
|
-
</Dialog>
|
|
285
|
-
);
|
|
286
|
-
}
|