@kabmo-labs/kabmo 0.3.1 → 0.4.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-twsk4wfa.js} +791 -791
- 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/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/SessionItem.tsx +30 -5
- 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/RightSidebar.tsx +8 -5
- package/src/client/components/mobile/SessionSidebar.tsx +6 -0
- package/src/client/components/mobile/mobile-session-sidebar/Header.tsx +6 -3
- 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 +41 -13
- package/src/client/components/mobile/mobile-session-sidebar/Skeleton.tsx +2 -2
- 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 +14 -9
- 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 +154 -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/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/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/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/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/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/dist/client/static/js/index-pdp5dwc8.css +0 -1
|
@@ -4,11 +4,14 @@ import { BrandWordmark } from '@/client/components/common/brand-wordmark';
|
|
|
4
4
|
export function MobileSessionHeader({ onClose }: { onClose: () => void }) {
|
|
5
5
|
return (
|
|
6
6
|
<div
|
|
7
|
-
className="border-b border-ink/10 flex items-center justify-between px-3
|
|
7
|
+
className="border-b border-ink/10 flex items-center justify-between px-3 flex-shrink-0 bg-canvas titlebar-drag-region select-none"
|
|
8
8
|
style={{
|
|
9
9
|
height: 'calc(3.5rem + env(safe-area-inset-top, 0px))',
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// `px-3`, the toolbar's own inset: the wordmark above and the New
|
|
11
|
+
// Session button below share one left edge, which the header's old
|
|
12
|
+
// 14px did not.
|
|
13
|
+
paddingLeft: 'max(0.75rem, env(safe-area-inset-left, 0px), env(titlebar-area-x, 0px))',
|
|
14
|
+
paddingRight: 'max(0.75rem, env(safe-area-inset-right, 0px))',
|
|
12
15
|
paddingTop: 'env(safe-area-inset-top, 0px)',
|
|
13
16
|
}}
|
|
14
17
|
>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRef, useState } from 'preact/hooks';
|
|
2
|
-
import { ChevronDown, ChevronRight,
|
|
2
|
+
import { ChevronDown, ChevronRight, GitBranch, MessageSquare, MoreHorizontal, Plus } from 'lucide-preact';
|
|
3
3
|
import { useSearchParams } from '@/client/lib/router/search-params';
|
|
4
4
|
import { MobileSessionRow } from '@/client/components/mobile/mobile-session-sidebar/SessionRow';
|
|
5
5
|
import { SubagentList } from '@/client/components/common/subagent-list';
|
|
@@ -17,6 +17,7 @@ import type { WorkspaceFolderData } from '@/shared/types';
|
|
|
17
17
|
import type { BatchBinding } from '@/client/components/common/batch-selection/types';
|
|
18
18
|
import { folderBatchState } from '@/client/components/common/batch-selection/folder-state';
|
|
19
19
|
import { SelectionCheckbox } from '@/client/components/common/batch-selection/SelectionCheckbox';
|
|
20
|
+
import { SessionRevealRow } from '@/client/components/mobile/mobile-session-sidebar/RevealRow';
|
|
20
21
|
|
|
21
22
|
interface MobileSessionCategoryProps {
|
|
22
23
|
folder: WorkspaceFolderData;
|
|
@@ -32,6 +33,8 @@ interface MobileSessionCategoryProps {
|
|
|
32
33
|
batch?: BatchBinding;
|
|
33
34
|
/** Start a session selection scoped to this workspace, from its `⋯` menu. */
|
|
34
35
|
onStartSessionSelection?: (folderId: number) => void;
|
|
36
|
+
/** Drop a held prompt from its row's menu. */
|
|
37
|
+
onCancelWait?: (waitId: string) => void;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
export function MobileSessionCategory({
|
|
@@ -46,6 +49,7 @@ export function MobileSessionCategory({
|
|
|
46
49
|
onToast,
|
|
47
50
|
batch,
|
|
48
51
|
onStartSessionSelection,
|
|
52
|
+
onCancelWait,
|
|
49
53
|
}: MobileSessionCategoryProps) {
|
|
50
54
|
const { visibleCount, showMore, showLess, canShowLess } = useShowMore();
|
|
51
55
|
const { refresh } = useSidebarData();
|
|
@@ -125,7 +129,7 @@ export function MobileSessionCategory({
|
|
|
125
129
|
<div className="mb-4">
|
|
126
130
|
<div
|
|
127
131
|
onClick={batch ? undefined : onToggleExpand}
|
|
128
|
-
className="flex items-center justify-between
|
|
132
|
+
className="flex items-center justify-between gap-2 py-2 cursor-pointer hover:bg-ink/5 rounded-lg select-none transition-colors"
|
|
129
133
|
>
|
|
130
134
|
<div className="flex items-center space-x-2 min-w-0">
|
|
131
135
|
{folderCheckbox && folderSummary ? (
|
|
@@ -138,11 +142,11 @@ export function MobileSessionCategory({
|
|
|
138
142
|
/>
|
|
139
143
|
) : (
|
|
140
144
|
<>
|
|
141
|
-
{/* The folder glyph occupies the same 16px
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
{/* The folder glyph occupies the same 16px slot the session rows give
|
|
146
|
+
their status/roster glyph, and the row carries NO leading inset of
|
|
147
|
+
its own: the drawer list's `p-3` is the gutter, so this glyph starts
|
|
148
|
+
at the same x as the New Session button above it (12px) and as the
|
|
149
|
+
search field's frame. One left edge down the whole drawer. */}
|
|
146
150
|
<span className="w-4 h-4 flex items-center justify-center flex-shrink-0">
|
|
147
151
|
{folder.customIconUrl ? (
|
|
148
152
|
<img
|
|
@@ -227,14 +231,23 @@ export function MobileSessionCategory({
|
|
|
227
231
|
)}
|
|
228
232
|
</div>
|
|
229
233
|
|
|
230
|
-
{
|
|
234
|
+
{/* The disclosure glyph's BOX ends on the list's content edge — the
|
|
235
|
+
same x the toolbar row's last control ends on above it — and its
|
|
236
|
+
ink sits 4px inside it (`pr-1`), which is the inset a session
|
|
237
|
+
row's `⋯` / roster chevron uses. So the drawer's right column is
|
|
238
|
+
one line of INK, not just one line of boxes: measured before, the
|
|
239
|
+
folder chevron's ink ran 6px further right than every row glyph
|
|
240
|
+
beneath it. */}
|
|
241
|
+
<span className="flex items-center justify-center pr-1">
|
|
242
|
+
{isExpanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
243
|
+
</span>
|
|
231
244
|
</>
|
|
232
245
|
)}
|
|
233
246
|
</div>
|
|
234
247
|
</div>
|
|
235
248
|
|
|
236
249
|
{listOpen && (
|
|
237
|
-
<div className="mt-0.5 space-y-0.5
|
|
250
|
+
<div className="mt-0.5 space-y-0.5">
|
|
238
251
|
{visibleSessions.map((session) => {
|
|
239
252
|
const sessionKey = String(session.id);
|
|
240
253
|
const isSessionActive = String(activeSessionId) === sessionKey;
|
|
@@ -272,6 +285,12 @@ export function MobileSessionCategory({
|
|
|
272
285
|
onRenameWithAi={
|
|
273
286
|
sessionKey.startsWith('new-') ? undefined : () => void handleRenameWithAi(session)
|
|
274
287
|
}
|
|
288
|
+
waitingFor={session.waitingFor ? { title: session.waitingFor.title } : undefined}
|
|
289
|
+
onCancelWait={
|
|
290
|
+
session.waitingFor
|
|
291
|
+
? () => onCancelWait?.(session.waitingFor!.waitId)
|
|
292
|
+
: undefined
|
|
293
|
+
}
|
|
275
294
|
selectable={sessionCheckboxes}
|
|
276
295
|
selected={Boolean(batch?.selected.includes(sessionKey))}
|
|
277
296
|
onToggleSelect={batch ? () => batch.toggle(sessionKey) : undefined}
|
|
@@ -283,42 +302,13 @@ export function MobileSessionCategory({
|
|
|
283
302
|
);
|
|
284
303
|
})}
|
|
285
304
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
<button
|
|
293
|
-
type="button"
|
|
294
|
-
onClick={showMore}
|
|
295
|
-
className="flex items-center min-w-0 text-left pl-2 pr-3 py-2 text-xs text-ink/60 hover:text-ink"
|
|
296
|
-
>
|
|
297
|
-
{/* `pl-2` inside the `pl-2` list is what lands this chevron's
|
|
298
|
-
centre on the folder header's icon column (16px + 8px), and
|
|
299
|
-
the label then starts at 40px — the x a row's title occupies
|
|
300
|
-
(16px slot + 8px gap). The `space-x-1.5` this replaced put
|
|
301
|
-
both 4px left of the list they belong to. */}
|
|
302
|
-
<span className="w-4 h-4 flex-shrink-0 flex items-center justify-center">
|
|
303
|
-
<ChevronDown size={12} />
|
|
304
|
-
</span>
|
|
305
|
-
<span className="w-2 shrink-0" />
|
|
306
|
-
<span className="truncate">Show more sessions ({hiddenCount})</span>
|
|
307
|
-
</button>
|
|
308
|
-
)}
|
|
305
|
+
<SessionRevealRow
|
|
306
|
+
hiddenCount={sessionCheckboxes ? 0 : hiddenCount}
|
|
307
|
+
canShowLess={!sessionCheckboxes && canShowLess}
|
|
308
|
+
onShowMore={showMore}
|
|
309
|
+
onShowLess={showLess}
|
|
310
|
+
/>
|
|
309
311
|
|
|
310
|
-
{canShowLess && (
|
|
311
|
-
<button
|
|
312
|
-
type="button"
|
|
313
|
-
onClick={showLess}
|
|
314
|
-
className="ml-auto shrink-0 flex items-center gap-1 text-xs text-ink/60 hover:text-ink px-3 py-2"
|
|
315
|
-
>
|
|
316
|
-
<ChevronUp size={12} />
|
|
317
|
-
<span className="truncate">Show less</span>
|
|
318
|
-
</button>
|
|
319
|
-
)}
|
|
320
|
-
</div>
|
|
321
|
-
)}
|
|
322
312
|
</div>
|
|
323
313
|
)}
|
|
324
314
|
|
|
@@ -16,6 +16,8 @@ interface MobileSessionListProps {
|
|
|
16
16
|
/** Present only while a batch selection is active. */
|
|
17
17
|
batch?: BatchBinding;
|
|
18
18
|
onStartSessionSelection?: (folderId: number) => void;
|
|
19
|
+
/** Drop a held prompt from its row's menu. */
|
|
20
|
+
onCancelWait?: (waitId: string) => void;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export function MobileSessionList({
|
|
@@ -30,12 +32,17 @@ export function MobileSessionList({
|
|
|
30
32
|
onToast,
|
|
31
33
|
batch,
|
|
32
34
|
onStartSessionSelection,
|
|
35
|
+
onCancelWait,
|
|
33
36
|
}: MobileSessionListProps) {
|
|
34
37
|
// No fade wiring here: the app-wide scrollbar rule reveals this list's bar
|
|
35
38
|
// from the document's own capture-phase listener (`lib/scrollbar-activity`),
|
|
36
39
|
// so the list does not need a ref, a handler, or a re-render per scroll.
|
|
40
|
+
// `pl-3 pr-2`, not an even `p-3`: this container scrolls, so its scrollbar is
|
|
41
|
+
// taken out of the box and the same 12px on both sides LOOKS wider on the
|
|
42
|
+
// right. One step less on the right evens the optical margin back up, and it
|
|
43
|
+
// is why the rows below are aligned to 12 and not to 12 + the bar.
|
|
37
44
|
return (
|
|
38
|
-
<div className="flex-1 scrollbar-overlay-container
|
|
45
|
+
<div className="flex-1 scrollbar-overlay-container pl-3 pr-2 py-3">
|
|
39
46
|
{folders.length === 0 ? (
|
|
40
47
|
<div className="text-center py-12 text-xs text-ink/50 italic">
|
|
41
48
|
No matching sessions found
|
|
@@ -55,6 +62,7 @@ export function MobileSessionList({
|
|
|
55
62
|
onToast={onToast}
|
|
56
63
|
batch={batch}
|
|
57
64
|
onStartSessionSelection={onStartSessionSelection}
|
|
65
|
+
onCancelWait={onCancelWait}
|
|
58
66
|
/>
|
|
59
67
|
))
|
|
60
68
|
)}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The drawer's reveal / collapse row, below a workspace's sessions.
|
|
8
|
+
*
|
|
9
|
+
* Split out of `Item.tsx` (which is the folder row AND its list) so that file
|
|
10
|
+
* stays under the repo's per-file ceiling. Parity with the desktop category is
|
|
11
|
+
* the whole point: the same window, so the phone cannot offer a different
|
|
12
|
+
* boundary than the sidebar.
|
|
13
|
+
*
|
|
14
|
+
* The chevron starts at the list's own gutter — the same x as a session row's
|
|
15
|
+
* status slot and the folder header's icon — because the list carries no
|
|
16
|
+
* `pl-2` of its own any more. The label then lands on the title column
|
|
17
|
+
* (16px slot + 8px gap).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { ChevronDown, ChevronUp } from 'lucide-preact';
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
hiddenCount: number;
|
|
24
|
+
canShowLess: boolean;
|
|
25
|
+
onShowMore: () => void;
|
|
26
|
+
onShowLess: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function SessionRevealRow({ hiddenCount, canShowLess, onShowMore, onShowLess }: Props) {
|
|
30
|
+
if (hiddenCount <= 0 && !canShowLess) return null;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex items-center gap-1">
|
|
34
|
+
{hiddenCount > 0 && (
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
onClick={onShowMore}
|
|
38
|
+
className="flex items-center min-w-0 text-left py-2 pr-3 text-xs text-ink/60 hover:text-ink"
|
|
39
|
+
>
|
|
40
|
+
<span className="w-4 h-4 flex-shrink-0 flex items-center justify-center">
|
|
41
|
+
<ChevronDown size={12} />
|
|
42
|
+
</span>
|
|
43
|
+
<span className="w-2 shrink-0" />
|
|
44
|
+
<span className="truncate">Show more sessions ({hiddenCount})</span>
|
|
45
|
+
</button>
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
{canShowLess && (
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
onClick={onShowLess}
|
|
52
|
+
className="ml-auto shrink-0 flex items-center gap-1 text-xs text-ink/60 hover:text-ink pl-3 py-2"
|
|
53
|
+
>
|
|
54
|
+
<ChevronUp size={12} />
|
|
55
|
+
<span className="truncate">Show less</span>
|
|
56
|
+
</button>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Check, ChevronDown, ChevronRight, CircleQuestionMark, Loader2, MoreHorizontal } from 'lucide-preact';
|
|
1
|
+
import { Check, ChevronDown, ChevronRight, CircleQuestionMark, Hourglass, Loader2, MoreHorizontal } from 'lucide-preact';
|
|
2
2
|
import type { SessionItemData } from '@/shared/types';
|
|
3
3
|
import { useInlineRename } from '@/client/hooks/ui/inline-rename';
|
|
4
4
|
import { useSessionRenaming } from '@/client/hooks/workspace/renaming-sessions';
|
|
@@ -28,6 +28,11 @@ export interface MobileSessionRowProps {
|
|
|
28
28
|
onRenameWithAi?: () => void;
|
|
29
29
|
/** Draw a checkbox and let the row's tap toggle the selection instead of
|
|
30
30
|
* opening the session (batch mode). */
|
|
31
|
+
/** The row's prompt is held until another session finishes (see the desktop
|
|
32
|
+
* row's `waitingFor`: a pending chat has nothing else to show). */
|
|
33
|
+
waitingFor?: { title: string | null };
|
|
34
|
+
/** Drop this row's held prompt, from the row's own menu. */
|
|
35
|
+
onCancelWait?: () => void;
|
|
31
36
|
selectable?: boolean;
|
|
32
37
|
selected?: boolean;
|
|
33
38
|
onToggleSelect?: () => void;
|
|
@@ -47,6 +52,8 @@ export function MobileSessionRow({
|
|
|
47
52
|
onDelete,
|
|
48
53
|
onRename,
|
|
49
54
|
onRenameWithAi,
|
|
55
|
+
waitingFor,
|
|
56
|
+
onCancelWait,
|
|
50
57
|
selectable = false,
|
|
51
58
|
selected = false,
|
|
52
59
|
onToggleSelect,
|
|
@@ -65,7 +72,8 @@ export function MobileSessionRow({
|
|
|
65
72
|
const renaming = useSessionRenaming(session.id);
|
|
66
73
|
// One trigger for every row action; also reachable by long-press contextmenu.
|
|
67
74
|
const menu = useRowMenu();
|
|
68
|
-
const hasActions = Boolean(onRename || onArchive || onDelete);
|
|
75
|
+
const hasActions = Boolean(onRename || onArchive || onDelete || onCancelWait);
|
|
76
|
+
const waiting = Boolean(waitingFor);
|
|
69
77
|
|
|
70
78
|
// The roster toggle is the row's LAST element, so its glyph lands in the same
|
|
71
79
|
// column as the folder header's expand chevron — one vertical line of
|
|
@@ -101,6 +109,15 @@ export function MobileSessionRow({
|
|
|
101
109
|
data-testid="session-item"
|
|
102
110
|
data-session-id={String(session.id)}
|
|
103
111
|
onContextMenu={hasActions && !selectable ? menu.openAtCursor : undefined}
|
|
112
|
+
// The tint covers the WHOLE row — the `⋯` included. It is the row that
|
|
113
|
+
// is active, not the part of it left over after the triggers, and a
|
|
114
|
+
// block that stopped short of the trigger read as a half-painted
|
|
115
|
+
// selection.
|
|
116
|
+
//
|
|
117
|
+
// The gap between the trigger's ink and the block's rounded corner is
|
|
118
|
+
// `pr-1.5` INSIDE the trigger (see below), never padding on this row:
|
|
119
|
+
// a row inset would shrink the tint and pull every trailing glyph 6px
|
|
120
|
+
// left of the folder header's chevron.
|
|
104
121
|
className={`w-full rounded-lg flex items-center transition-colors ${
|
|
105
122
|
selectable && selected
|
|
106
123
|
? 'bg-ink/10 ring-1 ring-inset ring-ink/20 text-ink'
|
|
@@ -110,7 +127,7 @@ export function MobileSessionRow({
|
|
|
110
127
|
<button
|
|
111
128
|
type="button"
|
|
112
129
|
onClick={selectable ? onToggleSelect : onSelect}
|
|
113
|
-
className="flex-1 text-left
|
|
130
|
+
className="flex-1 text-left py-2 flex items-center justify-between min-w-0"
|
|
114
131
|
>
|
|
115
132
|
<div className="flex items-center min-w-0 pr-2">
|
|
116
133
|
{/* Status slot, 16px at the same x as the folder header's icon slot,
|
|
@@ -135,6 +152,10 @@ export function MobileSessionRow({
|
|
|
135
152
|
</span>
|
|
136
153
|
) : awaitingInput ? (
|
|
137
154
|
<CircleQuestionMark size={13} className="text-ink/70 animate-pulse" />
|
|
155
|
+
) : waiting ? (
|
|
156
|
+
<span role="status" aria-label={`Waiting for ${waitingFor?.title ?? 'another session'}`}>
|
|
157
|
+
<Hourglass size={13} className="text-ink/60 animate-pulse" />
|
|
158
|
+
</span>
|
|
138
159
|
) : status === 'stream' ? (
|
|
139
160
|
<Loader2 size={13} className="text-ink/50 animate-spin" />
|
|
140
161
|
) : status ? (
|
|
@@ -142,7 +163,14 @@ export function MobileSessionRow({
|
|
|
142
163
|
) : null}
|
|
143
164
|
</span>
|
|
144
165
|
)}
|
|
145
|
-
<span className="
|
|
166
|
+
<span className="flex flex-col min-w-0 ml-2 leading-snug">
|
|
167
|
+
<span className="text-xs truncate">{title}</span>
|
|
168
|
+
{waiting && (
|
|
169
|
+
<span className="text-[10px] text-ink/40 truncate">
|
|
170
|
+
waiting for {waitingFor?.title ?? 'another session'}
|
|
171
|
+
</span>
|
|
172
|
+
)}
|
|
173
|
+
</span>
|
|
146
174
|
</div>
|
|
147
175
|
|
|
148
176
|
<span className="text-[11px] text-ink/45 font-mono flex-shrink-0 ml-2">{timeAgo ?? ''}</span>
|
|
@@ -151,10 +179,10 @@ export function MobileSessionRow({
|
|
|
151
179
|
{/* One trigger for rename / archive / delete, always visible: a touch
|
|
152
180
|
surface has no hover, and the always-on action buttons this replaced
|
|
153
181
|
consumed a third of a phone's row width.
|
|
154
|
-
`
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
182
|
+
`pl-1.5 pr-1`: the BOX still ends on the row's trailing edge (so its
|
|
183
|
+
right edge matches the folder header's chevron and the toolbar's
|
|
184
|
+
last control), while the glyph's ink sits 4px inside it — clear of
|
|
185
|
+
the tinted block's rounded corner without shrinking that block. */}
|
|
158
186
|
{hasActions && !selectable && (
|
|
159
187
|
<button
|
|
160
188
|
type="button"
|
|
@@ -163,15 +191,14 @@ export function MobileSessionRow({
|
|
|
163
191
|
aria-label="Session actions"
|
|
164
192
|
aria-haspopup="menu"
|
|
165
193
|
aria-expanded={menu.anchor !== null}
|
|
166
|
-
className=
|
|
194
|
+
className="flex-shrink-0 py-1.5 pl-1.5 pr-1 text-ink/35 hover:text-ink rounded-lg cursor-pointer"
|
|
167
195
|
>
|
|
168
196
|
<MoreHorizontal size={16} />
|
|
169
197
|
</button>
|
|
170
198
|
)}
|
|
171
199
|
|
|
172
|
-
{/* Last element, so its glyph
|
|
173
|
-
|
|
174
|
-
touch-sized tap target. */}
|
|
200
|
+
{/* Last element, so its glyph shares the trigger's column — the same
|
|
201
|
+
inset pair and the same 4px ink margin. */}
|
|
175
202
|
{showChevron && !selectable && (
|
|
176
203
|
<button
|
|
177
204
|
type="button"
|
|
@@ -179,7 +206,7 @@ export function MobileSessionRow({
|
|
|
179
206
|
title={isExpanded ? 'Collapse subagents' : 'Expand subagents'}
|
|
180
207
|
aria-expanded={isExpanded}
|
|
181
208
|
aria-label={isExpanded ? 'Collapse subagents' : 'Expand subagents'}
|
|
182
|
-
className="flex-shrink-0
|
|
209
|
+
className="flex-shrink-0 py-1.5 pl-1.5 pr-1 flex items-center justify-center text-ink/50 hover:text-ink rounded-lg cursor-pointer"
|
|
183
210
|
>
|
|
184
211
|
{isExpanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
185
212
|
</button>
|
|
@@ -194,6 +221,7 @@ export function MobileSessionRow({
|
|
|
194
221
|
onRenameWithAi={onRenameWithAi}
|
|
195
222
|
onArchive={onArchive}
|
|
196
223
|
onDelete={onDelete}
|
|
224
|
+
onCancelWait={onCancelWait}
|
|
197
225
|
onClose={menu.close}
|
|
198
226
|
/>
|
|
199
227
|
)}
|
|
@@ -16,12 +16,12 @@ export function MobileSessionListSkeleton() {
|
|
|
16
16
|
<div className="flex-1 p-3 space-y-4" aria-hidden="true">
|
|
17
17
|
{PLACEHOLDER_FOLDERS.map((rows, folderIndex) => (
|
|
18
18
|
<div key={folderIndex} className="space-y-2">
|
|
19
|
-
<div className="flex items-center gap-2
|
|
19
|
+
<div className="flex items-center gap-2 py-2">
|
|
20
20
|
<span className="w-4 h-4 rounded-sm animate-pulse bg-ink/10" />
|
|
21
21
|
<span className="h-3 w-24 rounded animate-pulse bg-ink/10" />
|
|
22
22
|
</div>
|
|
23
23
|
{Array.from({ length: rows }, (_, rowIndex) => (
|
|
24
|
-
<div key={rowIndex} className="flex items-center gap-2 rounded
|
|
24
|
+
<div key={rowIndex} className="flex items-center gap-2 rounded py-2">
|
|
25
25
|
<span className="w-4 h-4 rounded-full animate-pulse bg-ink/10" />
|
|
26
26
|
<span
|
|
27
27
|
className="h-3 rounded animate-pulse bg-ink/10"
|
|
@@ -6,7 +6,7 @@ import { PluginMark } from '@/client/components/settings/categories/panel-plugin
|
|
|
6
6
|
import { PluginReadmeModal } from '@/client/components/settings/categories/panel-plugins/ReadmeModal';
|
|
7
7
|
import { GitFreshness } from '@/client/components/settings/categories/panel-plugins/GitFreshness';
|
|
8
8
|
import type { PluginSlots, SlotName } from '@/client/lib/plugins/slots';
|
|
9
|
-
import {
|
|
9
|
+
import { pluginEnablementKey } from '@/shared/lib/workspace/panel-ids';
|
|
10
10
|
|
|
11
11
|
/** The four slots a plugin may fill, in the order a card lists them. */
|
|
12
12
|
const SLOT_ORDER: readonly SlotName[] = ['rightPanel', 'panel', 'headerPanel', 'settingsSection'];
|
|
@@ -74,7 +74,7 @@ export function InstalledList({
|
|
|
74
74
|
// The ENABLEMENT key is the panel key, not the plugin id: it is the
|
|
75
75
|
// id the client filters its panel catalog by, so a switch that wrote
|
|
76
76
|
// the bare id would report success while the panel stayed hidden.
|
|
77
|
-
const panelKey =
|
|
77
|
+
const panelKey = pluginEnablementKey(plugin.pluginId);
|
|
78
78
|
const toggling = busy === `enable:${panelKey}` || busy === `disable:${panelKey}`;
|
|
79
79
|
const contributed = entry ? SLOT_ORDER.filter((slot) => entry.components[slot]) : [];
|
|
80
80
|
|