dsh-multiview 0.0.0-stage → 0.1.8

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/client.js ADDED
@@ -0,0 +1,2250 @@
1
+ /**
2
+ * dsh-multiview 鈥?Client half.
3
+ * Generated from src/client/*.ts by scripts/build.mjs. Do not edit by hand.
4
+ */
5
+ window.__ModuleLoader__.load({
6
+ id: 'dsh-multiview',
7
+ factory(require) {
8
+ const React = require('react')
9
+ const h = React.createElement
10
+ const NS = 'multiview';
11
+ const MV_PANEL_ID = 'multiview';
12
+ const MV_PANE_SLOT = 'multiview.pane';
13
+ const MV_STORAGE_KEY = 'dsh.multiview.v1';
14
+ const MV_PLUGIN_VERSION = '0.1.8';
15
+ const MV_ZH = {
16
+ panel: '分屏',
17
+ title: '分屏',
18
+ empty: '选择一个会话',
19
+ pick: '选择会话',
20
+ pickSearch: '搜索会话',
21
+ pickEmpty: '没有匹配的会话',
22
+ loading: '正在打开会话…',
23
+ openFailed: '打开会话失败:{message}',
24
+ noContent: '当前 DSH 版本不支持在格内嵌入会话',
25
+ paneCrashed: '这一格出错了:{message}',
26
+ stageCrashed: '分屏面板出错了:{message}',
27
+ maximize: '最大化',
28
+ restore: '还原',
29
+ remove: '清空这一格',
30
+ openMain: '在原生视图打开',
31
+ shape: '格数',
32
+ shapeN: '{n} 格',
33
+ running: '运行中',
34
+ waiting: '等待你处理',
35
+ unread: '有新结果',
36
+ views: '视图',
37
+ viewsEmpty: '还没有保存的视图。绑定两个以上会话后会自动记住。',
38
+ viewsCurrent: '当前分屏',
39
+ viewsDelete: '删除视图',
40
+ viewsHint: '点击恢复这个分屏',
41
+ updateCheck: '检查更新',
42
+ updateChecking: '正在检查更新…',
43
+ updateLatest: '已是最新版本({version})',
44
+ updateAvailable: '发现新版本 {version}',
45
+ updateInstall: '立即更新',
46
+ updateInstalling: '正在更新…',
47
+ updateDone: '更新完成,重启 DSH 后生效',
48
+ updateRestart: '重启 DSH',
49
+ updateFailed: '检查更新失败:{message}',
50
+ updateUnsupported: '未检测到插件市场,无法自动更新',
51
+ updateLocal: '当前为本地链接安装,自动更新不可用',
52
+ updateIdle: '尚未检查更新',
53
+ cancel: '取消',
54
+ close: '关闭',
55
+ shortcutFocusPrev: '聚焦上一格',
56
+ shortcutFocusNext: '聚焦下一格',
57
+ shortcutMaximize: '最大化或还原当前格',
58
+ shortcutRemove: '从分屏移除当前格'
59
+ };
60
+ const MV_EN = {
61
+ panel: 'Split',
62
+ title: 'Split',
63
+ empty: 'Pick a session',
64
+ pick: 'Pick a session',
65
+ pickSearch: 'Search sessions',
66
+ pickEmpty: 'No matching session',
67
+ loading: 'Opening session…',
68
+ openFailed: 'Could not open this session: {message}',
69
+ noContent: 'This DSH version cannot host a conversation in a pane',
70
+ paneCrashed: 'This pane failed: {message}',
71
+ stageCrashed: 'The split view failed: {message}',
72
+ maximize: 'Maximize',
73
+ restore: 'Restore',
74
+ remove: 'Clear this pane',
75
+ openMain: 'Open in native view',
76
+ shape: 'Pane count',
77
+ shapeN: '{n} panes',
78
+ running: 'Running',
79
+ waiting: 'Waiting for you',
80
+ unread: 'New result',
81
+ views: 'Views',
82
+ viewsEmpty: 'No saved views yet. A split with two or more sessions is remembered automatically.',
83
+ viewsCurrent: 'Current split',
84
+ viewsDelete: 'Delete view',
85
+ viewsHint: 'Restore this split',
86
+ updateCheck: 'Check for updates',
87
+ updateChecking: 'Checking for updates…',
88
+ updateLatest: 'Up to date ({version})',
89
+ updateAvailable: 'Version {version} is available',
90
+ updateInstall: 'Update now',
91
+ updateInstalling: 'Updating…',
92
+ updateDone: 'Updated — restart DSH to apply',
93
+ updateRestart: 'Restart DSH',
94
+ updateFailed: 'Update check failed: {message}',
95
+ updateUnsupported: 'No plugin market detected; automatic updates unavailable',
96
+ updateLocal: 'Installed from a local link; automatic updates unavailable',
97
+ updateIdle: 'Not checked yet',
98
+ cancel: 'Cancel',
99
+ close: 'Close',
100
+ shortcutFocusPrev: 'Focus previous pane',
101
+ shortcutFocusNext: 'Focus next pane',
102
+ shortcutMaximize: 'Maximize or restore the current pane',
103
+ shortcutRemove: 'Remove the current pane from the split'
104
+ };
105
+ let PTX = null;
106
+ let T = function translateFallback(key) {
107
+ return MV_ZH[key] === undefined ? key : MV_ZH[key];
108
+ };
109
+ function mvFormat(template, params) {
110
+ if (params === undefined) return template;
111
+ return template.replace(/\{(\w+)\}/g, (match, name)=>params[name] === undefined ? match : String(params[name]));
112
+ }
113
+
114
+ function mvConversationService() {
115
+ try {
116
+ const direct = PTX.conversation;
117
+ if (direct !== undefined && direct !== null) return direct;
118
+ } catch (error) {}
119
+ try {
120
+ const get = PTX.get;
121
+ return typeof get === 'function' ? get.call(PTX, 'conversation') : undefined;
122
+ } catch (error) {
123
+ return undefined;
124
+ }
125
+ }
126
+ function mvFocusComposer(sessionId) {
127
+ if (sessionId === null || sessionId === undefined) return false;
128
+ try {
129
+ const scoped = PTX.sessions.scope(sessionId);
130
+ if (scoped === undefined || scoped === null) return false;
131
+ const conversation = mvConversationService();
132
+ const resolver = conversation === undefined ? undefined : conversation.input;
133
+ if (resolver === undefined || resolver === null || typeof resolver.for !== 'function') return false;
134
+ const facade = resolver.for(scoped);
135
+ if (facade === undefined || facade === null || typeof facade.focus !== 'function') return false;
136
+ facade.focus();
137
+ return true;
138
+ } catch (error) {
139
+ return false;
140
+ }
141
+ }
142
+ function mvConversationLive() {
143
+ try {
144
+ const slots = PTX.slots;
145
+ if (slots === undefined || slots === null) return false;
146
+ if (typeof slots.spec === 'function' && slots.spec('conversation.session') !== undefined) return true;
147
+ if (typeof slots.specDynamic === 'function' && slots.specDynamic('conversation.session') !== undefined) return true;
148
+ return typeof slots.entries === 'function' && slots.entries('conversation.session').length > 0;
149
+ } catch (error) {
150
+ return false;
151
+ }
152
+ }
153
+ function mvRow(sessionId) {
154
+ if (sessionId === null || sessionId === undefined) return undefined;
155
+ try {
156
+ return PTX.sessions.list.getSnapshot().byId[sessionId];
157
+ } catch (error) {
158
+ return undefined;
159
+ }
160
+ }
161
+ function mvTitle(sessionId) {
162
+ const row = mvRow(sessionId);
163
+ return row === undefined ? undefined : row.title;
164
+ }
165
+ function mvIsArchived(sessionId) {
166
+ try {
167
+ const archived = PTX.workspaces.list.getSnapshot().archivedSessionIds;
168
+ return archived.includes(sessionId);
169
+ } catch (error) {
170
+ return false;
171
+ }
172
+ }
173
+ function mvSeed(limit) {
174
+ const picked = [];
175
+ try {
176
+ const list = PTX.sessions.list.getSnapshot();
177
+ const current = list.ids.find((id)=>(list.byId[id]?.retainedBy?.mainView ?? 0) > 0);
178
+ const ordered = current === undefined ? list.ids.slice() : [
179
+ current,
180
+ ...list.ids
181
+ ];
182
+ for (const id of ordered){
183
+ if (picked.length >= limit) break;
184
+ if (picked.includes(id)) continue;
185
+ const row = list.byId[id];
186
+ if (row === undefined || row.blank === true) continue;
187
+ if (mvIsArchived(id)) continue;
188
+ picked.push(id);
189
+ }
190
+ } catch (error) {}
191
+ return picked;
192
+ }
193
+ function mvTopUp(current, limit) {
194
+ const next = current.slice(0, limit);
195
+ while(next.length < limit)next.push(null);
196
+ let changed = next.length !== current.length;
197
+ const used = new Set(next.filter((id)=>id !== null && id !== undefined));
198
+ try {
199
+ const list = PTX.sessions.list.getSnapshot();
200
+ const current = list.ids.find((id)=>(list.byId[id]?.retainedBy?.mainView ?? 0) > 0);
201
+ const ordered = current === undefined ? list.ids.slice() : [
202
+ current,
203
+ ...list.ids
204
+ ];
205
+ for(let index = 0; index < next.length; index += 1){
206
+ if (next[index] !== null && next[index] !== undefined) continue;
207
+ const candidate = ordered.find((id)=>{
208
+ if (used.has(id)) return false;
209
+ const row = list.byId[id];
210
+ return row !== undefined && row.blank !== true && !mvIsArchived(id);
211
+ });
212
+ if (candidate === undefined) continue;
213
+ next[index] = candidate;
214
+ used.add(candidate);
215
+ changed = true;
216
+ }
217
+ } catch (error) {}
218
+ return changed ? next : current;
219
+ }
220
+
221
+ const MV_MAX_PANES = 4;
222
+ const MV_MIN_RATIO = 0.22;
223
+ const MV_MAX_RATIO = 0.78;
224
+ function mvClampRatio(value) {
225
+ if (!Number.isFinite(value)) return 0.5;
226
+ return Math.min(MV_MAX_RATIO, Math.max(MV_MIN_RATIO, value));
227
+ }
228
+ function mvLeaf(id) {
229
+ return {
230
+ kind: 'leaf',
231
+ id,
232
+ sessionId: null
233
+ };
234
+ }
235
+ function mvSplit(id, dir, ratio, a, b) {
236
+ return {
237
+ kind: 'split',
238
+ id,
239
+ dir,
240
+ ratio: mvClampRatio(ratio),
241
+ a,
242
+ b
243
+ };
244
+ }
245
+ function mvPreset(count) {
246
+ if (count <= 2) return mvSplit('s1', 'row', 0.5, mvLeaf('p1'), mvLeaf('p2'));
247
+ if (count === 3) {
248
+ return mvSplit('s1', 'row', 0.55, mvLeaf('p1'), mvSplit('s2', 'col', 0.5, mvLeaf('p2'), mvLeaf('p3')));
249
+ }
250
+ return mvSplit('s1', 'row', 0.5, mvSplit('s2', 'col', 0.5, mvLeaf('p1'), mvLeaf('p2')), mvSplit('s3', 'col', 0.5, mvLeaf('p3'), mvLeaf('p4')));
251
+ }
252
+ function mvLeaves(node, out) {
253
+ const list = out === undefined ? [] : out;
254
+ if (node === undefined || node === null) return list;
255
+ if (node.kind === 'leaf') {
256
+ list.push(node);
257
+ return list;
258
+ }
259
+ mvLeaves(node.a, list);
260
+ mvLeaves(node.b, list);
261
+ return list;
262
+ }
263
+ function mvLeafCount(node) {
264
+ return mvLeaves(node).length;
265
+ }
266
+ function mvShapeTo(node, count) {
267
+ const target = Math.max(2, Math.min(MV_MAX_PANES, count));
268
+ const current = mvLeaves(node);
269
+ if (current.length === target) return node;
270
+ const sessions = current.map((leaf)=>leaf.sessionId);
271
+ const next = mvPreset(target);
272
+ const leaves = mvLeaves(next);
273
+ for(let index = 0; index < leaves.length; index += 1)leaves[index].sessionId = sessions[index] ?? null;
274
+ return next;
275
+ }
276
+ function mvMapLeaves(node, edit) {
277
+ if (node.kind === 'leaf') return edit(node);
278
+ const a = mvMapLeaves(node.a, edit);
279
+ const b = mvMapLeaves(node.b, edit);
280
+ if (a === node.a && b === node.b) return node;
281
+ return {
282
+ ...node,
283
+ a,
284
+ b
285
+ };
286
+ }
287
+ function mvWithSession(node, leafId, sessionId) {
288
+ return mvMapLeaves(node, (leaf)=>{
289
+ if (leaf.id !== leafId && sessionId !== null && leaf.sessionId === sessionId) return {
290
+ ...leaf,
291
+ sessionId: null
292
+ };
293
+ if (leaf.id !== leafId) return leaf;
294
+ if (leaf.sessionId === sessionId) return leaf;
295
+ return {
296
+ ...leaf,
297
+ sessionId
298
+ };
299
+ });
300
+ }
301
+ function mvSwapSessions(node, aId, bId) {
302
+ if (aId === bId) return node;
303
+ const leaves = mvLeaves(node);
304
+ const a = leaves.find((leaf)=>leaf.id === aId);
305
+ const b = leaves.find((leaf)=>leaf.id === bId);
306
+ if (a === undefined || b === undefined) return node;
307
+ const aSession = a.sessionId;
308
+ const bSession = b.sessionId;
309
+ if (aSession === bSession) return node;
310
+ return mvMapLeaves(node, (leaf)=>{
311
+ if (leaf.id === aId) return {
312
+ ...leaf,
313
+ sessionId: bSession
314
+ };
315
+ if (leaf.id === bId) return {
316
+ ...leaf,
317
+ sessionId: aSession
318
+ };
319
+ return leaf;
320
+ });
321
+ }
322
+ function mvResize(node, splitId, ratio) {
323
+ if (node === null || node === undefined) return node;
324
+ if (node.kind === 'leaf') return node;
325
+ if (node.id === splitId) {
326
+ const clamped = mvClampRatio(ratio);
327
+ return clamped === node.ratio ? node : {
328
+ ...node,
329
+ ratio: clamped
330
+ };
331
+ }
332
+ const a = mvResize(node.a, splitId, ratio);
333
+ const b = mvResize(node.b, splitId, ratio);
334
+ if (a === node.a && b === node.b) return node;
335
+ return {
336
+ ...node,
337
+ a,
338
+ b
339
+ };
340
+ }
341
+ function mvRemoveLeaf(node, leafId) {
342
+ if (node === null || node === undefined) return null;
343
+ if (node.kind === 'leaf') return node.id === leafId ? null : node;
344
+ const a = mvRemoveLeaf(node.a, leafId);
345
+ const b = mvRemoveLeaf(node.b, leafId);
346
+ if (a === null) return b;
347
+ if (b === null) return a;
348
+ if (a === node.a && b === node.b) return node;
349
+ return {
350
+ ...node,
351
+ a,
352
+ b
353
+ };
354
+ }
355
+ function mvLeafById(node, leafId) {
356
+ return mvLeaves(node).find((leaf)=>leaf.id === leafId);
357
+ }
358
+ function mvNeighbour(node, leafId, delta) {
359
+ const leaves = mvLeaves(node);
360
+ const index = leaves.findIndex((leaf)=>leaf.id === leafId);
361
+ if (index < 0 || leaves.length < 2) return leafId;
362
+ const next = (index + delta + leaves.length) % leaves.length;
363
+ return leaves[next].id;
364
+ }
365
+ function mvIsLeaf(value) {
366
+ return value !== null && typeof value === 'object' && value.kind === 'leaf' && typeof value.id === 'string' && (value.sessionId === null || typeof value.sessionId === 'string');
367
+ }
368
+ function mvIsSplit(value) {
369
+ if (value === null || typeof value !== 'object' || value.kind !== 'split') return false;
370
+ if (typeof value.id !== 'string') return false;
371
+ if (value.dir !== 'row' && value.dir !== 'col') return false;
372
+ if (typeof value.ratio !== 'number' || !Number.isFinite(value.ratio)) return false;
373
+ return value.a !== null && value.b !== null && typeof value.a === 'object' && typeof value.b === 'object';
374
+ }
375
+ function mvNormalizeIds(node, seen) {
376
+ if (node === null || node === undefined) return node;
377
+ if (node.kind === 'leaf') {
378
+ if (seen.has(node.id)) return {
379
+ ...node,
380
+ id: 'p' + String(seen.size + 1)
381
+ };
382
+ seen.add(node.id);
383
+ return node;
384
+ }
385
+ const a = mvNormalizeIds(node.a, seen);
386
+ const b = mvNormalizeIds(node.b, seen);
387
+ const id = seen.has(node.id) ? 's' + String(seen.size + 1) : node.id;
388
+ seen.add(id);
389
+ if (a === node.a && b === node.b && id === node.id) return node;
390
+ return {
391
+ ...node,
392
+ id,
393
+ a,
394
+ b
395
+ };
396
+ }
397
+ function mvSanitize(raw) {
398
+ const walk = (value, depth)=>{
399
+ if (depth > 8) return null;
400
+ if (mvIsLeaf(value)) return {
401
+ kind: 'leaf',
402
+ id: value.id,
403
+ sessionId: value.sessionId
404
+ };
405
+ if (!mvIsSplit(value)) return null;
406
+ const a = walk(value.a, depth + 1);
407
+ const b = walk(value.b, depth + 1);
408
+ if (a === null || b === null) return null;
409
+ const leaves = mvLeaves(a).length + mvLeaves(b).length;
410
+ if (leaves > MV_MAX_PANES) return a;
411
+ return {
412
+ kind: 'split',
413
+ id: value.id,
414
+ dir: value.dir,
415
+ ratio: mvClampRatio(value.ratio),
416
+ a,
417
+ b
418
+ };
419
+ };
420
+ const tree = walk(raw, 0);
421
+ if (tree === null) return null;
422
+ if (tree.kind === 'leaf') return tree;
423
+ return mvNormalizeIds(tree, new Set());
424
+ }
425
+ function mvReconcileTree(node, reconcile) {
426
+ return mvMapLeaves(node, (leaf)=>{
427
+ if (leaf.sessionId === null) return leaf;
428
+ const kept = reconcile(leaf.sessionId);
429
+ if (kept === leaf.sessionId) return leaf;
430
+ return {
431
+ ...leaf,
432
+ sessionId: kept
433
+ };
434
+ });
435
+ }
436
+
437
+ function mvInitialState() {
438
+ return {
439
+ layout: mvPreset(2),
440
+ focused: 'p1',
441
+ maximized: null,
442
+ preset: 2,
443
+ seeded: false,
444
+ reconciled: false,
445
+ views: [],
446
+ currentViewId: null
447
+ };
448
+ }
449
+ let mvState = mvInitialState();
450
+ const mvListeners = new Set();
451
+ let mvSaveTimer = 0;
452
+ function mvGetState() {
453
+ return mvState;
454
+ }
455
+ function mvSubscribe(listener) {
456
+ mvListeners.add(listener);
457
+ return ()=>{
458
+ mvListeners.delete(listener);
459
+ };
460
+ }
461
+ function mvScheduleSave() {
462
+ if (mvSaveTimer !== 0) clearTimeout(mvSaveTimer);
463
+ mvSaveTimer = setTimeout(()=>{
464
+ mvSaveTimer = 0;
465
+ try {
466
+ const durable = {
467
+ version: 3,
468
+ layout: mvState.layout,
469
+ focused: mvState.focused,
470
+ preset: mvState.preset,
471
+ seeded: mvState.seeded,
472
+ views: mvState.views,
473
+ currentViewId: mvState.currentViewId
474
+ };
475
+ window.localStorage.setItem(MV_STORAGE_KEY, JSON.stringify(durable));
476
+ } catch (error) {}
477
+ }, 200);
478
+ }
479
+ function mvViewKey(layout) {
480
+ return mvLeaves(layout).map((leaf)=>leaf.sessionId).filter((sessionId)=>sessionId !== null).join(',');
481
+ }
482
+ function mvViewName(layout) {
483
+ let list = null;
484
+ try {
485
+ list = PTX.sessions.list.getSnapshot();
486
+ } catch (error) {
487
+ list = null;
488
+ }
489
+ const titles = mvLeaves(layout).map((leaf)=>leaf.sessionId === null ? null : list?.byId[leaf.sessionId]?.title).filter((title)=>typeof title === 'string' && title.length > 0);
490
+ if (titles.length === 0) return T('viewsCurrent');
491
+ return titles.join(' | ');
492
+ }
493
+ function mvViewWorthy(layout) {
494
+ const key = mvViewKey(layout);
495
+ return key !== '' && key.indexOf(',') >= 0;
496
+ }
497
+ function mvSyncView(next) {
498
+ const views = next.views ?? [];
499
+ const worthy = mvViewWorthy(next.layout);
500
+ const current = views.find((view)=>view.id === next.currentViewId) ?? null;
501
+ if (!worthy) {
502
+ if (current === null) return next;
503
+ return {
504
+ ...next,
505
+ views: views.filter((view)=>view.id !== current.id),
506
+ currentViewId: null
507
+ };
508
+ }
509
+ if (current !== null) {
510
+ return {
511
+ ...next,
512
+ views: views.map((view)=>view.id === current.id ? {
513
+ ...view,
514
+ layout: next.layout,
515
+ name: mvViewName(next.layout)
516
+ } : view)
517
+ };
518
+ }
519
+ const key = mvViewKey(next.layout);
520
+ const existing = views.find((view)=>view.id === key);
521
+ if (existing !== undefined) {
522
+ return {
523
+ ...next,
524
+ currentViewId: existing.id,
525
+ views: views.map((view)=>view.id === existing.id ? {
526
+ ...view,
527
+ layout: next.layout,
528
+ name: mvViewName(next.layout)
529
+ } : view)
530
+ };
531
+ }
532
+ const created = {
533
+ id: key,
534
+ name: mvViewName(next.layout),
535
+ layout: next.layout,
536
+ savedAt: Date.now()
537
+ };
538
+ return {
539
+ ...next,
540
+ currentViewId: created.id,
541
+ views: [
542
+ created,
543
+ ...views
544
+ ].slice(0, 12)
545
+ };
546
+ }
547
+ function mvCommit(produce) {
548
+ const produced = produce(mvState);
549
+ if (produced === mvState || produced === null || produced === undefined) return;
550
+ mvState = mvSyncView(produced);
551
+ mvScheduleSave();
552
+ for (const listener of mvListeners){
553
+ try {
554
+ listener();
555
+ } catch (error) {
556
+ console.error('dsh-multiview: state listener failed', error);
557
+ }
558
+ }
559
+ }
560
+ function mvSanitizeView(raw) {
561
+ if (raw === null || typeof raw !== 'object') return null;
562
+ const layout = mvSanitize(raw.layout);
563
+ if (layout === null || !mvViewWorthy(layout)) return null;
564
+ return {
565
+ id: typeof raw.id === 'string' && raw.id.length > 0 ? raw.id : mvViewKey(layout),
566
+ name: typeof raw.name === 'string' && raw.name.length > 0 ? raw.name : T('viewsCurrent'),
567
+ layout,
568
+ savedAt: typeof raw.savedAt === 'number' ? raw.savedAt : Date.now()
569
+ };
570
+ }
571
+ function mvHydrate() {
572
+ let raw = null;
573
+ try {
574
+ raw = JSON.parse(window.localStorage.getItem(MV_STORAGE_KEY) ?? 'null');
575
+ } catch (error) {
576
+ raw = null;
577
+ }
578
+ if (raw === null || typeof raw !== 'object') return;
579
+ if (raw.version !== 1 && raw.version !== 2 && raw.version !== 3) return;
580
+ const layout = mvSanitize(raw.layout);
581
+ if (layout === null) return;
582
+ const leaves = mvLeaves(layout);
583
+ const focused = leaves.some((leaf)=>leaf.id === raw.focused) ? raw.focused : leaves[0].id;
584
+ const views = Array.isArray(raw.views) ? raw.views.map(mvSanitizeView).filter((view)=>view !== null) : [];
585
+ const currentViewId = typeof raw.currentViewId === 'string' && views.some((view)=>view.id === raw.currentViewId) ? raw.currentViewId : null;
586
+ mvState = {
587
+ layout,
588
+ focused,
589
+ maximized: null,
590
+ preset: typeof raw.preset === 'number' ? raw.preset : leaves.length,
591
+ seeded: raw.seeded === true,
592
+ reconciled: false,
593
+ views,
594
+ currentViewId
595
+ };
596
+ }
597
+ function mvReset() {
598
+ const views = mvState.views;
599
+ mvCommit(()=>({
600
+ ...mvInitialState(),
601
+ views,
602
+ currentViewId: null
603
+ }));
604
+ try {
605
+ window.localStorage.removeItem(MV_STORAGE_KEY);
606
+ } catch (error) {}
607
+ }
608
+ function mvReconcile() {
609
+ if (mvState.reconciled) return;
610
+ try {
611
+ const list = PTX.sessions.list.getSnapshot();
612
+ if (list.phase !== 'ready') return;
613
+ const kept = mvReconcileTree(mvState.layout, (sessionId)=>{
614
+ const row = list.byId[sessionId];
615
+ if (row === undefined || row.blank === true) return null;
616
+ if (mvIsArchived(sessionId)) return null;
617
+ return sessionId;
618
+ });
619
+ const leaves = mvLeaves(kept);
620
+ const focused = leaves.some((leaf)=>leaf.id === mvState.focused) ? mvState.focused : leaves[0].id;
621
+ mvCommit((current)=>({
622
+ ...current,
623
+ layout: kept,
624
+ focused,
625
+ reconciled: true
626
+ }));
627
+ } catch (error) {
628
+ mvCommit((current)=>({
629
+ ...current,
630
+ reconciled: true
631
+ }));
632
+ }
633
+ }
634
+ function mvOpenForSession(sessionId) {
635
+ if (sessionId === null) return;
636
+ mvCommit((current)=>{
637
+ const owners = current.views.filter((view)=>mvViewKey(view.layout).split(',').includes(sessionId));
638
+ const owner = owners[0];
639
+ if (owner !== undefined) {
640
+ if (current.currentViewId === owner.id && mvViewKey(current.layout) === mvViewKey(owner.layout)) return current;
641
+ return {
642
+ ...current,
643
+ layout: owner.layout,
644
+ currentViewId: owner.id,
645
+ maximized: null,
646
+ preset: mvLeaves(owner.layout).length
647
+ };
648
+ }
649
+ if (mvViewKey(current.layout).split(',').includes(sessionId)) {
650
+ return current.currentViewId === null ? {
651
+ ...current,
652
+ currentViewId: null
653
+ } : current;
654
+ }
655
+ const layout = mvWithSession(mvPreset(2), 'p1', sessionId);
656
+ return {
657
+ ...current,
658
+ layout,
659
+ currentViewId: null,
660
+ focused: 'p1',
661
+ preset: 2,
662
+ maximized: null
663
+ };
664
+ });
665
+ }
666
+ function mvViewOwner(sessionId, views, currentViewId) {
667
+ for (const view of views){
668
+ if (view.id === currentViewId) continue;
669
+ if (mvViewKey(view.layout).split(',').includes(sessionId)) return view;
670
+ }
671
+ return null;
672
+ }
673
+ function mvSetShape(count) {
674
+ mvCommit((current)=>{
675
+ const layout = mvShapeTo(current.layout, count);
676
+ if (layout === current.layout) return current;
677
+ const leaves = mvLeaves(layout);
678
+ const focused = leaves.some((leaf)=>leaf.id === current.focused) ? current.focused : leaves[0].id;
679
+ return {
680
+ ...current,
681
+ layout,
682
+ focused,
683
+ preset: count,
684
+ maximized: null,
685
+ seeded: true
686
+ };
687
+ });
688
+ }
689
+ function mvBind(leafId, sessionId) {
690
+ mvCommit((current)=>{
691
+ const layout = mvWithSession(current.layout, leafId, sessionId);
692
+ if (layout === current.layout) return current;
693
+ const focused = sessionId === null ? current.focused : leafId;
694
+ return {
695
+ ...current,
696
+ layout,
697
+ focused,
698
+ maximized: current.maximized === leafId ? null : current.maximized
699
+ };
700
+ });
701
+ }
702
+ function mvFocus(leafId) {
703
+ mvCommit((current)=>current.focused === leafId ? current : {
704
+ ...current,
705
+ focused: leafId
706
+ });
707
+ }
708
+ function mvToggleMaximize(leafId) {
709
+ mvCommit((current)=>current.maximized === leafId ? {
710
+ ...current,
711
+ maximized: null
712
+ } : {
713
+ ...current,
714
+ maximized: leafId,
715
+ focused: leafId
716
+ });
717
+ }
718
+ function mvRestore() {
719
+ mvCommit((current)=>current.maximized === null ? current : {
720
+ ...current,
721
+ maximized: null
722
+ });
723
+ }
724
+ function mvResizeSplit(splitId, ratio) {
725
+ mvCommit((current)=>{
726
+ const layout = mvResize(current.layout, splitId, ratio);
727
+ return layout === current.layout ? current : {
728
+ ...current,
729
+ layout
730
+ };
731
+ });
732
+ }
733
+ function mvSwapLeaves(aId, bId) {
734
+ mvCommit((current)=>{
735
+ const layout = mvSwapSessions(current.layout, aId, bId);
736
+ return layout === current.layout ? current : {
737
+ ...current,
738
+ layout,
739
+ maximized: null
740
+ };
741
+ });
742
+ }
743
+ function mvRemovePane(leafId) {
744
+ mvCommit((current)=>{
745
+ const leaf = mvLeafById(current.layout, leafId);
746
+ if (leaf === null) return current;
747
+ if (leaf.sessionId !== null) {
748
+ const cleared = mvWithSession(current.layout, leafId, null);
749
+ return cleared === current.layout ? current : {
750
+ ...current,
751
+ layout: cleared,
752
+ focused: leafId
753
+ };
754
+ }
755
+ const leaves = mvLeaves(current.layout);
756
+ if (leaves.length <= 1) return current;
757
+ const layout = mvRemoveLeaf(current.layout, leafId);
758
+ if (layout === null) return current;
759
+ const nextLeaves = mvLeaves(layout);
760
+ const focused = nextLeaves.some((item)=>item.id === current.focused) ? current.focused : nextLeaves[0].id;
761
+ return {
762
+ ...current,
763
+ layout,
764
+ focused,
765
+ preset: nextLeaves.length,
766
+ maximized: current.maximized === leafId ? null : current.maximized
767
+ };
768
+ });
769
+ }
770
+ function mvApplyView(viewId) {
771
+ mvCommit((current)=>{
772
+ const view = current.views.find((item)=>item.id === viewId);
773
+ if (view === undefined) return current;
774
+ const leaves = mvLeaves(view.layout);
775
+ return {
776
+ ...current,
777
+ layout: view.layout,
778
+ focused: leaves[0].id,
779
+ preset: leaves.length,
780
+ maximized: null,
781
+ currentViewId: view.id
782
+ };
783
+ });
784
+ }
785
+ function mvStartView() {
786
+ mvCommit((current)=>({
787
+ ...current,
788
+ layout: mvPreset(2),
789
+ focused: 'p1',
790
+ preset: 2,
791
+ maximized: null,
792
+ currentViewId: null
793
+ }));
794
+ }
795
+ function mvDeleteView(viewId) {
796
+ mvCommit((current)=>{
797
+ if (!current.views.some((item)=>item.id === viewId)) return current;
798
+ return {
799
+ ...current,
800
+ views: current.views.filter((item)=>item.id !== viewId),
801
+ currentViewId: current.currentViewId === viewId ? null : current.currentViewId
802
+ };
803
+ });
804
+ }
805
+
806
+ const MV_UPDATE_KEY = 'dsh.multiview.update.v1';
807
+ const MV_CHECK_INTERVAL = 24 * 60 * 60 * 1000;
808
+ const MV_BACKOFF_BASE = 60 * 60 * 1000;
809
+ const MV_BACKOFF_MAX = 12 * 60 * 60 * 1000;
810
+ const MV_POLL_INTERVAL = 1500;
811
+ const MV_POLL_LIMIT = 200;
812
+ const MV_REQUEST_TIMEOUT = 20000;
813
+ let mvUpdateState = {
814
+ status: 'idle',
815
+ installed: MV_PLUGIN_VERSION,
816
+ latest: null,
817
+ source: null,
818
+ available: false,
819
+ localOnly: false,
820
+ checkedAt: 0,
821
+ nextAt: 0,
822
+ failures: 0,
823
+ error: null,
824
+ operationId: null,
825
+ operationState: null,
826
+ restartRequired: false,
827
+ restartSupported: false
828
+ };
829
+ const mvUpdateListeners = new Set();
830
+ function mvUpdateGetState() {
831
+ return mvUpdateState;
832
+ }
833
+ function mvUpdateSubscribe(listener) {
834
+ mvUpdateListeners.add(listener);
835
+ return ()=>{
836
+ mvUpdateListeners.delete(listener);
837
+ };
838
+ }
839
+ function mvUpdateCommit(patch) {
840
+ mvUpdateState = {
841
+ ...mvUpdateState,
842
+ ...patch
843
+ };
844
+ try {
845
+ window.localStorage.setItem(MV_UPDATE_KEY, JSON.stringify({
846
+ checkedAt: mvUpdateState.checkedAt,
847
+ nextAt: mvUpdateState.nextAt,
848
+ failures: mvUpdateState.failures,
849
+ latest: mvUpdateState.latest,
850
+ available: mvUpdateState.available,
851
+ localOnly: mvUpdateState.localOnly,
852
+ source: mvUpdateState.source
853
+ }));
854
+ } catch (error) {}
855
+ for (const listener of mvUpdateListeners){
856
+ try {
857
+ listener();
858
+ } catch (error) {
859
+ console.error('dsh-multiview: update listener failed', error);
860
+ }
861
+ }
862
+ }
863
+ function mvUpdateHydrate() {
864
+ try {
865
+ const raw = JSON.parse(window.localStorage.getItem(MV_UPDATE_KEY) ?? 'null');
866
+ if (raw === null || typeof raw !== 'object') return;
867
+ mvUpdateState = {
868
+ ...mvUpdateState,
869
+ checkedAt: typeof raw.checkedAt === 'number' ? raw.checkedAt : 0,
870
+ nextAt: typeof raw.nextAt === 'number' ? raw.nextAt : 0,
871
+ failures: typeof raw.failures === 'number' ? raw.failures : 0,
872
+ latest: typeof raw.latest === 'string' ? raw.latest : null,
873
+ available: raw.available === true,
874
+ localOnly: raw.localOnly === true,
875
+ source: typeof raw.source === 'string' ? raw.source : null
876
+ };
877
+ } catch (error) {}
878
+ }
879
+ function mvVersionParts(version) {
880
+ const core = String(version).split('-')[0].split('+')[0];
881
+ return core.split('.').map((part)=>{
882
+ const value = Number.parseInt(part, 10);
883
+ return Number.isFinite(value) ? value : 0;
884
+ });
885
+ }
886
+ function mvVersionGt(left, right) {
887
+ const a = mvVersionParts(left);
888
+ const b = mvVersionParts(right);
889
+ for(let index = 0; index < Math.max(a.length, b.length); index += 1){
890
+ const first = a[index] ?? 0;
891
+ const second = b[index] ?? 0;
892
+ if (first !== second) return first > second;
893
+ }
894
+ return false;
895
+ }
896
+ async function mvUpdateGet(url) {
897
+ const controller = typeof AbortController === 'function' ? new AbortController() : null;
898
+ const timer = controller === null ? 0 : setTimeout(()=>controller.abort(), MV_REQUEST_TIMEOUT);
899
+ try {
900
+ const response = await fetch(url, {
901
+ credentials: 'same-origin',
902
+ headers: {
903
+ accept: 'application/json'
904
+ },
905
+ signal: controller === null ? undefined : controller.signal
906
+ });
907
+ if (!response.ok) throw new Error('HTTP ' + response.status);
908
+ return await response.json();
909
+ } finally{
910
+ if (timer !== 0) clearTimeout(timer);
911
+ }
912
+ }
913
+ async function mvUpdatePost(url, body) {
914
+ const controller = typeof AbortController === 'function' ? new AbortController() : null;
915
+ const timer = controller === null ? 0 : setTimeout(()=>controller.abort(), MV_REQUEST_TIMEOUT);
916
+ try {
917
+ const response = await fetch(url, {
918
+ method: 'POST',
919
+ credentials: 'same-origin',
920
+ headers: {
921
+ 'content-type': 'application/json',
922
+ accept: 'application/json'
923
+ },
924
+ body: JSON.stringify(body),
925
+ signal: controller === null ? undefined : controller.signal
926
+ });
927
+ if (!response.ok) throw new Error('HTTP ' + response.status);
928
+ return await response.json();
929
+ } finally{
930
+ if (timer !== 0) clearTimeout(timer);
931
+ }
932
+ }
933
+ async function mvUpdateCheck(force) {
934
+ const now = Date.now();
935
+ if (force !== true) {
936
+ if (now - mvUpdateState.checkedAt < MV_CHECK_INTERVAL) return;
937
+ if (mvUpdateState.nextAt > now) return;
938
+ }
939
+ if (mvUpdateState.status === 'checking' || mvUpdateState.status === 'installing') return;
940
+ mvUpdateCommit({
941
+ status: 'checking',
942
+ error: null
943
+ });
944
+ try {
945
+ let capabilities = null;
946
+ try {
947
+ capabilities = await mvUpdateGet('/dsh-market/api/v1/capabilities');
948
+ } catch (error) {
949
+ capabilities = null;
950
+ }
951
+ if (capabilities === null || capabilities.schema !== 'dsh-market/update-api/v1') {
952
+ mvUpdateCommit({
953
+ status: 'unsupported',
954
+ checkedAt: now,
955
+ nextAt: now + MV_CHECK_INTERVAL,
956
+ failures: 0
957
+ });
958
+ return;
959
+ }
960
+ const endpoints = capabilities.endpoints ?? {};
961
+ const checkPath = typeof endpoints.updates === 'string' ? endpoints.updates : '/dsh-market/api/v1/updates';
962
+ const answer = await mvUpdateGet(checkPath + '?name=dsh-multiview');
963
+ const pkg = answer?.package ?? null;
964
+ const installed = typeof pkg?.installedVersion === 'string' ? pkg.installedVersion : MV_PLUGIN_VERSION;
965
+ const latest = typeof pkg?.latestVersion === 'string' ? pkg.latestVersion : null;
966
+ const source = typeof pkg?.source === 'string' ? pkg.source : null;
967
+ const available = pkg?.updateAvailable === true || latest !== null && mvVersionGt(latest, installed);
968
+ mvUpdateCommit({
969
+ status: available ? 'available' : 'idle',
970
+ installed,
971
+ latest,
972
+ source,
973
+ available,
974
+ localOnly: source !== null && /link|local|file/i.test(source),
975
+ checkedAt: now,
976
+ nextAt: now + MV_CHECK_INTERVAL,
977
+ failures: 0,
978
+ error: null,
979
+ restartSupported: capabilities?.restart?.supported === true
980
+ });
981
+ } catch (error) {
982
+ const failures = mvUpdateState.failures + 1;
983
+ const delay = Math.min(MV_BACKOFF_BASE * Math.pow(2, failures - 1), MV_BACKOFF_MAX);
984
+ mvUpdateCommit({
985
+ status: 'error',
986
+ failures,
987
+ checkedAt: now,
988
+ nextAt: now + delay,
989
+ error: error instanceof Error ? error.message : String(error)
990
+ });
991
+ }
992
+ }
993
+ async function mvUpdateInstall() {
994
+ if (mvUpdateState.status === 'installing') return;
995
+ mvUpdateCommit({
996
+ status: 'installing',
997
+ error: null,
998
+ operationState: 'queued'
999
+ });
1000
+ try {
1001
+ let capabilities = null;
1002
+ try {
1003
+ capabilities = await mvUpdateGet('/dsh-market/api/v1/capabilities');
1004
+ } catch (error) {
1005
+ capabilities = null;
1006
+ }
1007
+ if (capabilities === null) {
1008
+ mvUpdateCommit({
1009
+ status: 'unsupported',
1010
+ error: null
1011
+ });
1012
+ return;
1013
+ }
1014
+ const endpoints = capabilities.endpoints ?? {};
1015
+ const updatesPath = typeof endpoints.updates === 'string' ? endpoints.updates : '/dsh-market/api/v1/updates';
1016
+ const operationsPath = typeof endpoints.operations === 'string' ? endpoints.operations : '/dsh-market/api/v1/operations';
1017
+ const accepted = await mvUpdatePost(updatesPath, {
1018
+ packageName: 'dsh-multiview'
1019
+ });
1020
+ const operationId = typeof accepted?.operationId === 'string' ? accepted.operationId : null;
1021
+ if (operationId === null) throw new Error('market returned no operation');
1022
+ mvUpdateCommit({
1023
+ operationId,
1024
+ operationState: 'running'
1025
+ });
1026
+ for(let attempt = 0; attempt < MV_POLL_LIMIT; attempt += 1){
1027
+ await new Promise((resolve)=>setTimeout(resolve, MV_POLL_INTERVAL));
1028
+ const record = await mvUpdateGet(operationsPath + '?operationId=' + encodeURIComponent(operationId));
1029
+ const operation = record?.operation ?? record;
1030
+ const state = typeof operation?.state === 'string' ? operation.state : null;
1031
+ mvUpdateCommit({
1032
+ operationState: state
1033
+ });
1034
+ if (state === 'succeeded') {
1035
+ mvUpdateCommit({
1036
+ status: 'done',
1037
+ available: false,
1038
+ installed: typeof operation?.installedVersion === 'string' ? operation.installedVersion : mvUpdateState.installed,
1039
+ restartRequired: operation?.restartRequired === true,
1040
+ checkedAt: Date.now(),
1041
+ nextAt: Date.now() + MV_CHECK_INTERVAL
1042
+ });
1043
+ return;
1044
+ }
1045
+ if (state === 'failed' || state === 'cancelled' || state === 'rolled-back') {
1046
+ const message = operation?.message ?? operation?.failureCode ?? state;
1047
+ mvUpdateCommit({
1048
+ status: 'error',
1049
+ error: typeof message === 'string' ? message : String(message)
1050
+ });
1051
+ return;
1052
+ }
1053
+ }
1054
+ mvUpdateCommit({
1055
+ status: 'error',
1056
+ error: 'operation timed out'
1057
+ });
1058
+ } catch (error) {
1059
+ mvUpdateCommit({
1060
+ status: 'error',
1061
+ error: error instanceof Error ? error.message : String(error)
1062
+ });
1063
+ }
1064
+ }
1065
+ function mvUpdateMaybeAutoCheck() {
1066
+ if (mvUpdateState.status === 'checking' || mvUpdateState.status === 'installing') return;
1067
+ if (Date.now() - mvUpdateState.checkedAt < MV_CHECK_INTERVAL) return;
1068
+ if (mvUpdateState.nextAt > Date.now()) return;
1069
+ void mvUpdateCheck(false);
1070
+ }
1071
+
1072
+ const MV_CSS = `
1073
+ .mv-root{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary)}
1074
+ .mv-bar{display:flex;align-items:center;gap:8px;height:36px;padding:0 10px 0 12px;flex:none;border-bottom:.5px solid var(--dsw-alias-border-l3)}
1075
+ .mv-bar-title{font-size:13px;line-height:16px;font-weight:500;color:var(--dsw-alias-label-secondary);white-space:nowrap;margin-right:2px}
1076
+ .mv-bar-spacer{flex:1;min-width:0}
1077
+ .mv-seg{display:flex;align-items:center;gap:2px;flex:none}
1078
+ .mv-chip{display:inline-flex;align-items:center;justify-content:center;gap:4px;height:24px;min-width:24px;padding:0 6px;border:none;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px;cursor:pointer;white-space:nowrap}
1079
+ .mv-chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
1080
+ .mv-chip[data-active="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
1081
+ .mv-chip:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
1082
+ .mv-body{display:flex;flex:1;min-height:0;min-width:0}
1083
+ .mv-split{display:flex;flex:1 1 0;min-width:0;min-height:0}
1084
+ .mv-split[data-dir="row"]{flex-direction:row}
1085
+ .mv-split[data-dir="col"]{flex-direction:column}
1086
+ .mv-slot{display:flex;min-width:0;min-height:0;flex-basis:0}
1087
+ .mv-slot[data-dir="row"]{flex-direction:column}
1088
+ .mv-divider{position:relative;flex:0 0 1px;background:var(--dsw-alias-border-l3);z-index:2}
1089
+ .mv-divider[data-dir="row"]{cursor:col-resize}
1090
+ .mv-divider[data-dir="col"]{cursor:row-resize}
1091
+ .mv-divider::after{content:"";position:absolute;background:transparent}
1092
+ .mv-divider[data-dir="row"]::after{top:0;bottom:0;left:-4px;right:-4px}
1093
+ .mv-divider[data-dir="col"]::after{left:0;right:0;top:-4px;bottom:-4px}
1094
+ .mv-divider:hover::before,.mv-divider[data-dragging="true"]::before{content:"";position:absolute;background:var(--dsw-alias-state-business-primary);opacity:.55;border-radius:2px}
1095
+ .mv-divider[data-dir="row"]:hover::before,.mv-divider[data-dir="row"][data-dragging="true"]::before{top:0;bottom:0;left:-1px;width:2px}
1096
+ .mv-divider[data-dir="col"]:hover::before,.mv-divider[data-dir="col"][data-dragging="true"]::before{left:0;right:0;top:-1px;height:2px}
1097
+ .mv-divider[data-dragging="true"]{z-index:6}
1098
+ .mv-pane{display:flex;flex-direction:column;flex:1 1 0;min-width:0;min-height:0;position:relative;background:var(--dsw-alias-bg-base)}
1099
+ .mv-head{display:flex;align-items:center;gap:8px;height:34px;padding:0 10px 0 12px;flex:none;color:var(--dsw-alias-label-tertiary);border-bottom:.5px solid transparent}
1100
+ .mv-pane[data-focused="true"] .mv-head{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-border-l3)}
1101
+ .mv-pane[data-drop="true"]::after{content:"";position:absolute;inset:0;border:1px solid var(--dsw-alias-state-business-primary);border-radius:8px;pointer-events:none;z-index:5}
1102
+ .mv-dot{width:6px;height:6px;border-radius:50%;flex:none;background:var(--dsw-alias-state-idle-primary)}
1103
+ .mv-dot[data-state="running"]{background:var(--dsw-alias-state-business-primary)}
1104
+ .mv-dot[data-state="waiting"]{background:var(--dsw-alias-state-warn-primary)}
1105
+ .mv-dot[data-state="unread"]{background:var(--dsw-alias-state-success-primary)}
1106
+ .mv-head-title{flex:1;min-width:0;font-size:13px;line-height:16px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:grab}
1107
+ .mv-head-title:active{cursor:grabbing}
1108
+ .mv-head-actions{display:flex;align-items:center;gap:2px;flex:none;opacity:0}
1109
+ .mv-pane:hover .mv-head-actions,.mv-pane[data-focused="true"] .mv-head-actions,.mv-head-actions:focus-within{opacity:1}
1110
+ .mv-icon{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:6px;background:transparent;color:inherit;cursor:pointer}
1111
+ .mv-icon:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
1112
+ .mv-icon:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
1113
+ .mv-pane-body{display:flex;flex-direction:column;flex:1;min-height:0;overflow:hidden}
1114
+ .mv-note{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;height:100%;padding:24px;font-size:13px;line-height:20px;text-align:center;color:var(--dsw-alias-label-tertiary)}
1115
+ .mv-note[data-error="true"]{color:var(--dsw-alias-state-error-primary)}
1116
+ .mv-note-action{height:28px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:13px;cursor:pointer}
1117
+ .mv-note-action:hover{background:var(--dsw-alias-interactive-bg-hover)}
1118
+ .mv-picker{display:flex;flex-direction:column;flex:1;min-height:0;padding:12px}
1119
+ .mv-picker-search{height:30px;flex:none;margin:0 0 8px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:13px;outline:none}
1120
+ .mv-picker-search:focus{border-color:var(--dsw-alias-state-business-primary)}
1121
+ .mv-picker-list{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:2px}
1122
+ .mv-picker-item{display:flex;flex-direction:row;justify-content:space-between;gap:8px;align-items:center;width:100%;padding:6px 8px;border:none;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;font-size:13px;line-height:18px}
1123
+ .mv-picker-item:hover{background:var(--dsw-alias-interactive-bg-hover)}
1124
+ .mv-picker-item[data-depth="1"]{color:var(--dsw-alias-label-secondary);font-size:12px}
1125
+ .mv-picker-item[data-depth="1"]:hover{color:var(--dsw-alias-label-primary)}
1126
+ .mv-picker-empty{padding:12px 8px;font-size:13px;color:var(--dsw-alias-label-tertiary)}
1127
+ .mv-picker-group{padding:10px 8px 4px;font-size:11px;font-weight:600;letter-spacing:0.02em;color:var(--dsw-alias-label-tertiary);text-transform:none}
1128
+ .mv-views{position:relative;display:inline-flex}
1129
+ .mv-views-toggle{position:relative}
1130
+ .mv-views-toggle[data-active="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
1131
+ .mv-views-count{position:absolute;top:-3px;right:-4px;min-width:12px;height:12px;padding:0 2px;border:1.5px solid var(--dsw-alias-bg-base);border-radius:999px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);font-size:8px;line-height:12px;text-align:center;pointer-events:none}
1132
+ .mv-views-pop{position:absolute;top:calc(100% + 6px);left:0;z-index:12;min-width:240px;max-width:min(520px, 64vw);max-height:320px;overflow:auto;padding:4px;border:0.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-base)}
1133
+ .mv-views-empty{padding:10px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
1134
+ .mv-views-row{display:flex;align-items:center;gap:4px;border-radius:6px}
1135
+ .mv-views-row[data-active="true"]{background:var(--dsw-alias-interactive-bg-hover)}
1136
+ .mv-views-name{flex:1;min-width:0;padding:6px 8px;border:none;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}
1137
+ .mv-views-name:hover{background:var(--dsw-alias-interactive-bg-hover)}
1138
+ .mv-views-del{flex:none;width:22px;height:22px;margin-right:4px;border:none;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:11px;cursor:pointer}
1139
+ .mv-views-del:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
1140
+ .mv-update{position:relative}
1141
+ .mv-update[data-has-update="true"]::after{content:"";position:absolute;top:2px;right:2px;width:6px;height:6px;border-radius:999px;background:var(--dsw-alias-state-error-primary)}
1142
+ .mv-update[data-busy="true"] svg{animation:mv-spin 1s linear infinite}
1143
+ @keyframes mv-spin{to{transform:rotate(360deg)}}
1144
+ .mv-dialog-backdrop{position:fixed;inset:0;z-index:40;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb, var(--dsw-alias-label-primary) 22%, transparent)}
1145
+ .mv-dialog{width:min(420px, calc(100vw - 48px));padding:16px;border:0.5px solid var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-base)}
1146
+ .mv-dialog-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}
1147
+ .mv-dialog-text{margin-top:8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);word-break:break-word}
1148
+ .mv-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}
1149
+ .mv-dialog-button{height:28px;padding:0 12px;border:0.5px solid var(--dsw-alias-border-l3);border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}
1150
+ .mv-dialog-button:hover{background:var(--dsw-alias-interactive-bg-hover)}
1151
+ .mv-dialog-button--primary{border-color:transparent;background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-bg-base)}
1152
+ .mv-picker-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1153
+ .mv-picker-owner{flex:none;max-width:45%;padding:0 6px;border-radius:999px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1154
+ .mv-picker-item[data-owned="true"]{opacity:0.45;cursor:not-allowed}
1155
+ .mv-picker-item[data-owned="true"]:hover{background:transparent}
1156
+ .mv-bar{color:var(--dsw-alias-label-primary)}
1157
+ .mv-icon{color:var(--dsw-alias-label-primary)}
1158
+ .mv-icon:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
1159
+ .mv-chip{color:var(--dsw-alias-label-secondary)}
1160
+ .mv-chip[data-active="true"]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
1161
+ @media (prefers-reduced-motion: reduce){*{transition:none !important;animation:none !important}}
1162
+ `;
1163
+ function MvGlyph(props) {
1164
+ const { path } = props;
1165
+ return h('svg', {
1166
+ viewBox: '0 0 16 16',
1167
+ width: 16,
1168
+ height: 16,
1169
+ 'aria-hidden': true,
1170
+ fill: 'none'
1171
+ }, h('path', {
1172
+ d: path,
1173
+ stroke: 'currentColor',
1174
+ strokeWidth: 1.4,
1175
+ strokeLinecap: 'round',
1176
+ strokeLinejoin: 'round'
1177
+ }));
1178
+ }
1179
+ function MvIconButton(props) {
1180
+ const { label, path, onClick } = props;
1181
+ return h('button', {
1182
+ type: 'button',
1183
+ className: 'mv-icon',
1184
+ title: label,
1185
+ 'aria-label': label,
1186
+ onClick
1187
+ }, h(MvGlyph, {
1188
+ path
1189
+ }));
1190
+ }
1191
+ function MvNote(props) {
1192
+ const { error, children } = props;
1193
+ return h('div', {
1194
+ className: 'mv-note',
1195
+ 'data-error': error === true ? 'true' : 'false'
1196
+ }, children);
1197
+ }
1198
+ const MV_ICON = {
1199
+ maximize: 'M3 6V3h3M13 10v3h-3M3 10v3h3M13 6V3h-3',
1200
+ restore: 'M6 6H3v3M10 10h3V7M10 6V3h3M6 10H3V7',
1201
+ close: 'M4 4l8 8M12 4l-8 8',
1202
+ external: 'M6 10L13 3M9 3h4v4',
1203
+ updateCheck: 'M8 2a6 6 0 1 0 0 12A6 6 0 1 0 8 2zM8 10.7V5.7M5.6 8.1 8 5.6l2.4 2.5',
1204
+ updateInstall: 'M3 9.5v3c0 .6.45 1 1 1h8c.55 0 1-.4 1-1v-3M8 2.6v6.2M5.4 6.4 8 9l2.6-2.6',
1205
+ views: 'M8 2.2 13.4 5 8 7.8 2.6 5 8 2.2M2.6 8.2 8 11l5.4-2.8M2.6 11.2 8 14l5.4-2.8'
1206
+ };
1207
+
1208
+ function mvPhase(session, conversation, summaryBlank) {
1209
+ const targeted = conversation.activeTargets !== undefined && conversation.activeTargets.size > 0;
1210
+ const shellPhase = targeted || !session.blank && !session.awaitingFirstTurn || session.running === true ? 'active' : session.promptAttempted === true ? 'engaging' : 'blank';
1211
+ const subagent = session.subagent;
1212
+ const parentPending = subagent !== null && subagent !== undefined && subagent.address !== undefined && subagent.address.mode === 'continuable' && subagent.parentAvailable === undefined;
1213
+ const settling = shellPhase === 'blank' && session.openState === 'loading' && summaryBlank !== true || parentPending;
1214
+ const hero = shellPhase === 'blank' && (session.openState === 'open' || summaryBlank === true);
1215
+ return settling ? 'settling' : hero ? 'hero' : 'active';
1216
+ }
1217
+ function FixedChatView(props) {
1218
+ return h(React.Fragment, null, props.renderSlot('conversation.session', {
1219
+ view: 'chat'
1220
+ }));
1221
+ }
1222
+ function PaneConversation(props) {
1223
+ const { sessionId, useSession, useConversation, useSessions, renderFactorySlot } = props;
1224
+ const session = useSession((value)=>value);
1225
+ const conversation = useConversation((value)=>value);
1226
+ const summaryBlank = useSessions((state)=>sessionId === undefined ? undefined : state.byId[sessionId] === undefined ? undefined : state.byId[sessionId].blank);
1227
+ if (session === undefined || conversation === undefined) {
1228
+ return h(MvNote, null, T('loading'));
1229
+ }
1230
+ const phase = mvPhase(session, conversation, summaryBlank);
1231
+ const hero = phase === 'hero';
1232
+ return renderFactorySlot('conversation.content', {
1233
+ variant: 'embedded',
1234
+ phase,
1235
+ hero
1236
+ }, {
1237
+ slots: {
1238
+ views: FixedChatView
1239
+ },
1240
+ fallback: h(MvNote, {
1241
+ error: true
1242
+ }, T('noContent'))
1243
+ });
1244
+ }
1245
+ class MvPaneBoundary extends React.Component {
1246
+ constructor(props){
1247
+ super(props);
1248
+ this.state = {
1249
+ failed: false,
1250
+ message: ''
1251
+ };
1252
+ }
1253
+ static getDerivedStateFromError(error) {
1254
+ return {
1255
+ failed: true,
1256
+ message: error instanceof Error ? error.message : String(error)
1257
+ };
1258
+ }
1259
+ componentDidCatch(error, info) {
1260
+ console.error('dsh-multiview: pane crashed', error, info === undefined ? '' : info.componentStack);
1261
+ }
1262
+ render() {
1263
+ if (this.state.failed) {
1264
+ return h(MvNote, {
1265
+ error: true
1266
+ }, mvFormat(T('paneCrashed'), {
1267
+ message: this.state.message
1268
+ }));
1269
+ }
1270
+ return this.props.children;
1271
+ }
1272
+ }
1273
+ function PaneHost(props) {
1274
+ const { sessionId, SessionProvider, renderSlot } = props;
1275
+ const [state, setState] = React.useState({
1276
+ reference: null,
1277
+ error: null
1278
+ });
1279
+ React.useEffect(()=>{
1280
+ if (sessionId === null || sessionId === undefined) {
1281
+ setState({
1282
+ reference: null,
1283
+ error: null
1284
+ });
1285
+ return undefined;
1286
+ }
1287
+ let reference = null;
1288
+ try {
1289
+ reference = PTX.sessions.retain(sessionId, {
1290
+ source: 'multiview'
1291
+ });
1292
+ } catch (error) {
1293
+ setState({
1294
+ reference: null,
1295
+ error: error instanceof Error ? error.message : String(error)
1296
+ });
1297
+ return undefined;
1298
+ }
1299
+ let cancelled = false;
1300
+ setState({
1301
+ reference,
1302
+ error: null
1303
+ });
1304
+ reference.ready.then(()=>{
1305
+ if (!cancelled) setState({
1306
+ reference,
1307
+ error: null
1308
+ });
1309
+ }, (error)=>{
1310
+ if (!cancelled) setState({
1311
+ reference: null,
1312
+ error: error instanceof Error ? error.message : String(error)
1313
+ });
1314
+ });
1315
+ return ()=>{
1316
+ cancelled = true;
1317
+ try {
1318
+ reference.release();
1319
+ } catch (error) {
1320
+ console.error('dsh-multiview: releasing a Session reference failed', error);
1321
+ }
1322
+ };
1323
+ }, [
1324
+ sessionId
1325
+ ]);
1326
+ if (sessionId === null || sessionId === undefined) {
1327
+ return h(MvNote, null, T('empty'));
1328
+ }
1329
+ if (state.error !== null) {
1330
+ return h(MvNote, {
1331
+ error: true
1332
+ }, mvFormat(T('openFailed'), {
1333
+ message: state.error
1334
+ }));
1335
+ }
1336
+ if (state.reference === null) {
1337
+ return h(MvNote, null, T('loading'));
1338
+ }
1339
+ return h(SessionProvider, {
1340
+ session: state.reference
1341
+ }, h(MvPaneBoundary, null, renderSlot(MV_PANE_SLOT, {})));
1342
+ }
1343
+
1344
+ function useMvState() {
1345
+ return React.useSyncExternalStore(mvSubscribe, mvGetState, mvGetState);
1346
+ }
1347
+ function MultiviewPanelIcon() {
1348
+ return h('svg', {
1349
+ viewBox: '0 0 16 16',
1350
+ width: 16,
1351
+ height: 16,
1352
+ 'aria-hidden': true,
1353
+ fill: 'none'
1354
+ }, h('rect', {
1355
+ x: 1.75,
1356
+ y: 2.75,
1357
+ width: 12.5,
1358
+ height: 10.5,
1359
+ rx: 2.5,
1360
+ stroke: 'currentColor',
1361
+ strokeWidth: 1.4
1362
+ }), h('path', {
1363
+ d: 'M8 2.75v10.5',
1364
+ stroke: 'currentColor',
1365
+ strokeWidth: 1.2
1366
+ }));
1367
+ }
1368
+ function mvDotState(status) {
1369
+ if (status === undefined || status === null) return 'idle';
1370
+ if (status.pendingInteraction !== undefined) return 'waiting';
1371
+ if (status.running === true) return 'running';
1372
+ if (status.completionUnread === true) return 'unread';
1373
+ return 'idle';
1374
+ }
1375
+ function mvDotLabel(state) {
1376
+ if (state === 'running') return T('running');
1377
+ if (state === 'waiting') return T('waiting');
1378
+ if (state === 'unread') return T('unread');
1379
+ return '';
1380
+ }
1381
+ function mvPickableRows(query, views = [], currentViewId = null) {
1382
+ const need = query.trim().toLowerCase();
1383
+ try {
1384
+ const list = PTX.sessions.list.getSnapshot();
1385
+ const rows = new Map();
1386
+ for (const id of list.ids){
1387
+ const row = list.byId[id];
1388
+ if (row === undefined || row.blank === true) continue;
1389
+ if (mvIsArchived(id)) continue;
1390
+ rows.set(id, row);
1391
+ }
1392
+ const snapshot = PTX.workspaces.list.getSnapshot();
1393
+ const items = Array.isArray(snapshot?.items) ? snapshot.items : [];
1394
+ const workspaceOf = new Map();
1395
+ for (const item of items){
1396
+ for (const id of item.sessionIds ?? [])workspaceOf.set(id, item.workspaceId);
1397
+ }
1398
+ const groups = [];
1399
+ for (const item of items){
1400
+ const ids = (item.sessionIds ?? []).filter((id)=>rows.has(id));
1401
+ if (ids.length === 0) continue;
1402
+ const label = item.title ?? String(item.path ?? '').split(/[\\/]/).pop() ?? item.workspaceId;
1403
+ groups.push({
1404
+ key: String(item.workspaceId),
1405
+ label: String(label),
1406
+ ids
1407
+ });
1408
+ }
1409
+ const loose = [
1410
+ ...rows.keys()
1411
+ ].filter((id)=>!workspaceOf.has(id));
1412
+ if (loose.length > 0) groups.push({
1413
+ key: '__loose__',
1414
+ label: '',
1415
+ ids: loose
1416
+ });
1417
+ if (groups.length === 0) return [];
1418
+ const rank = (id)=>{
1419
+ const value = rows.get(id)?.updatedAt;
1420
+ return typeof value === 'number' ? value : 0;
1421
+ };
1422
+ const out = [];
1423
+ const multiple = groups.filter((group)=>group.ids.length > 0 && group.label !== '').length > 1;
1424
+ for (const group of groups){
1425
+ const scoped = new Set(group.ids);
1426
+ const children = new Map();
1427
+ const roots = [];
1428
+ const orphan = new Set();
1429
+ for (const id of group.ids){
1430
+ const parentId = rows.get(id)?.parentId;
1431
+ if (typeof parentId !== 'string' || parentId === id) {
1432
+ roots.push(id);
1433
+ continue;
1434
+ }
1435
+ if (scoped.has(parentId)) {
1436
+ const bucket = children.get(parentId);
1437
+ if (bucket === undefined) children.set(parentId, [
1438
+ id
1439
+ ]);
1440
+ else bucket.push(id);
1441
+ } else {
1442
+ orphan.add(id);
1443
+ roots.push(id);
1444
+ }
1445
+ }
1446
+ const byRank = (left, right)=>rank(right) - rank(left);
1447
+ roots.sort(byRank);
1448
+ for (const bucket of children.values())bucket.sort(byRank);
1449
+ const before = out.length;
1450
+ const append = (id, depth)=>{
1451
+ const row = rows.get(id);
1452
+ if (row === undefined) return;
1453
+ const raw = row.title === undefined || row.title === '' ? id : row.title;
1454
+ const shown = orphan.has(id) ? Math.max(depth, 1) : depth;
1455
+ const title = shown > 0 ? '↳ ' + raw : raw;
1456
+ const matched = need === '' || raw.toLowerCase().includes(need) || id.toLowerCase().includes(need);
1457
+ const owner = mvViewOwner(id, views, currentViewId);
1458
+ if (matched) out.push({
1459
+ id,
1460
+ title,
1461
+ depth: shown,
1462
+ owner: owner === null ? null : owner.name
1463
+ });
1464
+ for (const childId of children.get(id) ?? [])append(childId, Math.min(shown + 1, 3));
1465
+ };
1466
+ for (const id of roots)append(id, 0);
1467
+ if (multiple && out.length > before && group.label !== '') {
1468
+ out.splice(before, 0, {
1469
+ kind: 'group',
1470
+ id: group.key,
1471
+ title: group.label
1472
+ });
1473
+ }
1474
+ }
1475
+ return out.slice(0, 240);
1476
+ } catch (error) {
1477
+ return [];
1478
+ }
1479
+ }
1480
+ function MvPanePicker(props) {
1481
+ const { onPick, views, currentViewId } = props;
1482
+ const [query, setQuery] = React.useState('');
1483
+ const rows = mvPickableRows(query, views ?? [], currentViewId ?? null);
1484
+ return h('div', {
1485
+ className: 'mv-picker',
1486
+ 'data-mv-picker': ''
1487
+ }, h('input', {
1488
+ className: 'mv-picker-search',
1489
+ type: 'search',
1490
+ value: query,
1491
+ placeholder: T('pickSearch'),
1492
+ 'aria-label': T('pickSearch'),
1493
+ autoFocus: true,
1494
+ onChange: (event)=>setQuery(event.target.value),
1495
+ onKeyDown: (event)=>{
1496
+ if (event.key === 'Enter' && rows.length > 0) onPick(rows[0].id);
1497
+ if (event.key === 'Escape') event.currentTarget.blur();
1498
+ }
1499
+ }), h('div', {
1500
+ className: 'mv-picker-list'
1501
+ }, rows.length === 0 ? h('div', {
1502
+ className: 'mv-picker-empty'
1503
+ }, T('pickEmpty')) : rows.map((row)=>row.kind === 'group' ? h('div', {
1504
+ key: 'group-' + row.id,
1505
+ className: 'mv-picker-group',
1506
+ role: 'presentation'
1507
+ }, row.title) : h('button', {
1508
+ key: row.id,
1509
+ type: 'button',
1510
+ className: 'mv-picker-item',
1511
+ 'data-depth': String(row.depth ?? 0),
1512
+ 'data-owned': row.owner === null || row.owner === undefined ? undefined : 'true',
1513
+ disabled: row.owner !== null && row.owner !== undefined,
1514
+ style: row.depth === 0 ? undefined : {
1515
+ paddingLeft: String(8 + row.depth * 14) + 'px'
1516
+ },
1517
+ title: row.owner === null || row.owner === undefined ? row.title : row.title + ' · ' + T('views') + ': ' + row.owner,
1518
+ onClick: ()=>onPick(row.id)
1519
+ }, h('span', {
1520
+ className: 'mv-picker-title'
1521
+ }, row.title), row.owner === null || row.owner === undefined ? null : h('span', {
1522
+ className: 'mv-picker-owner'
1523
+ }, row.owner)))));
1524
+ }
1525
+ function mvOpenFileInNative(sessionId, label) {
1526
+ try {
1527
+ PTX.uiWorkspace.openSession(sessionId);
1528
+ } catch (error) {
1529
+ console.error('dsh-multiview: switching to the native view failed', error);
1530
+ return;
1531
+ }
1532
+ const attempt = (remaining)=>{
1533
+ if (remaining <= 0) return;
1534
+ const chips = document.querySelectorAll('button[class*="fileLink"], a[class*="fileLink"]');
1535
+ for (const chip of chips){
1536
+ if (chip.closest('[data-multiview]') !== null) continue;
1537
+ if ((chip.textContent ?? '').trim() !== label) continue;
1538
+ chip.click();
1539
+ return;
1540
+ }
1541
+ setTimeout(()=>attempt(remaining - 1), 120);
1542
+ };
1543
+ setTimeout(()=>attempt(14), 140);
1544
+ }
1545
+ function MvDivider(props) {
1546
+ const { node, onResize } = props;
1547
+ const [dragging, setDragging] = React.useState(false);
1548
+ const ref = React.useRef(null);
1549
+ const lastRatio = React.useRef(node.ratio);
1550
+ const ratioAt = (dir, event)=>{
1551
+ const element = ref.current;
1552
+ if (element === null) return lastRatio.current;
1553
+ const parent = element.parentElement;
1554
+ if (parent === null) return lastRatio.current;
1555
+ const rect = parent.getBoundingClientRect();
1556
+ const size = dir === 'row' ? rect.width : rect.height;
1557
+ if (size === 0) return lastRatio.current;
1558
+ const offset = dir === 'row' ? event.clientX - rect.left : event.clientY - rect.top;
1559
+ return offset / size;
1560
+ };
1561
+ const beginDrag = (event)=>{
1562
+ if (event.button !== 0) return;
1563
+ event.preventDefault();
1564
+ setDragging(true);
1565
+ const dir = node.dir;
1566
+ const splitId = node.id;
1567
+ const divider = ref.current;
1568
+ const first = divider === null ? null : divider.previousElementSibling;
1569
+ const second = divider === null ? null : divider.nextElementSibling;
1570
+ let latest = node.ratio;
1571
+ const move = (moveEvent)=>{
1572
+ latest = mvClampRatio(ratioAt(dir, moveEvent));
1573
+ lastRatio.current = latest;
1574
+ if (first !== null) first.style.flexGrow = String(latest);
1575
+ if (second !== null) second.style.flexGrow = String(1 - latest);
1576
+ };
1577
+ const stop = ()=>{
1578
+ window.removeEventListener('pointermove', move);
1579
+ window.removeEventListener('pointerup', stop);
1580
+ window.removeEventListener('pointercancel', stop);
1581
+ setDragging(false);
1582
+ if (latest !== node.ratio) onResize(splitId, latest);
1583
+ };
1584
+ window.addEventListener('pointermove', move);
1585
+ window.addEventListener('pointerup', stop);
1586
+ window.addEventListener('pointercancel', stop);
1587
+ };
1588
+ return h('div', {
1589
+ ref,
1590
+ className: 'mv-divider',
1591
+ 'data-dir': node.dir,
1592
+ 'data-divider': node.id,
1593
+ 'data-dragging': dragging ? 'true' : 'false',
1594
+ role: 'separator',
1595
+ 'aria-orientation': node.dir === 'row' ? 'vertical' : 'horizontal',
1596
+ onPointerDown: beginDrag
1597
+ });
1598
+ }
1599
+ function MvSplitView(props) {
1600
+ const { node, surface } = props;
1601
+ return h('div', {
1602
+ className: 'mv-split',
1603
+ 'data-dir': node.dir
1604
+ }, h('div', {
1605
+ className: 'mv-slot',
1606
+ 'data-dir': node.dir,
1607
+ style: {
1608
+ flexGrow: node.ratio
1609
+ }
1610
+ }, renderMvNode(node.a, surface)), h(MvDivider, {
1611
+ node,
1612
+ onResize: surface.onResize
1613
+ }), h('div', {
1614
+ className: 'mv-slot',
1615
+ 'data-dir': node.dir,
1616
+ style: {
1617
+ flexGrow: 1 - node.ratio
1618
+ }
1619
+ }, renderMvNode(node.b, surface)));
1620
+ }
1621
+ function PaneFrame(props) {
1622
+ const { leaf, surface } = props;
1623
+ const { state, dragging } = surface;
1624
+ const focused = state.focused === leaf.id;
1625
+ const sessionId = leaf.sessionId;
1626
+ const title = sessionId === null ? T('empty') : mvTitle(sessionId) ?? T('empty');
1627
+ const status = sessionId === null ? undefined : surface.statuses.get(sessionId);
1628
+ const dot = mvDotState(status);
1629
+ const maximized = state.maximized === leaf.id;
1630
+ return h('div', {
1631
+ className: 'mv-pane',
1632
+ 'data-pane-id': leaf.id,
1633
+ 'data-pane': '',
1634
+ 'data-focused': focused ? 'true' : 'false',
1635
+ 'data-drop': dragging.over === leaf.id && dragging.from !== null && dragging.from !== leaf.id ? 'true' : 'false',
1636
+ onPointerDownCapture: ()=>surface.onFocus(leaf.id),
1637
+ onFocusCapture: ()=>surface.onFocus(leaf.id)
1638
+ }, h('div', {
1639
+ className: 'mv-head',
1640
+ 'data-pane-head': ''
1641
+ }, h('span', {
1642
+ className: 'mv-dot',
1643
+ 'data-state': dot,
1644
+ title: mvDotLabel(dot),
1645
+ 'aria-hidden': dot === 'idle'
1646
+ }), h('span', {
1647
+ className: 'mv-head-title',
1648
+ title: sessionId === null ? T('empty') : title,
1649
+ onPointerDown: (event)=>surface.onHeadPointerDown(leaf.id, event)
1650
+ }, title), h('div', {
1651
+ className: 'mv-head-actions'
1652
+ }, h(MvIconButton, {
1653
+ label: maximized ? T('restore') : T('maximize'),
1654
+ path: maximized ? MV_ICON.restore : MV_ICON.maximize,
1655
+ onClick: ()=>surface.onMaximize(leaf.id)
1656
+ }), sessionId === null ? null : h(MvIconButton, {
1657
+ label: T('openMain'),
1658
+ path: MV_ICON.external,
1659
+ onClick: ()=>surface.onOpenMain(sessionId)
1660
+ }), h(MvIconButton, {
1661
+ label: T('remove'),
1662
+ path: MV_ICON.close,
1663
+ onClick: ()=>surface.onRemove(leaf.id)
1664
+ }))), h('div', {
1665
+ className: 'mv-pane-body'
1666
+ }, sessionId === null ? h(MvPanePicker, {
1667
+ onPick: (id)=>surface.onBind(leaf.id, id),
1668
+ views: surface.state.views,
1669
+ currentViewId: surface.state.currentViewId
1670
+ }) : h(MvPaneHost, {
1671
+ sessionId,
1672
+ SessionProvider: surface.SessionProvider,
1673
+ renderSlot: surface.renderSlot
1674
+ })));
1675
+ }
1676
+ const MvPaneHost = React.memo(function MvPaneHost(props) {
1677
+ return h(PaneHost, {
1678
+ sessionId: props.sessionId,
1679
+ SessionProvider: props.SessionProvider,
1680
+ renderSlot: props.renderSlot
1681
+ });
1682
+ }, (previous, next)=>previous.sessionId === next.sessionId && previous.SessionProvider === next.SessionProvider && previous.renderSlot === next.renderSlot);
1683
+ function renderMvNode(node, surface) {
1684
+ if (node.kind === 'leaf') return h(PaneFrame, {
1685
+ key: node.id,
1686
+ leaf: node,
1687
+ surface
1688
+ });
1689
+ return h(MvSplitView, {
1690
+ key: node.id,
1691
+ node,
1692
+ surface
1693
+ });
1694
+ }
1695
+ class MvStageBoundary extends React.Component {
1696
+ constructor(props){
1697
+ super(props);
1698
+ this.state = {
1699
+ failed: false,
1700
+ message: ''
1701
+ };
1702
+ }
1703
+ static getDerivedStateFromError(error) {
1704
+ return {
1705
+ failed: true,
1706
+ message: error instanceof Error ? error.message : String(error)
1707
+ };
1708
+ }
1709
+ componentDidCatch(error, info) {
1710
+ console.error('dsh-multiview: stage crashed', error, info === undefined ? '' : info.componentStack);
1711
+ }
1712
+ render() {
1713
+ if (this.state.failed) {
1714
+ return h('div', {
1715
+ className: 'mv-root'
1716
+ }, h(MvNote, {
1717
+ error: true
1718
+ }, mvFormat(T('stageCrashed'), {
1719
+ message: this.state.message
1720
+ })));
1721
+ }
1722
+ return this.props.children;
1723
+ }
1724
+ }
1725
+ function useMvUpdate() {
1726
+ return React.useSyncExternalStore(mvUpdateSubscribe, mvUpdateGetState, mvUpdateGetState);
1727
+ }
1728
+ function mvUpdateCaption(update) {
1729
+ if (update.status === 'checking') return T('updateChecking');
1730
+ if (update.status === 'installing') return T('updateInstalling');
1731
+ if (update.status === 'done') return T('updateDone');
1732
+ if (update.status === 'unsupported') return T('updateUnsupported');
1733
+ if (update.status === 'error') return mvFormat(T('updateFailed'), {
1734
+ message: update.error ?? ''
1735
+ });
1736
+ if (update.available === true) return mvFormat(T('updateAvailable'), {
1737
+ version: update.latest ?? ''
1738
+ });
1739
+ if (update.localOnly === true) return T('updateLocal');
1740
+ if (update.checkedAt > 0) return mvFormat(T('updateLatest'), {
1741
+ version: update.installed ?? MV_PLUGIN_VERSION
1742
+ });
1743
+ return T('updateIdle');
1744
+ }
1745
+ function MvUpdateButton() {
1746
+ const update = useMvUpdate();
1747
+ const [open, setOpen] = React.useState(false);
1748
+ const caption = mvUpdateCaption(update);
1749
+ return h(React.Fragment, null, h('button', {
1750
+ type: 'button',
1751
+ className: 'mv-icon mv-update',
1752
+ 'data-has-update': update.available === true ? 'true' : 'false',
1753
+ 'data-busy': update.status === 'checking' || update.status === 'installing' ? 'true' : 'false',
1754
+ title: caption,
1755
+ 'aria-label': T('updateCheck'),
1756
+ onClick: ()=>{
1757
+ setOpen(true);
1758
+ if (update.available !== true) void mvUpdateCheck(true);
1759
+ }
1760
+ }, h(MvGlyph, {
1761
+ path: update.available === true ? MV_ICON.updateInstall : MV_ICON.updateCheck
1762
+ })), open ? h(MvUpdateDialog, {
1763
+ update,
1764
+ caption,
1765
+ onClose: ()=>setOpen(false)
1766
+ }) : null);
1767
+ }
1768
+ function MvUpdateDialog(props) {
1769
+ const { update, caption, onClose } = props;
1770
+ const busy = update.status === 'installing';
1771
+ const canInstall = update.available === true && !busy && update.localOnly !== true;
1772
+ const body = h('div', {
1773
+ className: 'mv-dialog-body'
1774
+ }, h('div', {
1775
+ className: 'mv-dialog-title'
1776
+ }, T('updateCheck')), h('div', {
1777
+ className: 'mv-dialog-text'
1778
+ }, caption), update.status === 'installing' && update.operationState !== null ? h('div', {
1779
+ className: 'mv-dialog-text'
1780
+ }, String(update.operationState)) : null, update.status === 'done' && update.restartRequired === true && update.restartSupported === true ? h('div', {
1781
+ className: 'mv-dialog-text'
1782
+ }, T('updateDone')) : null);
1783
+ const actions = h('div', {
1784
+ className: 'mv-dialog-actions'
1785
+ }, h('button', {
1786
+ type: 'button',
1787
+ className: 'mv-dialog-button',
1788
+ onClick: onClose
1789
+ }, T('cancel')), canInstall ? h('button', {
1790
+ type: 'button',
1791
+ className: 'mv-dialog-button mv-dialog-button--primary',
1792
+ onClick: ()=>void mvUpdateInstall()
1793
+ }, T('updateInstall')) : update.available !== true && update.status !== 'installing' ? h('button', {
1794
+ type: 'button',
1795
+ className: 'mv-dialog-button',
1796
+ onClick: ()=>void mvUpdateCheck(true)
1797
+ }, T('updateCheck')) : null);
1798
+ return h('div', {
1799
+ className: 'mv-dialog-backdrop',
1800
+ onClick: (event)=>{
1801
+ if (event.target === event.currentTarget) onClose();
1802
+ }
1803
+ }, h('div', {
1804
+ className: 'mv-dialog',
1805
+ role: 'dialog',
1806
+ 'aria-label': T('updateCheck')
1807
+ }, body, actions));
1808
+ }
1809
+ function MvViewsButton(props) {
1810
+ const { views, currentKey } = props;
1811
+ const [open, setOpen] = React.useState(false);
1812
+ return h('div', {
1813
+ className: 'mv-views'
1814
+ }, h('button', {
1815
+ type: 'button',
1816
+ className: 'mv-icon mv-views-toggle',
1817
+ 'data-active': open ? 'true' : 'false',
1818
+ 'data-count': views.length > 0 ? String(views.length) : undefined,
1819
+ title: T('views'),
1820
+ 'aria-label': T('views'),
1821
+ onClick: ()=>setOpen((value)=>!value)
1822
+ }, h(MvGlyph, {
1823
+ path: MV_ICON.views
1824
+ }), views.length > 0 ? h('span', {
1825
+ className: 'mv-views-count'
1826
+ }, String(views.length)) : null), open ? h('div', {
1827
+ className: 'mv-views-pop',
1828
+ role: 'menu',
1829
+ 'aria-label': T('views')
1830
+ }, views.length === 0 ? h('div', {
1831
+ className: 'mv-views-empty'
1832
+ }, T('viewsEmpty')) : views.map((view)=>h('div', {
1833
+ key: view.id,
1834
+ className: 'mv-views-row',
1835
+ 'data-active': view.id === currentKey ? 'true' : 'false'
1836
+ }, h('button', {
1837
+ type: 'button',
1838
+ className: 'mv-views-name',
1839
+ title: T('viewsHint'),
1840
+ onClick: ()=>{
1841
+ mvApplyView(view.id);
1842
+ setOpen(false);
1843
+ }
1844
+ }, view.name), h('button', {
1845
+ type: 'button',
1846
+ className: 'mv-views-del',
1847
+ title: T('viewsDelete'),
1848
+ 'aria-label': T('viewsDelete'),
1849
+ onClick: (event)=>{
1850
+ event.stopPropagation();
1851
+ mvDeleteView(view.id);
1852
+ }
1853
+ }, '✕')))) : null);
1854
+ }
1855
+ function MultiviewStage(props) {
1856
+ const { renderSlot, SessionProvider, useSessions, useSessionStatus } = props;
1857
+ const list = useSessions((state)=>state);
1858
+ const statuses = useSessionStatus((state)=>state);
1859
+ const state = useMvState();
1860
+ const rootRef = React.useRef(null);
1861
+ const [narrow, setNarrow] = React.useState(false);
1862
+ const [drag, setDrag] = React.useState({
1863
+ from: null,
1864
+ over: null
1865
+ });
1866
+ const dragRef = React.useRef({
1867
+ from: null,
1868
+ over: null,
1869
+ startX: 0,
1870
+ startY: 0,
1871
+ active: false
1872
+ });
1873
+ const openedRef = React.useRef(false);
1874
+ React.useEffect(()=>{
1875
+ mvReconcile();
1876
+ if (openedRef.current) return;
1877
+ try {
1878
+ const snapshot = PTX.sessions.list.getSnapshot();
1879
+ if (snapshot.phase !== 'ready') return;
1880
+ const current = snapshot.ids.find((id)=>(snapshot.byId[id]?.retainedBy?.mainView ?? 0) > 0);
1881
+ openedRef.current = true;
1882
+ if (typeof current === 'string') mvOpenForSession(current);
1883
+ } catch (error) {
1884
+ openedRef.current = true;
1885
+ }
1886
+ }, [
1887
+ list
1888
+ ]);
1889
+ React.useEffect(()=>{
1890
+ mvUpdateHydrate();
1891
+ mvUpdateMaybeAutoCheck();
1892
+ }, []);
1893
+ React.useEffect(()=>{
1894
+ const element = rootRef.current;
1895
+ if (element === null) return undefined;
1896
+ const onClick = (event)=>{
1897
+ const target = event.target;
1898
+ if (!(target instanceof Element)) return;
1899
+ const chip = target.closest('button[class*="fileLink"], a[class*="fileLink"]');
1900
+ if (chip === null) return;
1901
+ const pane = chip.closest('[data-pane]');
1902
+ const leafId = pane === null ? null : pane.getAttribute('data-pane-id');
1903
+ const leaf = leafId === null ? null : mvLeafById(mvGetState().layout, leafId);
1904
+ const sessionId = leaf === null ? null : leaf.sessionId;
1905
+ if (sessionId === null) return;
1906
+ event.preventDefault();
1907
+ event.stopPropagation();
1908
+ mvOpenFileInNative(sessionId, (chip.textContent ?? '').trim());
1909
+ };
1910
+ element.addEventListener('click', onClick, true);
1911
+ return ()=>element.removeEventListener('click', onClick, true);
1912
+ }, []);
1913
+ React.useEffect(()=>{
1914
+ const element = rootRef.current;
1915
+ if (element === null || typeof ResizeObserver !== 'function') return undefined;
1916
+ const observer = new ResizeObserver(()=>{
1917
+ setNarrow(element.clientWidth < 900 || element.clientHeight < 520);
1918
+ });
1919
+ observer.observe(element);
1920
+ return ()=>observer.disconnect();
1921
+ }, []);
1922
+ React.useEffect(()=>{
1923
+ const move = (event)=>{
1924
+ const record = dragRef.current;
1925
+ if (!record.active) return;
1926
+ const target = document.elementFromPoint(event.clientX, event.clientY);
1927
+ const pane = target === null ? null : target.closest('[data-pane]');
1928
+ const over = pane === null ? null : pane.getAttribute('data-pane-id');
1929
+ if (over === record.over) return;
1930
+ record.over = over;
1931
+ setDrag({
1932
+ from: record.from,
1933
+ over
1934
+ });
1935
+ };
1936
+ const up = ()=>{
1937
+ const record = dragRef.current;
1938
+ const from = record.from;
1939
+ const over = record.over;
1940
+ dragRef.current = {
1941
+ from: null,
1942
+ over: null,
1943
+ startX: 0,
1944
+ startY: 0,
1945
+ active: false
1946
+ };
1947
+ document.body.style.userSelect = '';
1948
+ setDrag({
1949
+ from: null,
1950
+ over: null
1951
+ });
1952
+ if (record.active && from !== null && over !== null && from !== over) mvSwapLeaves(from, over);
1953
+ };
1954
+ window.addEventListener('pointermove', move);
1955
+ window.addEventListener('pointerup', up);
1956
+ window.addEventListener('pointercancel', up);
1957
+ return ()=>{
1958
+ window.removeEventListener('pointermove', move);
1959
+ window.removeEventListener('pointerup', up);
1960
+ window.removeEventListener('pointercancel', up);
1961
+ };
1962
+ }, []);
1963
+ const surface = {
1964
+ state,
1965
+ statuses,
1966
+ SessionProvider,
1967
+ renderSlot,
1968
+ dragging: drag,
1969
+ onFocus: mvFocus,
1970
+ onResize: mvResizeSplit,
1971
+ onMaximize: mvToggleMaximize,
1972
+ onRemove: mvRemovePane,
1973
+ onBind: mvBind,
1974
+ onOpenMain: (sessionId)=>{
1975
+ try {
1976
+ PTX.uiWorkspace.openSession(sessionId);
1977
+ } catch (error) {
1978
+ console.error('dsh-multiview: opening the native view failed', error);
1979
+ }
1980
+ },
1981
+ onHeadPointerDown: (leafId, event)=>{
1982
+ if (event.button !== 0) return;
1983
+ dragRef.current = {
1984
+ from: leafId,
1985
+ over: null,
1986
+ startX: event.clientX,
1987
+ startY: event.clientY,
1988
+ active: false
1989
+ };
1990
+ const arm = (moveEvent)=>{
1991
+ const record = dragRef.current;
1992
+ if (record.from === null) return;
1993
+ if (record.active) return;
1994
+ const distance = Math.abs(moveEvent.clientX - record.startX) + Math.abs(moveEvent.clientY - record.startY);
1995
+ if (distance < 4) return;
1996
+ record.active = true;
1997
+ document.body.style.userSelect = 'none';
1998
+ setDrag({
1999
+ from: leafId,
2000
+ over: null
2001
+ });
2002
+ };
2003
+ window.addEventListener('pointermove', arm);
2004
+ window.addEventListener('pointerup', ()=>window.removeEventListener('pointermove', arm), {
2005
+ once: true
2006
+ });
2007
+ }
2008
+ };
2009
+ const leaves = mvLeaves(state.layout);
2010
+ const maximizedLeaf = state.maximized === null ? null : mvLeafById(state.layout, state.maximized);
2011
+ const focusedLeaf = leaves.find((leaf)=>leaf.id === state.focused) ?? leaves[0];
2012
+ const visibleLeaf = maximizedLeaf === null ? focusedLeaf : maximizedLeaf;
2013
+ const body = h('div', {
2014
+ className: 'mv-body',
2015
+ 'data-mv-body': ''
2016
+ }, renderMvNode(narrow ? visibleLeaf : maximizedLeaf === null ? state.layout : maximizedLeaf, surface));
2017
+ const chips = narrow ? leaves.map((leaf)=>{
2018
+ const title = leaf.sessionId === null ? T('empty') : mvTitle(leaf.sessionId) ?? T('empty');
2019
+ return h('button', {
2020
+ key: 'chip-' + leaf.id,
2021
+ type: 'button',
2022
+ className: 'mv-chip',
2023
+ 'data-active': leaf.id === focusedLeaf.id ? 'true' : 'false',
2024
+ title,
2025
+ onClick: ()=>mvFocus(leaf.id)
2026
+ }, title.length > 14 ? title.slice(0, 14) + '…' : title);
2027
+ }) : [];
2028
+ return h('div', {
2029
+ ref: rootRef,
2030
+ className: 'mv-root',
2031
+ 'data-multiview': ''
2032
+ }, h('header', {
2033
+ className: 'mv-bar',
2034
+ 'data-window-drag': true
2035
+ }, h('span', {
2036
+ className: 'mv-bar-title'
2037
+ }, T('title')), h('div', {
2038
+ className: 'mv-seg',
2039
+ role: 'group',
2040
+ 'aria-label': T('shape')
2041
+ }, [
2042
+ 2,
2043
+ 3,
2044
+ 4
2045
+ ].map((count)=>h('button', {
2046
+ key: 'shape-' + String(count),
2047
+ type: 'button',
2048
+ className: 'mv-chip',
2049
+ 'data-active': leaves.length === count && maximizedLeaf === null ? 'true' : 'false',
2050
+ title: mvFormat(T('shapeN'), {
2051
+ n: count
2052
+ }),
2053
+ 'aria-label': mvFormat(T('shapeN'), {
2054
+ n: count
2055
+ }),
2056
+ onClick: ()=>mvSetShape(count)
2057
+ }, String(count)))), state.maximized === null ? null : h('button', {
2058
+ type: 'button',
2059
+ className: 'mv-chip',
2060
+ 'data-active': 'true',
2061
+ onClick: mvRestore
2062
+ }, T('restore')), h(MvViewsButton, {
2063
+ views: state.views ?? [],
2064
+ currentKey: mvViewKey(state.layout)
2065
+ }), h(MvUpdateButton, null), h('span', {
2066
+ className: 'mv-bar-spacer'
2067
+ }), h('div', {
2068
+ className: 'mv-strip'
2069
+ }, chips)), h(MvStageBoundary, null, body));
2070
+ }
2071
+
2072
+ const MV_INJECT = [
2073
+ 'slots',
2074
+ 'sessions',
2075
+ 'workspaces',
2076
+ 'locale',
2077
+ 'layout',
2078
+ 'uiWorkspace',
2079
+ 'shortcuts'
2080
+ ];
2081
+ function mvShortcutDefaults(code) {
2082
+ const binding = {
2083
+ code,
2084
+ modifiers: [
2085
+ 'primary',
2086
+ 'alt'
2087
+ ]
2088
+ };
2089
+ return {
2090
+ 'desktop:macos': binding,
2091
+ 'desktop:windows': binding,
2092
+ 'desktop:linux': binding
2093
+ };
2094
+ }
2095
+ function mvRegisterShortcut(ctx, spec) {
2096
+ const label = ()=>T(spec.labelKey);
2097
+ const resolve = ()=>{
2098
+ const info = PTX.layout.panelInfo.getSnapshot();
2099
+ if (info.activePanelId !== MV_PANEL_ID) return {
2100
+ status: 'pass'
2101
+ };
2102
+ return {
2103
+ status: 'handled',
2104
+ run: ()=>{
2105
+ try {
2106
+ spec.run();
2107
+ } catch (error) {
2108
+ console.error('dsh-multiview: shortcut failed', spec.id, error);
2109
+ }
2110
+ }
2111
+ };
2112
+ };
2113
+ const base = {
2114
+ id: spec.id,
2115
+ label,
2116
+ aliases: [
2117
+ 'multiview',
2118
+ 'split'
2119
+ ],
2120
+ regions: [
2121
+ 'page',
2122
+ 'editable'
2123
+ ],
2124
+ modals: [],
2125
+ resolve
2126
+ };
2127
+ try {
2128
+ ctx.effect(()=>ctx.shortcuts.register({
2129
+ ...base,
2130
+ defaults: mvShortcutDefaults(spec.code)
2131
+ }), 'dsh-multiview: ' + spec.id);
2132
+ } catch (error) {
2133
+ console.warn('dsh-multiview: default binding rejected, registering without one:', spec.id, error);
2134
+ ctx.effect(()=>ctx.shortcuts.register({
2135
+ ...base,
2136
+ defaults: {}
2137
+ }), 'dsh-multiview: unbound ' + spec.id);
2138
+ }
2139
+ }
2140
+ function mvFocusPane(leafId) {
2141
+ mvFocus(leafId);
2142
+ const leaf = mvLeafById(mvGetState().layout, leafId);
2143
+ if (leaf !== undefined && leaf.sessionId !== null) mvFocusComposer(leaf.sessionId);
2144
+ }
2145
+ function apply(ctx) {
2146
+ PTX = ctx;
2147
+ T = ctx.locale.bind(NS);
2148
+ ctx.effect(()=>ctx.locale.register(NS, {
2149
+ zh: MV_ZH,
2150
+ en: MV_EN
2151
+ }), 'dsh-multiview: dictionaries');
2152
+ ctx.effect(()=>{
2153
+ const tag = document.createElement('style');
2154
+ tag.dataset.plugin = 'dsh-multiview';
2155
+ tag.dataset.pluginCss = 'dsh-multiview/styles';
2156
+ tag.textContent = MV_CSS;
2157
+ document.head.appendChild(tag);
2158
+ return ()=>{
2159
+ tag.remove();
2160
+ };
2161
+ }, 'dsh-multiview: styles');
2162
+ mvHydrate();
2163
+ ctx.slots.inject('main', function*() {
2164
+ yield ctx.slots.register({
2165
+ name: 'main',
2166
+ key: MV_PANEL_ID,
2167
+ locale: NS,
2168
+ children: {
2169
+ [MV_PANE_SLOT]: {
2170
+ kind: 'single',
2171
+ scope: 'session'
2172
+ }
2173
+ }
2174
+ }, MultiviewStage);
2175
+ yield ctx.slots.inject('sidebar.panellist', ()=>ctx.slots.register({
2176
+ name: 'sidebar.panellist',
2177
+ id: MV_PANEL_ID,
2178
+ order: 30,
2179
+ label: ()=>T('panel'),
2180
+ locale: NS
2181
+ }, MultiviewPanelIcon));
2182
+ });
2183
+ ctx.slots.inject(MV_PANE_SLOT, ()=>ctx.slots.register({
2184
+ name: MV_PANE_SLOT,
2185
+ locale: NS
2186
+ }, PaneConversation));
2187
+ mvRegisterShortcut(ctx, {
2188
+ id: 'multiview.pane.focusPrev',
2189
+ labelKey: 'shortcutFocusPrev',
2190
+ code: 'BracketLeft',
2191
+ run: ()=>mvFocusPane(mvNeighbour(mvGetState().layout, mvGetState().focused, -1))
2192
+ });
2193
+ mvRegisterShortcut(ctx, {
2194
+ id: 'multiview.pane.focusNext',
2195
+ labelKey: 'shortcutFocusNext',
2196
+ code: 'BracketRight',
2197
+ run: ()=>mvFocusPane(mvNeighbour(mvGetState().layout, mvGetState().focused, 1))
2198
+ });
2199
+ mvRegisterShortcut(ctx, {
2200
+ id: 'multiview.pane.maximize',
2201
+ labelKey: 'shortcutMaximize',
2202
+ code: 'KeyM',
2203
+ run: ()=>{
2204
+ const state = mvGetState();
2205
+ mvToggleMaximize(state.focused);
2206
+ }
2207
+ });
2208
+ mvRegisterShortcut(ctx, {
2209
+ id: 'multiview.pane.remove',
2210
+ labelKey: 'shortcutRemove',
2211
+ code: 'KeyW',
2212
+ run: ()=>{
2213
+ mvRemovePane(mvGetState().focused);
2214
+ }
2215
+ });
2216
+ const host = typeof window === 'undefined' ? '' : window.location.hostname;
2217
+ if (host === '127.0.0.1' || host === 'localhost') {
2218
+ window.__mvDebug = {
2219
+ version: '0.1.0',
2220
+ list: ()=>PTX.sessions.list.getSnapshot(),
2221
+ seed: (limit)=>mvSeed(limit),
2222
+ live: ()=>mvConversationLive(),
2223
+ focusComposer: (sessionId)=>mvFocusComposer(sessionId),
2224
+ state: ()=>mvGetState(),
2225
+ shape: (count)=>mvSetShape(count),
2226
+ bind: (leafId, sessionId)=>mvBind(leafId, sessionId),
2227
+ maximize: (leafId)=>mvToggleMaximize(leafId),
2228
+ restore: ()=>mvRestore(),
2229
+ resize: (splitId, ratio)=>mvResizeSplit(splitId, ratio),
2230
+ swap: (a, b)=>mvSwapLeaves(a, b),
2231
+ remove: (leafId)=>mvRemovePane(leafId),
2232
+ reset: ()=>mvReset(),
2233
+ neighbour: (leafId, delta)=>mvNeighbour(mvGetState().layout, leafId, delta),
2234
+ mainKeys: ()=>(PTX.slots.entries('main') || []).map((entry)=>entry.options.key),
2235
+ panelInfo: ()=>PTX.layout.panelInfo.getSnapshot(),
2236
+ paneEntries: ()=>(PTX.slots.entries(MV_PANE_SLOT) || []).length,
2237
+ open: ()=>PTX.layout.selectPanel(MV_PANEL_ID),
2238
+ close: ()=>PTX.layout.selectPanel(null),
2239
+ workspaces: ()=>PTX.workspaces.list.getSnapshot(),
2240
+ openFor: (sessionId)=>mvOpenForSession(sessionId),
2241
+ startView: ()=>mvStartView(),
2242
+ applyView: (viewId)=>mvApplyView(viewId),
2243
+ deleteView: (viewId)=>mvDeleteView(viewId)
2244
+ };
2245
+ }
2246
+ }
2247
+
2248
+ return { inject: MV_INJECT, apply }
2249
+ },
2250
+ })