agent-tempo 1.7.0 → 2.0.0-beta.2
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/CLAUDE.md +10 -13
- package/README.md +21 -15
- package/dashboard/dist/assets/index-BbR7zIdK.js.map +1 -1
- package/dashboard/package.json +1 -1
- package/dist/activities/hard-terminate.d.ts +2 -2
- package/dist/activities/hard-terminate.js +3 -9
- package/dist/activities/maestro.d.ts +8 -13
- package/dist/activities/maestro.js +3 -10
- package/dist/activities/outbox.js +16 -2
- package/dist/activities/resolve.d.ts +18 -7
- package/dist/activities/resolve.js +58 -46
- package/dist/adapters/base.js +3 -0
- package/dist/adapters/claude-code/adapter.js +7 -0
- package/dist/cli/command-center-command.js +15 -1
- package/dist/cli/commands.d.ts +8 -0
- package/dist/cli/commands.js +79 -11
- package/dist/cli/daemon-command.js +19 -33
- package/dist/cli/help-text.js +9 -7
- package/dist/cli/home-command.d.ts +26 -0
- package/dist/cli/home-command.js +156 -0
- package/dist/cli/removed-verbs.d.ts +0 -4
- package/dist/cli/removed-verbs.js +23 -12
- package/dist/cli/sa-preflight.d.ts +0 -14
- package/dist/cli/sa-preflight.js +1 -17
- package/dist/cli/startup.d.ts +1 -10
- package/dist/cli/startup.js +0 -40
- package/dist/cli.js +34 -80
- package/dist/client/core.js +13 -15
- package/dist/client/index.d.ts +2 -1
- package/dist/client/interface.d.ts +5 -14
- package/dist/client/with-spawn.d.ts +9 -4
- package/dist/client/with-spawn.js +7 -10
- package/dist/config.d.ts +14 -13
- package/dist/config.js +14 -0
- package/dist/constants.d.ts +50 -0
- package/dist/constants.js +60 -1
- package/dist/daemon.js +81 -0
- package/dist/http/auth.d.ts +3 -21
- package/dist/http/auth.js +10 -31
- package/dist/http/event-types.d.ts +38 -4
- package/dist/http/orphans.d.ts +3 -3
- package/dist/http/orphans.js +3 -3
- package/dist/http/server.d.ts +0 -5
- package/dist/http/server.js +79 -29
- package/dist/http/snapshot.js +3 -0
- package/dist/http/writes.d.ts +1 -1
- package/dist/http/writes.js +25 -2
- package/dist/observability/nondeterminism-alarm.d.ts +113 -0
- package/dist/observability/nondeterminism-alarm.js +162 -0
- package/dist/palette/index.d.ts +6 -7
- package/dist/palette/index.js +6 -7
- package/dist/pi/mission-control/actions.d.ts +67 -1
- package/dist/pi/mission-control/actions.js +61 -0
- package/dist/pi/mission-control/extension.d.ts +77 -0
- package/dist/pi/mission-control/extension.js +171 -0
- package/dist/pi/workflow-client.d.ts +14 -16
- package/dist/pi/workflow-client.js +18 -44
- package/dist/server-tools.js +30 -29
- package/dist/server.js +42 -0
- package/dist/tools/action-guard.d.ts +23 -0
- package/dist/tools/action-guard.js +33 -0
- package/dist/tools/coat-check.d.ts +20 -0
- package/dist/tools/coat-check.js +129 -0
- package/dist/tools/gate.d.ts +13 -0
- package/dist/tools/gate.js +88 -0
- package/dist/tools/schedule.d.ts +18 -0
- package/dist/tools/schedule.js +93 -1
- package/dist/tools/stage.d.ts +17 -0
- package/dist/tools/stage.js +85 -1
- package/dist/tools/state.d.ts +14 -0
- package/dist/tools/state.js +95 -0
- package/dist/types.d.ts +54 -7
- package/dist/upgrade/boot-guard.d.ts +95 -0
- package/dist/upgrade/boot-guard.js +88 -0
- package/dist/upgrade/from-upgrade.d.ts +95 -0
- package/dist/upgrade/from-upgrade.js +330 -0
- package/dist/upgrade/phase-engine.js +4 -1
- package/dist/utils/duration.d.ts +3 -6
- package/dist/utils/duration.js +3 -6
- package/dist/utils/format-hosts.d.ts +1 -1
- package/dist/utils/format-hosts.js +0 -2
- package/dist/utils/grpc-shutdown-guard.js +0 -4
- package/dist/utils/hosts.d.ts +4 -10
- package/dist/utils/hosts.js +12 -28
- package/dist/utils/orphan-guard.d.ts +37 -0
- package/dist/utils/orphan-guard.js +26 -0
- package/dist/utils/search-attributes.d.ts +28 -18
- package/dist/utils/search-attributes.js +48 -18
- package/dist/utils/visibility-deadline.d.ts +16 -0
- package/dist/utils/visibility-deadline.js +16 -0
- package/dist/workflows/maestro-signals.d.ts +0 -13
- package/dist/workflows/maestro-signals.js +6 -14
- package/dist/workflows/maestro.js +27 -32
- package/dist/workflows/scheduler.js +7 -1
- package/dist/workflows/session.js +237 -153
- package/dist/workflows/signals.d.ts +9 -1
- package/dist/workflows/signals.js +8 -6
- package/package.json +1 -6
- package/workflow-bundle.js +478 -225
- package/dist/cli/legacy-migration.d.ts +0 -35
- package/dist/cli/legacy-migration.js +0 -341
- package/dist/client/ensure-conductor-spawned.d.ts +0 -35
- package/dist/client/ensure-conductor-spawned.js +0 -48
- package/dist/tui/App.d.ts +0 -85
- package/dist/tui/App.js +0 -1791
- package/dist/tui/bootstrap-types.d.ts +0 -46
- package/dist/tui/bootstrap-types.js +0 -7
- package/dist/tui/commands.d.ts +0 -71
- package/dist/tui/commands.js +0 -1375
- package/dist/tui/components/ChatView.d.ts +0 -35
- package/dist/tui/components/ChatView.js +0 -54
- package/dist/tui/components/CommandPalette.d.ts +0 -21
- package/dist/tui/components/CommandPalette.js +0 -67
- package/dist/tui/components/ConversationStream.d.ts +0 -114
- package/dist/tui/components/ConversationStream.js +0 -307
- package/dist/tui/components/CreateEnsembleWizard.d.ts +0 -19
- package/dist/tui/components/CreateEnsembleWizard.js +0 -223
- package/dist/tui/components/DestroyConfirmModal.d.ts +0 -17
- package/dist/tui/components/DestroyConfirmModal.js +0 -62
- package/dist/tui/components/ErrorView.d.ts +0 -31
- package/dist/tui/components/ErrorView.js +0 -129
- package/dist/tui/components/HomeView.d.ts +0 -54
- package/dist/tui/components/HomeView.js +0 -306
- package/dist/tui/components/LoadLineupModal.d.ts +0 -18
- package/dist/tui/components/LoadLineupModal.js +0 -79
- package/dist/tui/components/MainView.d.ts +0 -21
- package/dist/tui/components/MainView.js +0 -107
- package/dist/tui/components/NewEnsembleModal.d.ts +0 -9
- package/dist/tui/components/NewEnsembleModal.js +0 -73
- package/dist/tui/components/Picker.d.ts +0 -23
- package/dist/tui/components/Picker.js +0 -70
- package/dist/tui/components/PlayerDetailView.d.ts +0 -26
- package/dist/tui/components/PlayerDetailView.js +0 -118
- package/dist/tui/components/PromptArea.d.ts +0 -50
- package/dist/tui/components/PromptArea.js +0 -303
- package/dist/tui/components/RecruitWizard.d.ts +0 -17
- package/dist/tui/components/RecruitWizard.js +0 -221
- package/dist/tui/components/RestoreConfirmModal.d.ts +0 -18
- package/dist/tui/components/RestoreConfirmModal.js +0 -71
- package/dist/tui/components/ScheduleWizard.d.ts +0 -19
- package/dist/tui/components/ScheduleWizard.js +0 -259
- package/dist/tui/components/Splash.d.ts +0 -23
- package/dist/tui/components/Splash.js +0 -221
- package/dist/tui/components/StatusBar.d.ts +0 -48
- package/dist/tui/components/StatusBar.js +0 -128
- package/dist/tui/components/StatusOverlay.d.ts +0 -15
- package/dist/tui/components/StatusOverlay.js +0 -76
- package/dist/tui/components/TitleBar.d.ts +0 -10
- package/dist/tui/components/TitleBar.js +0 -21
- package/dist/tui/index.d.ts +0 -15
- package/dist/tui/index.js +0 -162
- package/dist/tui/ink-context.d.ts +0 -18
- package/dist/tui/ink-context.js +0 -59
- package/dist/tui/ink-loader.d.ts +0 -26
- package/dist/tui/ink-loader.js +0 -42
- package/dist/tui/removed-commands.d.ts +0 -9
- package/dist/tui/removed-commands.js +0 -22
- package/dist/tui/sse-handler.d.ts +0 -52
- package/dist/tui/sse-handler.js +0 -157
- package/dist/tui/store.d.ts +0 -598
- package/dist/tui/store.js +0 -753
- package/dist/tui/utils/format.d.ts +0 -56
- package/dist/tui/utils/format.js +0 -155
- package/dist/tui/utils/fullscreen.d.ts +0 -23
- package/dist/tui/utils/fullscreen.js +0 -71
- package/dist/tui/utils/history.d.ts +0 -10
- package/dist/tui/utils/history.js +0 -85
- package/dist/tui/utils/platform.d.ts +0 -45
- package/dist/tui/utils/platform.js +0 -258
- package/dist/tui/utils/theme.d.ts +0 -21
- package/dist/tui/utils/theme.js +0 -24
package/dist/tui/store.js
DELETED
|
@@ -1,753 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_CREATE_ENSEMBLE_ANSWERS = exports.CREATE_ENSEMBLE_STEPS = exports.DEFAULT_SCHEDULE_ANSWERS = exports.SCHEDULE_STEPS = exports.RECRUIT_STEPS = exports.NOTIFICATION_CAP = exports.NOTIFICATION_TTL_MS = void 0;
|
|
4
|
-
exports.toConversationEntry = toConversationEntry;
|
|
5
|
-
exports.defaultRecruitAnswers = defaultRecruitAnswers;
|
|
6
|
-
exports.initialState = initialState;
|
|
7
|
-
exports.tuiReducer = tuiReducer;
|
|
8
|
-
/**
|
|
9
|
-
* Project an `EnsembleChatMessage` into a `ConversationEntry`. Shared by
|
|
10
|
-
* the reducer's incremental path (`APPEND_CHAT_MESSAGE`) and the SSE
|
|
11
|
-
* snapshot/recovery paths (`src/tui/sse-handler.ts`) so both produce the
|
|
12
|
-
* same shape.
|
|
13
|
-
*/
|
|
14
|
-
function toConversationEntry(m) {
|
|
15
|
-
return {
|
|
16
|
-
id: m.id,
|
|
17
|
-
from: m.from,
|
|
18
|
-
to: m.to,
|
|
19
|
-
text: m.text,
|
|
20
|
-
timestamp: m.timestamp,
|
|
21
|
-
direction: m.role === 'maestro-out' ? 'out' : 'in',
|
|
22
|
-
role: m.role,
|
|
23
|
-
thirdParty: m.role === 'conductor-out' || m.role === 'conductor-in',
|
|
24
|
-
// #357: forward broadcastId so the renderer can fold fan-out groups.
|
|
25
|
-
...(m.broadcastId !== undefined ? { broadcastId: m.broadcastId } : {}),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
/** Default TTLs by kind — errors get extra time to read. */
|
|
29
|
-
exports.NOTIFICATION_TTL_MS = {
|
|
30
|
-
error: 8000,
|
|
31
|
-
warn: 5000,
|
|
32
|
-
info: 5000,
|
|
33
|
-
};
|
|
34
|
-
/** Maximum concurrent notifications; oldest is dropped when exceeded. */
|
|
35
|
-
exports.NOTIFICATION_CAP = 3;
|
|
36
|
-
exports.RECRUIT_STEPS = ['name', 'agent', 'type', 'workDir', 'message', 'host', 'confirm'];
|
|
37
|
-
exports.SCHEDULE_STEPS = ['target', 'message', 'schedType', 'timing', 'timezone', 'confirm'];
|
|
38
|
-
exports.DEFAULT_SCHEDULE_ANSWERS = {
|
|
39
|
-
target: '',
|
|
40
|
-
message: '',
|
|
41
|
-
schedType: 'every',
|
|
42
|
-
timing: '',
|
|
43
|
-
timezone: '',
|
|
44
|
-
name: '',
|
|
45
|
-
};
|
|
46
|
-
function defaultRecruitAnswers(defaultAgent = 'claude') {
|
|
47
|
-
return {
|
|
48
|
-
name: '',
|
|
49
|
-
agent: defaultAgent,
|
|
50
|
-
playerType: '',
|
|
51
|
-
workDir: process.cwd(),
|
|
52
|
-
initialMessage: '',
|
|
53
|
-
host: 'localhost',
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
exports.CREATE_ENSEMBLE_STEPS = ['name', 'workDir', 'lineup', 'confirm'];
|
|
57
|
-
exports.DEFAULT_CREATE_ENSEMBLE_ANSWERS = {
|
|
58
|
-
name: '',
|
|
59
|
-
workDir: process.cwd(),
|
|
60
|
-
lineup: '',
|
|
61
|
-
};
|
|
62
|
-
function initialState(ensemble) {
|
|
63
|
-
return {
|
|
64
|
-
phase: ensemble ? 'main' : 'splash',
|
|
65
|
-
view: ensemble ? 'ensemble' : 'home',
|
|
66
|
-
splashStatus: 'Starting up...',
|
|
67
|
-
splashChecks: [],
|
|
68
|
-
splashConnected: false,
|
|
69
|
-
splashSummary: undefined,
|
|
70
|
-
ensembles: null,
|
|
71
|
-
selectedEnsembleIndex: 0,
|
|
72
|
-
activeEnsemble: ensemble || null,
|
|
73
|
-
players: [],
|
|
74
|
-
playersLoaded: false,
|
|
75
|
-
messages: [],
|
|
76
|
-
conductorHistory: [],
|
|
77
|
-
schedules: [],
|
|
78
|
-
conversation: null,
|
|
79
|
-
ensembleChat: [],
|
|
80
|
-
hasConductor: false,
|
|
81
|
-
ensemblePaused: false,
|
|
82
|
-
ensembleHeld: false,
|
|
83
|
-
selectedPlayerIndex: 0,
|
|
84
|
-
activePlayer: null,
|
|
85
|
-
playerMetadata: null,
|
|
86
|
-
playerMessages: [],
|
|
87
|
-
playerScrollOffset: 0,
|
|
88
|
-
staticItems: [],
|
|
89
|
-
notifications: [],
|
|
90
|
-
notificationTick: 0,
|
|
91
|
-
chatTarget: undefined,
|
|
92
|
-
sentMessages: [],
|
|
93
|
-
statusOverlay: false,
|
|
94
|
-
overlay: null,
|
|
95
|
-
statusScrollOffset: 0,
|
|
96
|
-
paletteVisible: false,
|
|
97
|
-
paletteIndex: 0,
|
|
98
|
-
pickerVisible: false,
|
|
99
|
-
pickerType: null,
|
|
100
|
-
pickerIntent: null,
|
|
101
|
-
pickerIndex: 0,
|
|
102
|
-
pickerStatusFilter: null,
|
|
103
|
-
confirmingStop: undefined,
|
|
104
|
-
confirmingStopReason: undefined,
|
|
105
|
-
scheduleWizard: undefined,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
// ── Reducer ──
|
|
109
|
-
function tuiReducer(state, action) {
|
|
110
|
-
switch (action.type) {
|
|
111
|
-
case 'SET_PHASE':
|
|
112
|
-
return { ...state, phase: action.phase, error: action.error };
|
|
113
|
-
case 'SET_SPLASH_STATUS':
|
|
114
|
-
return { ...state, splashStatus: action.status };
|
|
115
|
-
case 'SET_SPLASH_CHECKS':
|
|
116
|
-
return { ...state, splashChecks: action.checks };
|
|
117
|
-
case 'SET_SPLASH_CONNECTED':
|
|
118
|
-
return { ...state, splashConnected: true, splashSummary: action.summary };
|
|
119
|
-
// ── Navigation ──
|
|
120
|
-
case 'NAVIGATE_HOME':
|
|
121
|
-
// Auto-select was removed from the poller (#306 follow-up): HomeView
|
|
122
|
-
// is the explicit ensemble picker, so once the user lands on home they
|
|
123
|
-
// stay there until they pick an ensemble themselves. This action just
|
|
124
|
-
// resets ensemble/player state and the chat shell.
|
|
125
|
-
return {
|
|
126
|
-
...state,
|
|
127
|
-
view: 'home',
|
|
128
|
-
phase: 'main',
|
|
129
|
-
activeEnsemble: null,
|
|
130
|
-
activePlayer: null,
|
|
131
|
-
chatTarget: undefined,
|
|
132
|
-
players: [],
|
|
133
|
-
playersLoaded: false,
|
|
134
|
-
messages: [],
|
|
135
|
-
sentMessages: [],
|
|
136
|
-
conductorHistory: [],
|
|
137
|
-
schedules: [],
|
|
138
|
-
conversation: null,
|
|
139
|
-
playerMetadata: null,
|
|
140
|
-
playerMessages: [],
|
|
141
|
-
ensemblePaused: false,
|
|
142
|
-
ensembleHeld: false,
|
|
143
|
-
selectedPlayerIndex: 0,
|
|
144
|
-
};
|
|
145
|
-
case 'NAVIGATE_ENSEMBLE':
|
|
146
|
-
return {
|
|
147
|
-
...state,
|
|
148
|
-
view: 'ensemble',
|
|
149
|
-
phase: 'main',
|
|
150
|
-
activeEnsemble: action.ensemble,
|
|
151
|
-
activePlayer: null,
|
|
152
|
-
chatTarget: undefined,
|
|
153
|
-
players: [],
|
|
154
|
-
playersLoaded: false,
|
|
155
|
-
messages: [],
|
|
156
|
-
sentMessages: [],
|
|
157
|
-
conductorHistory: [],
|
|
158
|
-
schedules: [],
|
|
159
|
-
conversation: null,
|
|
160
|
-
playerMetadata: null,
|
|
161
|
-
playerMessages: [],
|
|
162
|
-
ensemblePaused: false,
|
|
163
|
-
ensembleHeld: false,
|
|
164
|
-
selectedPlayerIndex: 0,
|
|
165
|
-
};
|
|
166
|
-
case 'NAVIGATE_PLAYER':
|
|
167
|
-
return {
|
|
168
|
-
...state,
|
|
169
|
-
view: 'player',
|
|
170
|
-
activePlayer: action.playerId,
|
|
171
|
-
playerMetadata: null,
|
|
172
|
-
playerMessages: [],
|
|
173
|
-
playerScrollOffset: 0,
|
|
174
|
-
};
|
|
175
|
-
// ── Data refresh ──
|
|
176
|
-
case 'REFRESH_ENSEMBLES':
|
|
177
|
-
return {
|
|
178
|
-
...state,
|
|
179
|
-
ensembles: action.ensembles,
|
|
180
|
-
// Clamp selection index
|
|
181
|
-
selectedEnsembleIndex: Math.min(state.selectedEnsembleIndex, Math.max(0, action.ensembles.length - 1)),
|
|
182
|
-
};
|
|
183
|
-
case 'REFRESH_ENSEMBLE_DATA': {
|
|
184
|
-
const lastMsg = action.messages.length > 0 ? action.messages[action.messages.length - 1] : null;
|
|
185
|
-
return {
|
|
186
|
-
...state,
|
|
187
|
-
players: action.players,
|
|
188
|
-
playersLoaded: true,
|
|
189
|
-
messages: action.messages,
|
|
190
|
-
conductorHistory: action.history,
|
|
191
|
-
schedules: action.schedules ?? state.schedules,
|
|
192
|
-
lastSeenMessageId: lastMsg?.id ?? state.lastSeenMessageId,
|
|
193
|
-
// Clamp selection index
|
|
194
|
-
selectedPlayerIndex: Math.min(state.selectedPlayerIndex, Math.max(0, action.players.length - 1)),
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
case 'SET_CONVERSATION':
|
|
198
|
-
return { ...state, conversation: action.conversation };
|
|
199
|
-
case 'SET_ENSEMBLE_CHAT':
|
|
200
|
-
return {
|
|
201
|
-
...state,
|
|
202
|
-
ensembleChat: action.chat.messages,
|
|
203
|
-
hasConductor: action.chat.hasConductor,
|
|
204
|
-
};
|
|
205
|
-
// ── #94/#95 PR-4a — SSE event stream incremental updates ──
|
|
206
|
-
case 'APPEND_CHAT_MESSAGE': {
|
|
207
|
-
// `chat.appended` SSE events arrive one-per-message. We push onto
|
|
208
|
-
// `ensembleChat` and derive a matching conversation entry via
|
|
209
|
-
// `toConversationEntry` (also used by the snapshot path), so
|
|
210
|
-
// ChatView/ConversationStream see a continuous timeline. Cap the
|
|
211
|
-
// in-memory slice at 500 to bound memory on long-running sessions;
|
|
212
|
-
// PR-4b will adjust this as part of the scroll-view migration.
|
|
213
|
-
const m = action.message;
|
|
214
|
-
const newChat = [...state.ensembleChat, m];
|
|
215
|
-
const cappedChat = newChat.length > 500 ? newChat.slice(-500) : newChat;
|
|
216
|
-
const baseConv = state.conversation ?? [];
|
|
217
|
-
const newConv = [...baseConv, toConversationEntry(m)];
|
|
218
|
-
const cappedConv = newConv.length > 500 ? newConv.slice(-500) : newConv;
|
|
219
|
-
return { ...state, ensembleChat: cappedChat, conversation: cappedConv };
|
|
220
|
-
}
|
|
221
|
-
case 'UPSERT_PLAYER': {
|
|
222
|
-
// `player.added` and `player.phase_changed` both land here — added
|
|
223
|
-
// creates a new entry; phase_changed updates fields on the existing
|
|
224
|
-
// entry. Identity-preserving when the wire payload matches the
|
|
225
|
-
// current cached entry exactly so StatusBar/MainView don't churn
|
|
226
|
-
// on duplicate events (e.g. a quick disconnect/reconnect).
|
|
227
|
-
const incoming = action.player;
|
|
228
|
-
const idx = state.players.findIndex((p) => p.playerId === incoming.playerId);
|
|
229
|
-
if (idx === -1) {
|
|
230
|
-
return { ...state, players: [...state.players, incoming], playersLoaded: true };
|
|
231
|
-
}
|
|
232
|
-
const existing = state.players[idx];
|
|
233
|
-
const merged = { ...existing, ...incoming };
|
|
234
|
-
const fieldsEqual = existing.phase === merged.phase
|
|
235
|
-
&& existing.part === merged.part
|
|
236
|
-
&& existing.hostname === merged.hostname
|
|
237
|
-
&& existing.workDir === merged.workDir
|
|
238
|
-
&& existing.isConductor === merged.isConductor
|
|
239
|
-
&& existing.gitBranch === merged.gitBranch
|
|
240
|
-
&& existing.playerType === merged.playerType
|
|
241
|
-
&& existing.agentType === merged.agentType;
|
|
242
|
-
if (fieldsEqual)
|
|
243
|
-
return state;
|
|
244
|
-
const next = state.players.slice();
|
|
245
|
-
next[idx] = merged;
|
|
246
|
-
return { ...state, players: next };
|
|
247
|
-
}
|
|
248
|
-
case 'PATCH_PLAYER_PHASE': {
|
|
249
|
-
// Rationale on the action type. Identity-preserving guard mirrors
|
|
250
|
-
// SET_ENSEMBLE_PAUSED — same StatusBar churn rationale.
|
|
251
|
-
const idx = state.players.findIndex((p) => p.playerId === action.playerId);
|
|
252
|
-
if (idx === -1)
|
|
253
|
-
return state;
|
|
254
|
-
if (state.players[idx].phase === action.phase)
|
|
255
|
-
return state;
|
|
256
|
-
const next = state.players.slice();
|
|
257
|
-
next[idx] = { ...state.players[idx], phase: action.phase };
|
|
258
|
-
return { ...state, players: next };
|
|
259
|
-
}
|
|
260
|
-
case 'REMOVE_PLAYER': {
|
|
261
|
-
const next = state.players.filter((p) => p.playerId !== action.playerId);
|
|
262
|
-
if (next.length === state.players.length)
|
|
263
|
-
return state;
|
|
264
|
-
return {
|
|
265
|
-
...state,
|
|
266
|
-
players: next,
|
|
267
|
-
selectedPlayerIndex: Math.min(state.selectedPlayerIndex, Math.max(0, next.length - 1)),
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
case 'SET_SCHEDULES':
|
|
271
|
-
return { ...state, schedules: action.schedules };
|
|
272
|
-
case 'REFRESH_PLAYER_DATA':
|
|
273
|
-
return {
|
|
274
|
-
...state,
|
|
275
|
-
playerMetadata: action.metadata,
|
|
276
|
-
playerMessages: action.messages,
|
|
277
|
-
};
|
|
278
|
-
case 'PLAYER_SCROLL_UP':
|
|
279
|
-
if (state.playerScrollOffset <= 0)
|
|
280
|
-
return state;
|
|
281
|
-
return { ...state, playerScrollOffset: state.playerScrollOffset - 1 };
|
|
282
|
-
case 'PLAYER_SCROLL_DOWN': {
|
|
283
|
-
const maxScroll = Math.max(0, state.playerMessages.length - 20);
|
|
284
|
-
if (state.playerScrollOffset >= maxScroll)
|
|
285
|
-
return state;
|
|
286
|
-
return { ...state, playerScrollOffset: state.playerScrollOffset + 1 };
|
|
287
|
-
}
|
|
288
|
-
// ── Selection & Focus ──
|
|
289
|
-
// ── Chat shell ──
|
|
290
|
-
case 'COMMIT_STATIC': {
|
|
291
|
-
const newItems = [...state.staticItems, action.item];
|
|
292
|
-
// Trim to last 500 entries for memory management
|
|
293
|
-
const trimmed = newItems.length > 500 ? newItems.slice(-500) : newItems;
|
|
294
|
-
return { ...state, staticItems: trimmed };
|
|
295
|
-
}
|
|
296
|
-
// ── Bottom-pinned notifications (#306) ──
|
|
297
|
-
case 'ADD_NOTIFICATION': {
|
|
298
|
-
const now = Date.now();
|
|
299
|
-
// Filter out expired entries before appending so the cap check isn't
|
|
300
|
-
// polluted by stale notifications that should already be gone.
|
|
301
|
-
const live = state.notifications.filter(n => n.expiresAt > now);
|
|
302
|
-
const appended = [...live, action.notification];
|
|
303
|
-
// Cap at NOTIFICATION_CAP — oldest drops off when exceeded.
|
|
304
|
-
const capped = appended.length > exports.NOTIFICATION_CAP
|
|
305
|
-
? appended.slice(appended.length - exports.NOTIFICATION_CAP)
|
|
306
|
-
: appended;
|
|
307
|
-
return { ...state, notifications: capped };
|
|
308
|
-
}
|
|
309
|
-
case 'DISMISS_NOTIFICATION': {
|
|
310
|
-
const next = state.notifications.filter(n => n.id !== action.id);
|
|
311
|
-
if (next.length === state.notifications.length)
|
|
312
|
-
return state;
|
|
313
|
-
return { ...state, notifications: next };
|
|
314
|
-
}
|
|
315
|
-
case 'DISMISS_OLDEST_NOTIFICATION': {
|
|
316
|
-
// Filter-expired-first so Esc acts on what the user actually sees.
|
|
317
|
-
const now = Date.now();
|
|
318
|
-
const live = state.notifications.filter(n => n.expiresAt > now);
|
|
319
|
-
if (live.length === 0) {
|
|
320
|
-
// Still compact if any expired slipped through — keeps the array clean
|
|
321
|
-
if (live.length !== state.notifications.length) {
|
|
322
|
-
return { ...state, notifications: live };
|
|
323
|
-
}
|
|
324
|
-
return state;
|
|
325
|
-
}
|
|
326
|
-
return { ...state, notifications: live.slice(1) };
|
|
327
|
-
}
|
|
328
|
-
case 'CLEAR_NOTIFICATIONS': {
|
|
329
|
-
if (state.notifications.length === 0)
|
|
330
|
-
return state;
|
|
331
|
-
return { ...state, notifications: [] };
|
|
332
|
-
}
|
|
333
|
-
case 'NOTIFICATION_TICK': {
|
|
334
|
-
// Cheap re-render trigger so expired notifications disappear from the
|
|
335
|
-
// rendered stack without requiring a reducer action per expiration.
|
|
336
|
-
// The render pass filters by `expiresAt > Date.now()`. Only the counter
|
|
337
|
-
// changes — we leave the notifications array alone to avoid thrashing
|
|
338
|
-
// the React children identity.
|
|
339
|
-
return { ...state, notificationTick: state.notificationTick + 1 };
|
|
340
|
-
}
|
|
341
|
-
// ── Command palette ──
|
|
342
|
-
case 'SHOW_PALETTE':
|
|
343
|
-
if (state.paletteVisible && state.paletteIndex === 0)
|
|
344
|
-
return state;
|
|
345
|
-
return { ...state, paletteVisible: true, paletteIndex: 0 };
|
|
346
|
-
case 'HIDE_PALETTE':
|
|
347
|
-
if (!state.paletteVisible && state.paletteIndex === 0)
|
|
348
|
-
return state;
|
|
349
|
-
return { ...state, paletteVisible: false, paletteIndex: 0 };
|
|
350
|
-
case 'PALETTE_UP': {
|
|
351
|
-
// #108: identity-preserving — returning `{ ...state, paletteIndex: same }`
|
|
352
|
-
// forces a re-render of the full app tree even when the value didn't change
|
|
353
|
-
// (e.g. holding arrow-up at index 0). Mirrors the OVERLAY_SELECT pattern
|
|
354
|
-
// below. See CLAUDE.md "Reducer state identity matters".
|
|
355
|
-
const next = Math.max(0, state.paletteIndex - 1);
|
|
356
|
-
if (next === state.paletteIndex)
|
|
357
|
-
return state;
|
|
358
|
-
return { ...state, paletteIndex: next };
|
|
359
|
-
}
|
|
360
|
-
case 'PALETTE_DOWN': {
|
|
361
|
-
// #108: same identity-preserving guard — holding arrow-down at the clamped
|
|
362
|
-
// max must not trigger spurious re-renders.
|
|
363
|
-
const next = state.paletteIndex + 1;
|
|
364
|
-
const clamped = action.max != null ? Math.min(next, action.max) : next;
|
|
365
|
-
if (clamped === state.paletteIndex)
|
|
366
|
-
return state;
|
|
367
|
-
return { ...state, paletteIndex: clamped };
|
|
368
|
-
}
|
|
369
|
-
case 'PALETTE_SET_INDEX':
|
|
370
|
-
return { ...state, paletteIndex: action.index };
|
|
371
|
-
// ── Picker overlay ──
|
|
372
|
-
case 'SHOW_STATUS':
|
|
373
|
-
return { ...state, statusOverlay: true, statusScrollOffset: 0 };
|
|
374
|
-
case 'HIDE_STATUS':
|
|
375
|
-
return { ...state, statusOverlay: false, statusScrollOffset: 0 };
|
|
376
|
-
case 'SHOW_COMMAND_OVERLAY':
|
|
377
|
-
return { ...state, overlay: { type: 'command', title: action.title, items: [{ id: '_', label: action.content }], selectedIndex: 0, hint: 'esc \u2014 close' } };
|
|
378
|
-
case 'SHOW_OVERLAY':
|
|
379
|
-
return { ...state, overlay: { ...action.overlay, selectedIndex: 0 } };
|
|
380
|
-
case 'HIDE_OVERLAY':
|
|
381
|
-
if (!state.overlay)
|
|
382
|
-
return state;
|
|
383
|
-
return { ...state, overlay: null };
|
|
384
|
-
case 'OVERLAY_SELECT': {
|
|
385
|
-
if (!state.overlay || state.overlay.items.length === 0)
|
|
386
|
-
return state;
|
|
387
|
-
const len = state.overlay.items.length;
|
|
388
|
-
const idx = action.direction === 'up'
|
|
389
|
-
? (state.overlay.selectedIndex - 1 + len) % len
|
|
390
|
-
: (state.overlay.selectedIndex + 1) % len;
|
|
391
|
-
if (idx === state.overlay.selectedIndex)
|
|
392
|
-
return state;
|
|
393
|
-
return { ...state, overlay: { ...state.overlay, selectedIndex: idx } };
|
|
394
|
-
}
|
|
395
|
-
case 'STATUS_SCROLL_UP': {
|
|
396
|
-
const next = Math.max(0, state.statusScrollOffset - 1);
|
|
397
|
-
if (next === state.statusScrollOffset)
|
|
398
|
-
return state;
|
|
399
|
-
return { ...state, statusScrollOffset: next };
|
|
400
|
-
}
|
|
401
|
-
case 'STATUS_SCROLL_DOWN':
|
|
402
|
-
// #250: intentional — no upper-bound clamp lives in the reducer. The max
|
|
403
|
-
// offset depends on `players.length` and `contentHeight`, neither of which
|
|
404
|
-
// the reducer can see. `StatusOverlay` performs the final clamp via
|
|
405
|
-
// `Math.min(scrollOffset, max)` at render (components/StatusOverlay.tsx).
|
|
406
|
-
// Leaving the reducer unbounded + render-time clamping is cheaper than
|
|
407
|
-
// threading the render-time max back through every dispatch.
|
|
408
|
-
return { ...state, statusScrollOffset: state.statusScrollOffset + 1 };
|
|
409
|
-
case 'SHOW_PICKER':
|
|
410
|
-
return { ...state, pickerVisible: true, pickerType: action.pickerType, pickerIntent: action.intent || null, pickerIndex: 0, pickerStatusFilter: action.statusFilter || null };
|
|
411
|
-
case 'HIDE_PICKER':
|
|
412
|
-
return { ...state, pickerVisible: false, pickerType: null, pickerIntent: null, pickerIndex: 0, pickerStatusFilter: null };
|
|
413
|
-
case 'PICKER_UP': {
|
|
414
|
-
const next = Math.max(0, state.pickerIndex - 1);
|
|
415
|
-
if (next === state.pickerIndex)
|
|
416
|
-
return state;
|
|
417
|
-
return { ...state, pickerIndex: next };
|
|
418
|
-
}
|
|
419
|
-
case 'PICKER_DOWN': {
|
|
420
|
-
const maxIdx = (state.pickerType === 'ensembles' ? (state.ensembles?.length ?? 0) : state.players.length - 1);
|
|
421
|
-
if (state.pickerIndex >= maxIdx)
|
|
422
|
-
return state;
|
|
423
|
-
return { ...state, pickerIndex: state.pickerIndex + 1 };
|
|
424
|
-
}
|
|
425
|
-
case 'SET_ENSEMBLE_PAUSED':
|
|
426
|
-
// Identity-preserving: skip the dispatch when value didn't change so
|
|
427
|
-
// the StatusBar tree doesn't re-render every poll tick.
|
|
428
|
-
if (state.ensemblePaused === action.paused)
|
|
429
|
-
return state;
|
|
430
|
-
return { ...state, ensemblePaused: action.paused };
|
|
431
|
-
case 'SET_ENSEMBLE_HELD':
|
|
432
|
-
// Identity-preserving — same rationale as SET_ENSEMBLE_PAUSED. The
|
|
433
|
-
// 2s held-poll would otherwise reconcile the StatusBar every tick
|
|
434
|
-
// even when nothing changed.
|
|
435
|
-
if (state.ensembleHeld === action.held)
|
|
436
|
-
return state;
|
|
437
|
-
return { ...state, ensembleHeld: action.held };
|
|
438
|
-
case 'APPEND_SENT_MESSAGE': {
|
|
439
|
-
const newSent = [...state.sentMessages, { to: action.to, text: action.text, timestamp: new Date().toISOString() }];
|
|
440
|
-
const trimmedSent = newSent.length > 200 ? newSent.slice(-200) : newSent;
|
|
441
|
-
return { ...state, sentMessages: trimmedSent };
|
|
442
|
-
}
|
|
443
|
-
case 'HYDRATE_SENT_MESSAGES': {
|
|
444
|
-
// Merge server-side sent messages, dedup by text+timestamp
|
|
445
|
-
const existing = new Set(state.sentMessages.map(m => `${m.text.slice(0, 60)}:${m.timestamp}`));
|
|
446
|
-
const newMsgs = action.messages.filter(m => !existing.has(`${m.text.slice(0, 60)}:${m.timestamp}`));
|
|
447
|
-
if (newMsgs.length === 0)
|
|
448
|
-
return state;
|
|
449
|
-
return { ...state, sentMessages: [...state.sentMessages, ...newMsgs] };
|
|
450
|
-
}
|
|
451
|
-
case 'ENTER_CHAT':
|
|
452
|
-
return { ...state, phase: 'chat', chatTarget: action.target };
|
|
453
|
-
case 'EXIT_CHAT':
|
|
454
|
-
return { ...state, phase: 'main', chatTarget: undefined };
|
|
455
|
-
// ── Destroy confirmation (PR-H: was `/stop`; renamed to `/destroy`) ──
|
|
456
|
-
case 'CONFIRM_STOP':
|
|
457
|
-
return {
|
|
458
|
-
...state,
|
|
459
|
-
confirmingStop: action.player,
|
|
460
|
-
...(action.reason !== undefined ? { confirmingStopReason: action.reason } : { confirmingStopReason: undefined }),
|
|
461
|
-
};
|
|
462
|
-
case 'CANCEL_STOP':
|
|
463
|
-
return { ...state, confirmingStop: undefined, confirmingStopReason: undefined };
|
|
464
|
-
// ── Disband confirmation ──
|
|
465
|
-
case 'CONFIRM_DISBAND':
|
|
466
|
-
return { ...state, confirmingDisband: action.ensemble };
|
|
467
|
-
case 'CANCEL_DISBAND':
|
|
468
|
-
return { ...state, confirmingDisband: undefined };
|
|
469
|
-
// ── Lineup confirmation ──
|
|
470
|
-
case 'CONFIRM_LINEUP':
|
|
471
|
-
return { ...state, confirmingLineup: { action: action.action, path: action.path, summary: action.summary } };
|
|
472
|
-
case 'CANCEL_LINEUP':
|
|
473
|
-
return { ...state, confirmingLineup: undefined };
|
|
474
|
-
// ── Recruit wizard ──
|
|
475
|
-
case 'ENTER_RECRUIT':
|
|
476
|
-
return {
|
|
477
|
-
...state,
|
|
478
|
-
phase: 'recruit',
|
|
479
|
-
recruitState: {
|
|
480
|
-
step: 'name',
|
|
481
|
-
answers: { ...defaultRecruitAnswers(action.defaultAgent), ...action.answers },
|
|
482
|
-
preRecruitPhase: state.phase,
|
|
483
|
-
preRecruitChatTarget: state.chatTarget,
|
|
484
|
-
},
|
|
485
|
-
};
|
|
486
|
-
case 'RECRUIT_NEXT_STEP': {
|
|
487
|
-
if (!state.recruitState)
|
|
488
|
-
return state;
|
|
489
|
-
const answers = { ...state.recruitState.answers, ...action.answer };
|
|
490
|
-
const currentIdx = exports.RECRUIT_STEPS.indexOf(state.recruitState.step);
|
|
491
|
-
const nextStep = exports.RECRUIT_STEPS[currentIdx + 1] ?? state.recruitState.step;
|
|
492
|
-
return {
|
|
493
|
-
...state,
|
|
494
|
-
recruitState: { ...state.recruitState, step: nextStep, answers },
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
case 'RECRUIT_PREV_STEP': {
|
|
498
|
-
if (!state.recruitState)
|
|
499
|
-
return state;
|
|
500
|
-
const currentIdx = exports.RECRUIT_STEPS.indexOf(state.recruitState.step);
|
|
501
|
-
if (currentIdx <= 0)
|
|
502
|
-
return state;
|
|
503
|
-
const prevStep = exports.RECRUIT_STEPS[currentIdx - 1];
|
|
504
|
-
return {
|
|
505
|
-
...state,
|
|
506
|
-
recruitState: { ...state.recruitState, step: prevStep },
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
case 'RECRUIT_SUBMIT':
|
|
510
|
-
if (!state.recruitState)
|
|
511
|
-
return state;
|
|
512
|
-
return {
|
|
513
|
-
...state,
|
|
514
|
-
recruitState: { ...state.recruitState, submitting: true },
|
|
515
|
-
};
|
|
516
|
-
case 'RECRUIT_DONE':
|
|
517
|
-
if (!state.recruitState)
|
|
518
|
-
return state;
|
|
519
|
-
return {
|
|
520
|
-
...state,
|
|
521
|
-
recruitState: {
|
|
522
|
-
...state.recruitState,
|
|
523
|
-
step: 'done',
|
|
524
|
-
submitting: false,
|
|
525
|
-
error: action.error,
|
|
526
|
-
},
|
|
527
|
-
};
|
|
528
|
-
case 'EXIT_RECRUIT': {
|
|
529
|
-
const restorePhase = state.recruitState?.preRecruitPhase || 'main';
|
|
530
|
-
const restoreChat = state.recruitState?.preRecruitChatTarget;
|
|
531
|
-
return {
|
|
532
|
-
...state,
|
|
533
|
-
phase: restorePhase,
|
|
534
|
-
chatTarget: restoreChat,
|
|
535
|
-
recruitState: undefined,
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
// ── Schedule wizard ──
|
|
539
|
-
case 'ENTER_SCHEDULE_WIZARD':
|
|
540
|
-
return {
|
|
541
|
-
...state,
|
|
542
|
-
phase: 'schedule-create',
|
|
543
|
-
scheduleWizard: {
|
|
544
|
-
step: 'target',
|
|
545
|
-
answers: { ...exports.DEFAULT_SCHEDULE_ANSWERS, ...action.answers },
|
|
546
|
-
prePhase: state.phase,
|
|
547
|
-
preChatTarget: state.chatTarget,
|
|
548
|
-
},
|
|
549
|
-
};
|
|
550
|
-
case 'SCHEDULE_NEXT_STEP': {
|
|
551
|
-
if (!state.scheduleWizard)
|
|
552
|
-
return state;
|
|
553
|
-
const answers = { ...state.scheduleWizard.answers, ...action.answer };
|
|
554
|
-
const currentIdx = exports.SCHEDULE_STEPS.indexOf(state.scheduleWizard.step);
|
|
555
|
-
// Skip timezone step for non-cron types
|
|
556
|
-
let nextIdx = currentIdx + 1;
|
|
557
|
-
if (exports.SCHEDULE_STEPS[nextIdx] === 'timezone' && answers.schedType !== 'cron') {
|
|
558
|
-
nextIdx++;
|
|
559
|
-
}
|
|
560
|
-
const nextStep = exports.SCHEDULE_STEPS[nextIdx] ?? state.scheduleWizard.step;
|
|
561
|
-
return {
|
|
562
|
-
...state,
|
|
563
|
-
scheduleWizard: { ...state.scheduleWizard, step: nextStep, answers },
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
case 'SCHEDULE_PREV_STEP': {
|
|
567
|
-
if (!state.scheduleWizard)
|
|
568
|
-
return state;
|
|
569
|
-
const currentIdx = exports.SCHEDULE_STEPS.indexOf(state.scheduleWizard.step);
|
|
570
|
-
if (currentIdx <= 0)
|
|
571
|
-
return state;
|
|
572
|
-
let prevIdx = currentIdx - 1;
|
|
573
|
-
// Skip timezone going back for non-cron
|
|
574
|
-
if (exports.SCHEDULE_STEPS[prevIdx] === 'timezone' && state.scheduleWizard.answers.schedType !== 'cron') {
|
|
575
|
-
prevIdx--;
|
|
576
|
-
}
|
|
577
|
-
const prevStep = exports.SCHEDULE_STEPS[Math.max(0, prevIdx)];
|
|
578
|
-
return {
|
|
579
|
-
...state,
|
|
580
|
-
scheduleWizard: { ...state.scheduleWizard, step: prevStep },
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
case 'SCHEDULE_SUBMIT':
|
|
584
|
-
if (!state.scheduleWizard)
|
|
585
|
-
return state;
|
|
586
|
-
return {
|
|
587
|
-
...state,
|
|
588
|
-
scheduleWizard: { ...state.scheduleWizard, submitting: true },
|
|
589
|
-
};
|
|
590
|
-
case 'SCHEDULE_DONE':
|
|
591
|
-
if (!state.scheduleWizard)
|
|
592
|
-
return state;
|
|
593
|
-
return {
|
|
594
|
-
...state,
|
|
595
|
-
scheduleWizard: {
|
|
596
|
-
...state.scheduleWizard,
|
|
597
|
-
step: 'done',
|
|
598
|
-
submitting: false,
|
|
599
|
-
error: action.error,
|
|
600
|
-
},
|
|
601
|
-
};
|
|
602
|
-
case 'EXIT_SCHEDULE_WIZARD': {
|
|
603
|
-
const restoreP = state.scheduleWizard?.prePhase || 'main';
|
|
604
|
-
const restoreC = state.scheduleWizard?.preChatTarget;
|
|
605
|
-
return {
|
|
606
|
-
...state,
|
|
607
|
-
phase: restoreP,
|
|
608
|
-
chatTarget: restoreC,
|
|
609
|
-
scheduleWizard: undefined,
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
// ── Create Ensemble wizard ──
|
|
613
|
-
case 'ENTER_CREATE_ENSEMBLE':
|
|
614
|
-
return {
|
|
615
|
-
...state,
|
|
616
|
-
phase: 'create-ensemble',
|
|
617
|
-
createEnsembleState: {
|
|
618
|
-
step: 'name',
|
|
619
|
-
answers: { ...exports.DEFAULT_CREATE_ENSEMBLE_ANSWERS },
|
|
620
|
-
prePhase: state.phase,
|
|
621
|
-
},
|
|
622
|
-
};
|
|
623
|
-
case 'CREATE_ENSEMBLE_NEXT_STEP': {
|
|
624
|
-
if (!state.createEnsembleState)
|
|
625
|
-
return state;
|
|
626
|
-
const answers = { ...state.createEnsembleState.answers, ...action.answer };
|
|
627
|
-
const currentIdx = exports.CREATE_ENSEMBLE_STEPS.indexOf(state.createEnsembleState.step);
|
|
628
|
-
const nextStep = exports.CREATE_ENSEMBLE_STEPS[currentIdx + 1] ?? state.createEnsembleState.step;
|
|
629
|
-
return {
|
|
630
|
-
...state,
|
|
631
|
-
createEnsembleState: { ...state.createEnsembleState, step: nextStep, answers },
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
case 'CREATE_ENSEMBLE_PREV_STEP': {
|
|
635
|
-
if (!state.createEnsembleState)
|
|
636
|
-
return state;
|
|
637
|
-
const currentIdx = exports.CREATE_ENSEMBLE_STEPS.indexOf(state.createEnsembleState.step);
|
|
638
|
-
if (currentIdx <= 0)
|
|
639
|
-
return state;
|
|
640
|
-
const prevStep = exports.CREATE_ENSEMBLE_STEPS[currentIdx - 1];
|
|
641
|
-
return {
|
|
642
|
-
...state,
|
|
643
|
-
createEnsembleState: { ...state.createEnsembleState, step: prevStep },
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
case 'CREATE_ENSEMBLE_SUBMIT':
|
|
647
|
-
if (!state.createEnsembleState)
|
|
648
|
-
return state;
|
|
649
|
-
return {
|
|
650
|
-
...state,
|
|
651
|
-
createEnsembleState: { ...state.createEnsembleState, submitting: true },
|
|
652
|
-
};
|
|
653
|
-
case 'CREATE_ENSEMBLE_DONE':
|
|
654
|
-
if (!state.createEnsembleState)
|
|
655
|
-
return state;
|
|
656
|
-
if (!action.error && action.ensemble) {
|
|
657
|
-
// Success — navigate to new ensemble
|
|
658
|
-
return {
|
|
659
|
-
...state,
|
|
660
|
-
phase: 'main',
|
|
661
|
-
view: 'ensemble',
|
|
662
|
-
activeEnsemble: action.ensemble,
|
|
663
|
-
createEnsembleState: undefined,
|
|
664
|
-
players: [],
|
|
665
|
-
messages: [],
|
|
666
|
-
sentMessages: [],
|
|
667
|
-
conductorHistory: [],
|
|
668
|
-
schedules: [],
|
|
669
|
-
conversation: null,
|
|
670
|
-
selectedPlayerIndex: 0,
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
return {
|
|
674
|
-
...state,
|
|
675
|
-
createEnsembleState: {
|
|
676
|
-
...state.createEnsembleState,
|
|
677
|
-
step: 'done',
|
|
678
|
-
submitting: false,
|
|
679
|
-
error: action.error,
|
|
680
|
-
},
|
|
681
|
-
};
|
|
682
|
-
case 'EXIT_CREATE_ENSEMBLE': {
|
|
683
|
-
const restoreCE = state.createEnsembleState?.prePhase || 'main';
|
|
684
|
-
return {
|
|
685
|
-
...state,
|
|
686
|
-
phase: restoreCE,
|
|
687
|
-
createEnsembleState: undefined,
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
case 'OPEN_HOME_MODAL':
|
|
691
|
-
return {
|
|
692
|
-
...state,
|
|
693
|
-
homeModal: action.modal,
|
|
694
|
-
homeModalSubmitting: false,
|
|
695
|
-
homeModalError: undefined,
|
|
696
|
-
};
|
|
697
|
-
case 'CLOSE_HOME_MODAL':
|
|
698
|
-
return {
|
|
699
|
-
...state,
|
|
700
|
-
homeModal: undefined,
|
|
701
|
-
homeModalSubmitting: false,
|
|
702
|
-
homeModalError: undefined,
|
|
703
|
-
};
|
|
704
|
-
case 'SET_HOME_MODAL_STATUS':
|
|
705
|
-
return {
|
|
706
|
-
...state,
|
|
707
|
-
homeModalSubmitting: action.submitting ?? state.homeModalSubmitting,
|
|
708
|
-
homeModalError: action.error,
|
|
709
|
-
};
|
|
710
|
-
case 'CONFIRM_ENSEMBLE_DESTROY':
|
|
711
|
-
return {
|
|
712
|
-
...state,
|
|
713
|
-
confirmingEnsembleDestroy: { ensemble: action.ensemble, input: '' },
|
|
714
|
-
};
|
|
715
|
-
case 'ENSEMBLE_DESTROY_INPUT': {
|
|
716
|
-
if (!state.confirmingEnsembleDestroy)
|
|
717
|
-
return state;
|
|
718
|
-
return {
|
|
719
|
-
...state,
|
|
720
|
-
confirmingEnsembleDestroy: {
|
|
721
|
-
...state.confirmingEnsembleDestroy,
|
|
722
|
-
input: action.input,
|
|
723
|
-
error: undefined,
|
|
724
|
-
},
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
case 'ENSEMBLE_DESTROY_SUBMIT_BUSY': {
|
|
728
|
-
if (!state.confirmingEnsembleDestroy)
|
|
729
|
-
return state;
|
|
730
|
-
return {
|
|
731
|
-
...state,
|
|
732
|
-
confirmingEnsembleDestroy: { ...state.confirmingEnsembleDestroy, submitting: true, error: undefined },
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
case 'ENSEMBLE_DESTROY_MISMATCH': {
|
|
736
|
-
if (!state.confirmingEnsembleDestroy)
|
|
737
|
-
return state;
|
|
738
|
-
const { ensemble, input } = state.confirmingEnsembleDestroy;
|
|
739
|
-
return {
|
|
740
|
-
...state,
|
|
741
|
-
confirmingEnsembleDestroy: {
|
|
742
|
-
...state.confirmingEnsembleDestroy,
|
|
743
|
-
submitting: false,
|
|
744
|
-
error: `"${input}" \u2260 "${ensemble}" \u2014 type the ensemble name exactly to confirm.`,
|
|
745
|
-
},
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
case 'CANCEL_ENSEMBLE_DESTROY':
|
|
749
|
-
return { ...state, confirmingEnsembleDestroy: undefined };
|
|
750
|
-
default:
|
|
751
|
-
return state;
|
|
752
|
-
}
|
|
753
|
-
}
|