@echomem/mcp 1.4.27 → 1.4.29

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.
@@ -169,8 +169,111 @@ export const SETUP_PAGE_CLIENT_REPORT_CITY = String.raw ` /* ---------- ful
169
169
  setScanMode(false);
170
170
  setCityMode(false);
171
171
  }
172
+ function formatReportEta(seconds) {
173
+ seconds = Math.max(1, Math.round(seconds));
174
+ if (seconds >= 3600) {
175
+ var hours = Math.floor(seconds / 3600);
176
+ var minutesAfterHours = Math.ceil((seconds % 3600) / 60);
177
+ return "About " + hours + "h" + (minutesAfterHours ? " " + minutesAfterHours + "m" : "") + " remaining";
178
+ }
179
+ if (seconds >= 60) {
180
+ var minutes = Math.floor(seconds / 60);
181
+ var secondsAfterMinutes = seconds % 60;
182
+ return "About " + minutes + "m " + secondsAfterMinutes + "s remaining";
183
+ }
184
+ return "About " + seconds + "s remaining";
185
+ }
186
+ function estimateReportRemaining(progress, overall, stage) {
187
+ void overall;
188
+ var now = Date.now();
189
+ var stageElapsed = Math.max(0, Number(progress.stageElapsedMs || 0) / 1000);
190
+ var stageDone = Math.max(0, Number(progress.stageDone || 0));
191
+ var stageTotal = Math.max(0, Number(progress.stageTotal || 0));
192
+ function tickExistingEstimate() {
193
+ if (reportEtaDisplaySeconds <= 0) {
194
+ return { seconds: 0, overdue: false, calibrating: true };
195
+ }
196
+ var sinceLast = reportEtaUpdatedAt ? Math.max(0, (now - reportEtaUpdatedAt) / 1000) : 0;
197
+ reportEtaDisplaySeconds = Math.max(0, reportEtaDisplaySeconds - sinceLast);
198
+ reportEtaUpdatedAt = now;
199
+ return {
200
+ seconds: reportEtaDisplaySeconds,
201
+ overdue: now >= reportEtaDeadline || reportEtaDisplaySeconds <= 0,
202
+ calibrating: false,
203
+ };
204
+ }
205
+
206
+ // The visual progress bands describe work completed, not wall-clock cost. In particular,
207
+ // reaching the 75% classification band does NOT mean only 25% of the time remains.
208
+ // Calibrate only from movement inside the expensive classification stage.
209
+ if (stage !== "classifying-repeated-context") {
210
+ return tickExistingEstimate();
211
+ }
212
+ if (reportEtaStage !== stage) {
213
+ reportEtaStage = stage;
214
+ reportEtaStageDone = stageDone;
215
+ reportEtaStageElapsed = stageElapsed;
216
+ reportEtaObservedRate = 0;
217
+ return { seconds: 0, overdue: false, calibrating: true };
218
+ }
219
+
220
+ // The first canonical completions can be cache hits and arrive almost instantly. Treat the
221
+ // first observation as a baseline, then measure only later movement over real elapsed time.
222
+ if (stageDone > reportEtaStageDone && stageElapsed > reportEtaStageElapsed) {
223
+ var sampleElapsed = stageElapsed - reportEtaStageElapsed;
224
+ var sampleDone = stageDone - reportEtaStageDone;
225
+ if (sampleElapsed >= 2 && sampleDone > 0) {
226
+ var sampleRate = sampleDone / sampleElapsed;
227
+ reportEtaObservedRate = reportEtaObservedRate > 0
228
+ ? reportEtaObservedRate * 0.65 + sampleRate * 0.35
229
+ : sampleRate;
230
+ reportEtaStageDone = stageDone;
231
+ reportEtaStageElapsed = stageElapsed;
232
+ }
233
+ }
234
+ if (
235
+ reportEtaObservedRate <= 0 ||
236
+ !isFinite(reportEtaObservedRate)
237
+ ) {
238
+ return { seconds: 0, overdue: false, calibrating: true };
239
+ }
240
+ if (stageTotal <= stageDone) return tickExistingEstimate();
241
+
242
+ // Add a safety margin for the tail of slower sessions plus final report reconciliation.
243
+ // This intentionally prefers a slightly early completion over a fake three-second promise.
244
+ var observedRemaining = ((stageTotal - stageDone) / reportEtaObservedRate) * 1.35 + 5;
245
+ observedRemaining = Math.min(15 * 60, Math.max(8, observedRemaining));
246
+
247
+ if (!reportEtaDeadline) {
248
+ reportEtaDeadline = now + observedRemaining * 1000;
249
+ reportEtaDisplaySeconds = observedRemaining;
250
+ } else {
251
+ var sinceLast = reportEtaUpdatedAt ? Math.max(0, (now - reportEtaUpdatedAt) / 1000) : 0;
252
+ var clockRemaining = Math.max(0, reportEtaDisplaySeconds - sinceLast);
253
+ // A faster observed rate may shorten the estimate. A slower sample never pushes the
254
+ // countdown back upward; passing the first deadline changes the copy to "Finishing up".
255
+ reportEtaDisplaySeconds = Math.min(clockRemaining, observedRemaining);
256
+ }
257
+ reportEtaUpdatedAt = now;
258
+ return {
259
+ seconds: Math.max(0, reportEtaDisplaySeconds),
260
+ overdue: now >= reportEtaDeadline || reportEtaDisplaySeconds <= 0,
261
+ calibrating: false,
262
+ };
263
+ }
172
264
  function renderLoading() {
173
265
  var progress = reportEnvelope && reportEnvelope.kind === "scanning" ? (reportEnvelope.progress || {}) : {};
266
+ var scanId = String(reportEnvelope && reportEnvelope.scanId || "");
267
+ if (scanId !== reportEtaScanId) {
268
+ reportEtaScanId = scanId;
269
+ reportEtaDeadline = 0;
270
+ reportEtaDisplaySeconds = 0;
271
+ reportEtaUpdatedAt = 0;
272
+ reportEtaStage = "";
273
+ reportEtaStageDone = 0;
274
+ reportEtaStageElapsed = 0;
275
+ reportEtaObservedRate = 0;
276
+ }
174
277
  var scannedValue = Number(progress.scanned);
175
278
  var totalValue = Number(progress.total);
176
279
  var scanned = isFinite(scannedValue) ? Math.max(0, scannedValue) : 0;
@@ -182,26 +285,48 @@ export const SETUP_PAGE_CLIENT_REPORT_CITY = String.raw ` /* ---------- ful
182
285
  var overall = isFinite(overallValue) ? Math.min(1, Math.max(0, overallValue)) : 0;
183
286
  var pct = Math.round(overall * 100);
184
287
  var stage = String(progress.stage || "starting");
288
+ var stageDone = Math.max(0, Number(progress.stageDone || 0));
289
+ var stageTotal = Math.max(0, Number(progress.stageTotal || 0));
290
+ var stageElapsed = Math.max(0, Number(progress.stageElapsedMs || 0));
291
+ var beat = Math.floor(stageElapsed / 4500);
185
292
  var progressText = "";
186
293
  if (total > 0 && stage === "reading-transcripts") {
187
294
  progressText = "Reading " + number(visibleScanned) + " of " + number(total) + " sessions…";
188
- } else if (stage === "building-summary" || stage === "classifying-repeated-context") {
189
- progressText = total > 0
190
- ? "Analyzing repeated context across " + number(total) + " sessions…"
191
- : "Analyzing repeated context…";
295
+ } else if (stage === "building-summary") {
296
+ var summaryBeats = [
297
+ "Organizing what Echo found…",
298
+ "Building the report summary…"
299
+ ];
300
+ progressText = summaryBeats[beat % summaryBeats.length];
301
+ } else if (stage === "classifying-repeated-context") {
302
+ var analysisCount = stageTotal > 0
303
+ ? " · " + number(Math.min(stageDone, stageTotal)) + " of " + number(stageTotal)
304
+ : "";
305
+ var analysisBeats = [
306
+ "Comparing context across sessions" + analysisCount + "…",
307
+ "Finding repeated context" + analysisCount + "…",
308
+ "Separating useful context from noise" + analysisCount + "…"
309
+ ];
310
+ progressText = analysisBeats[beat % analysisBeats.length];
192
311
  } else if (stage === "finalizing-report") {
193
- progressText = "Preparing your report…";
312
+ progressText = beat % 2 === 0 ? "Checking the report totals…" : "Preparing your report…";
194
313
  }
314
+ var etaState = estimateReportRemaining(progress, overall, stage);
315
+ var etaText = etaState.overdue
316
+ ? "Taking a little longer than estimated…"
317
+ : (etaState.seconds > 0 ? formatReportEta(etaState.seconds) : "Estimating time remaining…");
195
318
  var fill = document.getElementById("scan-load-fill");
196
319
  var caption = document.getElementById("scan-load-caption");
320
+ var eta = document.getElementById("scan-load-eta");
197
321
  if (app.className === "scanLoading" && fill) {
198
322
  fill.style.width = pct + "%";
199
323
  fill.parentElement.setAttribute("aria-valuenow", String(pct));
200
- fill.parentElement.setAttribute("aria-valuetext", progressText || "Preparing session count");
324
+ fill.parentElement.setAttribute("aria-valuetext", (progressText || "Preparing session count") + " " + etaText);
201
325
  if (caption) {
202
326
  caption.textContent = progressText;
203
327
  caption.hidden = !progressText;
204
328
  }
329
+ if (eta) eta.textContent = etaText;
205
330
  return;
206
331
  }
207
332
  setScanMode(true);
@@ -210,10 +335,19 @@ export const SETUP_PAGE_CLIENT_REPORT_CITY = String.raw ` /* ---------- ful
210
335
  app.innerHTML =
211
336
  setupJourney(1) +
212
337
  '<section class="mvpReading" aria-labelledby="scanLoadingTitle">' +
213
- '<img class="mvpReadingMascot" src="/hud-assets/scan-loop/frame-04-read.png" alt="" aria-hidden="true" />' +
338
+ '<span class="mvpReadingMascot scanMascotFrames" aria-hidden="true">' +
339
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-01-ready.png" alt="" />' +
340
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-02-open.png" alt="" />' +
341
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-03-pull.png" alt="" />' +
342
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-04-read.png" alt="" />' +
343
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-04-read.png" alt="" />' +
344
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-05-file.png" alt="" />' +
345
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-06-close.png" alt="" />' +
346
+ '</span>' +
214
347
  '<h2 id="scanLoadingTitle">Building your report…</h2>' +
215
348
  '<p class="mvpReadingCount" id="scan-load-caption" aria-live="polite"' + (progressText ? "" : " hidden") + '>' + esc(progressText) + '</p>' +
216
- '<div class="scanProgress mvpReadingProgress" role="progressbar" aria-labelledby="scanLoadingTitle" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + pct + '" aria-valuetext="' + esc(progressText || "Preparing session count") + '">' +
349
+ '<p class="mvpReadingEta" id="scan-load-eta">' + esc(etaText) + '</p>' +
350
+ '<div class="scanProgress mvpReadingProgress" role="progressbar" aria-labelledby="scanLoadingTitle" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + pct + '" aria-valuetext="' + esc((progressText || "Preparing session count") + " " + etaText) + '">' +
217
351
  '<i id="scan-load-fill" class="scanProgressFill" style="width:' + pct + '%"></i>' +
218
352
  '</div>' +
219
353
  '</section>';
@@ -1462,6 +1462,43 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
1462
1462
  .savedHeading strong {
1463
1463
  font-variant-numeric: tabular-nums;
1464
1464
  }
1465
+ .firstRecallStage {
1466
+ display: flex;
1467
+ flex-direction: column;
1468
+ gap: clamp(22px, 3vw, 34px);
1469
+ }
1470
+ .firstRecallStage .resultTop h2 {
1471
+ max-width: none;
1472
+ margin: 0;
1473
+ color: var(--echo-ink-text);
1474
+ font-family: var(--echo-font-brand);
1475
+ font-size: clamp(42px, 6vw, 72px);
1476
+ font-weight: 700;
1477
+ line-height: 0.96;
1478
+ letter-spacing: -0.045em;
1479
+ }
1480
+ .firstRecallStage .savedHeading {
1481
+ display: block;
1482
+ }
1483
+ .firstRecallStage .savedHeading .savedMain {
1484
+ color: var(--echo-ink-text);
1485
+ }
1486
+ .firstRecallStage .savedHeading strong {
1487
+ color: var(--echo-ink-primary);
1488
+ font-weight: 900;
1489
+ }
1490
+ .firstRecallStage .tryCard {
1491
+ margin-top: 0;
1492
+ }
1493
+ .firstRecallStage .tryCard > .tryTitle {
1494
+ margin-bottom: 0;
1495
+ color: var(--echo-ink-primary);
1496
+ font-size: clamp(26px, 3vw, 38px);
1497
+ line-height: 1.04;
1498
+ }
1499
+ .firstRecallStage .recallPromptPrimary {
1500
+ margin-top: 18px;
1501
+ }
1465
1502
  .viewEchoLink {
1466
1503
  color: var(--echo-ink-mute);
1467
1504
  font-style: normal;
@@ -14,7 +14,8 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
14
14
  width: 100%;
15
15
  min-height: 100vh;
16
16
  margin: 0;
17
- padding: clamp(16px, 2.4vw, 34px) clamp(18px, 5vw, 64px) clamp(38px, 6vw, 72px);
17
+ padding: clamp(12px, 1.8vw, 24px) clamp(18px, 5vw, 64px) clamp(38px, 6vw, 72px);
18
+ display: block;
18
19
  }
19
20
  main > header { display: none; }
20
21
  #app.state { color: transparent; }
@@ -24,11 +25,17 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
24
25
  width: 100%;
25
26
  min-height: calc(100vh - clamp(56px, 10vw, 128px));
26
27
  }
28
+ body.readyMode #app:has(> .setupJourney),
29
+ body.scanMode #app:has(> .setupJourney),
30
+ body.extractMode #app:has(> .setupJourney) {
31
+ padding-top: 0 !important;
32
+ }
27
33
 
28
34
  /* One calm map across local scan, account/Stripe, import, and first value. */
29
35
  .setupJourney {
30
36
  width: min(760px, 100%);
31
- margin: 0 auto clamp(16px, 2.4vw, 26px);
37
+ flex: 0 0 auto;
38
+ margin: 0 auto 14px;
32
39
  color: var(--echo-ink-faint);
33
40
  }
34
41
  .setupJourney ol {
@@ -93,12 +100,13 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
93
100
  color: white;
94
101
  }
95
102
  body.cityMode .setupJourney {
96
- margin-top: 26px;
103
+ margin-top: 12px;
97
104
  margin-bottom: 8px;
98
105
  padding-inline: 18px;
99
106
  }
100
- body.readyMode #app:has(.readyDecision.extractionReady) {
101
- padding-top: 124px;
107
+ #setupJourneySlot {
108
+ width: 100%;
109
+ flex: 0 0 auto;
102
110
  }
103
111
 
104
112
  /* Permission ------------------------------------------------------------ */
@@ -277,6 +285,56 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
277
285
  .localAuthWelcomeStage.localAuthPassphraseStage .localAuthWelcome {
278
286
  margin: clamp(24px, 5vh, 48px) auto 0;
279
287
  }
288
+ body.readyMode #app.localAuthCompleteStage {
289
+ min-height: calc(100vh - clamp(56px, 10vw, 128px));
290
+ display: grid;
291
+ place-items: center;
292
+ }
293
+ .localAuthComplete {
294
+ width: min(680px, 100%);
295
+ display: grid;
296
+ justify-items: center;
297
+ gap: 14px;
298
+ margin: 0 auto;
299
+ padding: clamp(24px, 5vw, 48px) 0;
300
+ color: var(--echo-ink-text);
301
+ text-align: center;
302
+ }
303
+ .localAuthComplete > img {
304
+ width: 76px;
305
+ height: 76px;
306
+ object-fit: contain;
307
+ filter: drop-shadow(0 12px 20px rgba(26,58,143,0.18));
308
+ }
309
+ .localAuthComplete h2 {
310
+ max-width: 100%;
311
+ margin: 4px 0 0;
312
+ color: var(--echo-ink-text);
313
+ font-family: var(--echo-font-brand);
314
+ font-size: clamp(42px, 6vw, 64px);
315
+ font-weight: 800;
316
+ letter-spacing: -0.055em;
317
+ line-height: 0.98;
318
+ white-space: nowrap;
319
+ }
320
+ .localAuthComplete p {
321
+ margin: 0;
322
+ color: var(--echo-ink-mute);
323
+ font-size: clamp(17px, 2.2vw, 22px);
324
+ line-height: 1.45;
325
+ }
326
+ .localAuthComplete .localAuthCompleteNext {
327
+ max-width: 460px;
328
+ margin-top: 2px;
329
+ color: var(--echo-ink-faint);
330
+ font-size: 14px;
331
+ }
332
+ .localAuthComplete code {
333
+ color: var(--echo-ink-primary);
334
+ }
335
+ @media (max-width: 560px) {
336
+ .localAuthComplete h2 { white-space: normal; }
337
+ }
280
338
  .localAuthWelcome {
281
339
  width: min(440px, 100%);
282
340
  gap: 14px;
@@ -481,8 +539,13 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
481
539
 
482
540
  /* Reading --------------------------------------------------------------- */
483
541
  .scanLoading {
484
- display: grid !important;
485
- place-items: center;
542
+ display: flex !important;
543
+ flex-direction: column;
544
+ align-items: center;
545
+ justify-content: flex-start;
546
+ }
547
+ .scanLoading .mvpReading {
548
+ margin-block: auto;
486
549
  }
487
550
  .mvpReading {
488
551
  display: flex;
@@ -515,12 +578,45 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
515
578
  display: block;
516
579
  visibility: hidden;
517
580
  }
581
+ .mvpReadingEta {
582
+ min-height: 17px;
583
+ margin: 5px 0 0;
584
+ color: var(--echo-ink-faint);
585
+ font: 600 12px/1.4 var(--echo-font-mono);
586
+ font-variant-numeric: tabular-nums;
587
+ }
518
588
  .mvpReadingProgress {
519
589
  width: min(260px, 100%);
520
590
  height: 3px;
521
591
  margin: 8px 0 0;
522
592
  background: rgba(26,58,143,0.12);
523
593
  }
594
+ .discoveryLoader {
595
+ width: 100%;
596
+ min-height: min(62vh, 620px);
597
+ display: grid;
598
+ place-items: center;
599
+ }
600
+ .discoverySpinner span {
601
+ display: block;
602
+ width: 26px;
603
+ height: 26px;
604
+ border: 3px solid rgba(26,58,143,0.16);
605
+ border-top-color: var(--echo-ink-primary);
606
+ border-radius: 50%;
607
+ animation: localAuthSpin 700ms linear infinite;
608
+ }
609
+ .extractionStartStage {
610
+ display: flex !important;
611
+ flex-direction: column;
612
+ align-items: center;
613
+ justify-content: flex-start;
614
+ }
615
+ .extractionStartStage .discoverySpinner {
616
+ display: grid;
617
+ flex: 1 1 auto;
618
+ place-items: center;
619
+ }
524
620
  .reportMessageStage {
525
621
  display: flex !important;
526
622
  justify-content: center;
@@ -890,6 +986,112 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
890
986
  text-transform: uppercase;
891
987
  }
892
988
  .planGateAccount .setupAccountChange { min-height: 34px; }
989
+ .readyUtilityNav {
990
+ position: fixed;
991
+ top: 20px;
992
+ left: 22px;
993
+ z-index: 20;
994
+ display: grid;
995
+ width: min(310px, calc(100vw - 44px));
996
+ gap: 2px;
997
+ border: 1px solid rgba(26,58,143,0.10);
998
+ border-radius: 24px;
999
+ background: rgba(255,255,255,0.78);
1000
+ padding: 6px;
1001
+ box-shadow: 0 8px 24px rgba(26,58,143,0.08);
1002
+ backdrop-filter: blur(12px);
1003
+ }
1004
+ .readyUtilityNav .planGateAccount {
1005
+ position: static;
1006
+ top: auto;
1007
+ left: auto;
1008
+ z-index: auto;
1009
+ width: 100%;
1010
+ max-width: 100%;
1011
+ }
1012
+ .readyUtilityNav .planGateAccount .setupAccountCard {
1013
+ display: grid;
1014
+ width: 100%;
1015
+ min-height: 44px;
1016
+ grid-template-columns: 70px minmax(0, 1fr) auto;
1017
+ align-items: center;
1018
+ gap: 10px;
1019
+ border: 0;
1020
+ border-radius: 16px;
1021
+ background: transparent;
1022
+ padding: 5px 8px;
1023
+ box-shadow: none;
1024
+ backdrop-filter: none;
1025
+ }
1026
+ .readyUtilityNav .setupAccountCopy { display: contents; }
1027
+ .readyUtilityNav .setupAccountCopy small {
1028
+ grid-column: 1;
1029
+ margin: 0;
1030
+ align-self: center;
1031
+ }
1032
+ .readyUtilityNav .setupAccountCopy strong {
1033
+ grid-column: 2;
1034
+ overflow: hidden;
1035
+ align-self: center;
1036
+ text-overflow: ellipsis;
1037
+ white-space: nowrap;
1038
+ }
1039
+ .readyUtilityNav .setupAccountChange { grid-column: 3; }
1040
+ .readyPlanNav {
1041
+ display: grid;
1042
+ width: 100%;
1043
+ min-height: 44px;
1044
+ grid-template-columns: 70px minmax(0, 1fr);
1045
+ align-items: center;
1046
+ gap: 10px;
1047
+ border: 0;
1048
+ border-radius: 16px;
1049
+ background: transparent;
1050
+ padding: 5px 8px;
1051
+ box-shadow: none;
1052
+ backdrop-filter: none;
1053
+ }
1054
+ .readyPlanNav:has(.setupPlanSlot.is-hidden) { display: none; }
1055
+ .readyPlanNavLabel {
1056
+ grid-column: 1;
1057
+ color: var(--echo-ink-faint);
1058
+ font: 700 8px/1 var(--echo-font-mono);
1059
+ letter-spacing: 0.1em;
1060
+ text-transform: uppercase;
1061
+ }
1062
+ .readyPlanNav .setupPlanSlot {
1063
+ grid-column: 2;
1064
+ min-width: 0;
1065
+ }
1066
+ .readyPlanNav .setupPlanConfirmed {
1067
+ display: grid;
1068
+ min-width: 0;
1069
+ grid-template-columns: minmax(0, 1fr) auto;
1070
+ margin: 0;
1071
+ align-items: center;
1072
+ gap: 8px;
1073
+ border: 0;
1074
+ background: transparent;
1075
+ padding: 0;
1076
+ box-shadow: none;
1077
+ }
1078
+ .readyPlanNav .setupPlanConfirmedIcon,
1079
+ .readyPlanNav .setupPlanConfirmedCopy > span { display: none; }
1080
+ .readyPlanNav .setupPlanConfirmedCopy { min-width: 0; }
1081
+ .readyPlanNav .setupPlanConfirmedCopy strong {
1082
+ display: block;
1083
+ overflow: hidden;
1084
+ color: var(--echo-ink-mute);
1085
+ font-size: 13px;
1086
+ font-weight: 500;
1087
+ text-overflow: ellipsis;
1088
+ white-space: nowrap;
1089
+ }
1090
+ .readyPlanNav .textButton {
1091
+ min-height: 34px;
1092
+ padding: 0 9px;
1093
+ font-size: 11px;
1094
+ }
893
1095
 
894
1096
  /* Found sessions: one message, one next action -------------------------- */
895
1097
  .readyDecision.extractionReady {
@@ -951,6 +1153,7 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
951
1153
  line-height: 1.45;
952
1154
  text-align: left;
953
1155
  }
1156
+ .extractionReady .readyHero .exSub:empty { display: none; }
954
1157
  .extractionReady .proofStrip {
955
1158
  order: 1;
956
1159
  width: fit-content;
@@ -996,6 +1199,7 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
996
1199
  color: var(--echo-ink-faint);
997
1200
  font-size: 11px;
998
1201
  }
1202
+ .extractionReady .ctaMeta:empty { display: none; }
999
1203
  .extractionReady .ctaMeta .dataHandlingLink { font-size: inherit; }
1000
1204
  .extractionReady .readyFoot { order: 4; }
1001
1205
  .billingCommitmentSlot {
@@ -1257,6 +1461,34 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
1257
1461
  letter-spacing: -0.06em;
1258
1462
  }
1259
1463
  .sessionPickerHead p { margin-top: 12px; font-size: 14px; }
1464
+ .sessionPickerSummary {
1465
+ display: flex;
1466
+ flex-wrap: wrap;
1467
+ align-items: center;
1468
+ gap: 8px;
1469
+ }
1470
+ .sessionPickerQuota strong {
1471
+ color: var(--echo-ink-text);
1472
+ font-weight: 750;
1473
+ }
1474
+ .sessionPickerUpgrade {
1475
+ display: inline-flex;
1476
+ min-height: 30px;
1477
+ align-items: center;
1478
+ justify-content: center;
1479
+ border: 1px solid rgba(26,58,143,0.22);
1480
+ border-radius: 999px;
1481
+ background: rgba(255,255,255,0.78);
1482
+ padding: 0 12px;
1483
+ color: var(--echo-ink-primary);
1484
+ font-size: 12px;
1485
+ font-weight: 750;
1486
+ line-height: 1;
1487
+ text-decoration: none;
1488
+ box-shadow: 0 4px 12px rgba(26,58,143,0.07);
1489
+ }
1490
+ .sessionPickerUpgrade:hover { background: var(--echo-paper-white); }
1491
+ .sessionPickerOrder { color: var(--echo-ink-faint); }
1260
1492
  .sessionPickerClose {
1261
1493
  border: 0;
1262
1494
  background: rgba(255,255,255,0.72);
@@ -162,7 +162,12 @@ export const SETUP_PAGE_STYLES_WEBSITE_ALIGNMENT = String.raw `
162
162
  filter: drop-shadow(0 12px 14px rgba(26,58,143,0.16));
163
163
  animation: scanMascotFrame 4.2s linear infinite;
164
164
  }
165
- .scanMascotFrame:nth-child(1) { animation-delay: 0s; }
165
+ /* Keep the ready pose mounted underneath every animated frame. This prevents
166
+ a transparent flash at loop boundaries or while a later PNG is decoding. */
167
+ .scanMascotFrame:nth-child(1) {
168
+ opacity: 1;
169
+ animation: none;
170
+ }
166
171
  .scanMascotFrame:nth-child(2) { animation-delay: 0.6s; }
167
172
  .scanMascotFrame:nth-child(3) { animation-delay: 1.2s; }
168
173
  .scanMascotFrame:nth-child(4) { animation-delay: 1.8s; }
@@ -170,8 +175,8 @@ export const SETUP_PAGE_STYLES_WEBSITE_ALIGNMENT = String.raw `
170
175
  .scanMascotFrame:nth-child(6) { animation-delay: 3s; }
171
176
  .scanMascotFrame:nth-child(7) { animation-delay: 3.6s; }
172
177
  @keyframes scanMascotFrame {
173
- 0%, 14.2% { opacity: 1; }
174
- 14.3%, 100% { opacity: 0; }
178
+ 0%, 14.4% { opacity: 1; }
179
+ 14.5%, 100% { opacity: 0; }
175
180
  }
176
181
  .scanLoaderCopy { min-width: 0; }
177
182
  .scanEyebrow {
@@ -21,7 +21,9 @@ export const SETUP_PREVIEW_STATES = [
21
21
  "auth-login",
22
22
  "auth-login-pro",
23
23
  "auth-otp",
24
+ "auth-complete",
24
25
  "checkout-pro-selected",
26
+ "checkout-pro-stale",
25
27
  "auth-unlock",
26
28
  "auth-setup",
27
29
  "extract-run",
@@ -252,6 +254,7 @@ function extractionPreviewBootstrap(options) {
252
254
  connected = true;
253
255
  billingStatus = ${JSON.stringify(billingStatus)};
254
256
  setupPlanChoice = ${JSON.stringify(options.selectFree ? "free" : options.selectedPlanChoice || "")};
257
+ pendingCheckoutSessionId = ${JSON.stringify(options.checkoutPending ? "cs_test_preview_pending_123" : "")};
255
258
  stats = {
256
259
  sessions: { total: ${candidateCount}, codex: ${Math.ceil(candidateCount * 0.68)}, claudeCode: ${Math.floor(candidateCount * 0.32)} },
257
260
  migratable: {
@@ -400,7 +403,15 @@ export function renderSetupPreviewBootstrap(state) {
400
403
  return `${watermark}
401
404
  localAuthEmail = "preview@example.com";
402
405
  renderLocalOtp("Verification code sent. Check your email inbox.");`;
406
+ if (state === "auth-complete")
407
+ return `${watermark}
408
+ renderLocalLoginComplete();`;
403
409
  if (state === "checkout-pro-selected")
410
+ return `${watermark}${extractionPreviewBootstrap({
411
+ plan: "free", paid: false, trialAvailable: true, trialUsed: false,
412
+ quotaLimit: 100, quotaRemaining: 100, selectedPlanChoice: "pro", checkoutPending: true,
413
+ })}`;
414
+ if (state === "checkout-pro-stale")
404
415
  return `${watermark}${extractionPreviewBootstrap({
405
416
  plan: "free", paid: false, trialAvailable: true, trialUsed: false,
406
417
  quotaLimit: 100, quotaRemaining: 100, selectedPlanChoice: "pro",