clay-server 2.39.0-beta.5 → 2.40.0-beta.1

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/lib/public/app.js CHANGED
@@ -65,6 +65,7 @@ import { initLoopWizard, openRalphWizard as _loopOpenRalphWizard, closeRalphWiza
65
65
  import { initAppNotifications, handleNotificationsState as _notifHandleState, handleNotificationCreated as _notifHandleCreated, handleNotificationDismissed as _notifHandleDismissed, handleNotificationDismissedAll as _notifHandleDismissedAll } from './modules/app-notifications.js';
66
66
  import { initWhatsNew, handleWhatsNewState as _wnHandleState, handleWhatsNewSeenResult as _wnHandleSeenResult } from './modules/whats-new.js';
67
67
  import { initWhatsNewArticle, openArticle as openWhatsNewArticle } from './modules/whats-new-article.js';
68
+ import { initHeaderTuiFont } from './modules/header-tui-font.js';
68
69
  import { createStore, store } from './modules/store.js';
69
70
  import { initPanels, updateConfigChip as _panUpdateConfigChip, getModelEffortLevels as _panGetModelEffortLevels, accumulateUsage as _panAccumulateUsage, updateUsagePanel as _panUpdateUsagePanel, resetUsage as _panResetUsage, toggleUsagePanel as _panToggleUsagePanel, formatTokens as _panFormatTokens, updateStatusPanel as _panUpdateStatusPanel, requestProcessStats as _panRequestProcessStats, toggleStatusPanel as _panToggleStatusPanel, accumulateContext as _panAccumulateContext, updateContextPanel as _panUpdateContextPanel, resetContext as _panResetContext, resetContextData as _panResetContextData, minimizeContext as _panMinimizeContext, expandContext as _panExpandContext, toggleContextPanel as _panToggleContextPanel, getContextView as _panGetContextView, renderCtxPopover as _panRenderCtxPopover, hideCtxPopover as _panHideCtxPopover, formatBytes as _panFormatBytes, formatUptime as _panFormatUptime, getModelSupportsEffort as _panGetModelSupportsEffort, getSessionUsage, setSessionUsage, getContextData, setContextData, setContextView as _panSetContextView, applyContextView as _panApplyContextView } from './modules/app-panels.js';
70
71
  import { initProjects, updateProjectList as _projUpdateProjectList, renderProjectList as _projRenderProjectList, renderTopbarPresence as _projRenderTopbarPresence, switchProject as _projSwitchProject, resetClientState as _projResetClientState, confirmRemoveProject as _projConfirmRemoveProject, handleRemoveProjectCheckResult as _projHandleRemoveProjectCheckResult, handleRemoveProjectResult as _projHandleRemoveProjectResult, openAddProjectModal as _projOpenAddProjectModal, closeAddProjectModal as _projCloseAddProjectModal, handleBrowseDirResult as _projHandleBrowseDirResult, handleAddProjectResult as _projHandleAddProjectResult, handleCloneProgress as _projHandleCloneProgress, showUpdateAvailable as _projShowUpdateAvailable, getCachedProjects, setCachedProjects, getCachedProjectCount, getCachedRemovedProjects, setCachedRemovedProjects } from './modules/app-projects.js';
@@ -604,6 +605,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
604
605
  // --- Panels module ---
605
606
  initPanels();
606
607
 
608
+ // --- Header TUI font controls (visible only when TUI session active) ---
609
+ initHeaderTuiFont();
610
+
607
611
  // --- Rendering module ---
608
612
  initRendering();
609
613
 
@@ -860,45 +860,133 @@
860
860
  }
861
861
  }
862
862
 
863
- /* Terminal font picker row */
864
- .us-term-font-row {
863
+ /* Header TUI font icon button - single trigger that opens a popover
864
+ with the full font picker + size stepper. Visible only when a TUI
865
+ session is active. Inherits title-bar icon button styling so it
866
+ blends with the existing toolbar. */
867
+ .header-tui-font-btn.hidden { display: none; }
868
+
869
+ /* Floating popover that holds the font menu + size row. Anchored
870
+ under the title-bar button via JS positioning. */
871
+ .header-tui-font-popover {
872
+ position: fixed;
873
+ min-width: 220px;
874
+ max-height: 70vh;
875
+ overflow-y: auto;
876
+ padding: 8px;
877
+ background: var(--bg-alt);
878
+ border: 1px solid var(--border);
879
+ border-radius: 10px;
880
+ box-shadow: 0 8px 28px rgba(var(--shadow-rgb), 0.45);
881
+ z-index: 200;
882
+ opacity: 0;
883
+ transform: translateY(-4px);
884
+ pointer-events: none;
885
+ transition: opacity 0.12s ease, transform 0.12s ease;
886
+ }
887
+ .header-tui-font-popover.visible {
888
+ opacity: 1;
889
+ transform: translateY(0);
890
+ pointer-events: auto;
891
+ }
892
+ .header-tui-font-popover-label {
893
+ padding: 4px 8px 6px;
894
+ font-size: 11px;
895
+ font-weight: 600;
896
+ color: var(--text-dimmer);
897
+ text-transform: uppercase;
898
+ letter-spacing: 0.04em;
899
+ }
900
+ .header-tui-font-popover-list {
901
+ display: flex;
902
+ flex-direction: column;
903
+ gap: 1px;
904
+ }
905
+ .header-tui-font-popover-item {
865
906
  display: flex;
866
907
  align-items: center;
867
908
  gap: 8px;
868
- flex-wrap: wrap;
869
- }
870
- .us-term-font-family {
871
- flex: 1 1 auto;
872
- min-width: 180px;
909
+ width: 100%;
873
910
  padding: 7px 10px;
874
- border: 1px solid var(--border);
875
- border-radius: 8px;
876
- background: var(--input-bg);
911
+ border: none;
912
+ background: transparent;
877
913
  color: var(--text);
878
914
  font-size: 13px;
915
+ text-align: left;
916
+ cursor: pointer;
917
+ border-radius: 6px;
879
918
  }
880
- .us-term-font-custom {
881
- flex: 1 1 100%;
882
- padding: 7px 10px;
883
- border: 1px solid var(--border);
884
- border-radius: 8px;
885
- background: var(--input-bg);
886
- color: var(--text);
919
+ .header-tui-font-popover-item:hover {
920
+ background: rgba(var(--overlay-rgb), 0.06);
921
+ }
922
+ .header-tui-font-popover-check {
923
+ display: inline-flex;
924
+ width: 14px;
925
+ height: 14px;
926
+ align-items: center;
927
+ justify-content: center;
928
+ color: var(--accent);
929
+ opacity: 0;
930
+ flex-shrink: 0;
931
+ }
932
+ .header-tui-font-popover-item.active .header-tui-font-popover-check { opacity: 1; }
933
+ .header-tui-font-popover-item-label {
934
+ flex: 1 1 auto;
935
+ overflow: hidden;
936
+ text-overflow: ellipsis;
937
+ white-space: nowrap;
938
+ }
939
+ .header-tui-font-popover-divider {
940
+ height: 1px;
941
+ background: var(--border-subtle);
942
+ margin: 6px 4px;
943
+ }
944
+ .header-tui-font-popover-size {
945
+ display: flex;
946
+ align-items: center;
947
+ justify-content: space-between;
948
+ gap: 12px;
949
+ padding: 4px 10px 4px;
950
+ }
951
+ .header-tui-font-popover-size-label {
887
952
  font-size: 13px;
888
- font-family: "Roboto Mono", monospace;
953
+ color: var(--text-secondary);
889
954
  }
890
- .us-term-font-custom.hidden { display: none; }
891
- .us-term-font-size {
892
- width: 64px;
893
- padding: 7px 10px;
894
- border: 1px solid var(--border);
895
- border-radius: 8px;
896
- background: var(--input-bg);
955
+ .header-tui-font-popover-size-stepper {
956
+ display: inline-flex;
957
+ align-items: center;
958
+ height: 26px;
959
+ border: 1px solid var(--border-subtle);
960
+ border-radius: 6px;
961
+ overflow: hidden;
962
+ }
963
+ .header-tui-font-popover-size-stepper button {
964
+ width: 26px;
965
+ height: 24px;
966
+ border: none;
967
+ background: transparent;
968
+ color: var(--text-secondary);
969
+ font-size: 14px;
970
+ font-weight: 600;
971
+ cursor: pointer;
972
+ display: inline-flex;
973
+ align-items: center;
974
+ justify-content: center;
975
+ padding: 0;
976
+ }
977
+ .header-tui-font-popover-size-stepper button:hover {
978
+ background: rgba(var(--overlay-rgb), 0.06);
897
979
  color: var(--text);
898
- font-size: 13px;
899
- text-align: center;
900
980
  }
901
- .us-term-font-unit {
902
- font-size: 13px;
903
- color: var(--text-dimmer);
981
+ .header-tui-font-popover-size-stepper button:disabled {
982
+ opacity: 0.35;
983
+ cursor: default;
984
+ }
985
+ .header-tui-font-popover-size-val {
986
+ min-width: 28px;
987
+ text-align: center;
988
+ font-size: 12px;
989
+ color: var(--text);
990
+ font-variant-numeric: tabular-nums;
991
+ user-select: none;
904
992
  }
@@ -18,6 +18,10 @@
18
18
  <link rel="preconnect" href="https://fonts.googleapis.com">
19
19
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
20
20
  <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500;700&family=Nunito:wght@700;800&display=swap" rel="stylesheet">
21
+ <!-- Monospace web fonts used by the TUI font picker in the title bar.
22
+ SF Mono is Apple-system-only (no Google Fonts entry) and
23
+ ui-monospace is a system keyword - both rely on the user's OS. -->
24
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Fira+Code:wght@400;500;700&family=Cascadia+Code:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500;700&family=Source+Code+Pro:wght@400;500;700&display=swap" rel="stylesheet">
21
25
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css">
22
26
  <script>
23
27
  (function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
@@ -367,6 +371,9 @@
367
371
  <div id="ralph-sticky" class="hidden"></div>
368
372
  <div id="debate-sticky" class="hidden"></div>
369
373
  <div class="status">
374
+ <button type="button" id="header-tui-font-btn" class="header-tui-font-btn hidden" title="Terminal font" aria-label="Terminal font">
375
+ <i data-lucide="type"></i>
376
+ </button>
370
377
  <button id="debate-pdf-btn" class="hidden" title="Export debate as PDF"><i data-lucide="download"></i></button>
371
378
  <button id="find-in-session-btn" title="Search in session (Ctrl+F)"><i data-lucide="search"></i></button>
372
379
  <button id="terminal-toggle-btn" title="Terminal"><i data-lucide="square-terminal"></i><span id="terminal-count" class="hidden"></span></button>
@@ -1001,25 +1008,6 @@
1001
1008
  <!-- Appearance section -->
1002
1009
  <div class="us-section" data-section="us-appearance">
1003
1010
  <h2>Appearance</h2>
1004
- <div class="settings-card">
1005
- <div class="settings-label" style="margin-bottom:10px;">Terminal font</div>
1006
- <div class="us-term-font-row">
1007
- <select id="us-term-font-family" class="us-term-font-family">
1008
- <option value="'SF Mono', Menlo, Monaco, 'Courier New', monospace">SF Mono / Menlo (default)</option>
1009
- <option value="'JetBrains Mono', 'SF Mono', Menlo, monospace">JetBrains Mono</option>
1010
- <option value="'Fira Code', 'SF Mono', Menlo, monospace">Fira Code</option>
1011
- <option value="'Cascadia Code', 'SF Mono', Menlo, monospace">Cascadia Code</option>
1012
- <option value="'IBM Plex Mono', 'SF Mono', Menlo, monospace">IBM Plex Mono</option>
1013
- <option value="'Source Code Pro', 'SF Mono', Menlo, monospace">Source Code Pro</option>
1014
- <option value="'Roboto Mono', 'SF Mono', Menlo, monospace">Roboto Mono</option>
1015
- <option value="ui-monospace, monospace">System monospace</option>
1016
- <option value="__custom__">Custom...</option>
1017
- </select>
1018
- <input type="text" id="us-term-font-family-custom" class="us-term-font-custom hidden" placeholder="e.g. 'My Font', monospace" maxlength="200">
1019
- <input type="number" id="us-term-font-size" class="us-term-font-size" min="9" max="32" step="1">
1020
- <span class="us-term-font-unit">px</span>
1021
- </div>
1022
- </div>
1023
1011
  <div class="settings-card">
1024
1012
  <div class="settings-label" style="margin-bottom:10px;">Chat layout</div>
1025
1013
  <div class="layout-switcher" id="us-layout-switcher">
@@ -108,6 +108,7 @@ var CODEX_WEBSEARCH_OPTIONS = [
108
108
  var KNOWN_CONTEXT_WINDOWS = {
109
109
  "opus-4-6": 1000000,
110
110
  "claude-sonnet-4": 1000000,
111
+ "gpt-5.5": 1048576,
111
112
  "gpt-5.4": 1048576,
112
113
  "gpt-5.3": 1048576,
113
114
  "gpt-5.2": 1048576,
@@ -0,0 +1,233 @@
1
+ // header-tui-font.js
2
+ //
3
+ // Single icon button in the title bar (visible only while a TUI session
4
+ // is active) that opens a popover with terminal font settings: family
5
+ // picker (with per-item font preview) + size stepper. Values live in
6
+ // terminal-prefs.js and persist server-side via
7
+ // PUT /api/user/terminal-font.
8
+
9
+ import {
10
+ applyTerminalFont,
11
+ getTerminalFontFamily,
12
+ getTerminalFontSize,
13
+ onTerminalFontChange,
14
+ } from './terminal-prefs.js';
15
+
16
+ var FONT_OPTIONS = [
17
+ { label: "SF Mono", family: "'SF Mono', Menlo, Monaco, 'Courier New', monospace" },
18
+ { label: "JetBrains Mono", family: "'JetBrains Mono', 'SF Mono', Menlo, monospace" },
19
+ { label: "Fira Code", family: "'Fira Code', 'SF Mono', Menlo, monospace" },
20
+ { label: "Cascadia Code", family: "'Cascadia Code', 'SF Mono', Menlo, monospace" },
21
+ { label: "IBM Plex Mono", family: "'IBM Plex Mono', 'SF Mono', Menlo, monospace" },
22
+ { label: "Source Code Pro", family: "'Source Code Pro', 'SF Mono', Menlo, monospace" },
23
+ { label: "Roboto Mono", family: "'Roboto Mono', 'SF Mono', Menlo, monospace" },
24
+ { label: "System mono", family: "ui-monospace, monospace" },
25
+ ];
26
+
27
+ var MIN_SIZE = 9;
28
+ var MAX_SIZE = 32;
29
+
30
+ var btnEl = null;
31
+ var popoverEl = null;
32
+ var sizeValEl = null;
33
+ var sizeDecEl = null;
34
+ var sizeIncEl = null;
35
+ var menuItemEls = [];
36
+ var popoverOpen = false;
37
+ var outsideHandler = null;
38
+ var keyHandler = null;
39
+ var initialized = false;
40
+
41
+ function escapeHtml(s) {
42
+ return String(s)
43
+ .replace(/&/g, '&amp;')
44
+ .replace(/</g, '&lt;')
45
+ .replace(/>/g, '&gt;')
46
+ .replace(/"/g, '&quot;');
47
+ }
48
+
49
+ function persistTermFont(family, size) {
50
+ fetch('/api/user/terminal-font', {
51
+ method: 'PUT',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify({ family: family, size: size }),
54
+ }).catch(function () {});
55
+ }
56
+
57
+ function syncSize() {
58
+ if (!sizeValEl) return;
59
+ var sz = getTerminalFontSize();
60
+ sizeValEl.textContent = String(sz);
61
+ if (sizeDecEl) sizeDecEl.disabled = sz <= MIN_SIZE;
62
+ if (sizeIncEl) sizeIncEl.disabled = sz >= MAX_SIZE;
63
+ }
64
+
65
+ function syncActiveFamily() {
66
+ var fam = getTerminalFontFamily();
67
+ for (var i = 0; i < menuItemEls.length; i++) {
68
+ var el = menuItemEls[i];
69
+ el.classList.toggle('active', el.dataset.family === fam);
70
+ }
71
+ }
72
+
73
+ function buildPopover() {
74
+ if (popoverEl) return popoverEl;
75
+ popoverEl = document.createElement('div');
76
+ popoverEl.className = 'header-tui-font-popover';
77
+
78
+ var label = document.createElement('div');
79
+ label.className = 'header-tui-font-popover-label';
80
+ label.textContent = 'Font';
81
+ popoverEl.appendChild(label);
82
+
83
+ var list = document.createElement('div');
84
+ list.className = 'header-tui-font-popover-list';
85
+ for (var i = 0; i < FONT_OPTIONS.length; i++) {
86
+ var opt = FONT_OPTIONS[i];
87
+ var item = document.createElement('button');
88
+ item.type = 'button';
89
+ item.className = 'header-tui-font-popover-item';
90
+ item.dataset.family = opt.family;
91
+ item.style.fontFamily = opt.family;
92
+ item.innerHTML =
93
+ '<span class="header-tui-font-popover-check"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg></span>' +
94
+ '<span class="header-tui-font-popover-item-label">' + escapeHtml(opt.label) + '</span>';
95
+ (function (family) {
96
+ item.addEventListener('click', function (e) {
97
+ e.stopPropagation();
98
+ applyTerminalFont(family, undefined);
99
+ persistTermFont(family, undefined);
100
+ });
101
+ })(opt.family);
102
+ list.appendChild(item);
103
+ menuItemEls.push(item);
104
+ }
105
+ popoverEl.appendChild(list);
106
+
107
+ var divider = document.createElement('div');
108
+ divider.className = 'header-tui-font-popover-divider';
109
+ popoverEl.appendChild(divider);
110
+
111
+ var sizeRow = document.createElement('div');
112
+ sizeRow.className = 'header-tui-font-popover-size';
113
+ sizeRow.innerHTML =
114
+ '<span class="header-tui-font-popover-size-label">Size</span>' +
115
+ '<div class="header-tui-font-popover-size-stepper">' +
116
+ '<button type="button" data-step="-1" title="Smaller">−</button>' +
117
+ '<span class="header-tui-font-popover-size-val">13</span>' +
118
+ '<button type="button" data-step="1" title="Larger">+</button>' +
119
+ '</div>';
120
+ popoverEl.appendChild(sizeRow);
121
+
122
+ sizeValEl = sizeRow.querySelector('.header-tui-font-popover-size-val');
123
+ var stepBtns = sizeRow.querySelectorAll('button');
124
+ sizeDecEl = stepBtns[0];
125
+ sizeIncEl = stepBtns[1];
126
+ sizeDecEl.addEventListener('click', function (e) {
127
+ e.stopPropagation();
128
+ var next = Math.max(MIN_SIZE, getTerminalFontSize() - 1);
129
+ applyTerminalFont(undefined, next);
130
+ persistTermFont(undefined, next);
131
+ });
132
+ sizeIncEl.addEventListener('click', function (e) {
133
+ e.stopPropagation();
134
+ var next = Math.min(MAX_SIZE, getTerminalFontSize() + 1);
135
+ applyTerminalFont(undefined, next);
136
+ persistTermFont(undefined, next);
137
+ });
138
+
139
+ document.body.appendChild(popoverEl);
140
+ return popoverEl;
141
+ }
142
+
143
+ function positionPopover() {
144
+ if (!popoverEl || !btnEl) return;
145
+ var r = btnEl.getBoundingClientRect();
146
+ popoverEl.style.left = '0px';
147
+ popoverEl.style.top = '0px';
148
+ var pw = popoverEl.offsetWidth || 220;
149
+ var ph = popoverEl.offsetHeight || 340;
150
+ var vw = window.innerWidth;
151
+ var vh = window.innerHeight;
152
+ var left = r.right - pw;
153
+ if (left < 8) left = 8;
154
+ if (left + pw > vw - 8) left = vw - pw - 8;
155
+ var top = r.bottom + 8;
156
+ if (top + ph > vh - 8) {
157
+ var flipped = r.top - ph - 8;
158
+ if (flipped >= 8) top = flipped;
159
+ else top = Math.max(8, vh - ph - 8);
160
+ }
161
+ popoverEl.style.left = left + 'px';
162
+ popoverEl.style.top = top + 'px';
163
+ }
164
+
165
+ function openPopover() {
166
+ if (popoverOpen) return;
167
+ buildPopover();
168
+ syncActiveFamily();
169
+ syncSize();
170
+ popoverEl.classList.add('visible');
171
+ positionPopover();
172
+ popoverOpen = true;
173
+ if (btnEl) btnEl.classList.add('active');
174
+
175
+ outsideHandler = function (ev) {
176
+ if (!popoverEl) return;
177
+ if (popoverEl.contains(ev.target)) return;
178
+ if (btnEl && btnEl.contains(ev.target)) return;
179
+ closePopover();
180
+ };
181
+ document.addEventListener('mousedown', outsideHandler, true);
182
+
183
+ keyHandler = function (ev) {
184
+ if (ev.key === 'Escape') closePopover();
185
+ };
186
+ document.addEventListener('keydown', keyHandler, true);
187
+ }
188
+
189
+ function closePopover() {
190
+ if (!popoverOpen) return;
191
+ if (popoverEl) popoverEl.classList.remove('visible');
192
+ if (btnEl) btnEl.classList.remove('active');
193
+ popoverOpen = false;
194
+ if (outsideHandler) {
195
+ document.removeEventListener('mousedown', outsideHandler, true);
196
+ outsideHandler = null;
197
+ }
198
+ if (keyHandler) {
199
+ document.removeEventListener('keydown', keyHandler, true);
200
+ keyHandler = null;
201
+ }
202
+ }
203
+
204
+ export function initHeaderTuiFont() {
205
+ if (initialized) return;
206
+ btnEl = document.getElementById('header-tui-font-btn');
207
+ if (!btnEl) return;
208
+ btnEl.addEventListener('click', function (e) {
209
+ e.stopPropagation();
210
+ if (popoverOpen) closePopover();
211
+ else openPopover();
212
+ });
213
+ // Reflect external changes back into the popover (re-renders only if
214
+ // it's currently open).
215
+ onTerminalFontChange(function () {
216
+ if (popoverOpen) {
217
+ syncActiveFamily();
218
+ syncSize();
219
+ }
220
+ });
221
+ initialized = true;
222
+ }
223
+
224
+ export function showHeaderTuiFont() {
225
+ if (!btnEl) return;
226
+ btnEl.classList.remove('hidden');
227
+ }
228
+
229
+ export function hideHeaderTuiFont() {
230
+ closePopover();
231
+ if (!btnEl) return;
232
+ btnEl.classList.add('hidden');
233
+ }
@@ -18,6 +18,7 @@ import { getWs } from './ws-ref.js';
18
18
  import { store } from './store.js';
19
19
  import { getTerminalTheme } from './theme.js';
20
20
  import { getTerminalFontFamily, getTerminalFontSize, onTerminalFontChange } from './terminal-prefs.js';
21
+ import { showHeaderTuiFont, hideHeaderTuiFont } from './header-tui-font.js';
21
22
  import { openArticle as openWhatsNewArticle } from './whats-new-article.js';
22
23
 
23
24
  // Stable id of the canonical "Why TUI mode?" article in
@@ -377,6 +378,7 @@ export function attachTuiView(terminalId) {
377
378
  if (currentTermId === terminalId && xterm) {
378
379
  if (hostEl) hostEl.style.display = "flex";
379
380
  hideGuiChrome(true);
381
+ showHeaderTuiFont();
380
382
  fitNow();
381
383
  try { xterm.focus(); } catch (e) {}
382
384
  return;
@@ -388,6 +390,7 @@ export function attachTuiView(terminalId) {
388
390
  if (!ensureHostEl()) return;
389
391
  hostEl.style.display = "flex";
390
392
  hideGuiChrome(true);
393
+ showHeaderTuiFont();
391
394
  syncHostBounds();
392
395
 
393
396
  currentTermId = terminalId;
@@ -437,6 +440,7 @@ export function detachTuiView() {
437
440
  teardownXterm();
438
441
  if (hostEl) hostEl.style.display = "none";
439
442
  hideGuiChrome(false);
443
+ hideHeaderTuiFont();
440
444
  }
441
445
 
442
446
  // Route a term_output frame to the embedded xterm if it belongs to the
@@ -4,7 +4,7 @@
4
4
  import { refreshIcons } from './icons.js';
5
5
  import { showToast } from './utils.js';
6
6
  import { getChatLayout, setChatLayout } from './theme.js';
7
- import { applyTerminalFont, getTerminalFontFamily, getTerminalFontSize } from './terminal-prefs.js';
7
+ import { applyTerminalFont } from './terminal-prefs.js';
8
8
  import { showEmailSetupModal, getEmailAccountListCache } from './context-sources.js';
9
9
  import { setSTTLang } from './stt.js';
10
10
  import { userAvatarUrl } from './avatar.js';
@@ -234,47 +234,10 @@ export function initUserSettings(appCtx) {
234
234
  // Theme picker lives on the user island button now (palette icon).
235
235
  // No appearance-section mount required.
236
236
 
237
- // Terminal font: family select + optional custom input + size number.
238
- // Saves to server on change and immediately applies to every open
239
- // xterm via applyTerminalFont (terminal-prefs.js fanout).
240
- var tfSelect = document.getElementById('us-term-font-family');
241
- var tfCustom = document.getElementById('us-term-font-family-custom');
242
- var tfSize = document.getElementById('us-term-font-size');
243
-
244
- function persistTermFont(family, size) {
245
- fetch('/api/user/terminal-font', {
246
- method: 'PUT',
247
- headers: { 'Content-Type': 'application/json' },
248
- body: JSON.stringify({ family: family, size: size }),
249
- }).catch(function () {});
250
- }
251
-
252
- if (tfSelect && tfCustom && tfSize) {
253
- tfSelect.addEventListener('change', function () {
254
- if (tfSelect.value === '__custom__') {
255
- tfCustom.classList.remove('hidden');
256
- tfCustom.focus();
257
- return;
258
- }
259
- tfCustom.classList.add('hidden');
260
- applyTerminalFont(tfSelect.value, undefined);
261
- persistTermFont(tfSelect.value, undefined);
262
- });
263
- tfCustom.addEventListener('change', function () {
264
- var v = tfCustom.value.trim();
265
- if (!v) return;
266
- applyTerminalFont(v, undefined);
267
- persistTermFont(v, undefined);
268
- });
269
- tfSize.addEventListener('change', function () {
270
- var n = Number(tfSize.value);
271
- if (!isFinite(n)) return;
272
- n = Math.max(9, Math.min(32, Math.round(n)));
273
- tfSize.value = String(n);
274
- applyTerminalFont(undefined, n);
275
- persistTermFont(undefined, n);
276
- });
277
- }
237
+ // Terminal font picker lives in the title bar (visible only while a
238
+ // TUI session is active). See lib/public/modules/header-tui-font.js.
239
+ // We still seed terminal-prefs from /api/profile below so the
240
+ // controls populate correctly when they appear.
278
241
 
279
242
  // Layout switcher (Bubble / Channel)
280
243
  var layoutSwitcher = document.getElementById('us-layout-switcher');
@@ -489,32 +452,12 @@ function populateAccount() {
489
452
  // Hide account section in single-user mode (no username)
490
453
  var accountNav = settingsEl.querySelector('[data-section="us-account"]');
491
454
  if (accountNav) accountNav.style.display = data.username ? '' : 'none';
492
- // Terminal font: seed in-memory prefs from server, populate inputs.
455
+ // Terminal font: seed in-memory prefs from server. Header-mounted
456
+ // controls (header-tui-font.js) listen to applyTerminalFont and
457
+ // sync their UI - we don't touch any DOM here.
493
458
  if (data.terminalFont && typeof data.terminalFont === "object") {
494
459
  applyTerminalFont(data.terminalFont.family, data.terminalFont.size);
495
460
  }
496
- var tfSelectEl = document.getElementById('us-term-font-family');
497
- var tfCustomEl = document.getElementById('us-term-font-family-custom');
498
- var tfSizeEl = document.getElementById('us-term-font-size');
499
- if (tfSelectEl && tfCustomEl) {
500
- var fam = getTerminalFontFamily();
501
- var matched = false;
502
- for (var oi = 0; oi < tfSelectEl.options.length; oi++) {
503
- if (tfSelectEl.options[oi].value === fam) {
504
- tfSelectEl.value = fam;
505
- matched = true;
506
- break;
507
- }
508
- }
509
- if (!matched) {
510
- tfSelectEl.value = '__custom__';
511
- tfCustomEl.value = fam;
512
- tfCustomEl.classList.remove('hidden');
513
- } else {
514
- tfCustomEl.classList.add('hidden');
515
- }
516
- }
517
- if (tfSizeEl) tfSizeEl.value = String(getTerminalFontSize());
518
461
  // Auto-continue toggle
519
462
  var acToggle = document.getElementById('us-auto-continue');
520
463
  if (acToggle) acToggle.checked = !!data.autoContinueOnRateLimit;
@@ -586,7 +586,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
586
586
  done: false,
587
587
  aborted: false,
588
588
  loopStarted: false,
589
- model: queryOpts.model || "gpt-5.4",
589
+ model: queryOpts.model || "gpt-5.5",
590
590
  // Track incremental text deltas
591
591
  textBlocks: {}, // itemId -> true (text_start sent)
592
592
  textLengths: {}, // itemId -> last sent length
@@ -831,7 +831,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
831
831
 
832
832
  // Start or resume thread
833
833
  var threadParams = {
834
- model: queryOpts.model || "gpt-5.4",
834
+ model: queryOpts.model || "gpt-5.5",
835
835
  sandbox: queryOpts.sandboxMode || "workspace-write",
836
836
  approvalPolicy: queryOpts.approvalPolicy || "on-failure",
837
837
  cwd: queryOpts.cwd,
@@ -1085,7 +1085,7 @@ function createCodexAdapter(opts) {
1085
1085
  function buildReadyResponse(skillNames) {
1086
1086
  return {
1087
1087
  models: _cachedModels,
1088
- defaultModel: "gpt-5.4",
1088
+ defaultModel: "gpt-5.5",
1089
1089
  skills: skillNames || [],
1090
1090
  slashCommands: skillNames || [],
1091
1091
  fastModeState: null,
@@ -1297,9 +1297,10 @@ function createCodexAdapter(opts) {
1297
1297
  throw createShutdownError();
1298
1298
  }
1299
1299
 
1300
- console.log("[codex] App-server initialized, models: gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, gpt-5.3-codex-spark, gpt-5.2");
1300
+ console.log("[codex] App-server initialized, models: gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, gpt-5.3-codex-spark, gpt-5.2");
1301
1301
 
1302
1302
  _cachedModels = [
1303
+ "gpt-5.5",
1303
1304
  "gpt-5.4",
1304
1305
  "gpt-5.4-mini",
1305
1306
  "gpt-5.3-codex",
@@ -1387,7 +1388,7 @@ function createCodexAdapter(opts) {
1387
1388
  throw new Error("[yoke/codex] Adapter not initialized. Call init() first.");
1388
1389
  }
1389
1390
 
1390
- var model = queryOpts.model || "gpt-5.4";
1391
+ var model = queryOpts.model || "gpt-5.5";
1391
1392
  var ac = queryOpts.abortController || new AbortController();
1392
1393
  var activeEntry = {
1393
1394
  abort: function() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.39.0-beta.5",
3
+ "version": "2.40.0-beta.1",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",