drexler 0.2.6 → 0.2.7
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/ui/App.tsx +124 -87
- package/src/ui/SynergyEvent.tsx +73 -57
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.7
|
|
4
|
+
|
|
5
|
+
- Stabilized `/synergy` animation layout with fixed row budgeting, a capped centered event panel, and completion only at 100%.
|
|
6
|
+
- Hardened interactive busy-state handling so input stays locked during active LLM requests and synergy events.
|
|
7
|
+
- Added lifecycle and row-budget coverage for the animated synergy flow.
|
|
8
|
+
|
|
3
9
|
## 0.2.6
|
|
4
10
|
|
|
5
11
|
- Upgraded `/synergy` into a rotating animated Ink event with staged reveals, progress, KPI tickers, and themed finale copy.
|
package/package.json
CHANGED
package/src/ui/App.tsx
CHANGED
|
@@ -167,6 +167,7 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
167
167
|
const cursor = draft.cursor;
|
|
168
168
|
const [streaming, setStreaming] = useState<string | null>(null);
|
|
169
169
|
const [thinking, setThinking] = useState<string | null>(null);
|
|
170
|
+
const [requestInFlight, setRequestInFlight] = useState(false);
|
|
170
171
|
const [synergyEvent, setSynergyEvent] = useState<ActiveSynergyEvent | null>(
|
|
171
172
|
null,
|
|
172
173
|
);
|
|
@@ -219,6 +220,8 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
219
220
|
const streamTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
220
221
|
const abortRef = useRef<AbortController | null>(null);
|
|
221
222
|
const cancelledRef = useRef(false);
|
|
223
|
+
const requestInFlightRef = useRef(false);
|
|
224
|
+
const synergyActiveRef = useRef(false);
|
|
222
225
|
const mountedRef = useRef(true);
|
|
223
226
|
const exitingRef = useRef(false);
|
|
224
227
|
const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -234,6 +237,18 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
234
237
|
if (exitingRef.current) return;
|
|
235
238
|
exitingRef.current = true;
|
|
236
239
|
abortRef.current?.abort();
|
|
240
|
+
if (streamTimerRef.current !== null) {
|
|
241
|
+
clearTimeout(streamTimerRef.current);
|
|
242
|
+
streamTimerRef.current = null;
|
|
243
|
+
}
|
|
244
|
+
if (synergyTimerRef.current !== null) {
|
|
245
|
+
clearInterval(synergyTimerRef.current);
|
|
246
|
+
synergyTimerRef.current = null;
|
|
247
|
+
}
|
|
248
|
+
requestInFlightRef.current = false;
|
|
249
|
+
synergyActiveRef.current = false;
|
|
250
|
+
setRequestInFlight(false);
|
|
251
|
+
setSynergyEvent(null);
|
|
237
252
|
setExitMsg(msg);
|
|
238
253
|
exitTimerRef.current = setTimeout(() => exit(), 50);
|
|
239
254
|
},
|
|
@@ -263,6 +278,7 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
263
278
|
|
|
264
279
|
setThinking(null);
|
|
265
280
|
setStreaming(null);
|
|
281
|
+
synergyActiveRef.current = true;
|
|
266
282
|
setDeskStatus("idle");
|
|
267
283
|
setDeskNotice("synergy event");
|
|
268
284
|
setSynergyEvent({ event, frame });
|
|
@@ -282,7 +298,8 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
282
298
|
synergyTimerRef.current = null;
|
|
283
299
|
}
|
|
284
300
|
setSynergyEvent(null);
|
|
285
|
-
|
|
301
|
+
synergyActiveRef.current = false;
|
|
302
|
+
setDeskNotice("synergy complete");
|
|
286
303
|
setWitticism(event.finalLine);
|
|
287
304
|
addItem("system", event.transcriptLine);
|
|
288
305
|
}
|
|
@@ -290,99 +307,109 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
290
307
|
}, [addItem]);
|
|
291
308
|
|
|
292
309
|
const runLLM = useCallback(async (instruction?: string) => {
|
|
310
|
+
if (requestInFlightRef.current) return;
|
|
311
|
+
requestInFlightRef.current = true;
|
|
312
|
+
setRequestInFlight(true);
|
|
293
313
|
const startedAt = Date.now();
|
|
294
|
-
setThinking(pick(THINKING_LINES));
|
|
295
|
-
setDeskStatus("idle");
|
|
296
|
-
setDeskNotice(null);
|
|
297
|
-
setFallbackModel(null);
|
|
298
|
-
streamBufRef.current = "";
|
|
299
|
-
setStreaming(null);
|
|
300
|
-
let firstToken = true;
|
|
301
|
-
abortRef.current = new AbortController();
|
|
302
|
-
let result: Awaited<ReturnType<typeof streamChat>> | undefined;
|
|
303
|
-
let caughtErr: unknown = null;
|
|
304
314
|
try {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
315
|
+
setThinking(pick(THINKING_LINES));
|
|
316
|
+
setDeskStatus("idle");
|
|
317
|
+
setDeskNotice(null);
|
|
318
|
+
setFallbackModel(null);
|
|
319
|
+
streamBufRef.current = "";
|
|
320
|
+
setStreaming(null);
|
|
321
|
+
let firstToken = true;
|
|
322
|
+
abortRef.current = new AbortController();
|
|
323
|
+
let result: Awaited<ReturnType<typeof streamChat>> | undefined;
|
|
324
|
+
let caughtErr: unknown = null;
|
|
325
|
+
try {
|
|
326
|
+
result = await streamChat({
|
|
327
|
+
apiKey: config.apiKey,
|
|
328
|
+
model,
|
|
329
|
+
fallbackModel: pickFallback(model),
|
|
330
|
+
messages: instruction
|
|
331
|
+
? [
|
|
332
|
+
...buildMessagesWithReminder(conversation),
|
|
333
|
+
{ role: "system", content: instruction },
|
|
334
|
+
]
|
|
335
|
+
: buildMessagesWithReminder(conversation),
|
|
336
|
+
onToken: (t) => {
|
|
337
|
+
if (!mountedRef.current || exitingRef.current) return;
|
|
338
|
+
if (firstToken) {
|
|
339
|
+
setThinking(null);
|
|
340
|
+
firstToken = false;
|
|
341
|
+
}
|
|
342
|
+
pushTokenToStream(t);
|
|
343
|
+
},
|
|
344
|
+
signal: abortRef.current.signal,
|
|
345
|
+
fetchFn,
|
|
346
|
+
});
|
|
347
|
+
} catch (err) {
|
|
348
|
+
caughtErr = err;
|
|
349
|
+
} finally {
|
|
350
|
+
if (streamTimerRef.current !== null) {
|
|
351
|
+
clearTimeout(streamTimerRef.current);
|
|
352
|
+
streamTimerRef.current = null;
|
|
353
|
+
}
|
|
354
|
+
abortRef.current = null;
|
|
355
|
+
}
|
|
356
|
+
if (!mountedRef.current || exitingRef.current) return;
|
|
357
|
+
if (caughtErr) {
|
|
358
|
+
const msg = caughtErr instanceof Error ? caughtErr.message : String(caughtErr);
|
|
359
|
+
setThinking(null);
|
|
360
|
+
setStreaming(null);
|
|
361
|
+
addItem("system", `${STREAM_ERROR} [${msg}]`);
|
|
362
|
+
setDeskStatus("error");
|
|
363
|
+
setDeskNotice(msg);
|
|
364
|
+
setMsgCount(conversation.length);
|
|
365
|
+
return;
|
|
332
366
|
}
|
|
333
|
-
abortRef.current = null;
|
|
334
|
-
}
|
|
335
|
-
if (!mountedRef.current) return;
|
|
336
|
-
if (caughtErr) {
|
|
337
|
-
const msg = caughtErr instanceof Error ? caughtErr.message : String(caughtErr);
|
|
338
367
|
setThinking(null);
|
|
339
368
|
setStreaming(null);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
cancelledRef.current = false;
|
|
351
|
-
if (result?.content) {
|
|
369
|
+
setLastLatencyMs(Date.now() - startedAt);
|
|
370
|
+
if (cancelledRef.current) {
|
|
371
|
+
cancelledRef.current = false;
|
|
372
|
+
if (result?.content) {
|
|
373
|
+
conversation.push("assistant", result.content);
|
|
374
|
+
addItem("assistant", result.content);
|
|
375
|
+
}
|
|
376
|
+
addItem("system", "(cancelled — Drexler taking lunch)");
|
|
377
|
+
setDeskNotice("response cancelled");
|
|
378
|
+
} else if (result?.ok) {
|
|
352
379
|
conversation.push("assistant", result.content);
|
|
353
380
|
addItem("assistant", result.content);
|
|
381
|
+
const notices: string[] = [];
|
|
382
|
+
if (result.fellBack) {
|
|
383
|
+
addItem("system", `(fell back to ${result.modelUsed})`);
|
|
384
|
+
notices.push(`fallback ${result.modelUsed}`);
|
|
385
|
+
setFallbackModel(result.modelUsed);
|
|
386
|
+
}
|
|
387
|
+
if (detectPersonaDrift(result.content)) {
|
|
388
|
+
addItem("system", `(persona drift detected — model used 'I')`);
|
|
389
|
+
notices.push("persona drift detected");
|
|
390
|
+
}
|
|
391
|
+
setDeskNotice(notices.length > 0 ? notices.join(" · ") : null);
|
|
392
|
+
} else if (result?.interrupted) {
|
|
393
|
+
conversation.push("assistant", result.content);
|
|
394
|
+
addItem("assistant", result.content);
|
|
395
|
+
addItem("system", "(stream interrupted — partial response saved)");
|
|
396
|
+
setDeskStatus("error");
|
|
397
|
+
setDeskNotice("stream interrupted; partial response saved");
|
|
398
|
+
} else {
|
|
399
|
+
const detail = result?.error ? ` [${result.error}]` : "";
|
|
400
|
+
addItem("system", `${STREAM_ERROR}${detail}`);
|
|
401
|
+
setDeskStatus("error");
|
|
402
|
+
setDeskNotice(result?.error ?? "stream error");
|
|
354
403
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
addItem("system", `(fell back to ${result.modelUsed})`);
|
|
363
|
-
notices.push(`fallback ${result.modelUsed}`);
|
|
364
|
-
setFallbackModel(result.modelUsed);
|
|
365
|
-
}
|
|
366
|
-
if (detectPersonaDrift(result.content)) {
|
|
367
|
-
addItem("system", `(persona drift detected — model used 'I')`);
|
|
368
|
-
notices.push("persona drift detected");
|
|
404
|
+
setMsgCount(conversation.length);
|
|
405
|
+
setTokenCount(conversation.approximateTokens());
|
|
406
|
+
setWitticism(pick(WITTICISMS));
|
|
407
|
+
} finally {
|
|
408
|
+
requestInFlightRef.current = false;
|
|
409
|
+
if (mountedRef.current) {
|
|
410
|
+
setRequestInFlight(false);
|
|
369
411
|
}
|
|
370
|
-
setDeskNotice(notices.length > 0 ? notices.join(" · ") : null);
|
|
371
|
-
} else if (result?.interrupted) {
|
|
372
|
-
conversation.push("assistant", result.content);
|
|
373
|
-
addItem("assistant", result.content);
|
|
374
|
-
addItem("system", "(stream interrupted — partial response saved)");
|
|
375
|
-
setDeskStatus("error");
|
|
376
|
-
setDeskNotice("stream interrupted; partial response saved");
|
|
377
|
-
} else {
|
|
378
|
-
const detail = result?.error ? ` [${result.error}]` : "";
|
|
379
|
-
addItem("system", `${STREAM_ERROR}${detail}`);
|
|
380
|
-
setDeskStatus("error");
|
|
381
|
-
setDeskNotice(result?.error ?? "stream error");
|
|
382
412
|
}
|
|
383
|
-
setMsgCount(conversation.length);
|
|
384
|
-
setTokenCount(conversation.approximateTokens());
|
|
385
|
-
setWitticism(pick(WITTICISMS));
|
|
386
413
|
}, [
|
|
387
414
|
config,
|
|
388
415
|
model,
|
|
@@ -465,6 +492,7 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
465
492
|
|
|
466
493
|
const onSubmit = useCallback(
|
|
467
494
|
async (raw: string) => {
|
|
495
|
+
if (requestInFlightRef.current || synergyActiveRef.current) return;
|
|
468
496
|
const line = raw.trim();
|
|
469
497
|
if (line === "") {
|
|
470
498
|
addItem("system", EMPTY_NUDGE);
|
|
@@ -484,9 +512,15 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
484
512
|
);
|
|
485
513
|
|
|
486
514
|
useInput((char, key) => {
|
|
487
|
-
|
|
515
|
+
const busy =
|
|
516
|
+
requestInFlightRef.current ||
|
|
517
|
+
synergyActiveRef.current ||
|
|
518
|
+
streaming !== null ||
|
|
519
|
+
thinking !== null ||
|
|
520
|
+
synergyEvent !== null;
|
|
521
|
+
if (busy) {
|
|
488
522
|
if (key.escape) {
|
|
489
|
-
if (synergyEvent !== null) {
|
|
523
|
+
if (synergyActiveRef.current || synergyEvent !== null) {
|
|
490
524
|
return;
|
|
491
525
|
}
|
|
492
526
|
cancelledRef.current = true;
|
|
@@ -659,10 +693,13 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
659
693
|
if (synergyTimerRef.current !== null) {
|
|
660
694
|
clearInterval(synergyTimerRef.current);
|
|
661
695
|
}
|
|
696
|
+
requestInFlightRef.current = false;
|
|
697
|
+
synergyActiveRef.current = false;
|
|
662
698
|
};
|
|
663
699
|
}, []);
|
|
664
700
|
|
|
665
|
-
const isBusy =
|
|
701
|
+
const isBusy =
|
|
702
|
+
requestInFlight || streaming !== null || thinking !== null || synergyEvent !== null;
|
|
666
703
|
const headerStatus = isBusy ? "streaming" : deskStatus;
|
|
667
704
|
const visibleTranscriptRows = synergyEvent
|
|
668
705
|
? Math.max(1, maxTranscriptRows - synergyEventRows(chromeWidth, isCompact))
|
package/src/ui/SynergyEvent.tsx
CHANGED
|
@@ -15,6 +15,9 @@ export interface SynergyEventDefinition {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const SYNERGY_EVENT_FRAMES = 28;
|
|
18
|
+
const FULL_EVENT_WIDTH = 88;
|
|
19
|
+
const FULL_EVENT_ROWS = 12;
|
|
20
|
+
const FULL_EVENT_ART_ROWS = 4;
|
|
18
21
|
|
|
19
22
|
export const SYNERGY_EVENTS: readonly SynergyEventDefinition[] = [
|
|
20
23
|
{
|
|
@@ -67,7 +70,7 @@ export const SYNERGY_EVENTS: readonly SynergyEventDefinition[] = [
|
|
|
67
70
|
"status: billable",
|
|
68
71
|
"decision rights unclear",
|
|
69
72
|
],
|
|
70
|
-
finalLine: "Drexler
|
|
73
|
+
finalLine: "Drexler approves synergy. Nobody asks what changed.",
|
|
71
74
|
transcriptLine: "SYNERGY EVENT: boardroom siren produced measurable vibes.",
|
|
72
75
|
},
|
|
73
76
|
{
|
|
@@ -156,7 +159,10 @@ function stageAt(event: SynergyEventDefinition, frame: number): string {
|
|
|
156
159
|
|
|
157
160
|
function visibleArt(event: SynergyEventDefinition, frame: number): readonly string[] {
|
|
158
161
|
const progress = frameProgress(frame);
|
|
159
|
-
const count = Math.max(
|
|
162
|
+
const count = Math.max(
|
|
163
|
+
1,
|
|
164
|
+
Math.ceil(progress * Math.min(event.art.length, FULL_EVENT_ART_ROWS)),
|
|
165
|
+
);
|
|
160
166
|
return event.art.slice(0, count);
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -180,7 +186,7 @@ function SynergyEventInner({
|
|
|
180
186
|
const t = useTheme();
|
|
181
187
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
182
188
|
const progress = frameProgress(frame);
|
|
183
|
-
const done =
|
|
189
|
+
const done = frame >= SYNERGY_EVENT_FRAMES - 1;
|
|
184
190
|
const tiny = safeWidth < 38 || compact;
|
|
185
191
|
|
|
186
192
|
if (tiny) {
|
|
@@ -196,70 +202,80 @@ function SynergyEventInner({
|
|
|
196
202
|
);
|
|
197
203
|
}
|
|
198
204
|
|
|
199
|
-
const
|
|
205
|
+
const panelWidth = Math.min(safeWidth, FULL_EVENT_WIDTH);
|
|
206
|
+
const innerWidth = Math.max(1, panelWidth - 4);
|
|
200
207
|
const title = `${event.title} · ${event.subtitle}`;
|
|
201
208
|
const progressWidth = Math.max(8, Math.min(34, innerWidth - 18));
|
|
202
209
|
const progressPct = `${Math.round(progress * 100)
|
|
203
210
|
.toString()
|
|
204
211
|
.padStart(3, " ")}%`;
|
|
205
|
-
const artWidth = Math.max(1, innerWidth -
|
|
212
|
+
const artWidth = Math.max(1, innerWidth - 4);
|
|
206
213
|
const kpi = kpiAt(event, frame);
|
|
207
214
|
|
|
208
215
|
return (
|
|
209
|
-
<Box
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
</Text>
|
|
222
|
-
<Text color={t.primaryDim}> ─ </Text>
|
|
223
|
-
<Text color={t.dim} wrap="truncate">
|
|
224
|
-
{fitDisplayText(title, Math.max(1, innerWidth - 16))}
|
|
225
|
-
</Text>
|
|
226
|
-
</Box>
|
|
227
|
-
<Box marginTop={1} flexDirection="column">
|
|
228
|
-
{visibleArt(event, frame).map((line, idx) => (
|
|
229
|
-
<Text key={`${event.id}-${idx}`} color={t.primaryLight} wrap="truncate">
|
|
230
|
-
{fitDisplayText(line, artWidth)}
|
|
216
|
+
<Box width={safeWidth} justifyContent="center" flexShrink={1}>
|
|
217
|
+
<Box
|
|
218
|
+
flexDirection="column"
|
|
219
|
+
borderStyle="round"
|
|
220
|
+
borderColor={done ? t.primaryLight : t.warning}
|
|
221
|
+
paddingX={1}
|
|
222
|
+
width={panelWidth}
|
|
223
|
+
flexShrink={1}
|
|
224
|
+
>
|
|
225
|
+
<Box>
|
|
226
|
+
<Text color={t.warning} bold>
|
|
227
|
+
SYNERGY EVENT
|
|
231
228
|
</Text>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
<Text color={t.primaryDim}>[</Text>
|
|
236
|
-
<Text color={done ? t.primaryLight : t.warning}>
|
|
237
|
-
{bar(progress, progressWidth)}
|
|
238
|
-
</Text>
|
|
239
|
-
<Text color={t.primaryDim}>] </Text>
|
|
240
|
-
<Text color={t.dim}>{progressPct}</Text>
|
|
241
|
-
</Box>
|
|
242
|
-
<Box>
|
|
243
|
-
<Text color={t.primaryLight} bold>
|
|
244
|
-
◆{" "}
|
|
245
|
-
</Text>
|
|
246
|
-
<Text color={t.text} wrap="truncate">
|
|
247
|
-
{fitDisplayText(stageAt(event, frame), Math.max(1, innerWidth - 2))}
|
|
248
|
-
</Text>
|
|
249
|
-
</Box>
|
|
250
|
-
<Box>
|
|
251
|
-
<Text color={t.primaryDim}>ticker </Text>
|
|
252
|
-
<Text color={t.warning} wrap="truncate">
|
|
253
|
-
{fitDisplayText(kpi, Math.max(1, innerWidth - 7))}
|
|
254
|
-
</Text>
|
|
255
|
-
</Box>
|
|
256
|
-
{done ? (
|
|
257
|
-
<Box marginTop={1}>
|
|
258
|
-
<Text color={t.primaryLight} bold wrap="truncate">
|
|
259
|
-
{fitDisplayText(event.finalLine, innerWidth)}
|
|
229
|
+
<Text color={t.primaryDim}> ─ </Text>
|
|
230
|
+
<Text color={t.dim} wrap="truncate">
|
|
231
|
+
{fitDisplayText(title, Math.max(1, innerWidth - 18))}
|
|
260
232
|
</Text>
|
|
261
233
|
</Box>
|
|
262
|
-
|
|
234
|
+
<Box flexDirection="column">
|
|
235
|
+
{event.art.slice(0, FULL_EVENT_ART_ROWS).map((line, idx) => {
|
|
236
|
+
const revealed = idx < visibleArt(event, frame).length;
|
|
237
|
+
return (
|
|
238
|
+
<Text
|
|
239
|
+
key={`${event.id}-${idx}`}
|
|
240
|
+
color={revealed ? t.primaryLight : t.primaryDim}
|
|
241
|
+
wrap="truncate"
|
|
242
|
+
>
|
|
243
|
+
{revealed ? fitDisplayText(line, artWidth) : " "}
|
|
244
|
+
</Text>
|
|
245
|
+
);
|
|
246
|
+
})}
|
|
247
|
+
</Box>
|
|
248
|
+
<Box>
|
|
249
|
+
<Text color={t.primaryDim}>[</Text>
|
|
250
|
+
<Text color={done ? t.primaryLight : t.warning}>
|
|
251
|
+
{bar(progress, progressWidth)}
|
|
252
|
+
</Text>
|
|
253
|
+
<Text color={t.primaryDim}>] </Text>
|
|
254
|
+
<Text color={t.dim}>{progressPct}</Text>
|
|
255
|
+
</Box>
|
|
256
|
+
<Box>
|
|
257
|
+
<Text color={t.primaryLight} bold>
|
|
258
|
+
◆{" "}
|
|
259
|
+
</Text>
|
|
260
|
+
<Text color={t.text} wrap="truncate">
|
|
261
|
+
{fitDisplayText(stageAt(event, frame), Math.max(1, innerWidth - 4))}
|
|
262
|
+
</Text>
|
|
263
|
+
</Box>
|
|
264
|
+
<Box>
|
|
265
|
+
<Text color={t.primaryDim}>ticker </Text>
|
|
266
|
+
<Text color={t.warning} wrap="truncate">
|
|
267
|
+
{fitDisplayText(kpi, Math.max(1, innerWidth - 9))}
|
|
268
|
+
</Text>
|
|
269
|
+
</Box>
|
|
270
|
+
<Box>
|
|
271
|
+
<Text color={done ? t.primaryLight : t.primaryDim} bold={done} wrap="truncate">
|
|
272
|
+
{fitDisplayText(
|
|
273
|
+
done ? event.finalLine : "awaiting committee approval...",
|
|
274
|
+
Math.max(1, innerWidth - 2),
|
|
275
|
+
)}
|
|
276
|
+
</Text>
|
|
277
|
+
</Box>
|
|
278
|
+
</Box>
|
|
263
279
|
</Box>
|
|
264
280
|
);
|
|
265
281
|
}
|
|
@@ -268,7 +284,7 @@ export const SynergyEvent = memo(SynergyEventInner);
|
|
|
268
284
|
|
|
269
285
|
export function synergyEventRows(width: number, compact = false): number {
|
|
270
286
|
if (compact || width < 38) return 1;
|
|
271
|
-
return
|
|
287
|
+
return FULL_EVENT_ROWS;
|
|
272
288
|
}
|
|
273
289
|
|
|
274
290
|
export function synergyEventMaxRowWidth(
|