@kabmo-labs/kabmo 0.3.1 → 0.5.0
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/dist/client/index.html +1 -1
- package/dist/client/index.js +412 -383
- package/dist/client/static/js/index-3qamd33d.css +1 -0
- package/dist/client/static/js/{index-28z316qk.js → index-cs3b557t.js} +792 -792
- package/package.json +1 -1
- package/src/client/components/common/batch-selection/BatchActionBar.tsx +8 -1
- package/src/client/components/common/session-actions-menu/index.tsx +23 -2
- package/src/client/components/layout/{RightActivityBar.test.ts → ActivityBar.test.ts} +5 -5
- package/src/client/components/layout/{RightActivityBar.tsx → ActivityBar.tsx} +87 -52
- package/src/client/components/layout/LeftActivityBar.test.ts +201 -0
- package/src/client/components/layout/LeftDock.tsx +119 -0
- package/src/client/components/layout/desktop-layout/LeftColumn.tsx +172 -0
- package/src/client/components/layout/desktop-layout/TopNavbar.tsx +5 -3
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +22 -5
- package/src/client/components/layout/desktop-layout/index.tsx +88 -61
- package/src/client/components/layout/desktop-layout/left-column.test.tsx +192 -0
- package/src/client/components/layout/session-sidebar/CategoryHeader.tsx +6 -0
- package/src/client/components/layout/session-sidebar/CategoryItem.tsx +9 -0
- package/src/client/components/layout/session-sidebar/Header.tsx +2 -1
- package/src/client/components/layout/session-sidebar/SessionItem.tsx +42 -6
- package/src/client/components/layout/session-sidebar/SessionList.tsx +4 -0
- package/src/client/components/layout/session-sidebar/{index.tsx → SessionsView.tsx} +48 -72
- package/src/client/components/layout/session-sidebar/new-session-context.test.ts +55 -0
- package/src/client/components/mobile/LayoutWrapper.tsx +4 -0
- package/src/client/components/mobile/RightSidebar.tsx +30 -36
- package/src/client/components/mobile/SessionSidebar.tsx +124 -101
- package/src/client/components/mobile/mobile-main-view/Header.tsx +3 -2
- package/src/client/components/mobile/mobile-right-sidebar/FullDiff.tsx +3 -2
- package/src/client/components/mobile/mobile-right-sidebar/Header.tsx +3 -2
- package/src/client/components/mobile/mobile-session-sidebar/Dialogs.tsx +106 -0
- package/src/client/components/mobile/mobile-session-sidebar/Dock.tsx +80 -0
- package/src/client/components/mobile/mobile-session-sidebar/Header.tsx +11 -5
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +34 -44
- package/src/client/components/mobile/mobile-session-sidebar/List.tsx +9 -1
- package/src/client/components/mobile/mobile-session-sidebar/RevealRow.tsx +60 -0
- package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +47 -14
- package/src/client/components/mobile/mobile-session-sidebar/Skeleton.tsx +2 -2
- package/src/client/components/mobile/mobile-session-sidebar/use-dock.ts +89 -0
- package/src/client/components/mobile/panel-chips.test.tsx +155 -0
- package/src/client/components/mobile/panel-chips.tsx +74 -0
- package/src/client/components/settings/categories/panel-plugins/InstalledList.tsx +2 -2
- package/src/client/components/settings/categories/panel-plugins/LeftDockSettings.tsx +268 -0
- package/src/client/components/settings/categories/panel-plugins/Section.tsx +8 -0
- package/src/client/components/workspace/chat-timeline/EmptyWorkspacePrompt.tsx +69 -1
- package/src/client/components/workspace/chat-timeline/MessageItem.gutter.test.ts +118 -0
- package/src/client/components/workspace/chat-timeline/MessageItem.tsx +13 -3
- package/src/client/components/workspace/chat-timeline/MessageList.tsx +1 -0
- package/src/client/components/workspace/chat-timeline/PendingChatView.tsx +106 -0
- package/src/client/components/workspace/chat-timeline/SessionSkeleton.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/SubagentView.tsx +4 -1
- package/src/client/components/workspace/chat-timeline/SystemNotice.tsx +5 -2
- package/src/client/components/workspace/chat-timeline/ThinkingSection.tsx +6 -2
- package/src/client/components/workspace/chat-timeline/TimelineBody.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/ToolCallingSection.test.ts +5 -5
- package/src/client/components/workspace/chat-timeline/ToolCallingSection.tsx +5 -2
- package/src/client/components/workspace/chat-timeline/chat-input/AttachmentToolbar.tsx +25 -1
- package/src/client/components/workspace/chat-timeline/chat-input/Toolbar.tsx +15 -10
- package/src/client/components/workspace/chat-timeline/chat-input/WaitingDependencyPicker.test.ts +84 -0
- package/src/client/components/workspace/chat-timeline/chat-input/WaitingDependencyPicker.tsx +185 -0
- package/src/client/components/workspace/chat-timeline/chat-input/compact.test.tsx +162 -0
- package/src/client/components/workspace/chat-timeline/chat-input/expansion.test.ts +22 -21
- package/src/client/components/workspace/chat-timeline/chat-input/expansion.ts +12 -20
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +54 -27
- package/src/client/components/workspace/chat-timeline/chat-input/props.ts +16 -0
- package/src/client/components/workspace/chat-timeline/index.tsx +15 -16
- package/src/client/components/workspace/editor/PluginRail.tsx +79 -0
- package/src/client/components/workspace/editor/index.tsx +1 -0
- package/src/client/components/workspace/editor/plugin-rail.test.tsx +126 -0
- package/src/client/components/workspace/model-dropdown/groups.test.ts +60 -0
- package/src/client/components/workspace/model-dropdown/groups.ts +7 -1
- package/src/client/components/workspace/model-dropdown/index.tsx +3 -3
- package/src/client/components/workspace/model-dropdown/trigger.test.ts +123 -0
- package/src/client/components/workspace/model-dropdown/use-catalog.ts +8 -2
- package/src/client/components/workspace/panels/builtin.tsx +8 -0
- package/src/client/components/workspace/plugin-panel/catalog.tsx +237 -0
- package/src/client/components/workspace/plugin-panel/resolve.tsx +19 -122
- package/src/client/hooks/chat/composer/compact.ts +119 -0
- package/src/client/hooks/chat/omp/pending-rows.ts +4 -0
- package/src/client/hooks/chat/omp/pending-sessions.ts +47 -0
- package/src/client/hooks/chat/omp/pending-sessions.waiting.test.ts +102 -0
- package/src/client/hooks/chat/omp/pending-waiting.ts +48 -0
- package/src/client/hooks/chat/omp/session-sidebar-controller.ts +29 -0
- package/src/client/hooks/chat/timeline/actions.ts +34 -35
- package/src/client/hooks/chat/timeline/composer-refs.ts +47 -0
- package/src/client/hooks/chat/timeline/enqueue-follow-up.ts +37 -0
- package/src/client/hooks/chat/timeline/index.ts +15 -19
- package/src/client/hooks/chat/timeline/local-commands.ts +29 -0
- package/src/client/hooks/chat/timeline/queue-rewind.test.ts +6 -0
- package/src/client/hooks/chat/timeline/run-identity.ts +45 -0
- package/src/client/hooks/chat/waiting/cancel.ts +34 -0
- package/src/client/hooks/chat/waiting/choice.ts +53 -0
- package/src/client/hooks/chat/waiting/dispatch.ts +91 -0
- package/src/client/hooks/chat/waiting/held.ts +42 -0
- package/src/client/hooks/chat/waiting/hold.test.ts +139 -0
- package/src/client/hooks/chat/waiting/hold.ts +156 -0
- package/src/client/hooks/chat/waiting/store.ts +100 -0
- package/src/client/hooks/ui/toasts.ts +12 -1
- package/src/client/hooks/workspace/left-dock.ts +132 -0
- package/src/client/hooks/workspace/panel-registry.ts +13 -0
- package/src/client/tailwind.css +22 -0
- package/src/server/db.server.test.ts +52 -1
- package/src/server/db.server.ts +49 -1
- package/src/server/index.ts +9 -0
- package/src/server/lib/assets/compressed-cache.server.test.ts +78 -0
- package/src/server/lib/assets/compressed-cache.server.ts +88 -0
- package/src/server/lib/db/schema.test.ts +1 -0
- package/src/server/lib/db/schema.ts +3 -0
- package/src/server/lib/fs/fs-path.test.ts +1 -1
- package/src/server/lib/omp/rpc/terminal-settle.ts +7 -0
- package/src/server/lib/omp/session/delete.server.ts +8 -0
- package/src/server/lib/omp/session/ownership.server.test.ts +45 -4
- package/src/server/lib/panels/registry.server.ts +2 -2
- package/src/server/lib/queue/delivery.server.ts +31 -39
- package/src/server/lib/queue/payload.ts +73 -0
- package/src/server/lib/realtime/signals.server.ts +10 -1
- package/src/server/lib/realtime/topics/catalog.server.ts +2 -0
- package/src/server/lib/realtime/topics/global.server.ts +5 -0
- package/src/server/lib/realtime/topics.server.ts +2 -0
- package/src/server/lib/util/with-timeout.ts +29 -0
- package/src/server/lib/waiting/row-clock.server.ts +37 -0
- package/src/server/lib/waiting/runtime.server.test.ts +278 -0
- package/src/server/lib/waiting/runtime.server.ts +313 -0
- package/src/server/lib/waiting/schema.server.ts +46 -0
- package/src/server/lib/waiting/settle.test.ts +84 -0
- package/src/server/lib/waiting/settle.ts +77 -0
- package/src/server/lib/waiting/store.server.test.ts +167 -0
- package/src/server/lib/waiting/store.server.ts +268 -0
- package/src/server/lib/waiting/sweep-trigger.ts +35 -0
- package/src/server/plugins/compress.ts +18 -6
- package/src/server/plugins/ssr.ts +16 -8
- package/src/server/plugins/static.test.ts +59 -0
- package/src/server/plugins/static.ts +56 -14
- package/src/server/routes/index.ts +2 -0
- package/src/server/routes/waiting/bindings.ts +14 -0
- package/src/server/routes/waiting/index.ts +123 -0
- package/src/server/routes/waiting/routes.test.ts +121 -0
- package/src/shared/lib/chat/timeline/gutter.test.ts +37 -0
- package/src/shared/lib/chat/timeline/gutter.ts +37 -0
- package/src/shared/lib/realtime/protocol.ts +10 -0
- package/src/shared/lib/ui/titlebar.test.ts +124 -0
- package/src/shared/lib/ui/titlebar.ts +54 -0
- package/src/shared/lib/workspace/left-dock-width.ts +78 -0
- package/src/shared/lib/workspace/left-dock.test.ts +203 -0
- package/src/shared/lib/workspace/left-dock.ts +197 -0
- package/src/shared/lib/workspace/mobile-dock.test.ts +65 -0
- package/src/shared/lib/workspace/mobile-dock.ts +55 -0
- package/src/shared/lib/workspace/panel-ids.test.ts +59 -10
- package/src/shared/lib/workspace/panel-ids.ts +114 -26
- package/src/shared/lib/workspace/sidebar-width.ts +4 -0
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/waiting.ts +54 -0
- package/src/shared/types/workspace.ts +8 -0
- package/src/test-support/isolated-db.ts +3 -4
- package/src/test-support/silent-socket.ts +69 -0
- package/dist/client/static/js/index-pdp5dwc8.css +0 -1
|
@@ -5,8 +5,11 @@ import { PanelHostProvider } from '@kabmo-labs/kabmo-ui';
|
|
|
5
5
|
import { useGitStatus } from '@/client/hooks/workspace/git-status';
|
|
6
6
|
import { useResolvedRepo } from '@/client/hooks/workspace/repo-scope';
|
|
7
7
|
import { useSessionState } from '@/client/hooks/workspace/session-state';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { useLeftDock } from '@/client/hooks/workspace/left-dock';
|
|
9
|
+
import { SESSIONS_VIEW_ID } from '@/shared/lib/workspace/left-dock';
|
|
10
|
+
import { phoneRightPanels } from '@/shared/lib/workspace/mobile-dock';
|
|
11
|
+
import { PanelChips } from '@/client/components/mobile/panel-chips';
|
|
12
|
+
import { titlebarPaddingLeft, titlebarPaddingRight } from '@/shared/lib/ui/titlebar';
|
|
10
13
|
|
|
11
14
|
interface MobileRightSidebarProps {
|
|
12
15
|
enabled?: boolean;
|
|
@@ -43,12 +46,23 @@ export function MobileRightSidebar({
|
|
|
43
46
|
// the user never asked for a search. Restoring the tab they left also keeps
|
|
44
47
|
// the phone and the desktop on the same view.
|
|
45
48
|
const [activeTab, setActiveTab] = useSessionState<string>('layout.mobileRightPanel', 'files');
|
|
46
|
-
// ONE catalog, the same one the desktop
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
// ONE catalog, the same one the desktop bars read, and deliberately `all`:
|
|
50
|
+
// a phone has ONE drawer, so a plugin panel the user placed on the left must
|
|
51
|
+
// stay reachable here — reading only the right column would make placing a
|
|
52
|
+
// panel on the left hide it from every phone. The dock's own session list is
|
|
53
|
+
// filtered out below: the phone already has that as its own drawer screen,
|
|
54
|
+
// and a second copy inside this one would be two lists of the same sessions.
|
|
55
|
+
//
|
|
56
|
+
// The exception is the one case where the LEFT drawer can now reach them
|
|
57
|
+
// itself: with the rail on, a placed panel gets a chip there, and listing it
|
|
58
|
+
// here as well would put one panel on two strips of one phone.
|
|
59
|
+
const dock = useLeftDock();
|
|
60
|
+
const catalog = phoneRightPanels(usePanelCatalog('all'), SESSIONS_VIEW_ID, dock.enabled);
|
|
61
|
+
// A stored tab can name a panel the user has since placed on the LEFT rail.
|
|
62
|
+
// It is gone from this catalog, and saying "No session selected" over a
|
|
63
|
+
// drawer that has views would be a wrong answer — the first view is drawn
|
|
64
|
+
// instead, and the stored id is replaced on the next click.
|
|
65
|
+
const activePanel = catalog.find((panel) => panel.id === activeTab) ?? catalog[0];
|
|
52
66
|
// Same source the desktop activity bar uses — the Source Control view's own
|
|
53
67
|
// repo pick, read shared so switching repos moves this dot too. Polls only
|
|
54
68
|
// while this drawer is the mounted screen (the poll is visibility-gated).
|
|
@@ -81,37 +95,17 @@ export function MobileRightSidebar({
|
|
|
81
95
|
style={{
|
|
82
96
|
height: 'calc(3.5rem + env(safe-area-inset-top, 0px))',
|
|
83
97
|
paddingTop: 'env(safe-area-inset-top, 0px)',
|
|
84
|
-
paddingLeft: '
|
|
85
|
-
paddingRight: '
|
|
98
|
+
paddingLeft: titlebarPaddingLeft('0.75rem', true),
|
|
99
|
+
paddingRight: titlebarPaddingRight('0.75rem', true),
|
|
86
100
|
}}
|
|
87
101
|
>
|
|
88
102
|
|
|
89
|
-
<
|
|
90
|
-
{catalog
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
className={`relative flex items-center transition-all cursor-pointer ${
|
|
96
|
-
activeTab === panel.id
|
|
97
|
-
? 'space-x-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold bg-ink text-canvas shadow-sm'
|
|
98
|
-
: 'p-2 rounded-lg text-ink/70 hover:bg-ink/5'
|
|
99
|
-
}`}
|
|
100
|
-
title={panel.title}
|
|
101
|
-
>
|
|
102
|
-
{panel.builtin ? panel.icon : <PluginMark name={panel.name} iconUrl={panel.iconUrl} size={PANEL_MARK_SIZE} />}
|
|
103
|
-
{activeTab === panel.id && (
|
|
104
|
-
<span className={panel.id === 'git' ? 'tracking-wide' : undefined}>{panel.label}</span>
|
|
105
|
-
)}
|
|
106
|
-
{panel.id === 'git' && hasGitChanges && (
|
|
107
|
-
<span
|
|
108
|
-
className="absolute top-1 right-1 w-1.5 h-1.5 rounded-full bg-info"
|
|
109
|
-
title="Uncommitted changes"
|
|
110
|
-
/>
|
|
111
|
-
)}
|
|
112
|
-
</button>
|
|
113
|
-
))}
|
|
114
|
-
</div>
|
|
103
|
+
<PanelChips
|
|
104
|
+
panels={catalog}
|
|
105
|
+
activeId={activePanel?.id ?? activeTab}
|
|
106
|
+
onSelect={setActiveTab}
|
|
107
|
+
hasGitChanges={hasGitChanges}
|
|
108
|
+
/>
|
|
115
109
|
|
|
116
110
|
<button
|
|
117
111
|
type="button"
|
|
@@ -3,24 +3,24 @@ import { MobileSessionHeader } from '@/client/components/mobile/mobile-session-s
|
|
|
3
3
|
import { MobileSessionToolbar } from '@/client/components/mobile/mobile-session-sidebar/Toolbar';
|
|
4
4
|
import { MobileSessionList } from '@/client/components/mobile/mobile-session-sidebar/List';
|
|
5
5
|
import { MobileSessionFooter } from '@/client/components/mobile/mobile-session-sidebar/Footer';
|
|
6
|
-
import { ToastStack } from '@/client/components/common/ToastStack';
|
|
7
|
-
import { AboutModal, NewWorkspaceModal } from '@/client/components/layout/session-sidebar/Modals';
|
|
8
6
|
import { useAndroidDownload } from '@/client/hooks/ui/updates/android-download';
|
|
9
|
-
import { SchedulerModal } from '@/client/components/layout/session-sidebar/scheduler-modal';
|
|
10
|
-
import { UpdatePopup } from '@/client/components/layout/update-popup';
|
|
11
7
|
import { useOnClickOutside } from '@/client/hooks/ui/on-click-outside';
|
|
12
8
|
import { useKabmoEvent } from '@/client/hooks/ui/window-event';
|
|
13
9
|
import { useToasts } from '@/client/hooks/ui/toasts';
|
|
10
|
+
import { useCancelWait } from '@/client/hooks/chat/waiting/cancel';
|
|
11
|
+
import { useWaitingDispatch } from '@/client/hooks/chat/waiting/dispatch';
|
|
14
12
|
import { useUpdates } from '@/client/hooks/ui/updates';
|
|
15
13
|
import { UPDATE_REQUEST_EVENT } from '@/shared/lib/updates/popup-state';
|
|
16
14
|
import { useSessionSidebarController } from '@/client/hooks/chat/omp/session-sidebar-controller';
|
|
17
15
|
import { useScheduledTaskCount } from '@/client/hooks/workspace/scheduled-tasks';
|
|
18
|
-
import { cloneWorkspaceFolder, createWorkspaceFolder } from '@/client/hooks/workspace/new-workspace';
|
|
19
16
|
import { MobileSessionListSkeleton } from '@/client/components/mobile/mobile-session-sidebar/Skeleton';
|
|
20
17
|
import { useBatchSelection } from '@/client/hooks/workspace/batch-selection';
|
|
21
18
|
import { useBatchDeleteRun } from '@/client/hooks/workspace/batch-delete-run';
|
|
19
|
+
import { phoneLeftStripVisible } from '@/shared/lib/workspace/mobile-dock';
|
|
20
|
+
import { dockBodyProps, useMobileDock } from '@/client/components/mobile/mobile-session-sidebar/use-dock';
|
|
21
|
+
import { MobileDockBody, MobileDockStrip } from '@/client/components/mobile/mobile-session-sidebar/Dock';
|
|
22
|
+
import { MobileDrawerDialogs } from '@/client/components/mobile/mobile-session-sidebar/Dialogs';
|
|
22
23
|
import { BatchActionBar } from '@/client/components/common/batch-selection/BatchActionBar';
|
|
23
|
-
import { BatchDeleteModal } from '@/client/components/common/batch-selection/BatchDeleteModal';
|
|
24
24
|
import type { BatchBinding } from '@/client/components/common/batch-selection/types';
|
|
25
25
|
import { liveSelection, selectableSessionIds } from '@/shared/lib/workspace/batch-select';
|
|
26
26
|
|
|
@@ -32,6 +32,12 @@ interface MobileSessionSidebarProps {
|
|
|
32
32
|
onClose: () => void;
|
|
33
33
|
onDesktopToggle?: () => void;
|
|
34
34
|
appSettings?: Record<string, any>;
|
|
35
|
+
/** The active workspace's root, for a plugin panel placed on the dock. */
|
|
36
|
+
rootPath?: string;
|
|
37
|
+
/** Bumped after a write so a placed panel re-reads, like the right drawer. */
|
|
38
|
+
refreshKey?: number;
|
|
39
|
+
onRefresh?: () => void;
|
|
40
|
+
onOpenFile?: (file: unknown) => void;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
export function MobileSessionSidebar({
|
|
@@ -41,7 +47,11 @@ export function MobileSessionSidebar({
|
|
|
41
47
|
onNewSessionForFolder,
|
|
42
48
|
onClose,
|
|
43
49
|
onDesktopToggle,
|
|
44
|
-
appSettings = {}
|
|
50
|
+
appSettings = {},
|
|
51
|
+
rootPath,
|
|
52
|
+
refreshKey = 0,
|
|
53
|
+
onRefresh,
|
|
54
|
+
onOpenFile,
|
|
45
55
|
}: MobileSessionSidebarProps) {
|
|
46
56
|
const {
|
|
47
57
|
folders,
|
|
@@ -80,6 +90,8 @@ export function MobileSessionSidebar({
|
|
|
80
90
|
window.__OMP_BOOTSTRAP__?.isAndroid,
|
|
81
91
|
);
|
|
82
92
|
const { toasts, pushToast, dismissToast } = useToasts();
|
|
93
|
+
const cancelWait = useCancelWait(pushToast);
|
|
94
|
+
useWaitingDispatch({ onNotice: pushToast });
|
|
83
95
|
|
|
84
96
|
// The popup's "Update now" hands the run over rather than starting its own —
|
|
85
97
|
// see the desktop sidebar. The phone must also bring the drawer forward, or
|
|
@@ -186,64 +198,103 @@ export function MobileSessionSidebar({
|
|
|
186
198
|
else batch.begin('workspace');
|
|
187
199
|
};
|
|
188
200
|
|
|
201
|
+
const dock = useMobileDock();
|
|
202
|
+
const showingSessions = dock.showingSessions;
|
|
203
|
+
const activeView = dock.activeView;
|
|
204
|
+
const dockBody = dockBodyProps({
|
|
205
|
+
sessionId: activeSessionId,
|
|
206
|
+
refreshKey,
|
|
207
|
+
rootPath,
|
|
208
|
+
onRefresh,
|
|
209
|
+
onOpenFile,
|
|
210
|
+
onClose,
|
|
211
|
+
});
|
|
212
|
+
|
|
189
213
|
return (
|
|
190
214
|
<div className="flex flex-col h-full w-full bg-canvas text-ink relative select-none">
|
|
191
|
-
|
|
192
|
-
<MobileSessionHeader onClose={onClose} />
|
|
193
215
|
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
optionsOpen={optionsOpen}
|
|
198
|
-
showArchived={showArchived}
|
|
199
|
-
optionsRef={optionsRef}
|
|
200
|
-
onNewSession={handleNewSessionAndClose}
|
|
201
|
-
onNewWorkspace={() => setNewWorkspaceOpen(true)}
|
|
202
|
-
onScheduler={() => setSchedulerOpen(true)}
|
|
203
|
-
scheduledCount={scheduledCount}
|
|
204
|
-
onRefresh={refreshNow}
|
|
205
|
-
refreshing={isRefreshing}
|
|
206
|
-
onToggleOptions={() => setOptionsOpen(!optionsOpen)}
|
|
207
|
-
onSortChange={handleSortChange}
|
|
208
|
-
onResetSort={() => handleSortChange('A-Z')}
|
|
209
|
-
onToggleArchived={handleToggleArchived}
|
|
210
|
-
onHideArchived={() => setShowArchived(false)}
|
|
211
|
-
onSearchChange={setSearchQuery}
|
|
212
|
-
onClearSearch={() => setSearchQuery('')}
|
|
213
|
-
onSelectWorkspaces={startWorkspaceSelection}
|
|
214
|
-
selecting={batch.mode !== null}
|
|
216
|
+
<MobileSessionHeader
|
|
217
|
+
onClose={onClose}
|
|
218
|
+
viewLabel={showingSessions ? 'Sessions' : dock.activePanel?.label ?? 'Panel'}
|
|
215
219
|
/>
|
|
216
220
|
|
|
217
|
-
{
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
221
|
+
{/* The strip appears only once there is something to switch TO: with the
|
|
222
|
+
rail off, or with nothing placed on it, a row holding one chip would
|
|
223
|
+
be chrome that never changes. The exception is a stored view that no
|
|
224
|
+
longer resolves — the body is a notice, and without the strip the
|
|
225
|
+
session list would be unreachable (the desktop has the same escape in
|
|
226
|
+
its rail, which always holds Sessions). */}
|
|
227
|
+
{phoneLeftStripVisible(dock.enabled, dock.placed.length, showingSessions) ? (
|
|
228
|
+
<MobileDockStrip panels={dock.catalog} activeId={activeView} onSelect={dock.setActiveView} />
|
|
229
|
+
) : null}
|
|
230
|
+
|
|
231
|
+
{/* The list is HIDDEN, never unmounted, while a panel is on screen — the
|
|
232
|
+
same rule the desktop dock follows (`LeftDockView`): its folder
|
|
233
|
+
expansion, scroll position and batch selection must survive a look at
|
|
234
|
+
another view, and a re-mount would also re-seed every expansion from
|
|
235
|
+
the server's flags. */}
|
|
236
|
+
<div className={showingSessions ? 'contents' : 'hidden'}>
|
|
237
|
+
<>
|
|
238
|
+
<MobileSessionToolbar
|
|
239
|
+
searchQuery={searchQuery}
|
|
240
|
+
sortOption={sortOption}
|
|
241
|
+
optionsOpen={optionsOpen}
|
|
242
|
+
showArchived={showArchived}
|
|
243
|
+
optionsRef={optionsRef}
|
|
244
|
+
onNewSession={handleNewSessionAndClose}
|
|
245
|
+
onNewWorkspace={() => setNewWorkspaceOpen(true)}
|
|
246
|
+
onScheduler={() => setSchedulerOpen(true)}
|
|
247
|
+
scheduledCount={scheduledCount}
|
|
248
|
+
onRefresh={refreshNow}
|
|
249
|
+
refreshing={isRefreshing}
|
|
250
|
+
onToggleOptions={() => setOptionsOpen(!optionsOpen)}
|
|
251
|
+
onSortChange={handleSortChange}
|
|
252
|
+
onResetSort={() => handleSortChange('A-Z')}
|
|
253
|
+
onToggleArchived={handleToggleArchived}
|
|
254
|
+
onHideArchived={() => setShowArchived(false)}
|
|
255
|
+
onSearchChange={setSearchQuery}
|
|
256
|
+
onClearSearch={() => setSearchQuery('')}
|
|
257
|
+
onSelectWorkspaces={startWorkspaceSelection}
|
|
258
|
+
selecting={batch.mode !== null}
|
|
259
|
+
/>
|
|
234
260
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
261
|
+
{initializing ? (
|
|
262
|
+
<MobileSessionListSkeleton />
|
|
263
|
+
) : (
|
|
264
|
+
<MobileSessionList
|
|
265
|
+
folders={processedFolders}
|
|
266
|
+
activeSessionId={activeSessionId}
|
|
267
|
+
expandedFolders={expandedFolders}
|
|
268
|
+
showArchived={showArchived}
|
|
269
|
+
sessionStatus={sessionStatus}
|
|
270
|
+
onSelectSession={handleSelectSession}
|
|
271
|
+
onNewSessionForFolder={handleNewSessionForFolderAndClose}
|
|
272
|
+
onToggleFolder={toggleFolder}
|
|
273
|
+
onToast={pushToast}
|
|
274
|
+
batch={batchBinding}
|
|
275
|
+
onStartSessionSelection={(folderId) => batch.begin('session', folderId)}
|
|
276
|
+
onCancelWait={cancelWait}
|
|
277
|
+
/>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
{batch.mode && (
|
|
281
|
+
<BatchActionBar
|
|
282
|
+
drawer
|
|
283
|
+
mode={batch.mode}
|
|
284
|
+
count={batchSelected.length}
|
|
285
|
+
total={batchAvailable.length}
|
|
286
|
+
busy={batchDelete.isDeleting}
|
|
287
|
+
scopeLabel={scopeFolder?.name}
|
|
288
|
+
onSelectAll={() => batch.selectAllOf(batchAvailable)}
|
|
289
|
+
onUnselectAll={() => batch.unselectAllOf(batchAvailable)}
|
|
290
|
+
onDelete={() => batchDelete.request(batchSelected)}
|
|
291
|
+
onCancel={batch.end}
|
|
292
|
+
/>
|
|
293
|
+
)}
|
|
294
|
+
</>
|
|
295
|
+
</div>
|
|
296
|
+
{showingSessions ? null : (
|
|
297
|
+
<MobileDockBody panelKey={activeView} panel={dock.activePanel} body={dockBody} />
|
|
247
298
|
)}
|
|
248
299
|
|
|
249
300
|
<MobileSessionFooter
|
|
@@ -258,52 +309,24 @@ export function MobileSessionSidebar({
|
|
|
258
309
|
}}
|
|
259
310
|
/>
|
|
260
311
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
isOpen={schedulerOpen}
|
|
270
|
-
onClose={() => setSchedulerOpen(false)}
|
|
271
|
-
activeSessionId={activeSessionId === null ? null : String(activeSessionId)}
|
|
272
|
-
onToast={pushToast}
|
|
273
|
-
/>
|
|
274
|
-
<AboutModal
|
|
275
|
-
isOpen={aboutOpen}
|
|
276
|
-
onClose={() => setAboutOpen(false)}
|
|
312
|
+
<MobileDrawerDialogs
|
|
313
|
+
activeSessionId={activeSessionId}
|
|
314
|
+
newWorkspaceOpen={newWorkspaceOpen}
|
|
315
|
+
onCloseNewWorkspace={() => setNewWorkspaceOpen(false)}
|
|
316
|
+
schedulerOpen={schedulerOpen}
|
|
317
|
+
onCloseScheduler={() => setSchedulerOpen(false)}
|
|
318
|
+
aboutOpen={aboutOpen}
|
|
319
|
+
onCloseAbout={() => setAboutOpen(false)}
|
|
277
320
|
updates={updates}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
Its "Update now" hands the run to the About modal below. */}
|
|
283
|
-
<UpdatePopup updates={updates} />
|
|
284
|
-
|
|
285
|
-
{/* Batch delete confirmation — same dialog the desktop draws. */}
|
|
286
|
-
<BatchDeleteModal
|
|
287
|
-
mode={batch.mode ?? 'session'}
|
|
288
|
-
count={batchDelete.pending.length}
|
|
321
|
+
pushToast={pushToast}
|
|
322
|
+
batchDelete={batchDelete}
|
|
323
|
+
batchMode={batch.mode}
|
|
324
|
+
onEndBatch={batch.end}
|
|
289
325
|
scopeLabel={scopeFolder?.name}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
onClose={() => {
|
|
293
|
-
const refusedCount = batchDelete.outcome?.refused.length ?? 0;
|
|
294
|
-
batchDelete.cancel();
|
|
295
|
-
if (batchDelete.outcome && refusedCount === 0) batch.end();
|
|
296
|
-
}}
|
|
297
|
-
onConfirm={() => {
|
|
298
|
-
const mode = batch.mode ?? 'session';
|
|
299
|
-
void batchDelete.confirm(mode).then((result) => {
|
|
300
|
-
if (result && result.refused.length === 0) batch.end();
|
|
301
|
-
});
|
|
302
|
-
}}
|
|
326
|
+
toasts={toasts}
|
|
327
|
+
dismissToast={dismissToast}
|
|
303
328
|
/>
|
|
304
329
|
|
|
305
|
-
<ToastStack toasts={toasts} onDismiss={dismissToast} />
|
|
306
|
-
|
|
307
330
|
</div>
|
|
308
331
|
);
|
|
309
332
|
}
|
|
@@ -8,6 +8,7 @@ import { useSessionRenaming } from '@/client/hooks/workspace/renaming-sessions';
|
|
|
8
8
|
import { StreamStatusDot } from '@/client/components/common/StreamStatusDot';
|
|
9
9
|
import { BrandWordmark } from '@/client/components/common/brand-wordmark';
|
|
10
10
|
import { HeaderControls } from '@/client/components/common/header-controls';
|
|
11
|
+
import { titlebarPaddingLeft, titlebarPaddingRight } from '@/shared/lib/ui/titlebar';
|
|
11
12
|
|
|
12
13
|
interface MobileHeaderProps {
|
|
13
14
|
activeSessionTitle: string;
|
|
@@ -91,8 +92,8 @@ export function MobileHeader({
|
|
|
91
92
|
className="flex-shrink-0 bg-canvas border-b border-ink/10 flex items-center justify-between gap-2 px-3 z-20 titlebar-drag-region select-none"
|
|
92
93
|
style={{
|
|
93
94
|
height: 'calc(3.5rem + env(safe-area-inset-top, 0px))',
|
|
94
|
-
paddingLeft: '
|
|
95
|
-
paddingRight: '
|
|
95
|
+
paddingLeft: titlebarPaddingLeft('0.75rem', true),
|
|
96
|
+
paddingRight: titlebarPaddingRight('0.75rem', true),
|
|
96
97
|
paddingTop: 'env(safe-area-inset-top, 0px)',
|
|
97
98
|
}}
|
|
98
99
|
>
|
|
@@ -3,6 +3,7 @@ import { FileIcon } from '@/client/components/common/file-icon';
|
|
|
3
3
|
import { DiffPanel } from '@/client/components/workspace/diff-panel';
|
|
4
4
|
import { getGitStatusInfo } from '@/shared/lib/fs/git-status';
|
|
5
5
|
import { diffTabId } from '@/shared/lib/workspace/file-tab-id';
|
|
6
|
+
import { titlebarPaddingLeft, titlebarPaddingRight } from '@/shared/lib/ui/titlebar';
|
|
6
7
|
|
|
7
8
|
interface MobileFullDiffProps {
|
|
8
9
|
diff: {
|
|
@@ -37,8 +38,8 @@ export function MobileFullDiff({ diff, onClose, onFileSaved }: MobileFullDiffPro
|
|
|
37
38
|
style={{
|
|
38
39
|
height: 'calc(3rem + env(safe-area-inset-top, 0px))',
|
|
39
40
|
paddingTop: 'env(safe-area-inset-top, 0px)',
|
|
40
|
-
paddingLeft: '
|
|
41
|
-
paddingRight: '
|
|
41
|
+
paddingLeft: titlebarPaddingLeft('0.75rem', true),
|
|
42
|
+
paddingRight: titlebarPaddingRight('0.75rem', true),
|
|
42
43
|
}}
|
|
43
44
|
>
|
|
44
45
|
<div className="flex items-center space-x-2 min-w-0 pr-2">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ArrowLeft, Check, Copy, Download, Eye, EyeOff, Save, WrapText, ZoomIn, ZoomOut } from 'lucide-preact';
|
|
2
2
|
import { FileIcon } from '@/client/components/common/file-icon';
|
|
3
|
+
import { titlebarPaddingLeft, titlebarPaddingRight } from '@/shared/lib/ui/titlebar';
|
|
3
4
|
|
|
4
5
|
interface EditorHeaderProps {
|
|
5
6
|
fileName: string;
|
|
@@ -47,8 +48,8 @@ export function EditorHeader({
|
|
|
47
48
|
style={{
|
|
48
49
|
height: 'calc(3rem + env(safe-area-inset-top, 0px))',
|
|
49
50
|
paddingTop: 'env(safe-area-inset-top, 0px)',
|
|
50
|
-
paddingLeft: '
|
|
51
|
-
paddingRight: '
|
|
51
|
+
paddingLeft: titlebarPaddingLeft('0.75rem', true),
|
|
52
|
+
paddingRight: titlebarPaddingRight('0.75rem', true),
|
|
52
53
|
}}
|
|
53
54
|
>
|
|
54
55
|
<div className="flex items-center space-x-2 min-w-0 pr-2">
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The phone session drawer's dialogs.
|
|
8
|
+
*
|
|
9
|
+
* Split out of `SessionSidebar` for the file ceiling, and it is a clean seam:
|
|
10
|
+
* every one of these is app-level (a workspace, a schedule, the console's own
|
|
11
|
+
* version and update, a batch delete confirmation, the toast stack) and none of
|
|
12
|
+
* them reads the drawer's own view state. Keeping them here also means the
|
|
13
|
+
* drawer's body — session list or a placed panel — stays readable as one
|
|
14
|
+
* expression rather than being buried between modals.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { ToastStack } from '@/client/components/common/ToastStack';
|
|
18
|
+
import { AboutModal, NewWorkspaceModal } from '@/client/components/layout/session-sidebar/Modals';
|
|
19
|
+
import { SchedulerModal } from '@/client/components/layout/session-sidebar/scheduler-modal';
|
|
20
|
+
import { UpdatePopup } from '@/client/components/layout/update-popup';
|
|
21
|
+
import { BatchDeleteModal } from '@/client/components/common/batch-selection/BatchDeleteModal';
|
|
22
|
+
import { createWorkspaceFolder, cloneWorkspaceFolder } from '@/client/hooks/workspace/new-workspace';
|
|
23
|
+
import type { BatchDeleteRun } from '@/client/hooks/workspace/batch-delete-run';
|
|
24
|
+
import type { UseUpdatesResult } from '@/client/hooks/ui/updates';
|
|
25
|
+
import type { ToastsHandle } from '@/client/hooks/ui/toasts';
|
|
26
|
+
|
|
27
|
+
interface MobileDrawerDialogsProps {
|
|
28
|
+
activeSessionId: number | string | null;
|
|
29
|
+
newWorkspaceOpen: boolean;
|
|
30
|
+
onCloseNewWorkspace: () => void;
|
|
31
|
+
schedulerOpen: boolean;
|
|
32
|
+
onCloseScheduler: () => void;
|
|
33
|
+
aboutOpen: boolean;
|
|
34
|
+
onCloseAbout: () => void;
|
|
35
|
+
updates: UseUpdatesResult;
|
|
36
|
+
pushToast: ToastsHandle['pushToast'];
|
|
37
|
+
batchDelete: BatchDeleteRun;
|
|
38
|
+
/** The batch mode the confirmation names, and the way out of it. */
|
|
39
|
+
batchMode: 'workspace' | 'session' | null;
|
|
40
|
+
onEndBatch: () => void;
|
|
41
|
+
scopeLabel?: string;
|
|
42
|
+
toasts: ToastsHandle['toasts'];
|
|
43
|
+
dismissToast: ToastsHandle['dismissToast'];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function MobileDrawerDialogs({
|
|
47
|
+
activeSessionId,
|
|
48
|
+
newWorkspaceOpen,
|
|
49
|
+
onCloseNewWorkspace,
|
|
50
|
+
schedulerOpen,
|
|
51
|
+
onCloseScheduler,
|
|
52
|
+
aboutOpen,
|
|
53
|
+
onCloseAbout,
|
|
54
|
+
updates,
|
|
55
|
+
pushToast,
|
|
56
|
+
batchDelete,
|
|
57
|
+
batchMode,
|
|
58
|
+
onEndBatch,
|
|
59
|
+
scopeLabel,
|
|
60
|
+
toasts,
|
|
61
|
+
dismissToast,
|
|
62
|
+
}: MobileDrawerDialogsProps) {
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<NewWorkspaceModal
|
|
66
|
+
isOpen={newWorkspaceOpen}
|
|
67
|
+
onClose={onCloseNewWorkspace}
|
|
68
|
+
onCreate={createWorkspaceFolder}
|
|
69
|
+
onClone={cloneWorkspaceFolder}
|
|
70
|
+
/>
|
|
71
|
+
<SchedulerModal
|
|
72
|
+
isOpen={schedulerOpen}
|
|
73
|
+
onClose={onCloseScheduler}
|
|
74
|
+
activeSessionId={activeSessionId === null ? null : String(activeSessionId)}
|
|
75
|
+
onToast={pushToast}
|
|
76
|
+
/>
|
|
77
|
+
<AboutModal isOpen={aboutOpen} onClose={onCloseAbout} updates={updates} onToast={pushToast} />
|
|
78
|
+
|
|
79
|
+
{/* "Update available" announcement — once per version, gated on the check.
|
|
80
|
+
Its "Update now" hands the run to the About modal. */}
|
|
81
|
+
<UpdatePopup updates={updates} />
|
|
82
|
+
|
|
83
|
+
{/* Batch delete confirmation — same dialog the desktop draws. */}
|
|
84
|
+
<BatchDeleteModal
|
|
85
|
+
mode={batchMode ?? 'session'}
|
|
86
|
+
count={batchDelete.pending.length}
|
|
87
|
+
scopeLabel={scopeLabel}
|
|
88
|
+
isDeleting={batchDelete.isDeleting}
|
|
89
|
+
outcome={batchDelete.outcome}
|
|
90
|
+
onClose={() => {
|
|
91
|
+
const refusedCount = batchDelete.outcome?.refused.length ?? 0;
|
|
92
|
+
batchDelete.cancel();
|
|
93
|
+
if (batchDelete.outcome && refusedCount === 0) onEndBatch();
|
|
94
|
+
}}
|
|
95
|
+
onConfirm={() => {
|
|
96
|
+
const mode = batchMode ?? 'session';
|
|
97
|
+
void batchDelete.confirm(mode).then((result) => {
|
|
98
|
+
if (result && result.refused.length === 0) onEndBatch();
|
|
99
|
+
});
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
<ToastStack toasts={toasts} onDismiss={dismissToast} />
|
|
104
|
+
</>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The phone's LEFT dock: the chip strip and the body of a placed panel.
|
|
8
|
+
*
|
|
9
|
+
* The desktop's left column is a dock — the session list plus whatever plugin
|
|
10
|
+
* the user placed beside it, switched by a vertical rail. A phone has no room
|
|
11
|
+
* for a rail, so the rail is the same horizontal chip strip the right drawer
|
|
12
|
+
* uses (`panel-chips.tsx`), and the body is the same `panel.render`.
|
|
13
|
+
*
|
|
14
|
+
* The session list is NOT rendered here: the phone's list is its own component
|
|
15
|
+
* (`MobileSessionList`), unlike the desktop's `SessionsView`, so the caller
|
|
16
|
+
* keeps drawing it and only hands over the OTHER views. That is also why the
|
|
17
|
+
* strip is drawn only while it has something else to switch to.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { Suspense } from 'preact/compat';
|
|
21
|
+
import { PanelHostProvider } from '@kabmo-labs/kabmo-ui';
|
|
22
|
+
import type { ResolvedPanel } from '@/client/components/workspace/plugin-panel/catalog';
|
|
23
|
+
import { PluginPanelBody } from '@/client/components/workspace/plugin-panel/resolve';
|
|
24
|
+
import type { PanelBodyProps } from '@/client/components/workspace/panels/builtin';
|
|
25
|
+
import { PanelChips } from '@/client/components/mobile/panel-chips';
|
|
26
|
+
|
|
27
|
+
/** The strip row, drawn under the drawer's title. */
|
|
28
|
+
export function MobileDockStrip({
|
|
29
|
+
panels,
|
|
30
|
+
activeId,
|
|
31
|
+
onSelect,
|
|
32
|
+
}: {
|
|
33
|
+
panels: readonly ResolvedPanel[];
|
|
34
|
+
activeId: string;
|
|
35
|
+
onSelect: (id: string) => void;
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<div className="px-3 py-1.5 border-b border-ink/10 bg-canvas flex-shrink-0">
|
|
39
|
+
<PanelChips panels={panels} activeId={activeId} onSelect={onSelect} />
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The body of one placed panel.
|
|
46
|
+
*
|
|
47
|
+
* A selection naming a panel that is switched off, uninstalled or still loading
|
|
48
|
+
* has no entry — `PluginPanelBody` says which, rather than leaving the drawer
|
|
49
|
+
* blank. That is the same notice the desktop dock draws.
|
|
50
|
+
*/
|
|
51
|
+
export function MobileDockBody({
|
|
52
|
+
panelKey,
|
|
53
|
+
panel,
|
|
54
|
+
body,
|
|
55
|
+
}: {
|
|
56
|
+
panelKey: string;
|
|
57
|
+
panel?: ResolvedPanel;
|
|
58
|
+
body: PanelBodyProps;
|
|
59
|
+
}) {
|
|
60
|
+
if (!panel) {
|
|
61
|
+
return (
|
|
62
|
+
<div className="flex-1 min-h-0">
|
|
63
|
+
<PluginPanelBody panelKey={panelKey} />
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return (
|
|
68
|
+
<div className="flex-1 min-h-0 overflow-hidden relative">
|
|
69
|
+
<Suspense
|
|
70
|
+
fallback={
|
|
71
|
+
<div className="h-full flex items-center justify-center text-ink/40">
|
|
72
|
+
<span className="text-xs font-mono">Loading…</span>
|
|
73
|
+
</div>
|
|
74
|
+
}
|
|
75
|
+
>
|
|
76
|
+
<PanelHostProvider value={body}>{panel.render(body)}</PanelHostProvider>
|
|
77
|
+
</Suspense>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { X } from 'lucide-preact';
|
|
2
2
|
import { BrandWordmark } from '@/client/components/common/brand-wordmark';
|
|
3
|
+
import { titlebarPaddingLeft, titlebarPaddingRight } from '@/shared/lib/ui/titlebar';
|
|
3
4
|
|
|
4
|
-
export function MobileSessionHeader({ onClose }: { onClose: () => void }) {
|
|
5
|
+
export function MobileSessionHeader({ onClose, viewLabel = 'Sessions' }: { onClose: () => void; viewLabel?: string }) {
|
|
5
6
|
return (
|
|
6
7
|
<div
|
|
7
|
-
className="border-b border-ink/10 flex items-center justify-between px-3
|
|
8
|
+
className="border-b border-ink/10 flex items-center justify-between px-3 flex-shrink-0 bg-canvas titlebar-drag-region select-none"
|
|
8
9
|
style={{
|
|
9
10
|
height: 'calc(3.5rem + env(safe-area-inset-top, 0px))',
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
// `px-3`, the toolbar's own inset: the wordmark above and the New
|
|
12
|
+
// Session button below share one left edge, which the header's old
|
|
13
|
+
// 14px did not.
|
|
14
|
+
paddingLeft: titlebarPaddingLeft('0.75rem', true),
|
|
15
|
+
// The drawer is the full window width, so on a desktop showing the
|
|
16
|
+
// mobile layout its close button sits under the window controls too.
|
|
17
|
+
paddingRight: titlebarPaddingRight('0.75rem', true),
|
|
12
18
|
paddingTop: 'env(safe-area-inset-top, 0px)',
|
|
13
19
|
}}
|
|
14
20
|
>
|
|
@@ -17,7 +23,7 @@ export function MobileSessionHeader({ onClose }: { onClose: () => void }) {
|
|
|
17
23
|
<BrandWordmark className="text-[16px]" />
|
|
18
24
|
</span>
|
|
19
25
|
<span className="text-ink/25 text-xs select-none">/</span>
|
|
20
|
-
<span className="text-[11px] font-semibold text-ink/60 uppercase tracking-wider font-mono">
|
|
26
|
+
<span className="text-[11px] font-semibold text-ink/60 uppercase tracking-wider font-mono">{viewLabel}</span>
|
|
21
27
|
</div>
|
|
22
28
|
|
|
23
29
|
<button
|