clay-server 4.1.0-beta.14 → 4.1.0-beta.16
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/project-connection.js +5 -1
- package/lib/project-sessions.js +2 -0
- package/lib/project-user-message.js +4 -0
- package/lib/project.js +2 -2
- package/lib/public/app.js +7 -1
- package/lib/public/css/mobile-nav.css +7 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/modules/app-connection.js +9 -1
- package/lib/public/modules/app-messages.js +19 -9
- package/lib/public/modules/app-panels.js +68 -22
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/context-view-preference.js +143 -0
- package/lib/public/modules/default-vendor.js +116 -0
- package/lib/public/modules/pane-bridge.js +8 -8
- package/lib/public/modules/project-activation.js +49 -0
- package/lib/public/modules/sidebar-mobile.js +49 -2
- package/lib/public/modules/sidebar-sessions.js +65 -5
- package/lib/public/modules/split-session-boundary.js +28 -0
- package/lib/public/modules/split-view.js +40 -7
- package/lib/sdk-bridge.js +11 -44
- package/lib/sdk-message-processor.js +9 -2
- package/lib/server-context-view.js +53 -0
- package/lib/server-default-vendor.js +60 -0
- package/lib/server.js +8 -0
- package/lib/session-title-generator.js +89 -0
- package/lib/session-title-policy.js +8 -2
- package/lib/sessions.js +14 -0
- package/lib/user-presence.js +6 -0
- package/lib/users-context-view-preferences.js +65 -0
- package/lib/users-default-vendor-preferences.js +80 -0
- package/lib/users.js +12 -0
- package/lib/ws-schema.js +6 -0
- package/lib/yoke/acp-agent-profiles.js +15 -2
- package/lib/yoke/adapters/codex.js +56 -13
- package/package.json +1 -1
|
@@ -346,7 +346,11 @@ function attachConnection(ctx) {
|
|
|
346
346
|
var dcPresKey = ws._clayUser ? ws._clayUser.id : "_default";
|
|
347
347
|
var dcExisting = userPresence.getPresence(slug, dcPresKey);
|
|
348
348
|
var dcSession = sm.sessions.get(ws._clayActiveSession);
|
|
349
|
-
|
|
349
|
+
var dcSessionId = userPresence.sessionIdForPersistence(dcSession);
|
|
350
|
+
var dcExistingSession = dcExisting && userPresence.findSession(sm.sessions, dcExisting.sessionId);
|
|
351
|
+
if (userPresence.shouldPersistDisconnectPresence(dcExisting, dcSessionId, dcExistingSession, dcSession)) {
|
|
352
|
+
userPresence.setPresence(slug, dcPresKey, dcSessionId, dcExisting ? dcExisting.mateDm : null);
|
|
353
|
+
}
|
|
350
354
|
}
|
|
351
355
|
tm.detachAll(ws);
|
|
352
356
|
clients.delete(ws);
|
package/lib/project-sessions.js
CHANGED
|
@@ -984,6 +984,8 @@ function attachSessions(ctx) {
|
|
|
984
984
|
if (msg.id && sm.sessions.has(msg.id) && msg.title) {
|
|
985
985
|
var s = sm.sessions.get(msg.id);
|
|
986
986
|
s.title = String(msg.title).substring(0, 100);
|
|
987
|
+
s.titleProvisional = false;
|
|
988
|
+
s.titleAutoGenerated = false;
|
|
987
989
|
s.titleManuallySet = true;
|
|
988
990
|
sm.saveSessionFile(s);
|
|
989
991
|
sm.notifySessionRenamed(s.localId);
|
|
@@ -479,6 +479,10 @@ function attachUserMessage(ctx) {
|
|
|
479
479
|
function recordSessionTitle() {
|
|
480
480
|
if (!session.title) {
|
|
481
481
|
session.title = (msg.text || "Image").substring(0, 50);
|
|
482
|
+
// This title is an optimistic first-message label, not a semantic or
|
|
483
|
+
// explicit title. Persist its provenance so startQuery can preserve
|
|
484
|
+
// eligibility for model generation after this function returns.
|
|
485
|
+
session.titleProvisional = true;
|
|
482
486
|
sm.saveSessionFile(session);
|
|
483
487
|
sm.broadcastSessionList();
|
|
484
488
|
// Sync auto-title to SDK
|
package/lib/project.js
CHANGED
|
@@ -1598,8 +1598,8 @@ function createProjectContext(opts) {
|
|
|
1598
1598
|
}
|
|
1599
1599
|
|
|
1600
1600
|
// --- DM messages (delegated to server-level handler) ---
|
|
1601
|
-
if (msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
|
|
1602
|
-
if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg);
|
|
1601
|
+
if (msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "default_vendor_get" || msg.type === "default_vendor_set" || msg.type === "context_view_get" || msg.type === "context_view_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
|
|
1602
|
+
if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
|
|
1603
1603
|
return;
|
|
1604
1604
|
}
|
|
1605
1605
|
if (msg.type === "issue_reference_resolve" || msg.type === "home_clay_ask" || msg.type === "home_clay_session_resolve" || msg.type === "home_clay_log_resolve") {
|
package/lib/public/app.js
CHANGED
|
@@ -71,6 +71,7 @@ import { rememberHomePrimarySurface } from './modules/home-surface.js';
|
|
|
71
71
|
import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateRateLimitUsage as _rlUpdateRateLimitUsage, handleFastModeState as _rlHandleFastModeState, resetRateLimitState } from './modules/app-rate-limit.js';
|
|
72
72
|
import { initCursors, handleRemoteCursorMove as _curHandleRemoteCursorMove, handleRemoteCursorLeave as _curHandleRemoteCursorLeave, handleRemoteSelection as _curHandleRemoteSelection, clearRemoteCursors as _curClearRemoteCursors, initCursorToggle } from './modules/app-cursors.js';
|
|
73
73
|
import { initDefaultAi } from './modules/default-ai.js';
|
|
74
|
+
import { requestDefaultVendor } from './modules/default-vendor.js';
|
|
74
75
|
import { initFavicon, updateFavicon as _favUpdateFavicon, setSendBtnMode as _favSetSendBtnMode, blinkIO as _favBlinkIO, blinkSessionDot as _favBlinkSessionDot, updateCrossProjectBlink as _favUpdateCrossProjectBlink, startUrgentBlink as _favStartUrgentBlink, stopUrgentBlink as _favStopUrgentBlink, setActivity as _favSetActivity } from './modules/app-favicon.js';
|
|
75
76
|
import { initHeader, closeSessionInfoPopover as _hdrCloseSessionInfoPopover, updateHistorySentinel as _hdrUpdateHistorySentinel, requestMoreHistory as _hdrRequestMoreHistory, prependOlderHistory as _hdrPrependOlderHistory } from './modules/app-header.js';
|
|
76
77
|
import { initSessionActions } from './modules/session-actions.js';
|
|
@@ -286,6 +287,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
286
287
|
currentSlug: currentSlug,
|
|
287
288
|
activeProjectSlug: null,
|
|
288
289
|
sessionActivatedProjectSlug: null,
|
|
290
|
+
sessionListProjectSlug: null,
|
|
291
|
+
splitGroupsProjectSlug: null,
|
|
289
292
|
socketPath: null,
|
|
290
293
|
pendingHomeProjectSlug: null,
|
|
291
294
|
currentProjectOwnerId: null,
|
|
@@ -427,6 +430,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
427
430
|
defaultAiState: { loading: false, saving: false, refreshRequestId: null, saveRequestId: null, catalogRequestIds: {}, catalogs: {}, preference: null, selection: null, installedVendors: [], accountAvailable: true, accountId: null, serverEpoch: null, canonicalRevision: 0, error: "" },
|
|
428
431
|
defaultAiDraft: { vendor: "", model: "", effort: "" },
|
|
429
432
|
defaultAiDraftDirty: false,
|
|
433
|
+
defaultVendorState: { loading: false, saving: false, getRequestId: null, saveRequestId: null, preference: null, preferencePresent: false, installedVendors: [], accountId: null, projectSlug: null, serverEpoch: null, canonicalRevision: 0, error: "" },
|
|
430
434
|
|
|
431
435
|
// dm
|
|
432
436
|
dmTargetUser: null,
|
|
@@ -443,6 +447,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
443
447
|
pendingShellCommandId: null,
|
|
444
448
|
mateProjectSlug: null,
|
|
445
449
|
myUserId: null,
|
|
450
|
+
contextViewPreferenceState: { mode: "off", canonicalMode: "off", preferencePresent: false, loading: false, saving: false, requestId: null, saveRequestId: null, pendingSaves: [], accountId: null, serverEpoch: null, rejectedEpoch: null, canonicalRevision: 0, error: "" },
|
|
451
|
+
contextViewOverride: null,
|
|
446
452
|
isMultiUserMode: false,
|
|
447
453
|
dmUnread: {},
|
|
448
454
|
dmRemovedUsers: {},
|
|
@@ -1024,7 +1030,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1024
1030
|
// the project Mate DM preference therefore hides only Mate shortcuts.
|
|
1025
1031
|
if (document.body) document.body.classList.add('is-multi-user');
|
|
1026
1032
|
}
|
|
1027
|
-
if (d.user && d.user.id) { store.set({ myUserId: d.user.id }); }
|
|
1033
|
+
if (d.user && d.user.id) { store.set({ myUserId: d.user.id }); requestDefaultVendor(); }
|
|
1028
1034
|
if (d.permissions) store.set({ permissions: d.permissions });
|
|
1029
1035
|
if (d.mustChangePin) showForceChangePinOverlay();
|
|
1030
1036
|
// Single-user mode: clear user strip skeletons immediately (no presence message will arrive)
|
|
@@ -423,6 +423,13 @@
|
|
|
423
423
|
.mobile-vendor-list .mobile-session-new-vendor:active {
|
|
424
424
|
background: rgba(var(--overlay-rgb), 0.1);
|
|
425
425
|
}
|
|
426
|
+
.mobile-vendor-row { display: flex; align-items: stretch; gap: 4px; }
|
|
427
|
+
.mobile-vendor-row .mobile-session-new-vendor { flex: 1 1 auto; min-width: 0; }
|
|
428
|
+
.mobile-vendor-set-default { border: 1px solid var(--border-subtle); border-radius: 8px; background: transparent; color: var(--text-dimmer); padding: 0 8px; font-size: 11px; white-space: nowrap; }
|
|
429
|
+
.mobile-vendor-set-default:active { background: rgba(var(--overlay-rgb), 0.1); }
|
|
430
|
+
.mobile-vendor-set-default:disabled { opacity: 0.55; }
|
|
431
|
+
.mobile-session-new-preference-status { padding: 4px 8px 8px; color: var(--error); font-size: 11px; }
|
|
432
|
+
.mobile-session-new-fallback-note { color: var(--text-muted); }
|
|
426
433
|
.mobile-vendor-note {
|
|
427
434
|
margin-left: auto;
|
|
428
435
|
font-size: 12px;
|
|
@@ -1464,6 +1464,13 @@
|
|
|
1464
1464
|
|
|
1465
1465
|
/* --- New-session vendor picker --- */
|
|
1466
1466
|
.session-new-menu { min-width: 200px; }
|
|
1467
|
+
.session-new-vendor-row { display: flex; align-items: stretch; }
|
|
1468
|
+
.session-new-vendor-row .session-new-vendor { flex: 1 1 auto; min-width: 0; }
|
|
1469
|
+
.session-new-set-default { border: 0; background: transparent; color: var(--text-dimmer); padding: 0 8px; font-size: 10px; cursor: pointer; }
|
|
1470
|
+
.session-new-set-default:hover { color: var(--text); background: rgba(var(--overlay-rgb), 0.05); }
|
|
1471
|
+
.session-new-set-default:disabled { opacity: 0.55; cursor: wait; }
|
|
1472
|
+
.session-new-preference-status { padding: 6px 10px; color: var(--error); font-size: 11px; }
|
|
1473
|
+
.session-new-fallback-note { color: var(--text-muted); border-bottom: 1px solid var(--border); }
|
|
1467
1474
|
|
|
1468
1475
|
.session-ctx-sep {
|
|
1469
1476
|
height: 1px;
|
|
@@ -17,6 +17,9 @@ import { resumeHomeChat } from './home-mate-chat.js';
|
|
|
17
17
|
import { requestHomeSurfacePreference } from './home-surface.js';
|
|
18
18
|
import { isHomeDebatesSurface } from './home-sub-surface.js';
|
|
19
19
|
import { beginDefaultAiConnection, requestDefaultAi } from './default-ai.js';
|
|
20
|
+
import { beginContextViewConnection, requestContextView } from './context-view-preference.js';
|
|
21
|
+
import { beginDefaultVendorConnection, requestDefaultVendor } from './default-vendor.js';
|
|
22
|
+
import { clearProjectSplitState } from './split-session-boundary.js';
|
|
20
23
|
|
|
21
24
|
var reconnectTimer = null;
|
|
22
25
|
var reconnectDelay = 1000;
|
|
@@ -233,7 +236,8 @@ export function connect() {
|
|
|
233
236
|
|
|
234
237
|
var protocol = location.protocol === "https:" ? "wss:" : "ws:";
|
|
235
238
|
var socketPath = store.get('wsPath');
|
|
236
|
-
store.
|
|
239
|
+
if (store.get('socketPath') && store.get('socketPath') !== socketPath) clearProjectSplitState();
|
|
240
|
+
store.set({ socketPath: socketPath, activeProjectSlug: null, sessionActivatedProjectSlug: null, sessionListProjectSlug: null, splitGroupsProjectSlug: null });
|
|
237
241
|
var newWs = new WebSocket(protocol + "//" + location.host + socketPath);
|
|
238
242
|
setWs(newWs);
|
|
239
243
|
|
|
@@ -277,6 +281,10 @@ export function connect() {
|
|
|
277
281
|
onConnected();
|
|
278
282
|
beginDefaultAiConnection();
|
|
279
283
|
requestDefaultAi();
|
|
284
|
+
beginDefaultVendorConnection();
|
|
285
|
+
requestDefaultVendor();
|
|
286
|
+
beginContextViewConnection();
|
|
287
|
+
requestContextView();
|
|
280
288
|
};
|
|
281
289
|
|
|
282
290
|
newWs.onclose = function (e) {
|
|
@@ -19,6 +19,8 @@ import { refreshMobileChatSheet } from './sidebar-mobile.js';
|
|
|
19
19
|
import { renderMateSessionList, handleMateSearchResults, updateMateSidebarProfile } from './mate-sidebar.js';
|
|
20
20
|
import { openHomeChat, handleHomeMateHistory, handleHomeMateDelta, handleHomeMateSegment, handleHomeMateDone, handleHomeMateError, handleHomeMateSessionsState } from './home-mate-chat.js';
|
|
21
21
|
import { handleHomeSurfaceState } from './home-surface.js';
|
|
22
|
+
import { handleDefaultVendorMessage } from './default-vendor.js';
|
|
23
|
+
import { handleContextViewMessage } from './context-view-preference.js';
|
|
22
24
|
import { handleHomeMateMemoryState, handleHomeMateKnowledgeState } from './home-mate-settings.js';
|
|
23
25
|
import { renderKnowledgeList, handleKnowledgeContent } from './mate-knowledge.js';
|
|
24
26
|
import { renderMemoryList } from './mate-memory.js';
|
|
@@ -63,9 +65,10 @@ import { setStatus } from './app-connection.js';
|
|
|
63
65
|
import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWhatsNewKnownEntries } from './whats-new.js';
|
|
64
66
|
import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
|
|
65
67
|
import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
|
|
68
|
+
import { markProjectSessionListHydrated, applyProjectSplitGroups, clearProjectSplitState } from './split-session-boundary.js';
|
|
66
69
|
import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
|
|
67
70
|
import { getModelInfoUpdate, modelEntryValue, modelEntryMatches, handleModelSelectionResult, requestVendorModels } from './model-picker.js';
|
|
68
|
-
import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
|
|
71
|
+
import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel, markContextUnavailable } from './app-panels.js';
|
|
69
72
|
import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress, finishProjectSessionActivation } from './app-projects.js';
|
|
70
73
|
import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
|
|
71
74
|
import { hideHomeHub, showHomeHub } from './app-home-hub.js';
|
|
@@ -97,6 +100,8 @@ export function processMessage(msg) {
|
|
|
97
100
|
if (handleAutonomousRunMessage(msg)) return;
|
|
98
101
|
if (handleLoopInterviewMessage(msg)) return;
|
|
99
102
|
if (handleScheduledTaskMessage(msg)) return;
|
|
103
|
+
if (handleDefaultVendorMessage(msg)) return;
|
|
104
|
+
if (handleContextViewMessage(msg)) return;
|
|
100
105
|
if (msg && msg.type === "schedule_message_result") {
|
|
101
106
|
handleScheduleMessageResult(msg);
|
|
102
107
|
return;
|
|
@@ -226,9 +231,8 @@ export function processMessage(msg) {
|
|
|
226
231
|
applyDeadSessionTodoCompaction();
|
|
227
232
|
}
|
|
228
233
|
// Restore cached rich context usage BEFORE updateContextPanel runs
|
|
229
|
-
if (msg.contextUsage) {
|
|
230
|
-
|
|
231
|
-
}
|
|
234
|
+
if (msg.contextUsage && !msg.contextUsage.unavailable) store.set({ richContextUsage: msg.contextUsage });
|
|
235
|
+
else markContextUnavailable();
|
|
232
236
|
// Restore accurate context data from the last result in full history
|
|
233
237
|
if (msg.lastUsage || msg.lastModelUsage) {
|
|
234
238
|
accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage, msg.lastStreamInputTokens);
|
|
@@ -330,10 +334,13 @@ export function processMessage(msg) {
|
|
|
330
334
|
// start receiving the new project's session_switched. The TUI
|
|
331
335
|
// host lives on document.body (it's position: fixed), so it
|
|
332
336
|
// survives project navigation unless we detach explicitly here.
|
|
337
|
+
if (msg.slug && store.get('activeProjectSlug') && store.get('activeProjectSlug') !== msg.slug) {
|
|
338
|
+
clearProjectSplitState();
|
|
339
|
+
}
|
|
333
340
|
detachTuiView();
|
|
334
341
|
store.set({ projectName: msg.project || msg.cwd, vendorInfo: msg.vendors || {} });
|
|
335
342
|
if (msg.cwd) store.set({ cwd: msg.cwd });
|
|
336
|
-
if (msg.slug) store.set({ currentSlug: msg.slug, activeProjectSlug: msg.slug });
|
|
343
|
+
if (msg.slug) store.set({ currentSlug: msg.slug, activeProjectSlug: msg.slug, sessionListProjectSlug: null, splitGroupsProjectSlug: null });
|
|
337
344
|
try { var _is = store.snap(); localStorage.setItem("clay-project-name-" + (_is.currentSlug || "default"), _is.projectName); } catch (e) {}
|
|
338
345
|
// In mate DM, keep title as mate name and re-apply mate color
|
|
339
346
|
if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) {
|
|
@@ -690,6 +697,7 @@ export function processMessage(msg) {
|
|
|
690
697
|
break;
|
|
691
698
|
|
|
692
699
|
case "session_list":
|
|
700
|
+
markProjectSessionListHydrated();
|
|
693
701
|
renderMateSessionList(msg.sessions || []);
|
|
694
702
|
renderSessionList(msg.sessions || []);
|
|
695
703
|
syncPaneTitles();
|
|
@@ -701,13 +709,14 @@ export function processMessage(msg) {
|
|
|
701
709
|
}
|
|
702
710
|
}
|
|
703
711
|
handlePaletteSessionSwitch();
|
|
712
|
+
maybeRestoreSplitGroup();
|
|
704
713
|
break;
|
|
705
714
|
|
|
706
715
|
case "split_groups":
|
|
707
|
-
|
|
716
|
+
applyProjectSplitGroups(msg.groups || []);
|
|
708
717
|
renderSessionList(null);
|
|
709
|
-
maybeRestoreSplitGroup();
|
|
710
718
|
syncPaneTitles();
|
|
719
|
+
maybeRestoreSplitGroup();
|
|
711
720
|
break;
|
|
712
721
|
|
|
713
722
|
case "split_group_result":
|
|
@@ -888,8 +897,8 @@ export function processMessage(msg) {
|
|
|
888
897
|
}
|
|
889
898
|
// Reload survival: a restored active session that is a split-group
|
|
890
899
|
// member reopens its group (no-op when a split is already open).
|
|
891
|
-
maybeRestoreSplitGroup();
|
|
892
900
|
if (finishProjectSessionActivation()) hideHomeHub();
|
|
901
|
+
maybeRestoreSplitGroup();
|
|
893
902
|
break;
|
|
894
903
|
|
|
895
904
|
case "session_full_access_changed":
|
|
@@ -1273,7 +1282,8 @@ export function processMessage(msg) {
|
|
|
1273
1282
|
case "context_usage":
|
|
1274
1283
|
if (msg.sessionId != null && msg.sessionId !== store.get("activeSessionId")) break;
|
|
1275
1284
|
if (msg.data && !store.get('replayingHistory')) {
|
|
1276
|
-
|
|
1285
|
+
if (msg.data.unavailable) markContextUnavailable();
|
|
1286
|
+
else store.set({ richContextUsage: msg.data });
|
|
1277
1287
|
// UI sync handled by store subscriber in app-panels.js
|
|
1278
1288
|
}
|
|
1279
1289
|
break;
|
|
@@ -5,15 +5,17 @@ import { refreshIcons } from "./icons.js";
|
|
|
5
5
|
import { escapeHtml, showToast } from "./utils.js";
|
|
6
6
|
import { store } from './store.js';
|
|
7
7
|
import { getWs } from './ws-ref.js';
|
|
8
|
+
import { getContextView as getStoredContextView, getEffectiveContextView, setContextView as setStoredContextView } from './context-view-preference.js';
|
|
8
9
|
import { reportPaneContext } from './pane-bridge.js';
|
|
9
10
|
import { setupModelPicker, renderModelPicker, requestVendorModels, prepareModelPickerOpen, modelDisplayName } from './model-picker.js';
|
|
10
11
|
|
|
11
12
|
// --- Module-owned state (not in store) ---
|
|
12
13
|
var sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
|
|
13
|
-
var contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
|
|
14
|
+
var contextData = { contextWindow: 0, currentInput: null, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
|
|
14
15
|
var ctxPopoverEl = null;
|
|
15
16
|
var ctxHoverTimer = null;
|
|
16
17
|
var statusRefreshTimer = null;
|
|
18
|
+
var lastAppliedContextView = null;
|
|
17
19
|
|
|
18
20
|
// --- DOM refs ---
|
|
19
21
|
var configChipWrap = null;
|
|
@@ -374,6 +376,15 @@ export function initPanels() {
|
|
|
374
376
|
|
|
375
377
|
// --- Reactive UI sync ---
|
|
376
378
|
store.subscribe(function (state, prev) {
|
|
379
|
+
var effectiveContextView = getEffectiveContextView();
|
|
380
|
+
if (effectiveContextView !== lastAppliedContextView) {
|
|
381
|
+
lastAppliedContextView = effectiveContextView;
|
|
382
|
+
applyContextView(effectiveContextView);
|
|
383
|
+
}
|
|
384
|
+
if (state.contextViewPreferenceState && state.contextViewPreferenceState.error &&
|
|
385
|
+
state.contextViewPreferenceState.error !== (prev.contextViewPreferenceState && prev.contextViewPreferenceState.error)) {
|
|
386
|
+
showToast(state.contextViewPreferenceState.error, "error");
|
|
387
|
+
}
|
|
377
388
|
// richContextUsage changed -> update popover + panel
|
|
378
389
|
if (state.richContextUsage !== prev.richContextUsage) {
|
|
379
390
|
if (state.richContextUsage) {
|
|
@@ -540,7 +551,6 @@ export function initPanels() {
|
|
|
540
551
|
if (contextPanelClose) {
|
|
541
552
|
contextPanelClose.addEventListener("click", function () {
|
|
542
553
|
setContextView("off");
|
|
543
|
-
applyContextView("off");
|
|
544
554
|
});
|
|
545
555
|
}
|
|
546
556
|
|
|
@@ -553,7 +563,8 @@ export function initPanels() {
|
|
|
553
563
|
}
|
|
554
564
|
|
|
555
565
|
// Restore context view on load
|
|
556
|
-
|
|
566
|
+
lastAppliedContextView = getEffectiveContextView();
|
|
567
|
+
applyContextView(lastAppliedContextView);
|
|
557
568
|
}
|
|
558
569
|
|
|
559
570
|
// --- Config chip ---
|
|
@@ -718,9 +729,18 @@ export function contextPctClass(pct) {
|
|
|
718
729
|
|
|
719
730
|
export function updateContextPanel() {
|
|
720
731
|
if (!contextUsedEl) return;
|
|
721
|
-
// Context
|
|
722
|
-
|
|
723
|
-
|
|
732
|
+
// Context occupancy is separate from generic per-turn accounting. Codex
|
|
733
|
+
// only supplies currentInput from a verified last-turn snapshot; rich
|
|
734
|
+
// context usage is more authoritative when available.
|
|
735
|
+
var rich = store.get('richContextUsage');
|
|
736
|
+
var hasRich = rich && (
|
|
737
|
+
(typeof rich.totalTokens === "number" && typeof rich.maxTokens === "number" && rich.maxTokens > 0) ||
|
|
738
|
+
(typeof rich.input_tokens === "number" && typeof rich.contextWindow === "number" && rich.contextWindow > 0)
|
|
739
|
+
);
|
|
740
|
+
var hasCurrent = typeof contextData.currentInput === "number";
|
|
741
|
+
var hasContext = !!(hasRich || hasCurrent);
|
|
742
|
+
var used = hasRich ? (typeof rich.totalTokens === "number" ? rich.totalTokens : rich.input_tokens) : (hasCurrent ? contextData.currentInput : 0);
|
|
743
|
+
var win = hasRich ? (typeof rich.maxTokens === "number" ? rich.maxTokens : rich.contextWindow) : (hasCurrent ? contextData.contextWindow : 0);
|
|
724
744
|
var pct = win > 0 ? Math.min(100, (used / win) * 100) : 0;
|
|
725
745
|
var cls = contextPctClass(pct);
|
|
726
746
|
reportPaneContext({ pct: pct, used: used, win: win, cls: cls, model: contextData.model, cost: contextData.cost });
|
|
@@ -734,7 +754,7 @@ export function updateContextPanel() {
|
|
|
734
754
|
contextMiniFill.className = "context-mini-fill" + cls;
|
|
735
755
|
}
|
|
736
756
|
if (contextMiniLabel) {
|
|
737
|
-
contextMiniLabel.textContent = (win > 0 ? formatTokens(used) + "/" + formatTokens(win) : "
|
|
757
|
+
contextMiniLabel.textContent = (win > 0 ? formatTokens(used) + "/" + formatTokens(win) : "-");
|
|
738
758
|
}
|
|
739
759
|
// Header bar. A split group has no meaningful group-level context usage:
|
|
740
760
|
// the two members have separate windows, and the parent shell's own numbers
|
|
@@ -742,9 +762,9 @@ export function updateContextPanel() {
|
|
|
742
762
|
// chip instead, so hide the title-bar gauge while a split is open.
|
|
743
763
|
var splitOpen = !!store.get('splitPanes');
|
|
744
764
|
var headerCtxEl = store.get('headerContextEl');
|
|
745
|
-
if (headerCtxEl) headerCtxEl.classList.toggle("hidden", splitOpen);
|
|
746
|
-
if (contextMini) contextMini.classList.toggle("split-hidden", splitOpen);
|
|
747
|
-
if (
|
|
765
|
+
if (headerCtxEl) headerCtxEl.classList.toggle("hidden", splitOpen || !hasContext);
|
|
766
|
+
if (contextMini) contextMini.classList.toggle("split-hidden", splitOpen || !hasContext);
|
|
767
|
+
if (hasContext && !splitOpen) {
|
|
748
768
|
var statusArea = document.querySelector(".title-bar-content .status");
|
|
749
769
|
var hCtxEl = store.get('headerContextEl');
|
|
750
770
|
if (statusArea && !hCtxEl) {
|
|
@@ -808,7 +828,7 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
|
|
|
808
828
|
// when available -- result.usage.input_tokens sums all API calls in a turn,
|
|
809
829
|
// inflating context usage when tools are involved.
|
|
810
830
|
// Falls back to the summed value for setups that don't emit message_start.
|
|
811
|
-
if (lastStreamInputTokens) {
|
|
831
|
+
if (typeof lastStreamInputTokens === "number") {
|
|
812
832
|
contextData.input = lastStreamInputTokens;
|
|
813
833
|
} else {
|
|
814
834
|
contextData.input = (usage.input_tokens || usage.inputTokens || 0)
|
|
@@ -817,6 +837,11 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
|
|
|
817
837
|
contextData.output = usage.output_tokens || usage.outputTokens || 0;
|
|
818
838
|
contextData.cacheRead = usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0;
|
|
819
839
|
contextData.cacheWrite = usage.cache_creation_input_tokens || usage.cacheCreationInputTokens || 0;
|
|
840
|
+
if (store.get('currentVendor') === "codex") {
|
|
841
|
+
contextData.currentInput = typeof lastStreamInputTokens === "number" ? lastStreamInputTokens : null;
|
|
842
|
+
} else {
|
|
843
|
+
contextData.currentInput = typeof lastStreamInputTokens === "number" ? lastStreamInputTokens : contextData.input;
|
|
844
|
+
}
|
|
820
845
|
}
|
|
821
846
|
contextData.turns++;
|
|
822
847
|
if (modelUsage) {
|
|
@@ -830,6 +855,21 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
|
|
|
830
855
|
contextData.model = displayModel;
|
|
831
856
|
contextData.contextWindow = resolveContextWindow(displayModel, mu.contextWindow);
|
|
832
857
|
if (mu.maxOutputTokens) contextData.maxOutputTokens = mu.maxOutputTokens;
|
|
858
|
+
if (store.get('currentVendor') === "codex") {
|
|
859
|
+
var snapshot = mu.contextSnapshot;
|
|
860
|
+
if (snapshot) store.set({ richContextUsage: null });
|
|
861
|
+
if (snapshot && snapshot.valid === true
|
|
862
|
+
&& typeof snapshot.inputTokens === "number"
|
|
863
|
+
&& typeof snapshot.contextWindow === "number"
|
|
864
|
+
&& snapshot.contextWindow > 0) {
|
|
865
|
+
contextData.currentInput = snapshot.inputTokens;
|
|
866
|
+
contextData.contextWindow = snapshot.contextWindow;
|
|
867
|
+
} else {
|
|
868
|
+
// An explicit invalid marker is authoritative: do not retain an
|
|
869
|
+
// occupancy value from the previous live turn or history replay.
|
|
870
|
+
contextData.currentInput = null;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
833
873
|
|
|
834
874
|
// thinkingTokens and costBasis are per-model and optional: absent on
|
|
835
875
|
// older CLI builds and, for a resumed session, on the turns that ran
|
|
@@ -860,11 +900,15 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
|
|
|
860
900
|
|
|
861
901
|
// contextView: "off" | "mini" | "panel"
|
|
862
902
|
export function getContextView() {
|
|
863
|
-
|
|
903
|
+
return getStoredContextView();
|
|
864
904
|
}
|
|
865
905
|
|
|
866
906
|
export function setContextView(v) {
|
|
867
|
-
|
|
907
|
+
if (store.get("paneMode") === true) {
|
|
908
|
+
store.set({ contextViewOverride: v });
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
return setStoredContextView(v);
|
|
868
912
|
}
|
|
869
913
|
|
|
870
914
|
export function applyContextView(view) {
|
|
@@ -874,36 +918,38 @@ export function applyContextView(view) {
|
|
|
874
918
|
}
|
|
875
919
|
|
|
876
920
|
export function resetContextData() {
|
|
877
|
-
contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
|
|
921
|
+
contextData = { contextWindow: 0, currentInput: null, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
|
|
878
922
|
store.set({ richContextUsage: null });
|
|
879
923
|
// hideCtxPopover + updateContextPanel handled by store subscriber
|
|
880
924
|
}
|
|
881
925
|
|
|
926
|
+
export function markContextUnavailable() {
|
|
927
|
+
contextData.currentInput = null;
|
|
928
|
+
store.set({ richContextUsage: null });
|
|
929
|
+
updateContextPanel();
|
|
930
|
+
}
|
|
931
|
+
|
|
882
932
|
export function resetContext() {
|
|
883
933
|
resetContextData();
|
|
884
934
|
// Keep view state, just reset data
|
|
885
|
-
applyContextView(
|
|
935
|
+
applyContextView(getEffectiveContextView());
|
|
886
936
|
}
|
|
887
937
|
|
|
888
938
|
export function minimizeContext() {
|
|
889
939
|
setContextView("mini");
|
|
890
|
-
applyContextView("mini");
|
|
891
940
|
}
|
|
892
941
|
|
|
893
942
|
export function expandContext() {
|
|
894
943
|
setContextView("panel");
|
|
895
|
-
applyContextView("panel");
|
|
896
944
|
}
|
|
897
945
|
|
|
898
946
|
export function toggleContextPanel() {
|
|
899
947
|
if (!contextPanel) return;
|
|
900
|
-
var view =
|
|
948
|
+
var view = getEffectiveContextView();
|
|
901
949
|
if (view === "panel") {
|
|
902
950
|
setContextView("mini");
|
|
903
|
-
applyContextView("mini");
|
|
904
951
|
} else {
|
|
905
952
|
setContextView("panel");
|
|
906
|
-
applyContextView("panel");
|
|
907
953
|
}
|
|
908
954
|
}
|
|
909
955
|
|
|
@@ -952,8 +998,8 @@ export function renderCtxPopover() {
|
|
|
952
998
|
if (!ctxPopoverEl || !richContextUsage) return;
|
|
953
999
|
var d = richContextUsage;
|
|
954
1000
|
var cats = d.categories || [];
|
|
955
|
-
var total = d.totalTokens || 0;
|
|
956
|
-
var max = d.maxTokens || 0;
|
|
1001
|
+
var total = d.totalTokens != null ? d.totalTokens : (d.input_tokens || 0);
|
|
1002
|
+
var max = d.maxTokens != null ? d.maxTokens : (d.contextWindow || 0);
|
|
957
1003
|
var pct = d.percentage != null ? d.percentage : (max > 0 ? (total / max) * 100 : 0);
|
|
958
1004
|
|
|
959
1005
|
var html = "";
|
|
@@ -39,6 +39,7 @@ import { closeProjectSettings } from './project-settings.js';
|
|
|
39
39
|
import { chooseProjectAfterRemoval } from './project-removal-target.js';
|
|
40
40
|
import { isProjectActivated, isProjectActivationPending, isProjectContextConnected, projectWsPath } from './project-activation.js';
|
|
41
41
|
import { rememberHomePrimarySurface } from './home-surface.js';
|
|
42
|
+
import { clearProjectSplitState } from './split-session-boundary.js';
|
|
42
43
|
|
|
43
44
|
// --- Module-owned state ---
|
|
44
45
|
var cachedProjects = [];
|
|
@@ -447,6 +448,7 @@ export function switchProject(slug) {
|
|
|
447
448
|
}
|
|
448
449
|
if (homeVisible) store.set({ pendingHomeProjectSlug: slug });
|
|
449
450
|
if (isProjectActivationPending(store.snap(), slug, ws)) return;
|
|
451
|
+
if (store.get('currentSlug') !== slug) clearProjectSplitState();
|
|
450
452
|
resetFileBrowser();
|
|
451
453
|
if (typeof closeScheduledTasks === "function") closeScheduledTasks();
|
|
452
454
|
closeNotesBrowser();
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
|
|
4
|
+
var MODES = ["off", "mini", "panel"];
|
|
5
|
+
var sequence = 0;
|
|
6
|
+
|
|
7
|
+
function validMode(mode) { return MODES.indexOf(mode) !== -1; }
|
|
8
|
+
function nextRequestId(prefix) { sequence += 1; return prefix + Date.now() + "-" + sequence; }
|
|
9
|
+
function accountId() { return store.get("myUserId") || "default"; }
|
|
10
|
+
function initialState() {
|
|
11
|
+
return { mode: "off", canonicalMode: "off", preferencePresent: false, loading: false, saving: false, requestId: null, saveRequestId: null, pendingSaves: [], accountId: null, serverEpoch: null, rejectedEpoch: null, canonicalRevision: 0, error: "" };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function send(message) {
|
|
15
|
+
var ws = getWs();
|
|
16
|
+
if (!ws || ws.readyState !== 1) return false;
|
|
17
|
+
ws.send(JSON.stringify(message));
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getContextView() {
|
|
22
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
23
|
+
return validMode(state.mode) ? state.mode : "off";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getEffectiveContextView() {
|
|
27
|
+
if (store.get("paneMode") !== true) return getContextView();
|
|
28
|
+
var override = store.get("contextViewOverride");
|
|
29
|
+
return override === null || override === undefined ? getContextView() : (validMode(override) ? override : getContextView());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function beginContextViewConnection() {
|
|
33
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
34
|
+
store.set({ contextViewPreferenceState: Object.assign({}, state, {
|
|
35
|
+
mode: validMode(state.canonicalMode) ? state.canonicalMode : "off",
|
|
36
|
+
loading: false,
|
|
37
|
+
saving: false,
|
|
38
|
+
requestId: null,
|
|
39
|
+
saveRequestId: null,
|
|
40
|
+
pendingSaves: [],
|
|
41
|
+
rejectedEpoch: state.serverEpoch || null,
|
|
42
|
+
serverEpoch: null,
|
|
43
|
+
canonicalRevision: 0,
|
|
44
|
+
error: "",
|
|
45
|
+
}) });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function requestContextView() {
|
|
49
|
+
var id = nextRequestId("context-view-");
|
|
50
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
51
|
+
store.set({ contextViewPreferenceState: Object.assign({}, state, { loading: true, requestId: id, error: "" }) });
|
|
52
|
+
if (!send({ type: "context_view_get", requestId: id })) {
|
|
53
|
+
store.set({ contextViewPreferenceState: Object.assign({}, store.get("contextViewPreferenceState") || initialState(), { loading: false, requestId: null, error: "Clay is offline. Reconnect and try again." }) });
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function setContextView(mode) {
|
|
60
|
+
if (!validMode(mode)) return false;
|
|
61
|
+
var id = nextRequestId("context-view-save-");
|
|
62
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
63
|
+
var pendingSaves = (state.pendingSaves || []).concat([{ requestId: id, mode: mode }]);
|
|
64
|
+
store.set({ contextViewPreferenceState: Object.assign({}, state, { mode: mode, saving: true, saveRequestId: id, pendingSaves: pendingSaves, error: "" }) });
|
|
65
|
+
if (!send({ type: "context_view_set", requestId: id, mode: mode })) {
|
|
66
|
+
var reverted = store.get("contextViewPreferenceState") || initialState();
|
|
67
|
+
pendingSaves = (reverted.pendingSaves || []).filter(function (item) { return item.requestId !== id; });
|
|
68
|
+
var pendingMode = pendingSaves.length ? pendingSaves[pendingSaves.length - 1].mode : (reverted.canonicalMode || "off");
|
|
69
|
+
store.set({ contextViewPreferenceState: Object.assign({}, reverted, { mode: pendingMode, saving: pendingSaves.length > 0, saveRequestId: pendingSaves.length ? pendingSaves[pendingSaves.length - 1].requestId : null, pendingSaves: pendingSaves, error: "Clay is offline. Reconnect and try again." }) });
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function handleContextViewMessage(msg) {
|
|
76
|
+
if (!msg || msg.type !== "context_view_state") return false;
|
|
77
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
78
|
+
var currentAccount = accountId();
|
|
79
|
+
var isGetReply = !!msg.requestId && msg.requestId === state.requestId;
|
|
80
|
+
var pendingSaves = state.pendingSaves || [];
|
|
81
|
+
var saveIndex = -1;
|
|
82
|
+
for (var i = 0; i < pendingSaves.length; i++) if (pendingSaves[i].requestId === msg.requestId) saveIndex = i;
|
|
83
|
+
var isSaveReply = !!msg.requestId && saveIndex !== -1;
|
|
84
|
+
var isOlderSaveRevision = isSaveReply && typeof msg.canonicalRevision === "number" && msg.canonicalRevision < (state.canonicalRevision || 0);
|
|
85
|
+
var isCorrelatedAccountFailure = (isGetReply || isSaveReply) && msg.accountId === null && msg.accountAvailable === false;
|
|
86
|
+
if ((!isCorrelatedAccountFailure && msg.accountId !== currentAccount) || (msg.accountId === null && !isCorrelatedAccountFailure)) return true;
|
|
87
|
+
if (msg.serverEpoch && state.serverEpoch && msg.serverEpoch !== state.serverEpoch && (!msg.requestId || (!isGetReply && !isSaveReply))) return true;
|
|
88
|
+
if (msg.serverEpoch && state.rejectedEpoch && msg.serverEpoch === state.rejectedEpoch && (!isGetReply && !isSaveReply)) return true;
|
|
89
|
+
if (msg.canonicalRevision !== undefined && msg.canonicalRevision < (state.canonicalRevision || 0) && msg.serverEpoch === state.serverEpoch && !isSaveReply) return true;
|
|
90
|
+
if (msg.requestId && !isGetReply && !isSaveReply) return true;
|
|
91
|
+
if (msg.requestId && !isSaveReply && msg.ready === false && state.saving) return true;
|
|
92
|
+
var next = Object.assign({}, state, {
|
|
93
|
+
accountId: msg.accountId || currentAccount,
|
|
94
|
+
serverEpoch: msg.serverEpoch || state.serverEpoch || null,
|
|
95
|
+
rejectedEpoch: isGetReply || isSaveReply ? null : state.rejectedEpoch || null,
|
|
96
|
+
canonicalRevision: isOlderSaveRevision ? (state.canonicalRevision || 0) : (typeof msg.canonicalRevision === "number" ? msg.canonicalRevision : state.canonicalRevision || 0),
|
|
97
|
+
loading: isGetReply ? false : state.loading,
|
|
98
|
+
saving: state.saving,
|
|
99
|
+
requestId: isGetReply ? null : state.requestId,
|
|
100
|
+
saveRequestId: state.saveRequestId,
|
|
101
|
+
error: msg.error || "",
|
|
102
|
+
});
|
|
103
|
+
if (Object.prototype.hasOwnProperty.call(msg, "mode") && validMode(msg.mode) && msg.ready !== false && !isCorrelatedAccountFailure) {
|
|
104
|
+
if (isSaveReply) {
|
|
105
|
+
if (!isOlderSaveRevision && msg.ready !== false) {
|
|
106
|
+
pendingSaves = pendingSaves.slice(saveIndex + 1);
|
|
107
|
+
} else {
|
|
108
|
+
pendingSaves = pendingSaves.filter(function (item) { return item.requestId !== msg.requestId; });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!isOlderSaveRevision) {
|
|
112
|
+
next.canonicalMode = msg.mode;
|
|
113
|
+
next.preferencePresent = msg.preferencePresent === true;
|
|
114
|
+
}
|
|
115
|
+
if (pendingSaves.length) {
|
|
116
|
+
next.mode = pendingSaves[pendingSaves.length - 1].mode;
|
|
117
|
+
next.saving = true;
|
|
118
|
+
next.saveRequestId = pendingSaves[pendingSaves.length - 1].requestId;
|
|
119
|
+
} else {
|
|
120
|
+
next.mode = next.canonicalMode;
|
|
121
|
+
next.saving = false;
|
|
122
|
+
next.saveRequestId = null;
|
|
123
|
+
}
|
|
124
|
+
next.pendingSaves = pendingSaves;
|
|
125
|
+
} else if (isSaveReply && (msg.ready === false || isCorrelatedAccountFailure)) {
|
|
126
|
+
pendingSaves = pendingSaves.filter(function (item) { return item.requestId !== msg.requestId; });
|
|
127
|
+
next.pendingSaves = pendingSaves;
|
|
128
|
+
next.mode = pendingSaves.length ? pendingSaves[pendingSaves.length - 1].mode : (state.canonicalMode || "off");
|
|
129
|
+
next.saving = pendingSaves.length > 0;
|
|
130
|
+
next.saveRequestId = pendingSaves.length ? pendingSaves[pendingSaves.length - 1].requestId : null;
|
|
131
|
+
}
|
|
132
|
+
store.set({ contextViewPreferenceState: next });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
store.subscribe(function (state, previous) {
|
|
137
|
+
if (state.myUserId !== previous.myUserId || state.currentSlug !== previous.currentSlug) {
|
|
138
|
+
store.set({ contextViewPreferenceState: Object.assign({}, initialState(), { accountId: accountId() }), contextViewOverride: null });
|
|
139
|
+
if (state.connected) requestContextView();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export { validMode };
|