ompchamber 1.0.0 → 1.2.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/static/css/index.b938f4a693.css +2 -0
- package/dist/client/static/js/77.8022a800b5.js +139 -0
- package/dist/client/static/js/async/{1k.5974df3b87.js → 1k.a411fe5b43.js} +2 -2
- package/dist/client/static/js/async/{3y.71f2ccf3b6.js → 3y.3186ecdd63.js} +5 -5
- package/dist/client/static/js/async/5b.d88304bc13.js +1 -0
- package/dist/client/static/js/async/66.86544ad741.js +93 -0
- package/dist/client/static/js/async/7g.db82aca7ba.js +1 -0
- package/dist/client/static/js/async/7l.fadbbc0583.js +1 -0
- package/dist/client/static/js/async/{dc.856fda6560.js → ar.ad5c8822a8.js} +2 -2
- package/dist/client/static/js/async/cx.9c8268d542.js +226 -0
- package/dist/client/static/js/async/e.6443e739a2.js +79 -0
- package/dist/client/static/js/async/f7.46971e9d6d.js +2 -0
- package/dist/client/static/js/async/g.ee41bfed7f.js +2 -0
- package/dist/client/static/js/async/h1.d7091cef78.js +1 -0
- package/dist/client/static/js/async/hs.e7c59d8090.js +41 -0
- package/dist/client/static/js/async/jl.4911fa3383.js +1 -0
- package/dist/client/static/js/async/{k.7466330d1a.js → k.d16cd09ae7.js} +2 -2
- package/dist/client/static/js/async/{m.c038c71b9f.js → m.2e1ea3db9e.js} +2 -2
- package/dist/client/static/js/async/m3.d2b05f9604.js +2 -0
- package/dist/client/static/js/async/mt.853be2b5aa.js +1 -0
- package/dist/client/static/js/async/{oc.9c060c9779.js → oc.eb22093f05.js} +1 -1
- package/dist/client/static/js/async/{og.634fba99ac.js → og.53f068804c.js} +1 -1
- package/dist/client/static/js/async/{5b.0d6ae50fe8.js → q.b453193538.js} +10 -10
- package/dist/client/static/js/async/q5.88b50ffa95.js +286 -0
- package/dist/client/static/js/async/q8.7f1b48e9cd.js +21 -0
- package/dist/client/static/js/async/{sy.d16be843a7.js → s.aad2ecebc6.js} +1 -1
- package/dist/client/static/js/async/{tr.e878658140.js → tr.2a0d2b7600.js} +1 -1
- package/dist/client/static/js/async/vt.20406a5604.js +1 -0
- package/dist/client/static/js/async/x1.fe8af17016.js +90 -0
- package/dist/client/static/js/async/yg.5a94e1fc8a.js +149 -0
- package/dist/client/static/js/async/yx.af2936c368.js +10 -0
- package/dist/client/static/js/index.57b39ffa7f.js +34 -0
- package/package.json +1 -1
- package/src/client/components/common/ComposerPicker.tsx +16 -4
- package/src/client/components/common/ComposerTextarea.tsx +1 -0
- package/src/client/components/common/file-icon/index.tsx +4 -1
- package/src/client/components/common/image-viewer/index.tsx +224 -0
- package/src/client/components/layout/RightActivityBar.tsx +1 -1
- package/src/client/components/layout/desktop-layout/ResizeHandle.tsx +2 -2
- package/src/client/components/layout/desktop-layout/TopNavbar.tsx +13 -2
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +107 -56
- package/src/client/components/layout/desktop-layout/index.tsx +25 -96
- package/src/client/components/layout/desktop-layout/resizer/index.tsx +79 -118
- package/src/client/components/layout/desktop-layout/resizer/separator-drag.ts +140 -0
- package/src/client/components/layout/session-sidebar/Header.tsx +1 -1
- package/src/client/components/layout/session-sidebar/SessionItem.tsx +26 -12
- package/src/client/components/layout/session-sidebar/SubagentList.tsx +6 -4
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +57 -43
- package/src/client/components/mobile/mobile-right-sidebar/Header.tsx +31 -24
- package/src/client/components/settings/Modal.tsx +1 -0
- package/src/client/components/settings/categories/ChatSettings.tsx +6 -2
- package/src/client/components/settings/categories/chat-settings/TitleSection.tsx +99 -0
- package/src/client/components/workspace/btw-panel/AskCard.tsx +155 -0
- package/src/client/components/workspace/btw-panel/Composer.tsx +181 -0
- package/src/client/components/workspace/btw-panel/Messages.tsx +100 -0
- package/src/client/components/workspace/btw-panel/TopicMenu.tsx +106 -0
- package/src/client/components/workspace/btw-panel/attachments.ts +80 -0
- package/src/client/components/workspace/btw-panel/index.tsx +83 -0
- package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +39 -20
- package/src/client/components/workspace/chat-timeline/EmptyWorkspacePrompt.tsx +3 -0
- package/src/client/components/workspace/chat-timeline/MinimapShortcuts.tsx +49 -12
- package/src/client/components/workspace/chat-timeline/SubagentView.tsx +3 -1
- package/src/client/components/workspace/chat-timeline/TimelineBody.tsx +10 -6
- package/src/client/components/workspace/chat-timeline/chat-input/AttachmentToolbar.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/chat-input/DropOverlay.tsx +17 -0
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +67 -43
- package/src/client/components/workspace/chat-timeline/index.tsx +14 -9
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Goal.tsx +3 -3
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Read.tsx +25 -7
- package/src/client/components/workspace/editor/Toolbar.tsx +32 -23
- package/src/client/components/workspace/editor/index.tsx +45 -36
- package/src/client/components/workspace/model-dropdown/groups.ts +35 -2
- package/src/client/components/workspace/model-dropdown/index.tsx +48 -7
- package/src/client/components/workspace/model-dropdown/use-catalog.ts +28 -4
- package/src/client/hooks/chat/btw/index.ts +180 -0
- package/src/client/hooks/chat/btw/intercept.ts +43 -0
- package/src/client/hooks/chat/btw/mode.ts +179 -0
- package/src/client/hooks/chat/composer/attachments.ts +198 -0
- package/src/client/hooks/chat/composer/drop-entries.ts +204 -0
- package/src/client/hooks/chat/composer/drop-payload.test.ts +97 -0
- package/src/client/hooks/chat/composer/drop-payload.ts +130 -0
- package/src/client/hooks/chat/composer/drop-references.ts +106 -0
- package/src/client/hooks/chat/composer/file-drop.test.ts +194 -0
- package/src/client/hooks/chat/composer/file-drop.ts +226 -0
- package/src/client/hooks/chat/composer/file-reads.test.ts +211 -0
- package/src/client/hooks/chat/composer/file-reads.ts +238 -0
- package/src/client/hooks/chat/composer/index.ts +35 -9
- package/src/client/hooks/chat/composer/pipeline.ts +181 -0
- package/src/client/hooks/chat/omp/index.ts +3 -3
- package/src/client/hooks/chat/omp/prompt-send.ts +5 -5
- package/src/client/hooks/chat/timeline/actions.ts +40 -4
- package/src/client/hooks/chat/timeline/deferred-model.test.ts +219 -0
- package/src/client/hooks/chat/timeline/deferred-model.ts +92 -0
- package/src/client/hooks/chat/timeline/index.ts +29 -9
- package/src/client/hooks/chat/timeline/prompt-text.ts +6 -1
- package/src/client/hooks/chat/timeline/send.ts +45 -70
- package/src/client/hooks/chat/timeline/session-load.ts +2 -1
- package/src/client/hooks/chat/timeline/session-pagination.ts +161 -14
- package/src/client/hooks/chat/timeline/user-turns.ts +149 -0
- package/src/client/hooks/editor/file-actions.ts +49 -0
- package/src/client/hooks/editor/use-file-editor.ts +38 -27
- package/src/client/hooks/editor/use-file-transfer.ts +78 -0
- package/src/client/hooks/workspace/available-width.ts +33 -0
- package/src/client/hooks/workspace/panel-widths.ts +34 -37
- package/src/client/tailwind.css +22 -0
- package/src/server/db.server.ts +21 -287
- package/src/server/lib/btw/frames.test.ts +68 -0
- package/src/server/lib/btw/frames.ts +56 -0
- package/src/server/lib/btw/prompt.ts +28 -0
- package/src/server/lib/btw/registry.server.ts +141 -0
- package/src/server/lib/btw/runtime.server.ts +324 -0
- package/src/server/lib/btw/schema.server.ts +44 -0
- package/src/server/lib/btw/service.server.ts +171 -0
- package/src/server/lib/btw/session-copy.server.ts +213 -0
- package/src/server/lib/btw/session-copy.test.ts +166 -0
- package/src/server/lib/btw/store.server.ts +240 -0
- package/src/server/lib/db/schema.ts +110 -0
- package/src/server/lib/db/seed.ts +160 -0
- package/src/server/lib/db/workspace-sync.ts +70 -0
- package/src/server/lib/fs/root.ts +37 -0
- package/src/server/lib/models/preferences.server.ts +110 -0
- package/src/server/lib/models/registry.server.ts +206 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +207 -0
- package/src/server/lib/omp/rpc/manager.ts +27 -123
- package/src/server/lib/omp/rpc/utility.ts +7 -1
- package/src/server/lib/omp/session/auto-title.server.test.ts +137 -0
- package/src/server/lib/omp/session/auto-title.server.ts +152 -0
- package/src/server/lib/omp/session/blobs.server.test.ts +100 -0
- package/src/server/lib/omp/session/blobs.server.ts +76 -0
- package/src/server/lib/omp/session/file-scan.ts +174 -0
- package/src/server/lib/omp/session/files.test.ts +147 -0
- package/src/server/lib/omp/session/files.ts +52 -35
- package/src/server/lib/omp/session/locator.ts +2 -2
- package/src/server/lib/omp/session/merge-stored.test.ts +118 -0
- package/src/server/lib/omp/session/merge-stored.ts +171 -0
- package/src/server/lib/omp/session/messages.ts +34 -0
- package/src/server/lib/omp/session/reader.ts +2 -2
- package/src/server/lib/omp/session/subagent-presence.ts +9 -6
- package/src/server/lib/queue/delivery.server.test.ts +260 -0
- package/src/server/lib/queue/delivery.server.ts +63 -11
- package/src/server/plugins/ssr.ts +29 -2
- package/src/server/routes/btw/events.ts +70 -0
- package/src/server/routes/btw/index.ts +8 -0
- package/src/server/routes/btw/topics.ts +87 -0
- package/src/server/routes/btw/ws.ts +136 -0
- package/src/server/routes/chat/index.ts +2 -0
- package/src/server/routes/chat/session.ts +1 -115
- package/src/server/routes/chat/turns.ts +87 -0
- package/src/server/routes/fs/index.ts +4 -1
- package/src/server/routes/fs/raw.ts +85 -0
- package/src/server/routes/fs/read.ts +58 -3
- package/src/server/routes/index.ts +7 -1
- package/src/server/routes/models/root.ts +88 -215
- package/src/server/routes/settings/commands.ts +59 -18
- package/src/shared/lib/chat/attachments.test.ts +208 -0
- package/src/shared/lib/chat/attachments.ts +200 -15
- package/src/shared/lib/chat/btw/client.ts +94 -0
- package/src/shared/lib/chat/composer/client.test.ts +48 -0
- package/src/shared/lib/chat/composer/client.ts +23 -9
- package/src/shared/lib/chat/composer/commands.ts +290 -0
- package/src/shared/lib/chat/composer/composer.test.ts +195 -0
- package/src/shared/lib/chat/composer/filter.ts +21 -7
- package/src/shared/lib/chat/composer/trigger.ts +154 -21
- package/src/shared/lib/chat/omp/agent-events.ts +33 -80
- package/src/shared/lib/chat/omp/socket.ts +11 -6
- package/src/shared/lib/chat/omp/sse.ts +11 -6
- package/src/shared/lib/chat/omp/tool-results.ts +102 -0
- package/src/shared/lib/chat/omp/transport.ts +19 -3
- package/src/shared/lib/chat/text-file.ts +148 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.ts +18 -32
- package/src/shared/lib/chat/timeline/stream-coalescer.ts +66 -0
- package/src/shared/lib/chat/timeline/turns.ts +7 -0
- package/src/shared/lib/fs/file-kind.ts +50 -0
- package/src/shared/lib/fs/paths.ts +19 -1
- package/src/shared/lib/markdown/math.test.ts +20 -3
- package/src/shared/lib/models/preferences.test.ts +131 -0
- package/src/shared/lib/models/preferences.ts +88 -0
- package/src/shared/lib/models/provider-models.ts +8 -1
- package/src/shared/lib/omp/session/inlined-attachments.test.ts +82 -0
- package/src/shared/lib/omp/session/mapper.ts +24 -4
- package/src/shared/lib/omp/session/messages-map.ts +16 -3
- package/src/shared/lib/omp/session/messages-parse.ts +2 -2
- package/src/shared/lib/omp/session/parse-message-blocks.ts +85 -5
- package/src/shared/lib/omp/subagent/label.test.ts +94 -0
- package/src/shared/lib/omp/subagent/label.ts +84 -0
- package/src/shared/lib/workspace/panel-widths.test.ts +113 -18
- package/src/shared/lib/workspace/panel-widths.ts +161 -30
- package/src/shared/lib/workspace/right-panels.ts +50 -11
- package/src/shared/lib/workspace/session-state/store.ts +3 -0
- package/src/shared/types/btw.ts +65 -0
- package/src/shared/types/chat.ts +77 -66
- package/src/shared/types/index.ts +2 -0
- package/src/shared/types/models.ts +101 -0
- package/src/shared/types/omp/agent.ts +14 -1
- package/src/shared/types/omp/session.ts +4 -1
- package/src/shared/types/omp/subagent.ts +4 -0
- package/src/shared/types/settings/command.ts +13 -0
- package/src/shared/types/settings/state.ts +5 -0
- package/dist/client/static/css/index.e880517fcd.css +0 -2
- package/dist/client/static/js/async/5bh.8b2363424f.js +0 -1
- package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
- package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
- package/dist/client/static/js/async/7l.7a6a377ecc.js +0 -1
- package/dist/client/static/js/async/cx.ca5feec570.js +0 -226
- package/dist/client/static/js/async/e.93214d622b.js +0 -79
- package/dist/client/static/js/async/g.a06ca3b3e9.js +0 -2
- package/dist/client/static/js/async/h1.e8d5aa8e80.js +0 -1
- package/dist/client/static/js/async/hs.6d480d0ded.js +0 -41
- package/dist/client/static/js/async/jl.874d352278.js +0 -1
- package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
- package/dist/client/static/js/async/nam.b5663e919f.js +0 -10
- package/dist/client/static/js/async/q.23af2a358f.js +0 -21
- package/dist/client/static/js/async/q5.e683f7c26e.js +0 -286
- package/dist/client/static/js/async/s.222ee974f6.js +0 -2
- package/dist/client/static/js/async/uf.46ad6209fa.js +0 -2
- package/dist/client/static/js/async/vt.42416b707d.js +0 -1
- package/dist/client/static/js/async/x1.da8687159b.js +0 -90
- package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
- package/dist/client/static/js/index.0de15c5e17.js +0 -33
- package/dist/client/static/js/jp.50817896f3.js +0 -139
- /package/dist/client/static/js/{jp.50817896f3.js.LICENSE.txt → 77.8022a800b5.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{1k.5974df3b87.js.LICENSE.txt → 1k.a411fe5b43.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3y.71f2ccf3b6.js.LICENSE.txt → 3y.3186ecdd63.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{dc.856fda6560.js.LICENSE.txt → ar.ad5c8822a8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{m.c038c71b9f.js.LICENSE.txt → f7.46971e9d6d.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{5b.0d6ae50fe8.js.LICENSE.txt → g.ee41bfed7f.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.23af2a358f.js.LICENSE.txt → m.2e1ea3db9e.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{s.222ee974f6.js.LICENSE.txt → m3.d2b05f9604.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.a06ca3b3e9.js.LICENSE.txt → q.b453193538.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{uf.46ad6209fa.js.LICENSE.txt → q8.7f1b48e9cd.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{nam.b5663e919f.js.LICENSE.txt → yx.af2936c368.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.0de15c5e17.js.LICENSE.txt → index.57b39ffa7f.js.LICENSE.txt} +0 -0
|
@@ -5,37 +5,63 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, test } from 'bun:test';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
HANDLE_WIDTH,
|
|
10
|
+
MIN_CHAT_PANEL_WIDTH,
|
|
11
|
+
mergePanelWidths,
|
|
12
|
+
normalizePanelWidths,
|
|
13
|
+
resolvePanelWidth,
|
|
14
|
+
} from '@/shared/lib/workspace/panel-widths';
|
|
9
15
|
|
|
10
16
|
describe('normalizePanelWidths', () => {
|
|
11
17
|
test('keeps every panel slot from a current blob', () => {
|
|
12
18
|
const raw = {
|
|
13
19
|
left: 342,
|
|
14
|
-
editor: 620,
|
|
15
|
-
diff:
|
|
16
|
-
right: { files: 300, browser: 804 },
|
|
20
|
+
editor: { px: 620, fraction: 0.54 },
|
|
21
|
+
diff: { fraction: 0.6 },
|
|
22
|
+
right: { files: { px: 300, fraction: 0.26 }, browser: { px: 804 } },
|
|
17
23
|
};
|
|
18
24
|
|
|
19
25
|
expect(normalizePanelWidths(raw, 'files')).toEqual(raw);
|
|
20
26
|
});
|
|
21
27
|
|
|
28
|
+
test('reads a bare number as the px-only shape and keeps it for conversion', () => {
|
|
29
|
+
// Blobs written before fractions existed: the pixel width must survive so
|
|
30
|
+
// the layout can convert it once the group's area is known.
|
|
31
|
+
const widths = normalizePanelWidths({ left: 342, editor: 620, right: { files: 300 } }, 'files');
|
|
32
|
+
|
|
33
|
+
expect(widths).toEqual({ left: 342, editor: { px: 620 }, right: { files: { px: 300 } } });
|
|
34
|
+
});
|
|
35
|
+
|
|
22
36
|
test('adopts a legacy shared right width into the view that was open', () => {
|
|
23
37
|
const widths = normalizePanelWidths({ left: 342, editor: 620, right: 1091 }, 'browser');
|
|
24
38
|
|
|
25
|
-
expect(widths.right).toEqual({ browser: 1091 });
|
|
39
|
+
expect(widths.right).toEqual({ browser: { px: 1091 } });
|
|
26
40
|
expect(widths.left).toBe(342);
|
|
27
|
-
expect(widths.editor).
|
|
41
|
+
expect(widths.editor).toEqual({ px: 620 });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('drops an out-of-range fraction instead of trusting it', () => {
|
|
45
|
+
// A fraction is a share of the area, so 0 and >1 are meaningless; the px
|
|
46
|
+
// fallback in the same slot survives, and a slot left with neither unit
|
|
47
|
+
// disappears rather than being kept as an empty object.
|
|
48
|
+
const widths = normalizePanelWidths(
|
|
49
|
+
{ editor: { px: 620, fraction: 1.25 }, diff: { fraction: 0 }, right: { files: { fraction: -0.5 } } },
|
|
50
|
+
'files',
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(widths).toEqual({ editor: { px: 620 } });
|
|
28
54
|
});
|
|
29
55
|
|
|
30
56
|
test('drops unusable entries instead of trusting the blob', () => {
|
|
31
57
|
// `chat` is an obsolete slot — the chat column is the filler now — and a
|
|
32
58
|
// blob written before that must not resurrect it.
|
|
33
59
|
const widths = normalizePanelWidths(
|
|
34
|
-
{ left: -10, chat: 700, editor: 620.4, diff: null, right: { files: 0, bogus: 300, git: 260 } },
|
|
60
|
+
{ left: -10, chat: 700, editor: { px: 620.4 }, diff: null, right: { files: { px: 0 }, bogus: 300, git: { px: 260 } } },
|
|
35
61
|
'files',
|
|
36
62
|
);
|
|
37
63
|
|
|
38
|
-
expect(widths).toEqual({ editor: 620, right: { git: 260 } });
|
|
64
|
+
expect(widths).toEqual({ editor: { px: 620 }, right: { git: { px: 260 } } });
|
|
39
65
|
});
|
|
40
66
|
|
|
41
67
|
test('tolerates a missing or malformed blob', () => {
|
|
@@ -48,25 +74,94 @@ describe('mergePanelWidths', () => {
|
|
|
48
74
|
test('merging one view keeps every other remembered width', () => {
|
|
49
75
|
const stored = {
|
|
50
76
|
left: 342,
|
|
51
|
-
editor: 620,
|
|
52
|
-
diff:
|
|
53
|
-
right: { files: 300, git: 260 },
|
|
77
|
+
editor: { px: 620, fraction: 0.54 },
|
|
78
|
+
diff: { fraction: 0.6 },
|
|
79
|
+
right: { files: { px: 300, fraction: 0.26 }, git: { px: 260 } },
|
|
54
80
|
};
|
|
55
81
|
|
|
56
|
-
const merged = mergePanelWidths(stored, { right: { browser: 804 } });
|
|
82
|
+
const merged = mergePanelWidths(stored, { right: { browser: { px: 804, fraction: 0.7 } } });
|
|
57
83
|
|
|
58
|
-
expect(merged.right).toEqual({
|
|
84
|
+
expect(merged.right).toEqual({
|
|
85
|
+
files: { px: 300, fraction: 0.26 },
|
|
86
|
+
git: { px: 260 },
|
|
87
|
+
browser: { px: 804, fraction: 0.7 },
|
|
88
|
+
});
|
|
59
89
|
expect(merged.left).toBe(342);
|
|
60
|
-
expect(merged.editor).
|
|
61
|
-
expect(merged.diff).
|
|
90
|
+
expect(merged.editor).toEqual({ px: 620, fraction: 0.54 });
|
|
91
|
+
expect(merged.diff).toEqual({ fraction: 0.6 });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('a patch adds a unit without discarding the one already stored', () => {
|
|
95
|
+
// A drag reports px + fraction together, but the conversion pass reports a
|
|
96
|
+
// fraction alone; it must not erase the px fallback it was derived from.
|
|
97
|
+
const merged = mergePanelWidths({ editor: { px: 620 } }, { editor: { fraction: 0.54 } });
|
|
98
|
+
|
|
99
|
+
expect(merged.editor).toEqual({ px: 620, fraction: 0.54 });
|
|
62
100
|
});
|
|
63
101
|
|
|
64
102
|
test('a resized panel replaces only its own slot', () => {
|
|
65
103
|
const merged = mergePanelWidths(
|
|
66
|
-
{ left: 300, editor: 620, diff: 900, right: { files: 300 } },
|
|
67
|
-
{ editor: 480 },
|
|
104
|
+
{ left: 300, editor: { px: 620 }, diff: { px: 900 }, right: { files: { px: 300 } } },
|
|
105
|
+
{ editor: { px: 480, fraction: 0.4 } },
|
|
68
106
|
);
|
|
69
107
|
|
|
70
|
-
expect(merged).toEqual({
|
|
108
|
+
expect(merged).toEqual({
|
|
109
|
+
left: 300,
|
|
110
|
+
editor: { px: 480, fraction: 0.4 },
|
|
111
|
+
diff: { px: 900 },
|
|
112
|
+
right: { files: { px: 300 } },
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('resolvePanelWidth', () => {
|
|
118
|
+
const base = {
|
|
119
|
+
defaultFraction: 0.6,
|
|
120
|
+
defaultPx: 600,
|
|
121
|
+
min: 320,
|
|
122
|
+
siblingMin: 0,
|
|
123
|
+
handleCount: 0,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
test('opens at its default share of the measured area', () => {
|
|
127
|
+
expect(resolvePanelWidth({ ...base, available: 1000 })).toBe(600);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('falls back to the pixel width before the area is known', () => {
|
|
131
|
+
// Not zero: a panel whose area has not been measured yet must still render
|
|
132
|
+
// at the width it is supposed to open at.
|
|
133
|
+
expect(resolvePanelWidth({ ...base, available: null })).toBe(600);
|
|
134
|
+
expect(resolvePanelWidth({ ...base, available: null, stored: { px: 512 } })).toBe(512);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('a px-only value holds its pixel width; only a fraction follows the area', () => {
|
|
138
|
+
// The conversion is an identity (`px / available * available`), which is
|
|
139
|
+
// the point: a pixel value someone chose is honoured as written rather than
|
|
140
|
+
// being rescaled behind their back. It stops following the window only
|
|
141
|
+
// until that slot is dragged once, which stores a fraction alongside it.
|
|
142
|
+
expect(resolvePanelWidth({ ...base, available: 1000, stored: { px: 480 } })).toBe(480);
|
|
143
|
+
expect(resolvePanelWidth({ ...base, available: 2000, stored: { px: 480 } })).toBe(480);
|
|
144
|
+
// With both units stored, the fraction is what the panel follows.
|
|
145
|
+
expect(resolvePanelWidth({ ...base, available: 2000, stored: { px: 480, fraction: 0.24 } })).toBe(480);
|
|
146
|
+
expect(resolvePanelWidth({ ...base, available: 2000, stored: { px: 480, fraction: 0.3 } })).toBe(600);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('a stored fraction beats the default share', () => {
|
|
150
|
+
// 0.45 of 1000 is 450: above the 320 floor and below the 0.6 default, so
|
|
151
|
+
// the result can only come from the stored fraction.
|
|
152
|
+
expect(resolvePanelWidth({ ...base, available: 1000, stored: { px: 480, fraction: 0.45 } })).toBe(450);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('the ceiling leaves the chat its floor, the siblings theirs, and the handles their width', () => {
|
|
156
|
+
const available = 2000;
|
|
157
|
+
const expected = available - MIN_CHAT_PANEL_WIDTH - 320 - 2 * HANDLE_WIDTH;
|
|
158
|
+
|
|
159
|
+
expect(resolvePanelWidth({ ...base, available, siblingMin: 320, handleCount: 2, stored: { fraction: 0.9 } })).toBe(expected);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('the floor wins over a ceiling squeezed below it', () => {
|
|
163
|
+
// A window too narrow for the chat floor, a sibling and this panel: the
|
|
164
|
+
// panel keeps its minimum and the row clips, rather than collapsing.
|
|
165
|
+
expect(resolvePanelWidth({ ...base, available: 400, siblingMin: 320, handleCount: 2 })).toBe(320);
|
|
71
166
|
});
|
|
72
167
|
});
|
|
@@ -8,75 +8,192 @@
|
|
|
8
8
|
* like a reset. The chat column is the group's filler — flexbox sizes it — so
|
|
9
9
|
* it has no remembered width, only a floor it may not be pushed below.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Two units, deliberately:
|
|
12
|
+
*
|
|
13
|
+
* - The **sidebar** is pixels. It holds a session list, which does not get more
|
|
14
|
+
* useful on a wider monitor, and OpenChamber's sidebar is pixels too.
|
|
15
|
+
* - The **editor and right panels** are a *share of the group's available
|
|
16
|
+
* area*. A pixel width is only right on the display it was dragged on; a
|
|
17
|
+
* fraction follows a window resize. A remembered pixel value is kept
|
|
18
|
+
* alongside as the fallback for the window before the area is measured and
|
|
19
|
+
* for blobs written before the fraction existed.
|
|
20
|
+
*
|
|
21
|
+
* Sizes persist as `app_settings.desktopLayoutSizes`.
|
|
13
22
|
*/
|
|
14
23
|
import { isRightPanelType, type RightPanelType } from '@/shared/lib/workspace/right-panels';
|
|
15
24
|
|
|
16
25
|
/** The editor panel keeps a separate width while a diff tab is active. */
|
|
17
26
|
export type EditorWidthMode = 'editor' | 'diff';
|
|
18
27
|
|
|
28
|
+
/**
|
|
29
|
+
* A panel width in both units. `fraction` is authoritative once the group's
|
|
30
|
+
* area is known; `px` is what the panel falls back to before then, and what a
|
|
31
|
+
* blob written before fractions existed carries.
|
|
32
|
+
*/
|
|
33
|
+
export interface PanelWidth {
|
|
34
|
+
px?: number;
|
|
35
|
+
fraction?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
19
38
|
export interface PanelWidths {
|
|
20
|
-
/** Session sidebar. */
|
|
39
|
+
/** Session sidebar. Pixels only — it does not scale with the window. */
|
|
21
40
|
left?: number;
|
|
22
41
|
/** Editor panel while a source file tab is active. */
|
|
23
|
-
editor?:
|
|
42
|
+
editor?: PanelWidth;
|
|
24
43
|
/** Editor panel while a diff tab is active. */
|
|
25
|
-
diff?:
|
|
44
|
+
diff?: PanelWidth;
|
|
26
45
|
/** Right panel, keyed by the activity-bar view it currently shows. */
|
|
27
|
-
right?: Partial<Record<RightPanelType,
|
|
46
|
+
right?: Partial<Record<RightPanelType, PanelWidth>>;
|
|
28
47
|
}
|
|
29
48
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Fraction each editor mode opens at, and the px width used before the group's
|
|
51
|
+
* area is known. The diff fraction matches OpenChamber's `diff` surface (3/5);
|
|
52
|
+
* its px fallback stays wider than the source editor's because `SplitView`
|
|
53
|
+
* states a 700px floor of its own, so anything below it scrolls sideways.
|
|
54
|
+
*/
|
|
55
|
+
export const DEFAULT_EDITOR_FRACTIONS: Record<EditorWidthMode, number> = {
|
|
56
|
+
editor: 0.6,
|
|
57
|
+
diff: 0.6,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const DEFAULT_EDITOR_WIDTHS: Record<EditorWidthMode, number> = {
|
|
33
61
|
editor: 600,
|
|
34
|
-
// Side-by-side diffs need both halves readable at once: `SplitView` states a
|
|
35
|
-
// 700px floor of its own, so anything below it scrolls sideways on open.
|
|
36
62
|
diff: 720,
|
|
37
63
|
};
|
|
38
64
|
|
|
65
|
+
/** Fallback for the sidebar, which has no fraction. */
|
|
66
|
+
export const DEFAULT_LEFT_PANEL_WIDTH = 280;
|
|
67
|
+
|
|
39
68
|
/**
|
|
40
69
|
* Panel floors and ceilings. The sidebar ceiling and the chat floor are a pair:
|
|
41
70
|
* `MIN_CHAT_PANEL_WIDTH` is what stops the workspace stack from eating the
|
|
42
|
-
* conversation, and it is also why the sidebar cannot open past
|
|
43
|
-
* leave the editor (
|
|
71
|
+
* conversation, and it is also why the sidebar cannot open past 500 and still
|
|
72
|
+
* leave the editor (320) and a browser view (320) room on a 1440px display.
|
|
73
|
+
*
|
|
74
|
+
* `MIN_EDITOR_PANEL_WIDTH` matches the shared right-panel floor so the two
|
|
75
|
+
* panels cannot disagree about the narrowest width the same content is shown
|
|
76
|
+
* at.
|
|
44
77
|
*/
|
|
45
|
-
export const MIN_LEFT_PANEL_WIDTH =
|
|
46
|
-
export const MAX_LEFT_PANEL_WIDTH =
|
|
47
|
-
export const MIN_CHAT_PANEL_WIDTH =
|
|
48
|
-
export const MIN_EDITOR_PANEL_WIDTH =
|
|
49
|
-
|
|
78
|
+
export const MIN_LEFT_PANEL_WIDTH = 264;
|
|
79
|
+
export const MAX_LEFT_PANEL_WIDTH = 500;
|
|
80
|
+
export const MIN_CHAT_PANEL_WIDTH = 400;
|
|
81
|
+
export const MIN_EDITOR_PANEL_WIDTH = 320;
|
|
82
|
+
|
|
83
|
+
/** Draggable separator width in px (`w-[3px]` in `ResizeHandle`). */
|
|
84
|
+
export const HANDLE_WIDTH = 3;
|
|
85
|
+
|
|
86
|
+
/** How much of a drag may re-apply the real width, in ms. */
|
|
87
|
+
export const RESIZE_FOLLOW_INTERVAL_MS = 100;
|
|
88
|
+
|
|
89
|
+
export interface ResolveWidthArgs {
|
|
90
|
+
/** Remembered width for this slot, if the user ever resized it. */
|
|
91
|
+
stored?: PanelWidth;
|
|
92
|
+
/** Fraction this slot opens at when nothing was remembered. */
|
|
93
|
+
defaultFraction: number;
|
|
94
|
+
/** px this slot opens at before the area is known. */
|
|
95
|
+
defaultPx: number;
|
|
96
|
+
/** Measured area of the group, or null before the observer reports. */
|
|
97
|
+
available: number | null;
|
|
98
|
+
min: number;
|
|
99
|
+
/** Floors the other fixed panels in the same group must keep. */
|
|
100
|
+
siblingMin: number;
|
|
101
|
+
/** Separators in the group, which also consume width. */
|
|
102
|
+
handleCount: number;
|
|
103
|
+
}
|
|
50
104
|
|
|
51
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Resolve one panel's width in px.
|
|
107
|
+
*
|
|
108
|
+
* The ceiling is dynamic — `available − chat floor − the other panels' floors`
|
|
109
|
+
* — rather than a fixed number, because a fixed one either wastes a large
|
|
110
|
+
* monitor or eats the chat on a small one. Both the editor and the right panel
|
|
111
|
+
* compete for the same budget, so whichever renders first takes its share and
|
|
112
|
+
* the other gets what is left; the caller passes `siblingMin` to say which.
|
|
113
|
+
*
|
|
114
|
+
* Without a measured area there is no budget to divide, so the stored or
|
|
115
|
+
* default pixel width is used unchanged.
|
|
116
|
+
*/
|
|
117
|
+
export function resolvePanelWidth(args: ResolveWidthArgs): number {
|
|
118
|
+
const { stored, defaultFraction, defaultPx, available, min, siblingMin, handleCount } = args;
|
|
119
|
+
|
|
120
|
+
if (available === null || available <= 0) return Math.max(min, Math.round(stored?.px ?? defaultPx));
|
|
121
|
+
|
|
122
|
+
const ceiling = Math.max(
|
|
123
|
+
min,
|
|
124
|
+
Math.floor(available - MIN_CHAT_PANEL_WIDTH - siblingMin - handleCount * HANDLE_WIDTH),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// A remembered fraction wins. A px-only value is honoured as written — the
|
|
128
|
+
// conversion below is an identity, deliberately: a pixel width someone chose
|
|
129
|
+
// is not silently rescaled, and it starts following the window only once that
|
|
130
|
+
// slot is dragged, which stores a fraction beside it. Reading rather than
|
|
131
|
+
// rewriting the stored blob is also what keeps a session's own widths from
|
|
132
|
+
// being clobbered by the global seed before that session's blob has loaded.
|
|
133
|
+
const fraction = stored?.fraction
|
|
134
|
+
?? (stored?.px != null ? Math.min(1, stored.px / available) : defaultFraction);
|
|
135
|
+
const wanted = Math.round(fraction * available);
|
|
136
|
+
|
|
137
|
+
return Math.min(Math.max(wanted, min), ceiling);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function panelWidth(value: unknown): PanelWidth | undefined {
|
|
141
|
+
if (typeof value === 'number') {
|
|
142
|
+
return Number.isFinite(value) && value > 0 ? { px: Math.round(value) } : undefined;
|
|
143
|
+
}
|
|
144
|
+
if (!value || typeof value !== 'object') return undefined;
|
|
145
|
+
|
|
146
|
+
const source = value as Record<string, unknown>;
|
|
147
|
+
const px = Number.isFinite(source.px) && (source.px as number) > 0 ? Math.round(source.px as number) : undefined;
|
|
148
|
+
const fraction = Number.isFinite(source.fraction) && (source.fraction as number) > 0 && (source.fraction as number) <= 1
|
|
149
|
+
? (source.fraction as number)
|
|
150
|
+
: undefined;
|
|
151
|
+
|
|
152
|
+
if (px === undefined && fraction === undefined) return undefined;
|
|
153
|
+
return { ...(px !== undefined ? { px } : {}), ...(fraction !== undefined ? { fraction } : {}) };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sidebarWidth(value: unknown): number | undefined {
|
|
52
157
|
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.round(value) : undefined;
|
|
53
158
|
}
|
|
54
159
|
|
|
55
160
|
/**
|
|
56
|
-
* Coerce a stored blob into the current shape.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
161
|
+
* Coerce a stored blob into the current shape.
|
|
162
|
+
*
|
|
163
|
+
* Three older shapes are accepted, because a user's layout must survive the
|
|
164
|
+
* upgrade rather than silently reset:
|
|
165
|
+
*
|
|
166
|
+
* - a bare number is the px-only shape this app shipped before fractions;
|
|
167
|
+
* - a single number under `right` is older still, holding one width shared by
|
|
168
|
+
* every view — it belongs to whichever view was open when it was written, so
|
|
169
|
+
* it seeds `legacyView` alone instead of being copied onto all eight;
|
|
170
|
+
* - an unknown `right` key is dropped rather than trusted.
|
|
171
|
+
*
|
|
172
|
+
* Nothing is converted to a fraction here: this function is pure and the
|
|
173
|
+
* conversion needs the group's measured area. The layout performs it once the
|
|
174
|
+
* area is known (see `usePanelWidths`).
|
|
60
175
|
*/
|
|
61
176
|
export function normalizePanelWidths(raw: unknown, legacyView: RightPanelType): PanelWidths {
|
|
62
177
|
if (!raw || typeof raw !== 'object') return {};
|
|
63
178
|
const source = raw as Record<string, unknown>;
|
|
64
179
|
|
|
65
180
|
const widths: PanelWidths = {};
|
|
66
|
-
|
|
67
|
-
|
|
181
|
+
const left = sidebarWidth(source.left);
|
|
182
|
+
if (left !== undefined) widths.left = left;
|
|
183
|
+
for (const key of ['editor', 'diff'] as const) {
|
|
184
|
+
const value = panelWidth(source[key]);
|
|
68
185
|
if (value !== undefined) widths[key] = value;
|
|
69
186
|
}
|
|
70
187
|
|
|
71
|
-
const right: Partial<Record<RightPanelType,
|
|
188
|
+
const right: Partial<Record<RightPanelType, PanelWidth>> = {};
|
|
72
189
|
if (source.right && typeof source.right === 'object') {
|
|
73
190
|
for (const [view, value] of Object.entries(source.right as Record<string, unknown>)) {
|
|
74
191
|
if (!isRightPanelType(view)) continue;
|
|
75
|
-
const size =
|
|
192
|
+
const size = panelWidth(value);
|
|
76
193
|
if (size !== undefined) right[view] = size;
|
|
77
194
|
}
|
|
78
195
|
} else {
|
|
79
|
-
const legacy =
|
|
196
|
+
const legacy = panelWidth(source.right);
|
|
80
197
|
if (legacy !== undefined) right[legacyView] = legacy;
|
|
81
198
|
}
|
|
82
199
|
if (Object.keys(right).length > 0) widths.right = right;
|
|
@@ -84,9 +201,23 @@ export function normalizePanelWidths(raw: unknown, legacyView: RightPanelType):
|
|
|
84
201
|
return widths;
|
|
85
202
|
}
|
|
86
203
|
|
|
87
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* Fold a freshly measured patch over the stored widths (one level deep for
|
|
206
|
+
* `right`, and within a slot for its two units — a fraction patch must not
|
|
207
|
+
* discard the px fallback it was derived from).
|
|
208
|
+
*/
|
|
88
209
|
export function mergePanelWidths(current: PanelWidths, patch: PanelWidths): PanelWidths {
|
|
89
210
|
const next: PanelWidths = { ...current, ...patch };
|
|
90
|
-
if (patch.right)
|
|
211
|
+
if (patch.right) {
|
|
212
|
+
const right: Partial<Record<RightPanelType, PanelWidth>> = { ...current.right };
|
|
213
|
+
for (const [view, value] of Object.entries(patch.right)) {
|
|
214
|
+
if (!isRightPanelType(view) || !value) continue;
|
|
215
|
+
right[view] = { ...current.right?.[view], ...value };
|
|
216
|
+
}
|
|
217
|
+
next.right = right;
|
|
218
|
+
}
|
|
219
|
+
for (const key of ['editor', 'diff'] as const) {
|
|
220
|
+
if (patch[key]) next[key] = { ...current[key], ...patch[key] };
|
|
221
|
+
}
|
|
91
222
|
return next;
|
|
92
223
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Right developer panel catalog: the activity-bar views, the
|
|
3
|
-
* opens at, and the floor at which its content
|
|
2
|
+
* Right developer panel catalog: the activity-bar views, the share of the
|
|
3
|
+
* group's available area each one opens at, and the floor at which its content
|
|
4
|
+
* clips.
|
|
4
5
|
*
|
|
5
6
|
* The list lives here rather than in the activity bar component so the layout
|
|
6
7
|
* can key one remembered width per view (`panel-widths.ts`) and validate a
|
|
@@ -21,36 +22,74 @@ export const RIGHT_PANEL_TYPES = [
|
|
|
21
22
|
|
|
22
23
|
export type RightPanelType = (typeof RIGHT_PANEL_TYPES)[number];
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Share of the group's available area a view takes the first time it is shown.
|
|
27
|
+
*
|
|
28
|
+
* A fraction rather than a pixel width is what lets a panel follow a window
|
|
29
|
+
* resize instead of holding a number that was only right on the display it was
|
|
30
|
+
* dragged on. Where OpenChamber ships the same surface its `defaultWidthFraction`
|
|
31
|
+
* is reused verbatim; the rest are derived.
|
|
32
|
+
*
|
|
33
|
+
* - `files` is the file tree *alone*. OpenChamber's 3/5 `file` surface holds a
|
|
34
|
+
* tree **and** an editor column, so its fraction is not ours to borrow — their
|
|
35
|
+
* tree-only column is 200–480px, which is what this 0.22 approximates at a
|
|
36
|
+
* 1440-class viewport.
|
|
37
|
+
* - `search` matches `files`: both are single narrow columns of rows.
|
|
38
|
+
* - `usage` has no OpenChamber counterpart; 0.48 keeps the pixel default it
|
|
39
|
+
* shipped with.
|
|
40
|
+
* - `context`, `git`, `terminal` and both browsers take OpenChamber's values.
|
|
41
|
+
*/
|
|
42
|
+
export const DEFAULT_RIGHT_PANEL_FRACTIONS: Record<RightPanelType, number> = {
|
|
43
|
+
context: 0.45,
|
|
44
|
+
files: 0.22,
|
|
45
|
+
search: 0.22,
|
|
46
|
+
git: 0.4,
|
|
47
|
+
// 80 columns at the terminal's 12px Fira Code (≈7.2px/char) need ≈600px.
|
|
48
|
+
terminal: 0.6,
|
|
49
|
+
'user-browser': 0.45,
|
|
50
|
+
browser: 0.45,
|
|
51
|
+
usage: 0.48,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Width (px) a view opens at before the group's area has been measured. Kept at
|
|
56
|
+
* the values these views shipped with, so the first paint is unchanged and a
|
|
57
|
+
* window that never reports an area still opens sensibly.
|
|
58
|
+
*/
|
|
25
59
|
export const DEFAULT_RIGHT_PANEL_WIDTHS: Record<RightPanelType, number> = {
|
|
60
|
+
context: 536,
|
|
26
61
|
files: 268,
|
|
27
62
|
search: 268,
|
|
28
63
|
git: 268,
|
|
29
|
-
// 80 columns at the terminal's 12px Fira Code (≈7.2px/char) need ≈600px
|
|
30
|
-
// before the scrollbar; the old 536 landed at ≈74 columns and wrapped.
|
|
31
64
|
terminal: 640,
|
|
32
|
-
context: 536,
|
|
33
65
|
'user-browser': 804,
|
|
34
66
|
browser: 804,
|
|
35
67
|
usage: 536,
|
|
36
68
|
};
|
|
37
69
|
|
|
38
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Minimum width (px) each view needs before its toolbar or list breaks.
|
|
72
|
+
*
|
|
73
|
+
* Per view, not one shared floor: a file tree reads fine at 200 while a shell
|
|
74
|
+
* does not, and OpenChamber draws the same line (its tree-only column floors at
|
|
75
|
+
* 200, its shared panel at 320). A uniform floor would either clip the tree or
|
|
76
|
+
* force every view to shell width.
|
|
77
|
+
*/
|
|
39
78
|
export const MIN_RIGHT_PANEL_WIDTHS: Record<RightPanelType, number> = {
|
|
40
79
|
files: 200,
|
|
41
80
|
search: 200,
|
|
81
|
+
// The commit graph is a fixed-width gutter plus a message column.
|
|
42
82
|
git: 260,
|
|
43
|
-
// A
|
|
44
|
-
//
|
|
83
|
+
// A 320px terminal is ≈27 columns — below every shell's assumption, but the
|
|
84
|
+
// point where a two-pane TUI or a git log with the graph still reads.
|
|
45
85
|
terminal: 320,
|
|
86
|
+
// Telemetry charts and tables need room before they wrap.
|
|
46
87
|
context: 420,
|
|
47
88
|
'user-browser': 320,
|
|
48
89
|
browser: 320,
|
|
49
90
|
usage: 420,
|
|
50
91
|
};
|
|
51
92
|
|
|
52
|
-
export const MAX_RIGHT_PANEL_WIDTH = 1200;
|
|
53
|
-
|
|
54
93
|
/** Guards a persisted or URL-provided view id before it keys a width. */
|
|
55
94
|
export function isRightPanelType(value: unknown): value is RightPanelType {
|
|
56
95
|
return typeof value === 'string' && (RIGHT_PANEL_TYPES as readonly string[]).includes(value);
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* Key namespace contract (dot-prefixed by owning panel):
|
|
13
13
|
* - layout.activeRightPanel RightPanelType
|
|
14
14
|
* - layout.showRightPanel boolean
|
|
15
|
+
* - layout.showLeftPanel boolean
|
|
16
|
+
* - layout.userToggledEditor boolean | null (null = follow whether a file is open)
|
|
17
|
+
* - layout.panelWidths PanelWidths (per-panel widths, seeded from app_settings.desktopLayoutSizes)
|
|
15
18
|
* - layout.openedFiles editor file entries array
|
|
16
19
|
* - layout.activeFileId number | null
|
|
17
20
|
* - editor.previewMode Record<fileId, boolean>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* BTW (side question) domain types.
|
|
8
|
+
*
|
|
9
|
+
* A BTW topic is an independent question plus its explicit follow-ups, run
|
|
10
|
+
* against the parent session's context but never appended to its transcript —
|
|
11
|
+
* the same contract omp's own `/btw` implements in the TUI. The server is the
|
|
12
|
+
* source of truth for a session's topics; the panel renders the state it is
|
|
13
|
+
* sent and streams answer text through `btw_delta` frames.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Lifecycle of one question/answer pair inside a topic. */
|
|
17
|
+
export type BtwTurnStatus = 'running' | 'complete' | 'cancelled' | 'failed' | 'interrupted';
|
|
18
|
+
|
|
19
|
+
/** Model a topic's side conversation runs on — the parent session's model,
|
|
20
|
+
* captured when the topic was created (omp's `/btw` uses the active model). */
|
|
21
|
+
export interface BtwModel {
|
|
22
|
+
provider: string;
|
|
23
|
+
id: string;
|
|
24
|
+
/** Display name reported by the running side session (`DeepSeek-V4.1-Flash`). */
|
|
25
|
+
name?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** One question and its answer inside a topic. */
|
|
29
|
+
export interface BtwTurn {
|
|
30
|
+
index: number;
|
|
31
|
+
question: string;
|
|
32
|
+
answer: string;
|
|
33
|
+
status: BtwTurnStatus;
|
|
34
|
+
createdAt: number;
|
|
35
|
+
updatedAt: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A side conversation scoped to one chamber session. */
|
|
39
|
+
export interface BtwTopic {
|
|
40
|
+
id: string;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
/** The first question, used as the topic's label in history. */
|
|
43
|
+
title: string;
|
|
44
|
+
createdAt: number;
|
|
45
|
+
updatedAt: number;
|
|
46
|
+
model?: BtwModel;
|
|
47
|
+
/** Set once the topic was promoted into the chat as a session of its own. */
|
|
48
|
+
promotedSessionId?: string;
|
|
49
|
+
turns: BtwTurn[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A session's whole BTW view. */
|
|
53
|
+
export interface BtwState {
|
|
54
|
+
topics: BtwTopic[];
|
|
55
|
+
/** Topic with a question in flight; at most one per session, because a
|
|
56
|
+
* session runs one side conversation at a time. */
|
|
57
|
+
runningTopicId: string | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Server → client frames on `/api/btw/:sessionId/*` (WS and SSE alike). */
|
|
61
|
+
export type BtwFrame =
|
|
62
|
+
| { type: 'connected'; sessionId: string }
|
|
63
|
+
| { type: 'btw_state'; state: BtwState }
|
|
64
|
+
| { type: 'btw_delta'; topicId: string; turnIndex: number; text: string }
|
|
65
|
+
| { type: 'btw_error'; topicId: string | null; message: string };
|