@monster-orange/den 0.4.10 → 0.4.12

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.
@@ -138,7 +138,7 @@ function rowHtml(r) {
138
138
  ? ` <span class="tbl-muted">· ${esc(r.model)}</span>`
139
139
  : "";
140
140
  return `<tr class="agent" data-agent="${esc(r.agent)}" data-state="${esc(r.state)}">
141
- <th scope="row"><span class="repo-name">${esc(r.agent)}</span>${persona}</th>
141
+ <th scope="row">${r.displayName ? `<span class="repo-name">${esc(r.displayName)}</span> <span class="tbl-muted">${esc(r.agent)}</span>` : `<span class="repo-name">${esc(r.agent)}</span>`}${persona}</th>
142
142
  <td class="tbl-mono">${esc(r.runtime ?? "—")}${model}</td>
143
143
  <td>${stateCell(r.state)}</td>
144
144
  <td class="tbl-mono tbl-muted agent-cwd" title="${esc(r.cwd ?? "")}">${esc(r.cwd ?? "—")}</td>
@@ -50,13 +50,26 @@
50
50
  flex-direction: column;
51
51
  overflow: hidden;
52
52
  }
53
+ /* AG-4b: the main area is a column that fills the window between
54
+ the top bar and the runs dock (the body is the viewport's height,
55
+ the shell's column puts the dock at its foot); the list and the
56
+ panes take what is left, 14px above the dock. No 100vh guess. */
57
+ html body.den-has-shell {
58
+ height: 100%;
59
+ min-height: 0;
60
+ }
53
61
  html body.den-has-shell > #den-content {
54
62
  overflow: auto;
55
63
  min-width: 0;
56
- padding: 20px 24px 32px 24px;
64
+ min-height: 0;
65
+ padding: 20px 24px 14px 24px;
57
66
  font: 14px/1.45 var(--sans);
58
67
  color: var(--text);
59
- display: block;
68
+ display: flex;
69
+ flex-direction: column;
70
+ }
71
+ #den-content > .ph {
72
+ flex: none;
60
73
  }
61
74
  #den-content :focus-visible {
62
75
  outline: 2px solid var(--accent);
@@ -94,9 +107,38 @@
94
107
  .agx {
95
108
  display: grid;
96
109
  grid-template-columns: 300px minmax(0, 1fr);
110
+ grid-template-rows: minmax(0, 1fr);
97
111
  gap: 14px;
98
- min-height: 420px;
99
- height: calc(100vh - 300px);
112
+ flex: 1;
113
+ min-height: 0;
114
+ }
115
+ /* The layout switch (AG-4b), the prototype's .seg. */
116
+ .seg {
117
+ display: inline-flex;
118
+ border: 1px solid var(--border);
119
+ border-radius: var(--radius-button);
120
+ overflow: hidden;
121
+ flex-wrap: wrap;
122
+ }
123
+ .seg button {
124
+ background: none;
125
+ border: 0;
126
+ padding: 6px 12px;
127
+ cursor: pointer;
128
+ color: var(--muted);
129
+ font: inherit;
130
+ }
131
+ .seg button[aria-pressed="true"] {
132
+ background: color-mix(in srgb, var(--accent) 16%, transparent);
133
+ color: var(--text);
134
+ font-weight: 500;
135
+ }
136
+ .agl .aghint {
137
+ padding: 10px 12px;
138
+ font-size: 12px;
139
+ color: var(--faint);
140
+ border-top: 1px solid var(--border);
141
+ margin-top: 6px;
100
142
  }
101
143
  .agl {
102
144
  border: 1px solid var(--border);
@@ -245,6 +287,98 @@
245
287
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
246
288
  grid-auto-rows: minmax(0, 1fr);
247
289
  }
290
+ .agp.tabs {
291
+ grid-template-columns: minmax(0, 1fr);
292
+ grid-template-rows: auto minmax(0, 1fr);
293
+ gap: 0;
294
+ }
295
+ .agp > .pane[hidden] {
296
+ display: none;
297
+ }
298
+ .agp.tabs > .pane {
299
+ border-top-left-radius: 0;
300
+ }
301
+ /* The tab strip (the prototype's .agtabs): name, persona · workspace. */
302
+ .agtabs {
303
+ display: flex;
304
+ gap: 2px;
305
+ border-bottom: 1px solid var(--border);
306
+ overflow-x: auto;
307
+ min-width: 0;
308
+ }
309
+ .agtab-w {
310
+ position: relative;
311
+ display: inline-flex;
312
+ flex: none;
313
+ max-width: 280px;
314
+ }
315
+ .agtab {
316
+ display: inline-flex;
317
+ align-items: center;
318
+ gap: 6px;
319
+ min-width: 0;
320
+ border: 1px solid transparent;
321
+ border-bottom: 0;
322
+ background: none;
323
+ padding: 6px 28px 6px 10px;
324
+ border-radius: var(--radius-button) var(--radius-button) 0 0;
325
+ cursor: pointer;
326
+ color: var(--muted);
327
+ white-space: nowrap;
328
+ font: 13px var(--sans);
329
+ }
330
+ .agtab[hidden] {
331
+ display: none;
332
+ }
333
+ .agtab[aria-selected="true"] {
334
+ background: var(--surface);
335
+ border-color: var(--border);
336
+ color: var(--text);
337
+ margin-bottom: -1px;
338
+ }
339
+ .agtab .den-dot {
340
+ margin: 0;
341
+ }
342
+ .agtab .tn {
343
+ overflow: hidden;
344
+ text-overflow: ellipsis;
345
+ }
346
+ .agtab .tm {
347
+ color: var(--faint);
348
+ font-size: 11px;
349
+ overflow: hidden;
350
+ text-overflow: ellipsis;
351
+ }
352
+ .agtab-x {
353
+ position: absolute;
354
+ top: 0;
355
+ bottom: 0;
356
+ right: 4px;
357
+ display: flex;
358
+ align-items: center;
359
+ padding: 0 4px;
360
+ border: 0;
361
+ background: none;
362
+ cursor: pointer;
363
+ font: 13px var(--sans);
364
+ color: var(--faint);
365
+ }
366
+ .agtab-x:hover {
367
+ color: var(--bad);
368
+ }
369
+ .agtab-in {
370
+ font: 13px var(--sans);
371
+ width: 200px;
372
+ margin: 3px 28px 3px 6px;
373
+ padding: 2px 6px;
374
+ border: 1px solid var(--accent);
375
+ border-radius: var(--radius-button);
376
+ background: var(--bg);
377
+ color: var(--text);
378
+ }
379
+ .den-sr[hidden] {
380
+ display: none;
381
+ }
248
382
  .pane {
249
383
  display: grid;
250
384
  grid-template-rows: auto minmax(0, 1fr);
@@ -292,6 +426,9 @@
292
426
  gap: 6px;
293
427
  flex: none;
294
428
  }
429
+ .pane .bar2 .x [hidden] {
430
+ display: none;
431
+ }
295
432
  /* The body: mono 12px/1.5 (xterm draws inside it). */
296
433
  .pane .term {
297
434
  min-height: 0;
@@ -301,6 +438,15 @@
301
438
  color: var(--text);
302
439
  overflow: hidden;
303
440
  }
441
+ .pane .term {
442
+ display: grid;
443
+ grid-template: minmax(0, 1fr) / minmax(0, 1fr);
444
+ }
445
+ .pane .term .tbox {
446
+ min-height: 0;
447
+ min-width: 0;
448
+ overflow: hidden;
449
+ }
304
450
  .pane .term .xterm {
305
451
  height: 100%;
306
452
  }
@@ -430,7 +576,8 @@
430
576
  @media (max-width: 860px) {
431
577
  .agx {
432
578
  grid-template-columns: minmax(0, 1fr);
433
- height: auto;
579
+ grid-template-rows: none;
580
+ flex: none;
434
581
  }
435
582
  .ph .act {
436
583
  flex-wrap: wrap;
@@ -465,6 +612,32 @@
465
612
  </div>
466
613
  </div>
467
614
  <div class="act">
615
+ <div class="seg" role="group" aria-label="Layout" data-layouts>
616
+ <button
617
+ type="button"
618
+ data-layout="focus"
619
+ aria-pressed="true"
620
+ aria-keyshortcuts="Control+Shift+1"
621
+ >
622
+ Focus
623
+ </button>
624
+ <button
625
+ type="button"
626
+ data-layout="tabs"
627
+ aria-pressed="false"
628
+ aria-keyshortcuts="Control+Shift+2"
629
+ >
630
+ Tabs
631
+ </button>
632
+ <button
633
+ type="button"
634
+ data-layout="tile"
635
+ aria-pressed="false"
636
+ aria-keyshortcuts="Control+Shift+3"
637
+ >
638
+ Tile
639
+ </button>
640
+ </div>
468
641
  <button type="button" class="den-btn" id="ag-clear" hidden>
469
642
  Clear stopped
470
643
  </button>
@@ -792,10 +792,11 @@ export function groupReposByApplication(repos, apps) {
792
792
 
793
793
  /**
794
794
  * Feed the shell's Workspace part (shell.js #ws-pill, a menu): "All active"
795
- * and each active workspace, the focused one marked. Choosing one sets the
796
- * view scope (POST /api/scope, a preference) for every page. It replaces
797
- * dev.17's workspace pill and its focus menu (one control, not two): the
798
- * focus moves only through the menu's separate "Set <ws> as focus".
795
+ * and each active workspace, the focused one marked. Choosing a workspace
796
+ * makes it the CLI default (POST /api/workspaces `focus`, workspaces.yml)
797
+ * and sets the view (POST /api/scope, a preference) for every page: one
798
+ * "current workspace" (Ward's decision, 0.4.11). "All active" sets only the
799
+ * view; the CLI default stays.
799
800
  *
800
801
  * The arguments are dev.17's (an env select, an onSwitch callback) and are
801
802
  * no longer used: the view applies to every env.
@@ -822,7 +823,6 @@ async function _renderScopeMenu() {
822
823
  sh.setWorkspaces({
823
824
  ...buildScopeMenuModel(pageScope(scope, data), data, t),
824
825
  onSelect: _switchScope,
825
- onFocus: _setFocus,
826
826
  });
827
827
  } catch {
828
828
  sh.setWorkspaces(null);
@@ -852,25 +852,36 @@ function pageScope(scope, data) {
852
852
  }
853
853
 
854
854
  /**
855
- * On the Graph page a choice replaces ?ws= (All active drops it, a
856
- * workspace sets it), so the reload shows the choice rather than the old
857
- * ?ws=, which would win over it. The history entry is replaced, not
858
- * pushed: Back does not return to a view the picker has left.
855
+ * A choice replaces ?ws= (All active drops it, a workspace sets it) on the
856
+ * Graph and Agents pages, and on any page whose URL names a workspace
857
+ * (Workspaces' detail pane), so the reload shows the choice rather than
858
+ * the old ?ws=. The history entry is replaced, not pushed: Back does not
859
+ * return to a view the picker has left.
859
860
  */
860
861
  function followScopeInUrl(value) {
861
- if (!WS_URL_PAGES.has(location.pathname)) return;
862
862
  const u = new URL(location.href);
863
+ if (!WS_URL_PAGES.has(location.pathname) && !u.searchParams.has("ws")) return;
863
864
  if (value === "all") u.searchParams.delete("ws");
864
865
  else u.searchParams.set("ws", value);
865
866
  history.replaceState(history.state, "", u.pathname + u.search + u.hash);
866
867
  }
867
868
 
868
869
  /**
869
- * A view filter only: POST /api/scope stores the preference (user.yml
870
- * `view`), then the page reloads to show the new view whole. No service,
871
- * container, daemon or route changes, and the focus stays where it is.
870
+ * A workspace: first the CLI default (workspaces.yml), then the view. When
871
+ * the CLI default can't be set, nothing changes and the error shows. "All
872
+ * active": the view only. POST /api/scope stores the view (user.yml
873
+ * `view`), then the page reloads to show it whole. The workspace already
874
+ * in view (`inView`): the CLI default only, the view untouched. No
875
+ * service, container, daemon or route changes.
872
876
  */
873
- async function _switchScope(value) {
877
+ async function _switchScope(value, focus, inView) {
878
+ if (value !== "all" && value !== focus && !(await _setFocus(value))) return;
879
+ if (inView) {
880
+ // A ?ws= page shows the pick after the reload, not its old ?ws=.
881
+ followScopeInUrl(value);
882
+ location.reload();
883
+ return;
884
+ }
874
885
  try {
875
886
  await window.denShell.api("POST", "/api/scope", { scope: value });
876
887
  } catch (err) {
@@ -885,9 +896,9 @@ async function _switchScope(value) {
885
896
  }
886
897
 
887
898
  /**
888
- * "Set <ws> as focus": Local Dev's focus action (POST /api/workspaces
889
- * `focus`, which writes workspaces.yml and retargets the CLI). Separate
890
- * from the view: it is the only thing in the menu that moves the focus.
899
+ * Make `name` the CLI default: Local Dev's focus action (POST
900
+ * /api/workspaces `focus`, which writes workspaces.yml and retargets the
901
+ * CLI). False, with the error shown, when it couldn't.
891
902
  */
892
903
  async function _setFocus(name) {
893
904
  try {
@@ -900,9 +911,9 @@ async function _setFocus(name) {
900
911
  err.message = t("ws.focusFailed", { msg: err.message });
901
912
  window.denShell.errorToast(err);
902
913
  } else window.denShell?.toast?.(t("ws.focusFailed", { msg: err.message }));
903
- return;
914
+ return false;
904
915
  }
905
- location.reload();
916
+ return true;
906
917
  }
907
918
 
908
919
  // ── Health: the status chip and drawer (topbar.js, R6 and R15) ─────────────
@@ -29,7 +29,9 @@
29
29
  "ws.all": "Todos los activos",
30
30
  "ws.focus": "predeterminado de la CLI",
31
31
  "ws.focusTip":
32
- "Los comandos de la CLI se ejecutan aquí si no indicas un espacio de trabajo. Elegir un espacio en la barra superior solo cambia lo que muestran las páginas.",
32
+ "Los comandos de la CLI se ejecutan aquí si no indicas un espacio de trabajo. Elegir un espacio en la barra superior también lo convierte en el predeterminado de la CLI.",
33
+ "ws.pickTip":
34
+ "Elegir un espacio de trabajo aquí también lo convierte en el predeterminado de la CLI, así que los comandos de __DEN_CLI__ se ejecutan allí.",
33
35
  "ws.none": "No hay espacios de trabajo activos",
34
36
  "ws.manage": "Gestionar espacios de trabajo…",
35
37
  "ws.allN": "Todos los activos ({n})",
@@ -39,10 +41,7 @@
39
41
  "ws.viewTitle":
40
42
  "Vista: lo que muestra cada página ({ws}). Es solo un filtro: no inicia, detiene ni enruta nada.",
41
43
  "ws.viewTitleFocus":
42
- "Vista: lo que muestra cada página ({ws}). Es solo un filtro: no inicia, detiene ni enruta nada, y el predeterminado de la CLI ({focus}) sigue siendo el destino de los comandos.",
43
- "ws.setFocus": "Hacer de {ws} el predeterminado de la CLI",
44
- "ws.setFocusTip":
45
- "Lo convierte en el destino por defecto de los comandos (escribe workspaces.yml)",
44
+ "Vista: lo que muestra cada página ({ws}). No inicia, detiene ni enruta nada. Predeterminado de la CLI: {focus}.",
46
45
  "ws.viewFailed": "No se pudo cambiar la vista: {msg}",
47
46
  "ws.focusFailed": "No se pudo poner el foco: {msg}",
48
47
  "org.n": "{n} espacios de trabajo",
@@ -328,6 +327,7 @@
328
327
  "set.language": "Idioma",
329
328
  "set.appearance.org-theme": "Tema predeterminado de la org",
330
329
  "set.editor": "IDE predeterminado",
330
+ "set.editor.open": "Abrir enlaces de archivo en",
331
331
  "set.editor_overrides": "IDE por lenguaje",
332
332
  "set.terminal": "Terminal predeterminada",
333
333
  "set.shell.pages": "Páginas",
@@ -342,6 +342,7 @@
342
342
  "set.graph.panelWidth": "Panel de detalles",
343
343
  "set.workspace.editor": "IDE de este espacio de trabajo",
344
344
  "set.agents.runtimes": "Runtimes",
345
+ "set.agents.layout": "Disposición de la pestaña Agentes",
345
346
  "set.cli.local_dev": "Entorno local",
346
347
  "set.cli.cli_tabs": "Pestañas de terminal",
347
348
  "set.cli.log_window_lines": "Líneas del panel de registros",
@@ -476,6 +477,23 @@
476
477
  "ag.palStart": "Iniciar agente…",
477
478
  "ag.cap":
478
479
  "Hasta 4 paneles: se cerró el de {id} para hacer sitio (sigue en marcha)",
480
+ "ag.layout": "Disposición",
481
+ "ag.l.focus": "Enfoque",
482
+ "ag.l.tabs": "Pestañas",
483
+ "ag.l.tile": "Mosaico",
484
+ "ag.l.tip": "{name} ({key})",
485
+ "ag.hint.keys":
486
+ "⌃⇧1 / ⌃⇧2 / ⌃⇧3 cambian la disposición. ⌘ y clic (Ctrl y clic) abre un enlace o una ruta.",
487
+ "ag.pal.layout": "Disposición: {name}",
488
+ "ag.copied": "Copiado",
489
+ "ag.tabs": "Sesiones abiertas",
490
+ "ag.hint.focus":
491
+ "Un clic muestra una sesión. Mayús o ⌘ y clic la añade al lado.",
492
+ "ag.hint.tabs":
493
+ "Un clic abre una sesión en una pestaña. Doble clic en una pestaña para cambiarle el nombre.",
494
+ "ag.hint.tile": "Un clic añade una sesión al mosaico, hasta 4.",
495
+ "ag.renameTip": "Doble clic para cambiar el nombre",
496
+ "ag.renameName": "Cambiar el nombre de {id}",
479
497
  "ag.rt.ready": "Inicia {name} {version} desde",
480
498
  "ag.rt.readyNoVer": "Inicia {name} desde",
481
499
  "ag.rt.notFound": "No se encontró {name}.",
@@ -29,7 +29,9 @@
29
29
  "ws.all": "すべてのアクティブ",
30
30
  "ws.focus": "CLI の既定",
31
31
  "ws.focusTip":
32
- "ワークスペースを指定しないとき、CLI のコマンドはここで実行されます。上部バーでワークスペースを選んでも、ページの表示が変わるだけです。",
32
+ "ワークスペースを指定しないと CLI のコマンドはここで実行されます。上部バーでワークスペースを選ぶと、それが CLI の既定にもなります。",
33
+ "ws.pickTip":
34
+ "ここでワークスペースを選ぶと CLI の既定にもなり、__DEN_CLI__ のコマンドはそこで実行されます。",
33
35
  "ws.none": "アクティブなワークスペースはありません",
34
36
  "ws.manage": "ワークスペースを管理…",
35
37
  "ws.allN": "すべてのアクティブ ({n})",
@@ -39,10 +41,7 @@
39
41
  "ws.viewTitle":
40
42
  "表示: すべてのページに表示する範囲 ({ws})。表示の絞り込みだけで、何も起動・停止・ルーティングしません。",
41
43
  "ws.viewTitleFocus":
42
- "表示: すべてのページに表示する範囲 ({ws})。表示の絞り込みだけで、何も起動・停止・ルーティングしません。コマンドの対象は CLI の既定 ({focus}) のままです。",
43
- "ws.setFocus": "{ws} を CLI の既定にする",
44
- "ws.setFocusTip":
45
- "コマンドの既定の対象にします (workspaces.yml に書き込みます)",
44
+ "表示: 各ページに出るもの ({ws})。起動・停止・ルーティングは行いません。CLI の既定: {focus}。",
46
45
  "ws.viewFailed": "表示を変更できませんでした: {msg}",
47
46
  "ws.focusFailed": "フォーカスを設定できませんでした: {msg}",
48
47
  "org.n": "{n} 個のワークスペース",
@@ -317,6 +316,7 @@
317
316
  "set.language": "言語",
318
317
  "set.appearance.org-theme": "組織の既定テーマ",
319
318
  "set.editor": "既定の IDE",
319
+ "set.editor.open": "ファイルリンクを開くエディター",
320
320
  "set.editor_overrides": "言語ごとの IDE",
321
321
  "set.terminal": "既定のターミナル",
322
322
  "set.shell.pages": "ページ",
@@ -331,6 +331,7 @@
331
331
  "set.graph.panelWidth": "詳細パネル",
332
332
  "set.workspace.editor": "このワークスペースの IDE",
333
333
  "set.agents.runtimes": "ランタイム",
334
+ "set.agents.layout": "エージェントタブのレイアウト",
334
335
  "set.cli.local_dev": "ローカル環境",
335
336
  "set.cli.cli_tabs": "ターミナルのタブ",
336
337
  "set.cli.log_window_lines": "ログ欄の行数",
@@ -456,6 +457,23 @@
456
457
  "ag.palStart": "エージェントを開始…",
457
458
  "ag.cap":
458
459
  "ペインは最大 4 つです。{id} のペインを閉じました(実行は続きます)",
460
+ "ag.layout": "レイアウト",
461
+ "ag.l.focus": "フォーカス",
462
+ "ag.l.tabs": "タブ",
463
+ "ag.l.tile": "タイル",
464
+ "ag.l.tip": "{name}({key})",
465
+ "ag.hint.keys":
466
+ "⌃⇧1 / ⌃⇧2 / ⌃⇧3 でレイアウトを切り替えます。⌘ クリック(Ctrl クリック)でリンクやパスを開きます。",
467
+ "ag.pal.layout": "レイアウト: {name}",
468
+ "ag.copied": "コピーしました",
469
+ "ag.tabs": "開いているセッション",
470
+ "ag.hint.focus":
471
+ "クリックで 1 つのセッションを表示します。Shift か ⌘ を押しながらクリックすると横に追加します。",
472
+ "ag.hint.tabs":
473
+ "クリックでセッションをタブで開きます。タブをダブルクリックすると名前を変えられます。",
474
+ "ag.hint.tile": "クリックでセッションをタイルに追加します(最大 4)。",
475
+ "ag.renameTip": "ダブルクリックで名前を変更",
476
+ "ag.renameName": "{id} の名前を変更",
459
477
  "ag.rt.ready": "{name} {version} を起動:",
460
478
  "ag.rt.readyNoVer": "{name} を起動:",
461
479
  "ag.rt.notFound": "{name} が見つかりません。",
@@ -1613,7 +1613,7 @@
1613
1613
  ? `<button class="danger" data-action="delete">Delete</button>`
1614
1614
  : "";
1615
1615
  return `<div class="ws-item ${n === activeWs ? "active" : ""}" data-name="${escHtml(n)}" data-action="select">
1616
- <span>${escHtml(n)}${isWsActive(n) ? ' <span style="font-size:10px;color:var(--muted)">(active)</span>' : ""}${n === focusName() ? ` <span class="ws-cli-default" title="${escHtml(wsT("ws.focusTip", "CLI commands run here when you don't name a workspace. Picking a workspace in the top bar only changes what pages show."))}">${escHtml(wsT("ws.focus", "CLI default"))}</span>` : ""}</span>
1616
+ <span>${escHtml(n)}${isWsActive(n) ? ' <span style="font-size:10px;color:var(--muted)">(active)</span>' : ""}${n === focusName() ? ` <span class="ws-cli-default" title="${escHtml(wsT("ws.focusTip", "CLI commands run here when you don't name a workspace. Picking a workspace in the top bar also makes it the CLI default."))}">${escHtml(wsT("ws.focus", "CLI default"))}</span>` : ""}</span>
1617
1617
  <span class="ws-item-right">
1618
1618
  <button class="ws-term-btn" title="Open in terminal" data-open-term="${escHtml(dirPath)}">&#62;_</button>
1619
1619
  <button class="ws-open-btn" title="Open in editor" data-open-path="${escHtml(wsData.codeWorkspacePaths?.[n] || dirPath)}">↗</button>
@@ -628,6 +628,33 @@ export const DEN_SETTINGS = [
628
628
  read: (ctx) => effective(ctx.data.editors, ctx.data.editor),
629
629
  write: (v, ctx) => ctx.post({ editor: v || null }),
630
630
  },
631
+ {
632
+ id: "editor.open",
633
+ category: "editor",
634
+ label: "Open file links in",
635
+ description:
636
+ "⌘-click (Ctrl-click on Windows and Linux) on a file path in an agent's pane opens it here, at the line. None copies the path instead.",
637
+ keywords: [
638
+ "editor",
639
+ "link",
640
+ "path",
641
+ "file",
642
+ "agents",
643
+ "vscode",
644
+ "cursor",
645
+ "zed",
646
+ ],
647
+ scope: "user",
648
+ control: seg([
649
+ { value: "vscode", label: "VS Code" },
650
+ { value: "cursor", label: "Cursor" },
651
+ { value: "zed", label: "Zed" },
652
+ { value: "none", label: "None (copy)" },
653
+ ]),
654
+ keys: ["editor_open"],
655
+ read: (ctx) => ctx.data.editor_open ?? "none",
656
+ write: (v, ctx) => ctx.post({ editor_open: v }),
657
+ },
631
658
  {
632
659
  id: "editor_overrides",
633
660
  category: "editor",
@@ -813,6 +840,23 @@ export const DEN_SETTINGS = [
813
840
  },
814
841
 
815
842
  // ── Agents ─────────────────────────────────────────────────────────────
843
+ {
844
+ id: "agents.layout",
845
+ category: "agents",
846
+ label: "Agents tab layout",
847
+ description:
848
+ "Focus shows one agent, Tabs shows one with a tab for each, Tile shows up to four in a grid. The Agents tab's switch, ⌃⇧1, ⌃⇧2, ⌃⇧3 and ⌘K set it too.",
849
+ keywords: ["agents", "layout", "focus", "tabs", "tile", "grid", "panes"],
850
+ scope: "user",
851
+ control: seg([
852
+ { value: "focus", label: "Focus" },
853
+ { value: "tabs", label: "Tabs" },
854
+ { value: "tile", label: "Tile" },
855
+ ]),
856
+ keys: ["agents_layout"],
857
+ read: (ctx) => ctx.data.agents_layout ?? "focus",
858
+ write: (v, ctx) => ctx.post({ agents_layout: v }),
859
+ },
816
860
  {
817
861
  id: "agents.runtimes",
818
862
  category: "agents",
@@ -62,7 +62,7 @@
62
62
  * setOrgs([{ id, title, workspaces? }], currentId, onSelect?)
63
63
  * two or more: the Org ▾ menu; a pick
64
64
  * calls onSelect(id), fires `den:org`
65
- * setWorkspaces({ label, title, items, focusable, onSelect, onFocus })
65
+ * setWorkspaces({ label, title, items, focus, onSelect })
66
66
  * the Workspace ▾ menu (header.js)
67
67
  * syncEnv() repaint Env ▾ from #env-sel
68
68
  * registerCommand({ id, label, keywords?, kind?, run })
@@ -134,7 +134,9 @@
134
134
  "ws.all": "All active",
135
135
  "ws.focus": "CLI default",
136
136
  "ws.focusTip":
137
- "CLI commands run here when you don't name a workspace. Picking a workspace in the top bar only changes what pages show.",
137
+ "CLI commands run here when you don't name a workspace. Picking a workspace in the top bar also makes it the CLI default.",
138
+ "ws.pickTip":
139
+ "Picking a workspace here also makes it the CLI default, so __DEN_CLI__ commands run there.",
138
140
  "ws.none": "No active workspaces",
139
141
  "ws.manage": "Manage workspaces…",
140
142
  "ws.allN": "All active ({n})",
@@ -144,10 +146,7 @@
144
146
  "ws.viewTitle":
145
147
  "View: what every page shows ({ws}). A view filter only: it starts, stops and routes nothing.",
146
148
  "ws.viewTitleFocus":
147
- "View: what every page shows ({ws}). A view filter only: it starts, stops and routes nothing, and the CLI default ({focus}) stays the target for commands.",
148
- "ws.setFocus": "Make {ws} the CLI default",
149
- "ws.setFocusTip":
150
- "CLI commands run in it when you don't name a workspace (writes workspaces.yml)",
149
+ "View: what every page shows ({ws}). It starts, stops and routes nothing. CLI default: {focus}.",
151
150
  "ws.viewFailed": "Could not change the view: {msg}",
152
151
  "ws.focusFailed": "Could not change the CLI default: {msg}",
153
152
  // A count's singular is its .one (0.3.0-dev.40: en had none, so the org
@@ -439,6 +438,7 @@
439
438
  "set.language": "Language",
440
439
  "set.appearance.org-theme": "Org default theme",
441
440
  "set.editor": "Default IDE",
441
+ "set.editor.open": "Open file links in",
442
442
  "set.editor_overrides": "Per-language IDE",
443
443
  "set.terminal": "Default terminal",
444
444
  "set.shell.pages": "Pages",
@@ -453,6 +453,7 @@
453
453
  "set.graph.panelWidth": "Details panel",
454
454
  "set.workspace.editor": "IDE for this workspace",
455
455
  "set.agents.runtimes": "Runtimes",
456
+ "set.agents.layout": "Agents tab layout",
456
457
  "set.cli.local_dev": "Local environment",
457
458
  "set.cli.cli_tabs": "Terminal tabs",
458
459
  "set.cli.log_window_lines": "Log pane lines",
@@ -586,6 +587,24 @@
586
587
  "ag.pal": "Agents › {ws} / {name}",
587
588
  "ag.palStart": "Start agent…",
588
589
  "ag.cap": "Up to 4 panes: {id}'s closed to make room (it keeps running)",
590
+ // AG-4b: the Agents tab's layouts and named tabs.
591
+ "ag.layout": "Layout",
592
+ "ag.l.focus": "Focus",
593
+ "ag.l.tabs": "Tabs",
594
+ "ag.l.tile": "Tile",
595
+ "ag.l.tip": "{name} ({key})",
596
+ "ag.hint.keys":
597
+ "⌃⇧1 / ⌃⇧2 / ⌃⇧3 switch layout. ⌘-click (Ctrl-click) opens a link or path.",
598
+ "ag.pal.layout": "Layout: {name}",
599
+ "ag.copied": "Copied",
600
+ "ag.tabs": "Open sessions",
601
+ "ag.hint.focus":
602
+ "Click shows one session. Shift- or ⌘-click adds it alongside.",
603
+ "ag.hint.tabs":
604
+ "Click opens a session as a tab. Double-click a tab to rename it.",
605
+ "ag.hint.tile": "Click adds a session as a tile, up to 4.",
606
+ "ag.renameTip": "Double-click to rename",
607
+ "ag.renameName": "Rename {id}",
589
608
  "ag.rt.ready": "Starts {name} {version} from",
590
609
  "ag.rt.readyNoVer": "Starts {name} from",
591
610
  "ag.rt.notFound": "{name} not found.",
@@ -1722,8 +1741,9 @@
1722
1741
  /**
1723
1742
  * The Workspace part: { label, title, items: [{ value, label, checked,
1724
1743
  * focused, title? }], onSelect(value) }. "All active" and the active
1725
- * workspaces, the focused one marked; then, apart, "Set <ws> as focus"
1726
- * (model.focusable, model.onFocus(name)) and a link to manage them.
1744
+ * workspaces, the focused one (model.focus, the CLI default) marked; then
1745
+ * a link to manage them. onSelect(value, focus): choosing a workspace
1746
+ * also makes it the CLI default (header.js).
1727
1747
  */
1728
1748
  function setWorkspaces(model) {
1729
1749
  var btn = document.getElementById("ws-pill");
@@ -1748,22 +1768,16 @@
1748
1768
  tag: it.focused ? t("ws.focus") : null,
1749
1769
  title: it.title || null,
1750
1770
  onSelect: function () {
1751
- if (!it.checked && typeof model.onSelect === "function")
1752
- model.onSelect(it.value);
1771
+ if (typeof model.onSelect !== "function") return;
1772
+ // The workspace in view still becomes the CLI default when it
1773
+ // isn't one; picking what is both already does nothing.
1774
+ if (it.checked && (it.value === "all" || it.value === model.focus))
1775
+ return;
1776
+ model.onSelect(it.value, model.focus || null, !!it.checked);
1753
1777
  },
1754
1778
  });
1755
1779
  });
1756
1780
  items.push({ hr: true });
1757
- // Focus is a separate action (it writes workspaces.yml and retargets
1758
- // the CLI); picking a view above never moves it.
1759
- if (model.focusable && typeof model.onFocus === "function")
1760
- items.push({
1761
- label: t("ws.setFocus", { ws: model.focusable }),
1762
- title: t("ws.setFocusTip"),
1763
- onSelect: function () {
1764
- model.onFocus(model.focusable);
1765
- },
1766
- });
1767
1781
  items.push({ label: t("ws.manage"), href: navHref("local-dev") });
1768
1782
  scopeMenus.ws = { label: t("k.ws"), items: items };
1769
1783
  }