@brimveyn/aimux 1.14.13 → 1.14.14
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/package.json
CHANGED
|
@@ -28,12 +28,24 @@ export function reduceSessionState(state: AppState, action: AppAction): AppState
|
|
|
28
28
|
// honor the patched worktree by filtering the restored tab list.
|
|
29
29
|
const loadedSession = state.sessions.find((entry) => entry.id === action.sessionId)
|
|
30
30
|
const visible = filterTabsForActiveWorktree(restored.tabs, loadedSession)
|
|
31
|
-
|
|
31
|
+
// Prefer the snapshot's tab; if it isn't visible under the active
|
|
32
|
+
// worktree (e.g. a multi-worktree session restored onto a different
|
|
33
|
+
// worktree), fall back to the last tab we viewed in that worktree,
|
|
34
|
+
// then to the first visible tab.
|
|
35
|
+
const rememberedForWorktree =
|
|
36
|
+
loadedSession?.activeWorktreeId != null && loadedSession.activeWorktreeId !== ''
|
|
37
|
+
? state.lastActiveTabByWorktree[loadedSession.activeWorktreeId]
|
|
38
|
+
: undefined
|
|
39
|
+
const snapshotTabVisible =
|
|
32
40
|
restored.activeTabId != null &&
|
|
33
41
|
restored.activeTabId !== '' &&
|
|
34
42
|
visible.some((t) => t.id === restored.activeTabId)
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
const rememberedTabVisible =
|
|
44
|
+
rememberedForWorktree != null &&
|
|
45
|
+
rememberedForWorktree !== '' &&
|
|
46
|
+
visible.some((t) => t.id === rememberedForWorktree)
|
|
47
|
+
const fallbackTabId = rememberedTabVisible ? rememberedForWorktree : (visible[0]?.id ?? null)
|
|
48
|
+
const activeTabId = snapshotTabVisible ? restored.activeTabId : fallbackTabId
|
|
37
49
|
return {
|
|
38
50
|
...state,
|
|
39
51
|
...restored,
|
|
@@ -196,15 +208,29 @@ export function reduceSessionState(state: AppState, action: AppAction): AppState
|
|
|
196
208
|
const sessions = state.sessions.map((session) =>
|
|
197
209
|
session.id === action.sessionId ? withActiveWorktree(session, action.worktreeId) : session
|
|
198
210
|
)
|
|
211
|
+
// Remember the tab we're leaving so coming back to this worktree later
|
|
212
|
+
// restores it instead of snapping to the first tab. Keyed by the
|
|
213
|
+
// worktree we're departing (the current session's active one).
|
|
214
|
+
const leavingSession = state.sessions.find((s) => s.id === action.sessionId)
|
|
215
|
+
const leavingWorktreeId = leavingSession?.activeWorktreeId
|
|
216
|
+
const lastActiveTabByWorktree =
|
|
217
|
+
action.sessionId === state.currentSessionId &&
|
|
218
|
+
leavingWorktreeId != null &&
|
|
219
|
+
leavingWorktreeId !== '' &&
|
|
220
|
+
state.activeTabId != null &&
|
|
221
|
+
state.activeTabId !== ''
|
|
222
|
+
? { ...state.lastActiveTabByWorktree, [leavingWorktreeId]: state.activeTabId }
|
|
223
|
+
: state.lastActiveTabByWorktree
|
|
199
224
|
// Changing the worktree of a non-current session has no effect on
|
|
200
225
|
// which tab the user is looking at — leave activeTabId alone.
|
|
201
226
|
if (action.sessionId !== state.currentSessionId) {
|
|
202
|
-
return { ...state, sessions }
|
|
227
|
+
return { ...state, lastActiveTabByWorktree, sessions }
|
|
203
228
|
}
|
|
204
229
|
// For the current session: if the existing activeTabId belongs to
|
|
205
|
-
// the new worktree, keep it. Otherwise
|
|
206
|
-
//
|
|
207
|
-
// pane then renders the "this
|
|
230
|
+
// the new worktree, keep it. Otherwise restore the last tab we viewed
|
|
231
|
+
// in that worktree, falling back to the first visible tab (or clearing
|
|
232
|
+
// it if the worktree is empty — the pane then renders the "this
|
|
233
|
+
// worktree has no tabs" placeholder).
|
|
208
234
|
const updated = sessions.find((s) => s.id === action.sessionId)
|
|
209
235
|
const visible = filterTabsForActiveWorktree(state.tabs, updated)
|
|
210
236
|
const currentStillVisible =
|
|
@@ -212,9 +238,14 @@ export function reduceSessionState(state: AppState, action: AppAction): AppState
|
|
|
212
238
|
state.activeTabId !== '' &&
|
|
213
239
|
visible.some((t) => t.id === state.activeTabId)
|
|
214
240
|
if (currentStillVisible) {
|
|
215
|
-
return { ...state, sessions }
|
|
241
|
+
return { ...state, lastActiveTabByWorktree, sessions }
|
|
216
242
|
}
|
|
217
|
-
|
|
243
|
+
const remembered = lastActiveTabByWorktree[action.worktreeId]
|
|
244
|
+
const nextActiveTabId =
|
|
245
|
+
remembered != null && remembered !== '' && visible.some((t) => t.id === remembered)
|
|
246
|
+
? remembered
|
|
247
|
+
: (visible[0]?.id ?? null)
|
|
248
|
+
return { ...state, activeTabId: nextActiveTabId, lastActiveTabByWorktree, sessions }
|
|
218
249
|
}
|
|
219
250
|
case 'update-worktree-record':
|
|
220
251
|
return {
|
|
@@ -15,6 +15,15 @@ export function createEmptyWorkspaceSnapshot(): WorkspaceSnapshotV1 {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* The per-worktree last-tab memory is serialized into every snapshot now so the
|
|
20
|
+
* data accrues across sessions, but restoring it at startup stays dormant until
|
|
21
|
+
* a future (non-breaking) change is ready to consume it. Flipping this to true
|
|
22
|
+
* makes restart restore the last-viewed tab per worktree; until then restart
|
|
23
|
+
* behavior is unchanged and the live in-memory map is never clobbered on switch.
|
|
24
|
+
*/
|
|
25
|
+
const RESTORE_LAST_ACTIVE_TAB_BY_WORKTREE: boolean = false
|
|
26
|
+
|
|
18
27
|
function getDisconnectedStatus(status: TabStatus): TabStatus {
|
|
19
28
|
if (status === 'running' || status === 'starting') {
|
|
20
29
|
return 'disconnected'
|
|
@@ -26,6 +35,10 @@ function getDisconnectedStatus(status: TabStatus): TabStatus {
|
|
|
26
35
|
export function serializeWorkspace(state: AppState): WorkspaceSnapshotV1 {
|
|
27
36
|
return {
|
|
28
37
|
activeTabId: state.activeTabId,
|
|
38
|
+
lastActiveTabByWorktree:
|
|
39
|
+
Object.keys(state.lastActiveTabByWorktree).length > 0
|
|
40
|
+
? state.lastActiveTabByWorktree
|
|
41
|
+
: undefined,
|
|
29
42
|
layoutTree: Object.values(state.layoutTrees)[0] ?? undefined,
|
|
30
43
|
layoutTrees: Object.keys(state.layoutTrees).length > 0 ? state.layoutTrees : undefined,
|
|
31
44
|
savedAt: new Date().toISOString(),
|
|
@@ -241,7 +254,8 @@ export function restoreWorkspaceState(
|
|
|
241
254
|
): Pick<
|
|
242
255
|
AppState,
|
|
243
256
|
'tabs' | 'activeTabId' | 'focusMode' | 'sidebar' | 'layoutTrees' | 'tabGroupMap'
|
|
244
|
-
>
|
|
257
|
+
> &
|
|
258
|
+
Partial<Pick<AppState, 'lastActiveTabByWorktree'>> {
|
|
245
259
|
const tabs = restoreTabsFromWorkspace(workspaceSnapshot, options)
|
|
246
260
|
const activeTabId =
|
|
247
261
|
workspaceSnapshot?.activeTabId != null &&
|
|
@@ -253,6 +267,14 @@ export function restoreWorkspaceState(
|
|
|
253
267
|
const { layoutTrees, tabGroupMap } = restoreLayoutTrees(workspaceSnapshot, tabs)
|
|
254
268
|
const orderedTabs = normalizeGroupedTabOrder(tabs, layoutTrees, tabGroupMap)
|
|
255
269
|
|
|
270
|
+
// Dormant until the gate flips: merge the persisted per-worktree memory over
|
|
271
|
+
// the live in-memory map. While off, the key is omitted entirely so callers
|
|
272
|
+
// that spread the result never overwrite their existing map on a switch.
|
|
273
|
+
const persistedLastActiveTab =
|
|
274
|
+
RESTORE_LAST_ACTIVE_TAB_BY_WORKTREE && workspaceSnapshot?.lastActiveTabByWorktree
|
|
275
|
+
? { ...state.lastActiveTabByWorktree, ...workspaceSnapshot.lastActiveTabByWorktree }
|
|
276
|
+
: undefined
|
|
277
|
+
|
|
256
278
|
return {
|
|
257
279
|
activeTabId,
|
|
258
280
|
focusMode: 'navigation',
|
|
@@ -260,5 +282,6 @@ export function restoreWorkspaceState(
|
|
|
260
282
|
sidebar: state.sidebar,
|
|
261
283
|
tabGroupMap,
|
|
262
284
|
tabs: orderedTabs,
|
|
285
|
+
...(persistedLastActiveTab ? { lastActiveTabByWorktree: persistedLastActiveTab } : {}),
|
|
263
286
|
}
|
|
264
287
|
}
|
package/src/state/store.ts
CHANGED
package/src/state/types.ts
CHANGED
|
@@ -117,6 +117,13 @@ export interface WorkspaceSnapshotV1 {
|
|
|
117
117
|
layoutTree?: LayoutNode
|
|
118
118
|
layoutTrees?: Record<string, LayoutNode>
|
|
119
119
|
tabGroupMap?: Record<string, string>
|
|
120
|
+
/**
|
|
121
|
+
* Last viewed tab per worktree, keyed by worktree id. Optional and additive
|
|
122
|
+
* (no version bump): older builds ignore it, newer builds tolerate its
|
|
123
|
+
* absence. Written now so the data accrues, but restoring it at startup is
|
|
124
|
+
* gated off until a future change flips RESTORE_LAST_ACTIVE_TAB_BY_WORKTREE.
|
|
125
|
+
*/
|
|
126
|
+
lastActiveTabByWorktree?: Record<string, string>
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
export type WorktreeSource = 'primary' | 'aimux-temp' | 'external'
|
|
@@ -489,6 +496,12 @@ export interface AppState {
|
|
|
489
496
|
* keyed by worktree id. Ephemeral (polled); not persisted to the catalog.
|
|
490
497
|
*/
|
|
491
498
|
worktreeDivergence: Record<string, BranchDivergence>
|
|
499
|
+
/**
|
|
500
|
+
* Last active tab a user viewed within each worktree, keyed by worktree id.
|
|
501
|
+
* Lets switching back to a worktree restore its last-viewed tab instead of
|
|
502
|
+
* snapping to the first one. Ephemeral (in-memory); not persisted to the catalog.
|
|
503
|
+
*/
|
|
504
|
+
lastActiveTabByWorktree: Record<string, string>
|
|
492
505
|
/** Chord prefix the sequence resolver is currently waiting on, or null when idle. */
|
|
493
506
|
pendingChords: string[] | null
|
|
494
507
|
}
|