@incodetech/core 2.2.0-rc.6 → 2.2.0

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.
Files changed (123) hide show
  1. package/dist/{BrowserDiagnosticsCryptoProvider-Ce3rctBe.esm.js → BrowserDiagnosticsCryptoProvider-B8vorxN5.esm.js} +1 -1
  2. package/dist/{addressSearch-DQyrvDQj.esm.js → addressSearch-D1XpBdUS.esm.js} +1 -1
  3. package/dist/ae-signature.esm.js +1 -1
  4. package/dist/antifraud.esm.js +2 -2
  5. package/dist/{antifraudManager-BMZvuxv0.esm.js → antifraudManager-CQFNgSPt.esm.js} +2 -2
  6. package/dist/{antifraudStateMachine-r4VxSBS4.esm.js → antifraudStateMachine-Bf_JVNwn.esm.js} +2 -2
  7. package/dist/{apiError-BSXFYi4z.esm.js → apiError-BA1d_au0.esm.js} +1 -1
  8. package/dist/authentication.esm.js +2 -2
  9. package/dist/{authenticationManager-DYqYkfs6.esm.js → authenticationManager-zh9TC62P.esm.js} +3 -3
  10. package/dist/{authenticationStateMachine-9tP0fEe0.esm.js → authenticationStateMachine-xzi-vnvE.esm.js} +4 -4
  11. package/dist/certificate-issuance.esm.js +2 -2
  12. package/dist/{certificateIssuanceStateMachine-COd7_l2T.esm.js → certificateIssuanceStateMachine-D_R92TYu.esm.js} +2 -2
  13. package/dist/consent.esm.js +2 -2
  14. package/dist/{consentManager-DyBXdkFn.esm.js → consentManager-BxjpLZAG.esm.js} +2 -2
  15. package/dist/{consentStateMachine-WD-S-Yhs.esm.js → consentStateMachine-D9D4ON8Z.esm.js} +2 -2
  16. package/dist/cross-document-data-match.esm.js +1 -1
  17. package/dist/{crossDocumentDataMatchManager-pNkutndd.esm.js → crossDocumentDataMatchManager-CGT0V6RV.esm.js} +2 -2
  18. package/dist/curp-validation.esm.js +2 -2
  19. package/dist/{curpValidationManager-DEesnLee.esm.js → curpValidationManager-6Nmkn5u1.esm.js} +2 -2
  20. package/dist/{curpValidationStateMachine-9KwnqeoB.esm.js → curpValidationStateMachine-Ds7KMacK.esm.js} +2 -2
  21. package/dist/custom-fields.esm.js +2 -2
  22. package/dist/custom-watchlist.esm.js +2 -2
  23. package/dist/{customWatchlistStateMachine-B_ey3TFj.esm.js → customWatchlistStateMachine-YWSgKLbF.esm.js} +2 -2
  24. package/dist/{deepsightLoader-CraWVfCs.esm.js → deepsightLoader--wMKWECS.esm.js} +3 -3
  25. package/dist/document-capture.esm.js +2 -2
  26. package/dist/document-upload.esm.js +2 -2
  27. package/dist/{documentCaptureManager-DwM7AmEJ.esm.js → documentCaptureManager-DsQQhWry.esm.js} +2 -2
  28. package/dist/{documentCaptureStateMachine-D2jBwwVb.esm.js → documentCaptureStateMachine-BM4Ex_5x.esm.js} +2 -2
  29. package/dist/dynamic-forms.esm.js +2 -2
  30. package/dist/{dynamicFormsManager-CXkZG8c6.esm.js → dynamicFormsManager-fFVIRBNv.esm.js} +2 -2
  31. package/dist/{dynamicFormsStateMachine-Cl3kW4E7.esm.js → dynamicFormsStateMachine-D6D7oEYk.esm.js} +3 -3
  32. package/dist/ekyb.esm.js +3 -3
  33. package/dist/{ekybManager-BY8ijn3M.esm.js → ekybManager-Y3MkoXpC.esm.js} +2 -2
  34. package/dist/{ekybStateMachine-Cyv8iPyH.esm.js → ekybStateMachine-3nlnqdlr.esm.js} +4 -4
  35. package/dist/ekyc.esm.js +2 -2
  36. package/dist/{ekycManager-CasdX1Ou.esm.js → ekycManager-DgT48ky6.esm.js} +2 -2
  37. package/dist/{ekycStateMachine-DHvksnrr.esm.js → ekycStateMachine-CQwqiR-R.esm.js} +3 -3
  38. package/dist/electronic-signature.esm.js +1 -1
  39. package/dist/{electronicSignatureManager-CAHjq6JR.esm.js → electronicSignatureManager-DGbAJ00W.esm.js} +3 -3
  40. package/dist/email.esm.js +2 -2
  41. package/dist/{emailManager-NLzaP12q.esm.js → emailManager-BY0jeYwR.esm.js} +2 -2
  42. package/dist/{emailStateMachine-Bm894MHB.esm.js → emailStateMachine-Crw8QVmc.esm.js} +2 -2
  43. package/dist/extensibility.d.ts +2 -2
  44. package/dist/extensibility.esm.js +28 -28
  45. package/dist/face-match.esm.js +2 -2
  46. package/dist/{faceCaptureManagerFactory-BdAtEQUX.esm.js → faceCaptureManagerFactory-CVnZ0fa5.esm.js} +2 -2
  47. package/dist/{faceCaptureSetup-BUNskJhm.esm.js → faceCaptureSetup-Cd-04W_X.esm.js} +4 -4
  48. package/dist/{faceMatchManager-DEVhhdf4.esm.js → faceMatchManager-Ri4UbNz0.esm.js} +2 -2
  49. package/dist/{faceMatchStateMachine-CJiO2yLO.esm.js → faceMatchStateMachine-20DdZ3jg.esm.js} +2 -2
  50. package/dist/{feature-management-Bo_xbwQa.esm.js → feature-management-CPQ4qv3F.esm.js} +1 -1
  51. package/dist/feature-management.esm.js +1 -1
  52. package/dist/field-comparison.esm.js +1 -1
  53. package/dist/{fieldComparisonManager-BRYsIi34.esm.js → fieldComparisonManager-Cg6kPQQx.esm.js} +2 -2
  54. package/dist/fiscal-qr.esm.js +2 -2
  55. package/dist/flow.d.ts +1 -1
  56. package/dist/flow.esm.js +5 -5
  57. package/dist/{flowCompletionService-J_2XiDZG.esm.js → flowCompletionService-tfeoGre9.esm.js} +1 -1
  58. package/dist/{flowServices-BydHVxc7.esm.js → flowServices-f5Ruz7R9.esm.js} +4 -4
  59. package/dist/geolocation.esm.js +2 -2
  60. package/dist/{geolocationManager-BF_JcCgS.esm.js → geolocationManager-CH6RfqFH.esm.js} +2 -2
  61. package/dist/{geolocationStateMachine-rYPrOUyM.esm.js → geolocationStateMachine-DmbWFely.esm.js} +3 -3
  62. package/dist/government-validation.esm.js +2 -2
  63. package/dist/{governmentValidationStateMachine-BYKYUdza.esm.js → governmentValidationStateMachine-CK9DrZXX.esm.js} +2 -2
  64. package/dist/home.esm.js +1 -1
  65. package/dist/id-ocr.esm.js +2 -2
  66. package/dist/id-selfie-flow.esm.js +8 -8
  67. package/dist/id-verification.esm.js +2 -2
  68. package/dist/id.d.ts +2 -2
  69. package/dist/id.esm.js +5 -5
  70. package/dist/{idCaptureManager-C5zp-Xje.esm.js → idCaptureManager-BYwTZ2H8.esm.js} +12 -16
  71. package/dist/{idCaptureManager-BjU-Wyow.d.ts → idCaptureManager-CqrqSjJV.d.ts} +20 -10
  72. package/dist/{idCaptureServices-28d5mDGM.esm.js → idCaptureServices-44kwQItl.esm.js} +141 -15
  73. package/dist/{idCaptureStateMachine-3jTqEo76.esm.js → idCaptureStateMachine-kZ5OpdfM.esm.js} +445 -54
  74. package/dist/{idOcrManager-D_2k-6Vv.esm.js → idOcrManager-BIQYQXXj.esm.js} +2 -2
  75. package/dist/{idOcrStateMachine-Bq_e3l4E.esm.js → idOcrStateMachine-cSxzHpHt.esm.js} +2 -2
  76. package/dist/{idVerificationManager-DdlrWSSB.esm.js → idVerificationManager-JpV_SAsl.esm.js} +2 -2
  77. package/dist/{idVerificationStateMachine-GEQWUPU2.esm.js → idVerificationStateMachine-nPeUNyFq.esm.js} +2 -2
  78. package/dist/identity-reuse.esm.js +2 -2
  79. package/dist/{identityReuseManager-CzLZNgdh.esm.js → identityReuseManager-kNCg9AbD.esm.js} +2 -2
  80. package/dist/{identityReuseStateMachine-AubRPhIi.esm.js → identityReuseStateMachine-DyulxDjP.esm.js} +2 -2
  81. package/dist/index.esm.js +1 -1
  82. package/dist/mandatory-consent.esm.js +2 -2
  83. package/dist/{mandatoryConsentManager-CEIkNukS.esm.js → mandatoryConsentManager-B4Niyg_u.esm.js} +2 -2
  84. package/dist/{mandatoryConsentStateMachine-zroQGzwb.esm.js → mandatoryConsentStateMachine-B4-1u-L8.esm.js} +2 -2
  85. package/dist/{onDeviceFaceResults-CTinxa9H.esm.js → onDeviceFaceResults-CUHwyZT4.esm.js} +3 -3
  86. package/dist/personhood.esm.js +5 -5
  87. package/dist/phone.esm.js +2 -2
  88. package/dist/{phoneManager-B_03mKyi.esm.js → phoneManager-FsJ0W2Ei.esm.js} +2 -2
  89. package/dist/{phoneStateMachine-E1fUUcNM.esm.js → phoneStateMachine-Ba0wKHyX.esm.js} +2 -2
  90. package/dist/qe-signature.esm.js +1 -1
  91. package/dist/{recordingService-DtWTn91V.esm.js → recordingService-3NSENVwp.esm.js} +1 -1
  92. package/dist/redirect-to-mobile.esm.js +2 -2
  93. package/dist/{redirectToMobileManager-Bgo6eqri.esm.js → redirectToMobileManager-y4VQB11M.esm.js} +3 -3
  94. package/dist/{redirectToMobileStateMachine-C9SYCJcH.esm.js → redirectToMobileStateMachine-Cyv2x0PP.esm.js} +2 -2
  95. package/dist/{runChildModule-Bl5F16W9.esm.js → runChildModule-Dukuw23X.esm.js} +3 -3
  96. package/dist/{sdkVersion-DEks9mOi.esm.js → sdkVersion-D1Bm59g6.esm.js} +1 -1
  97. package/dist/selfie.esm.js +6 -6
  98. package/dist/{selfieManager-D1jZ1_6k.esm.js → selfieManager-Bj6B2ZVy.esm.js} +3 -3
  99. package/dist/{selfieStateMachine-CW_c5AnO.esm.js → selfieStateMachine-CHV_WNst.esm.js} +4 -4
  100. package/dist/{session-ChIfu6mP.esm.js → session-BFdLgN4i.esm.js} +4 -4
  101. package/dist/session.esm.js +4 -4
  102. package/dist/{sessionInitializer-BFJAT3u0.esm.js → sessionInitializer-dlfDehHk.esm.js} +4 -4
  103. package/dist/{setup-CcxP8TVK.esm.js → setup-BIAtLa8k.esm.js} +5 -5
  104. package/dist/signature.esm.js +2 -2
  105. package/dist/{signatureManager-DoACIR02.esm.js → signatureManager-Ca43yhwk.esm.js} +2 -2
  106. package/dist/{signatureStateMachine-Cf73DGWO.esm.js → signatureStateMachine-K1LMRrxG.esm.js} +2 -2
  107. package/dist/trueSight-B_icM8r2.esm.js +1 -0
  108. package/dist/{trueSightService-B49-Eq-5.esm.js → trueSightService---iW2KKJ.esm.js} +2 -2
  109. package/dist/trust-graph.esm.js +1 -1
  110. package/dist/{types-M6eb82ra2.d.ts → types-Bdf8_LYB2.d.ts} +69 -13
  111. package/dist/video-selfie.esm.js +5 -5
  112. package/dist/{videoSelfieCameraWarmer-qld900rY.esm.js → videoSelfieCameraWarmer-NEhFZvWx.esm.js} +2 -2
  113. package/dist/wasm.esm.js +1 -1
  114. package/dist/watchlist-for-business.esm.js +2 -2
  115. package/dist/watchlist.esm.js +2 -2
  116. package/dist/{watchlistForBusinessStateMachine-BcBMkMvV.esm.js → watchlistForBusinessStateMachine-CxFnlshm.esm.js} +2 -2
  117. package/dist/{watchlistManager-CPBZnFYj.esm.js → watchlistManager-LoNZH9qm.esm.js} +2 -2
  118. package/dist/{watchlistStateMachine-Bl7Wy9Cm.esm.js → watchlistStateMachine-DIKQDoUT.esm.js} +2 -2
  119. package/dist/workflow.esm.js +36 -36
  120. package/dist/{xstate-actors.esm-CtI5S0f4.esm.js → xstate-actors.esm-BXeQ-Ucz.esm.js} +1 -1
  121. package/dist/{xstate.esm-Cf4e0Cfd.esm.js → xstate.esm-BuFLFuzz.esm.js} +1 -1
  122. package/package.json +2 -2
  123. package/dist/trueSight-CAaztf3O.esm.js +0 -1
@@ -1,18 +1,18 @@
1
1
  import { t as BrowserTimerProvider } from "./BrowserTimerProvider-BXhgNxLt.esm.js";
2
- import { s as exposeExperimentOnce } from "./feature-management-Bo_xbwQa.esm.js";
2
+ import { s as exposeExperimentOnce } from "./feature-management-CPQ4qv3F.esm.js";
3
3
  import { n as revokeObjectURL } from "./IBrowserEnvironmentCapability-DEaRRk-R.esm.js";
4
4
  import { r as getToken, t as api } from "./api-Bx3gKW_y.esm.js";
5
- import { c as trueSightHintTransition, l as trueSightLog, r as noteTrueSightCaptureActivity } from "./trueSightService-B49-Eq-5.esm.js";
5
+ import { c as trueSightHintTransition, l as trueSightLog, r as noteTrueSightCaptureActivity } from "./trueSightService---iW2KKJ.esm.js";
6
6
  import { n as eventModuleNames, s as recordExperimentAssignmentOnce } from "./events-DYiyoSlx.esm.js";
7
- import { t as getDisableIpify } from "./sessionInitializer-BFJAT3u0.esm.js";
8
- import "./trueSight-CAaztf3O.esm.js";
7
+ import { t as getDisableIpify } from "./sessionInitializer-dlfDehHk.esm.js";
8
+ import "./trueSight-B_icM8r2.esm.js";
9
9
  import { t as addDeviceStats } from "./stats-B0nFKawG.esm.js";
10
10
  import { t as isAndroid } from "./platform-BJrm5nzZ.esm.js";
11
- import "./session-ChIfu6mP.esm.js";
12
- import { a as and, n as sendTo, r as assign, s as not, t as setup } from "./xstate.esm-Cf4e0Cfd.esm.js";
13
- import { n as fromPromise, t as fromCallback } from "./xstate-actors.esm-CtI5S0f4.esm.js";
11
+ import "./session-BFdLgN4i.esm.js";
12
+ import { c as not, i as assign, l as raise, n as enqueueActions, o as and, r as sendTo, t as setup } from "./xstate.esm-BuFLFuzz.esm.js";
13
+ import { n as fromPromise, t as fromCallback } from "./xstate-actors.esm-BXeQ-Ucz.esm.js";
14
14
  import { n as invokeRequestPermissionActor, t as checkPermission } from "./permissionServices-UvUcOSRb.esm.js";
15
- import { _ as StreamCanvasProcessingSession } from "./onDeviceFaceResults-CTinxa9H.esm.js";
15
+ import { _ as StreamCanvasProcessingSession } from "./onDeviceFaceResults-CUHwyZT4.esm.js";
16
16
  import { t as sleep } from "./ITimerCapability-DgdDzp8G.esm.js";
17
17
  import { t as IncodeCanvas } from "./canvas-E1zGkel4.esm.js";
18
18
  import { t as getBrowser } from "./getBrowser-DNrQeL4T.esm.js";
@@ -20,7 +20,7 @@ import { n as getWindowDimensions } from "./getDeviceClass-CeBiEayS.esm.js";
20
20
  import { t as StreamCanvasCapture } from "./StreamCanvasCapture-D2XcXCZ4.esm.js";
21
21
  import { n as needsMotionPrimeOnlyFromPermission, r as resolveStoredPermissionResult, t as isReadyForCaptureFromPermission } from "./permissionGuards-KINpmKtI.esm.js";
22
22
  import { n as flagIdManualReview } from "./manualReviewService-D4IkipnN.esm.js";
23
- import { a as processId, c as stopStream, d as validateUploadResponse, f as ID_ERROR_CODES, g as getIdCameraStream, h as CAPTURE_RESULT_PREVIEW, i as preloadIdRecordingProvider, l as uploadIdImage, m as resolveIdCaptureThresholds, o as startRecordingSession, r as initializeIdCapture, s as stopRecording, t as addBarcode, u as uploadManualIdFile } from "./idCaptureServices-28d5mDGM.esm.js";
23
+ import { _ as getIdCameraStream, a as processId, c as stopStream, d as validateUploadResponse, f as ID_ERROR_CODES, g as OPTIMIZING_WATCHDOG_MARGIN_MS, h as CAPTURE_OPTIMIZING, i as preloadIdRecordingProvider, l as uploadIdImage, m as resolveIdCaptureThresholds, o as startRecordingSession, r as initializeIdCapture, s as stopRecording, t as addBarcode, u as uploadManualIdFile } from "./idCaptureServices-44kwQItl.esm.js";
24
24
  //#region src/modules/id/digitalUpload/digitalUploadErrors.ts
25
25
  const TERMINAL_FAIL_REASONS = /* @__PURE__ */ new Set(["DIGITAL_ID_REQUESTED_BUT_OTHER_PROVIDED", "ID_TYPE_UNACCEPTABLE"]);
26
26
  function isKnownFailReason(reason) {
@@ -791,6 +791,20 @@ const hasAttemptsRemainingGuard = ({ context }) => context.attemptsRemaining > 0
791
791
  const hasCapturedImageGuard = ({ context }) => {
792
792
  return context.provider?.getCapturedCanvas() !== null;
793
793
  };
794
+ const hasCandidateFrameGuard = ({ event }) => {
795
+ return event.type === "DETECTION_OPTIMIZING" && Boolean(event.candidateCanvas);
796
+ };
797
+ /**
798
+ * IDC-05: whether WASM's commit arrived while the optimizing phase was still
799
+ * spending its dwell, and is therefore parked in `optimizing.holding` waiting
800
+ * for the release.
801
+ *
802
+ * Gates the phase's own `SWITCH_TO_MANUAL_CAPTURE` / `MANUAL_CAPTURE` handlers:
803
+ * while a commit is parked those escapes end the dwell early instead of leaving
804
+ * the phase, so WASM's committed frame is honored rather than dropped on the way
805
+ * out. False leaves both events to the arm parent, the documented escape hatch.
806
+ */
807
+ const hasStashedCommitGuard = ({ context }) => context.stashedCommit !== void 0;
794
808
  const hasUploadValidationErrorGuard = ({ context }) => {
795
809
  if (!context.uploadResponse) return false;
796
810
  return validateUploadResponse(context.uploadResponse, {
@@ -835,16 +849,31 @@ const shouldContinueToBackGuard = ({ context }) => {
835
849
  const shouldContinueToFrontGuard = ({ context }) => context.config.usSmartCapture === true && context.currentMode === "back" && context.uploadResponse?.forceFrontIdCapture === true;
836
850
  const hasMandatoryConsentGuard = ({ context }) => context.uploadResponse?.showMandatoryConsent === true;
837
851
  /**
838
- * Capture-result-preview feedback mode, resolved once per session by
839
- * `resolveCaptureResultPreviewTreatment` at actor creation.
852
+ * IDC-05 arm, as resolved once per session by
853
+ * `resolveCaptureOptimizingTreatment` at actor creation.
840
854
  */
841
- const isCaptureResultPreviewModeGuard = ({ context }) => context.captureFeedbackMode === "result-preview";
855
+ const isCaptureOptimizingModeGuard = ({ context }) => context.captureFeedbackMode === "optimizing";
842
856
  /**
843
- * Whether the rendering layer opted into handling `resultPreview` and
844
- * advancing it with `manager.nextStep()`. Headless consumers that omit this
845
- * capability retain the direct capture-to-upload path.
857
+ * IDC-05 rendering-layer capability: whether the consumer opted into rendering
858
+ * `captureStatus === 'optimizing'`. Consumers that omit it retain the direct
859
+ * capture-to-upload path. Nothing is advanced by the host — core owns the phase's
860
+ * timing on both paths (WASM's commit on auto capture, `optimizingDwell` on
861
+ * manual), so there is no acknowledgement call.
862
+ *
863
+ * Paired with `isCaptureOptimizingModeGuard` at both branch sites
864
+ * (`choosingDetectionStrategy`, `capturingManual`). Through
865
+ * `createIdCaptureActor` the two can never disagree — that path derives
866
+ * `captureFeedbackMode` from `resolveCaptureOptimizingTreatment`, which
867
+ * already requires this capability — so the pairing looks redundant, and for
868
+ * SDK-built sessions it is.
869
+ *
870
+ * It is NOT redundant on the public surface: `idCaptureMachine` is exported from
871
+ * `@incodetech/core/id` and its `input.captureFeedbackMode` is settable, so a
872
+ * host wiring the machine up directly could otherwise reach the phase without
873
+ * declaring the capability. This guard is what makes the documented opt-in true
874
+ * for every route into the machine, not just the factory.
846
875
  */
847
- const supportsCaptureResultPreviewGuard = ({ context }) => context.config.supportsCaptureResultPreview === true;
876
+ const supportsOptimizingStatusGuard = ({ context }) => context.config.supportsOptimizingStatus === true;
848
877
  const isDeepsightEnabledGuard = ({ context }) => context.dependencies !== void 0;
849
878
  const isDeepsightReadyGuard = ({ context }) => context.deepsightService !== void 0;
850
879
  const needsDeepsightInitGuard = ({ context }) => context.dependencies !== void 0 && context.deepsightService === void 0 && !context.deepsightInitAttempted;
@@ -933,6 +962,7 @@ const isBackModeInMultiSideFlowGuard = ({ context }) => context.currentMode ===
933
962
  * max-attempts/OCR cap (ENG-46592 / re-respects ENG-45666).
934
963
  */
935
964
  const wasExpiredRedirectedGuard = ({ context }) => context.expiredRedirected === true;
965
+ const hasManualCaptureTriggeredGuard = ({ context }) => context.manualCaptureTriggered;
936
966
  //#endregion
937
967
  //#region src/modules/id/idCaptureHelpers.ts
938
968
  /**
@@ -1041,6 +1071,13 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1041
1071
  let isCapturing = false;
1042
1072
  const timer = BrowserTimerProvider.getInstance();
1043
1073
  let notificationTimeout = null;
1074
+ let optimizingBoundaryTimeout = null;
1075
+ /**
1076
+ * IDC-05 latch for the boundary clock: whether a boundary is currently
1077
+ * pending. `onCapturing` is WASM's "aggregation window opened" callback and is
1078
+ * the clock's only T0; nothing in WASM enforces that it fires once per window.
1079
+ */
1080
+ let isOptimizingBoundaryArmed = false;
1044
1081
  let currentNotificationStatus = null;
1045
1082
  let lastReportedSide = null;
1046
1083
  const NOTIFICATION_DURATION_MS = 500;
@@ -1059,6 +1096,50 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1059
1096
  currentNotificationStatus = null;
1060
1097
  }
1061
1098
  };
1099
+ const clearOptimizingBoundary = () => {
1100
+ if (optimizingBoundaryTimeout) {
1101
+ timer.clearTimeout(optimizingBoundaryTimeout);
1102
+ optimizingBoundaryTimeout = null;
1103
+ }
1104
+ isOptimizingBoundaryArmed = false;
1105
+ };
1106
+ /**
1107
+ * IDC-05 boundary clock — the actor's *only* timer, and the only piece of the
1108
+ * optimizing phase it owns. WASM's `CapturingManager` opens the
1109
+ * frames-aggregation window at `onCapturing` and closes it with `onCapture` at
1110
+ * `T0 + framesAggregationInterval` (already resolved to
1111
+ * `captureWindowMs + optimizingDwellMs`). The phase begins at
1112
+ * `T0 + captureWindowMs`, so it is timed from the very callback that defines
1113
+ * T0; how long it then lasts is the machine's business.
1114
+ *
1115
+ * No best frame yet at that point means the session simply behaves like the
1116
+ * countdown arm — no event, no phase — which mirrors iOS's "auto capture with
1117
+ * no best crop yet" fallback.
1118
+ *
1119
+ * At most one boundary pending at a time — see `isOptimizingBoundaryArmed`.
1120
+ */
1121
+ const scheduleOptimizingBoundary = () => {
1122
+ if (input.optimizingAfterMs == null || isOptimizingBoundaryArmed) return;
1123
+ isOptimizingBoundaryArmed = true;
1124
+ optimizingBoundaryTimeout = timer.setTimeout(() => {
1125
+ optimizingBoundaryTimeout = null;
1126
+ isOptimizingBoundaryArmed = false;
1127
+ enterOptimizing();
1128
+ }, input.optimizingAfterMs);
1129
+ };
1130
+ /**
1131
+ * Reports the boundary with the candidate frame to freeze. Silent when there
1132
+ * is no candidate — no event means no phase, and the session behaves like the
1133
+ * countdown arm (see `scheduleOptimizingBoundary`).
1134
+ */
1135
+ const enterOptimizing = () => {
1136
+ const candidateCanvas = bestCanvas?.clone();
1137
+ if (!candidateCanvas) return;
1138
+ sendBack({
1139
+ type: "DETECTION_OPTIMIZING",
1140
+ candidateCanvas
1141
+ });
1142
+ };
1062
1143
  const sendNotification = (status) => {
1063
1144
  if (isReportedSideWrong()) return;
1064
1145
  if (notificationTimeout && currentNotificationStatus !== status) return;
@@ -1146,6 +1227,7 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1146
1227
  },
1147
1228
  onSwitchToManualCapture: () => {
1148
1229
  isCapturing = false;
1230
+ clearOptimizingBoundary();
1149
1231
  sendBack({ type: "SWITCH_TO_MANUAL_CAPTURE" });
1150
1232
  },
1151
1233
  onCapturing: () => {
@@ -1157,6 +1239,7 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1157
1239
  type: "DETECTION_UPDATE",
1158
1240
  status: "capturing"
1159
1241
  });
1242
+ scheduleOptimizingBoundary();
1160
1243
  },
1161
1244
  onBestFrame: (blur, glare, orientation) => {
1162
1245
  if (currentCanvas) {
@@ -1173,16 +1256,24 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1173
1256
  },
1174
1257
  onCapture: () => {
1175
1258
  isCapturing = false;
1176
- if (bestCanvas) sendBack({
1259
+ if (isOptimizingBoundaryArmed) {
1260
+ clearOptimizingBoundary();
1261
+ enterOptimizing();
1262
+ }
1263
+ if (!bestCanvas) {
1264
+ const clonedCanvas = currentCanvas?.clone();
1265
+ if (clonedCanvas) sendBack({
1266
+ type: "DETECTION_SUCCESS",
1267
+ canvas: clonedCanvas,
1268
+ qualityElements: {}
1269
+ });
1270
+ return;
1271
+ }
1272
+ sendBack({
1177
1273
  type: "DETECTION_SUCCESS",
1178
1274
  canvas: bestCanvas,
1179
1275
  qualityElements: storedQualityElements
1180
1276
  });
1181
- else if (currentCanvas) sendBack({
1182
- type: "DETECTION_SUCCESS",
1183
- canvas: currentCanvas.clone(),
1184
- qualityElements: {}
1185
- });
1186
1277
  },
1187
1278
  onIdTypeChange: (idType) => {
1188
1279
  sendBack({
@@ -1233,6 +1324,7 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1233
1324
  notificationTimeout = null;
1234
1325
  currentNotificationStatus = null;
1235
1326
  }
1327
+ clearOptimizingBoundary();
1236
1328
  currentCanvas = null;
1237
1329
  bestCanvas = null;
1238
1330
  storedQualityElements = {};
@@ -1251,6 +1343,7 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1251
1343
  });
1252
1344
  return () => {
1253
1345
  clearNotificationTimeout();
1346
+ clearOptimizingBoundary();
1254
1347
  session?.dispose();
1255
1348
  noteTrueSightCaptureActivity(false);
1256
1349
  };
@@ -1338,7 +1431,7 @@ const startRecordingActor = fromPromise(async ({ input }) => {
1338
1431
  });
1339
1432
  const initializeDeepsightSessionActor = fromPromise(async ({ input }) => {
1340
1433
  if (!input.dependencies) return;
1341
- const { loadDeepsightSession } = await import("./deepsightLoader-CraWVfCs.esm.js");
1434
+ const { loadDeepsightSession } = await import("./deepsightLoader--wMKWECS.esm.js");
1342
1435
  return loadDeepsightSession({
1343
1436
  ds: input.ds,
1344
1437
  storage: input.dependencies.storage,
@@ -1376,11 +1469,11 @@ function performResetForFrontCapture(context) {
1376
1469
  function performResetDetection(context) {
1377
1470
  context.resetDetection?.();
1378
1471
  }
1379
- function performReportCaptureResultPreviewAssignment(context) {
1380
- if (context.config.supportsCaptureResultPreview !== true) return;
1381
- const assignment = exposeExperimentOnce(CAPTURE_RESULT_PREVIEW);
1472
+ function performReportCaptureOptimizingAssignment(context) {
1473
+ if (context.config.supportsOptimizingStatus !== true) return;
1474
+ const assignment = exposeExperimentOnce(CAPTURE_OPTIMIZING);
1382
1475
  if (!assignment) return;
1383
- recordExperimentAssignmentOnce(eventModuleNames.idCapture, CAPTURE_RESULT_PREVIEW.storeParam, assignment);
1476
+ recordExperimentAssignmentOnce(eventModuleNames.idCapture, CAPTURE_OPTIMIZING.storeParam, assignment);
1384
1477
  }
1385
1478
  function performStopMediaRecording(context) {
1386
1479
  if (context.recordingSession) stopRecording(context.recordingSession);
@@ -1443,6 +1536,7 @@ function getResetContextValues(context) {
1443
1536
  idType: void 0,
1444
1537
  qualityElements: void 0,
1445
1538
  previewImageUrl: void 0,
1539
+ ...getClearOptimizingValues(context),
1446
1540
  uploadProgress: void 0,
1447
1541
  currentMode,
1448
1542
  selectedDocumentType: void 0,
@@ -1457,6 +1551,7 @@ function getClearUploadFailureValues(context) {
1457
1551
  uploadError: void 0,
1458
1552
  detectionStatus: "idle",
1459
1553
  previewImageUrl: void 0,
1554
+ ...getClearOptimizingValues(context),
1460
1555
  uploadProgress: void 0
1461
1556
  };
1462
1557
  }
@@ -1476,6 +1571,39 @@ function storeCapturedCanvasInProviderLogic(context, event) {
1476
1571
  const canvasHeight = canvas.height();
1477
1572
  if (!canvasWidth || !canvasHeight) return;
1478
1573
  const originalCanvas = canvas;
1574
+ const frameRect = resolveCaptureFrameRect(context, canvasWidth, canvasHeight);
1575
+ const transformedCanvas = context.provider.transformPerspective(originalCanvas, frameRect);
1576
+ context.provider.setCapturedCanvases(originalCanvas, transformedCanvas);
1577
+ }
1578
+ /**
1579
+ * JPEG quality for the IDC-05 optimizing phase's display-only still.
1580
+ *
1581
+ * `updateBlob()` is synchronous and expensive: `toDataURL` → `atob` → a per-byte
1582
+ * loop building the blob (`IncodeCanvas.base64ToBlob`). At the default quality of
1583
+ * 1.0 on a 1080p frame that is tens of milliseconds of main-thread work — and on
1584
+ * this path it runs inside an `assign`, in the same tick that starts the phase
1585
+ * animation, with the 30fps WASM detection loop still live. Both halves of the
1586
+ * cost scale with the encoded byte count, so dropping quality cuts the encode and
1587
+ * the byte loop together.
1588
+ *
1589
+ * Treatment-arm only, deliberately: the committed frame keeps quality 1.0, since
1590
+ * it is what uploads (`getStoreCapturedImageValues` → `getBase64Image(1.0)`) and
1591
+ * its encode happens on the analyzing screen with no animation to jank. This
1592
+ * still is on screen for one dwell at roughly card size and never leaves the
1593
+ * device.
1594
+ */
1595
+ const CANDIDATE_FRAME_JPEG_QUALITY = .8;
1596
+ /**
1597
+ * The document's rectangle within a camera frame, in canvas coordinates —
1598
+ * the input to `transformPerspective`.
1599
+ *
1600
+ * Shared by the commit path (`storeCapturedCanvasInProviderLogic`) and the
1601
+ * IDC-05 optimizing phase (`getCandidateFrameValues`) so the still shown
1602
+ * mid-capture is cropped and warped exactly like the frame that later uploads.
1603
+ * Deriving it twice is what made the phase show an uncropped full frame that then
1604
+ * visibly jumped to a cropped one on the analyzing screen.
1605
+ */
1606
+ function resolveCaptureFrameRect(context, canvasWidth, canvasHeight) {
1479
1607
  let frameRect;
1480
1608
  const { innerWidth: viewportWidth, innerHeight: viewportHeight } = getWindowDimensions();
1481
1609
  if (context.detectionArea) {
@@ -1507,7 +1635,7 @@ function storeCapturedCanvasInProviderLogic(context, event) {
1507
1635
  h: context.frameRect.h / scale
1508
1636
  };
1509
1637
  } else {
1510
- const quadValue = (context.provider.getLastProcessResult?.())?.quad;
1638
+ const quadValue = (context.provider?.getLastProcessResult?.())?.quad;
1511
1639
  const hasQuad = !!quadValue;
1512
1640
  const quadSize = quadValue?.size ? quadValue.size() : quadValue?.length ?? 0;
1513
1641
  if (hasQuad && quadSize >= 4 && quadValue.get) {
@@ -1538,8 +1666,7 @@ function storeCapturedCanvasInProviderLogic(context, event) {
1538
1666
  };
1539
1667
  }
1540
1668
  }
1541
- const transformedCanvas = context.provider.transformPerspective(originalCanvas, frameRect);
1542
- context.provider.setCapturedCanvases(originalCanvas, transformedCanvas);
1669
+ return frameRect;
1543
1670
  }
1544
1671
  function getStoreCapturedImageValues(context, event) {
1545
1672
  if (!context.currentMode) return {
@@ -1573,7 +1700,79 @@ function getStoreCapturedImageValues(context, event) {
1573
1700
  }
1574
1701
  return {
1575
1702
  capturedImages,
1576
- previewImageUrl
1703
+ previewImageUrl,
1704
+ ...getClearOptimizingValues(context)
1705
+ };
1706
+ }
1707
+ /**
1708
+ * IDC-05: freezes WASM's best-scoring frame *so far* for the optimizing phase.
1709
+ * Display-only — the committed frame arrives later, from `onCapture`, and
1710
+ * `storeCapturedImage` puts that one on `previewImageUrl` while clearing this
1711
+ * one in the same assign, so the rendering layer's
1712
+ * `candidateImageUrl ?? previewImageUrl` falls through to the frame that
1713
+ * actually uploads for the crossfade.
1714
+ *
1715
+ * Revokes any previous URL rather than leaking one per retry.
1716
+ */
1717
+ function getCandidateFrameValues(context, event) {
1718
+ if (event.type !== "DETECTION_OPTIMIZING" || !event.candidateCanvas) return { candidateImageUrl: context.candidateImageUrl };
1719
+ const candidateCanvas = event.candidateCanvas;
1720
+ const canvas = cropCanvasForPreview(context, candidateCanvas);
1721
+ canvas.updateBlob(CANDIDATE_FRAME_JPEG_QUALITY);
1722
+ const url = canvas.getBlobData()?.url;
1723
+ if (!url) {
1724
+ releasePreviewCanvases(candidateCanvas, canvas, { shouldReleasePreview: true });
1725
+ return { candidateImageUrl: context.candidateImageUrl };
1726
+ }
1727
+ releasePreviewCanvases(candidateCanvas, canvas, { shouldReleasePreview: false });
1728
+ if (context.candidateImageUrl) revokeObjectURL(context.candidateImageUrl);
1729
+ return { candidateImageUrl: url };
1730
+ }
1731
+ /**
1732
+ * Reclaims the full-frame canvases the phase allocated. At 1080p each is ~8MB of
1733
+ * backing store, and `release()` frees it now instead of at the next GC — which
1734
+ * is what matters on iOS Safari, where canvas memory is its own budget.
1735
+ *
1736
+ * `source` — the clone the detection actor snapshotted — is always dead by the
1737
+ * time this runs: its pixels live on in `preview`'s blob.
1738
+ *
1739
+ * `preview` is the caller's decision, because `release()` also revokes
1740
+ * `blobData.url`. On the success path that URL is the very thing being handed to
1741
+ * the machine as `candidateImageUrl`, so releasing it would blank the phase's
1742
+ * own still; pass `shouldReleasePreview: true` only where no URL survives. Either way
1743
+ * the identity guard covers `transformPerspective` returning the canvas it was
1744
+ * given (no provider, or a WASM failure), so the aliased canvas is never
1745
+ * released twice or released out from under a live URL.
1746
+ */
1747
+ function releasePreviewCanvases(source, preview, { shouldReleasePreview }) {
1748
+ if (preview === source) {
1749
+ if (shouldReleasePreview) source.release();
1750
+ return;
1751
+ }
1752
+ if (shouldReleasePreview) preview.release();
1753
+ source.release();
1754
+ }
1755
+ function cropCanvasForPreview(context, canvas) {
1756
+ const canvasWidth = canvas.width();
1757
+ const canvasHeight = canvas.height();
1758
+ if (!context.provider || !canvasWidth || !canvasHeight) return canvas;
1759
+ const frameRect = resolveCaptureFrameRect(context, canvasWidth, canvasHeight);
1760
+ return context.provider.transformPerspective(canvas, frameRect);
1761
+ }
1762
+ /**
1763
+ * Drops the IDC-05 best-so-far still at every attempt/side boundary. Unlike
1764
+ * `previewImageUrl` it is never preserved into `flipPreview` (it was never a
1765
+ * committed frame), so it can always be revoked outright.
1766
+ *
1767
+ * Clears `stashedCommit` alongside it: a commit parked for the phase's dwell is
1768
+ * only meaningful inside that phase, and holding the canvas reference past it
1769
+ * would keep a full-res frame alive for the rest of the attempt.
1770
+ */
1771
+ function getClearOptimizingValues(context) {
1772
+ if (context.candidateImageUrl) revokeObjectURL(context.candidateImageUrl);
1773
+ return {
1774
+ candidateImageUrl: void 0,
1775
+ stashedCommit: void 0
1577
1776
  };
1578
1777
  }
1579
1778
  function getDetectionStatusFromUpdate(context, event) {
@@ -1706,6 +1905,7 @@ const idCaptureMachine = setup({
1706
1905
  qualityElements: void 0,
1707
1906
  frameRect: void 0,
1708
1907
  previewImageUrl: void 0,
1908
+ ...getClearOptimizingValues(context),
1709
1909
  flipPreview: context.previewImageUrl ? {
1710
1910
  url: context.previewImageUrl,
1711
1911
  orientation: context.orientation,
@@ -1735,6 +1935,7 @@ const idCaptureMachine = setup({
1735
1935
  qualityElements: void 0,
1736
1936
  frameRect: void 0,
1737
1937
  previewImageUrl: void 0,
1938
+ ...getClearOptimizingValues(context),
1738
1939
  flipPreview: context.previewImageUrl ? {
1739
1940
  url: context.previewImageUrl,
1740
1941
  orientation: context.orientation,
@@ -1763,6 +1964,7 @@ const idCaptureMachine = setup({
1763
1964
  qualityElements: void 0,
1764
1965
  frameRect: void 0,
1765
1966
  previewImageUrl: void 0,
1967
+ ...getClearOptimizingValues(context),
1766
1968
  uploadProgress: void 0,
1767
1969
  manualCaptureTriggered: false,
1768
1970
  attemptsRemaining: context.config.captureAttempts
@@ -1783,8 +1985,8 @@ const idCaptureMachine = setup({
1783
1985
  trackTutorialId: () => {},
1784
1986
  trackContinue: () => {},
1785
1987
  trackCameraId: ({ context }) => {},
1786
- reportCaptureResultPreviewAssignment: ({ context }) => {
1787
- performReportCaptureResultPreviewAssignment(context);
1988
+ reportCaptureOptimizingAssignment: ({ context }) => {
1989
+ performReportCaptureOptimizingAssignment(context);
1788
1990
  },
1789
1991
  preloadRecordingProvider: ({ context }) => {
1790
1992
  preloadIdRecordingProvider(context.config);
@@ -1825,6 +2027,53 @@ const idCaptureMachine = setup({
1825
2027
  } }),
1826
2028
  setCurrentMode: assign({ currentMode: ({ event, context }) => getCurrentModeFromEvent(context, event) }),
1827
2029
  storeCapturedImage: assign(({ context, event }) => getStoreCapturedImageValues(context, event)),
2030
+ setCandidateFrame: assign(({ context, event }) => getCandidateFrameValues(context, event)),
2031
+ /**
2032
+ * IDC-05: parks WASM's commit while the optimizing phase spends the rest of
2033
+ * its dwell. The frame travels in context only for that stretch —
2034
+ * `releaseStashedCommit` re-raises it and `getClearOptimizingValues` drops it
2035
+ * on every other exit from the phase.
2036
+ */
2037
+ stashCommittedFrame: assign({ stashedCommit: ({ event }) => {
2038
+ const commit = event;
2039
+ return {
2040
+ canvas: commit.canvas,
2041
+ qualityElements: commit.qualityElements
2042
+ };
2043
+ } }),
2044
+ /**
2045
+ * IDC-05: hands a parked commit back to the machine as the very event that
2046
+ * would have carried it, so `capturing`'s entry actions stay event-driven.
2047
+ * Re-raised rather than read from context by `capturing` because
2048
+ * `storeCapturedCanvasInProvider` falls back to the *live* frame when the
2049
+ * entering event has no canvas — a timer transition into `capturing` would
2050
+ * stage a live frame for upload instead of the one WASM committed.
2051
+ */
2052
+ releaseStashedCommit: enqueueActions(({ context, enqueue }) => {
2053
+ const stashed = context.stashedCommit;
2054
+ if (!stashed) return;
2055
+ enqueue(raise({
2056
+ type: "DETECTION_SUCCESS",
2057
+ canvas: stashed.canvas,
2058
+ qualityElements: stashed.qualityElements
2059
+ }));
2060
+ }),
2061
+ /**
2062
+ * IDC-05: releases everything the optimizing phase allocated — the parked
2063
+ * commit AND the frozen still's blob URL. Bound to `optimizing.exit`, so it
2064
+ * covers every route out of the phase, not just the two that already clear
2065
+ * up after themselves (`getStoreCapturedImageValues` on the commit,
2066
+ * `optimizingGrace`'s own action on the abandon path). Before this it
2067
+ * cleared only `stashedCommit`, and an exit through the manual-capture
2068
+ * escape leaked the URL until the next attempt/side boundary.
2069
+ *
2070
+ * Safe to revoke here on the commit path: exit and `capturing`'s entry run
2071
+ * in the same macrostep, and `capturing`'s eventless `always` means the UI
2072
+ * only ever observes the resulting `uploading` snapshot — by which point
2073
+ * `previewImageUrl` is set, so `candidateImageUrl ?? previewImageUrl` never
2074
+ * resolves to nothing (see `getStoreCapturedImageValues`).
2075
+ */
2076
+ clearOptimizingPhase: assign(({ context }) => getClearOptimizingValues(context)),
1828
2077
  storeBarcodeResponse: assign(({ context, event }) => getBarcodeResponseValues(context, event)),
1829
2078
  storeDecodedBarcode: assign({ decodedBarcode: ({ event }) => event.decoded }),
1830
2079
  setBarcodeFastPathAttempted: assign({ barcodeFastPathAttempted: true }),
@@ -1892,13 +2141,15 @@ const idCaptureMachine = setup({
1892
2141
  hasInitializedCamera: hasInitializedCameraGuard,
1893
2142
  hasAttemptsRemaining: hasAttemptsRemainingGuard,
1894
2143
  hasCapturedImage: hasCapturedImageGuard,
2144
+ hasCandidateFrame: hasCandidateFrameGuard,
2145
+ hasStashedCommit: hasStashedCommitGuard,
1895
2146
  hasUploadValidationError: hasUploadValidationErrorGuard,
1896
2147
  isFrontMode: isFrontModeGuard,
1897
2148
  shouldContinueToBack: shouldContinueToBackGuard,
1898
2149
  shouldContinueToFront: shouldContinueToFrontGuard,
1899
2150
  hasMandatoryConsent: hasMandatoryConsentGuard,
1900
- isCaptureResultPreviewMode: isCaptureResultPreviewModeGuard,
1901
- supportsCaptureResultPreview: supportsCaptureResultPreviewGuard,
2151
+ isCaptureOptimizingMode: isCaptureOptimizingModeGuard,
2152
+ supportsOptimizingStatus: supportsOptimizingStatusGuard,
1902
2153
  isDeepsightEnabled: isDeepsightEnabledGuard,
1903
2154
  isDeepsightReady: isDeepsightReadyGuard,
1904
2155
  needsDeepsightInit: needsDeepsightInitGuard,
@@ -1914,6 +2165,7 @@ const idCaptureMachine = setup({
1914
2165
  shouldSkipProcessId: shouldSkipProcessIdGuard,
1915
2166
  shouldSubmitBarcode: shouldSubmitBarcodeGuard,
1916
2167
  canRunBarcodeFastPath: canRunBarcodeFastPathGuard,
2168
+ hasManualCaptureTriggered: hasManualCaptureTriggeredGuard,
1917
2169
  isProcessingExpiredExhausted: isProcessingExpiredExhaustedGuard,
1918
2170
  shouldProcessAfterExhaustion: shouldProcessAfterExhaustionGuard,
1919
2171
  forceFinishAfterProcessing: forceFinishAfterProcessingGuard,
@@ -1922,6 +2174,36 @@ const idCaptureMachine = setup({
1922
2174
  isManualUploadExhausted: ({ event }) => event.output?.result === "exhausted",
1923
2175
  isDigitalUploadExhausted: ({ event }) => event.output?.result === "exhausted",
1924
2176
  isDigitalUploadSuccessful: ({ event }) => event.output?.result === "success"
2177
+ },
2178
+ delays: {
2179
+ /**
2180
+ * IDC-05: how long the optimizing phase lasts, on BOTH paths — the auto
2181
+ * path's `optimizing.holding` and the manual path's `optimizingHold`. One
2182
+ * value, read from context rather than hardcoded, so the experiment payload
2183
+ * governs the phase wherever it is played.
2184
+ *
2185
+ * `?? 0` rather than the default dwell, deliberately: neither state is
2186
+ * reachable outside the treatment arm, and the treatment arm is the only
2187
+ * thing that populates `optimizingDwellMs`
2188
+ * (`resolveCaptureOptimizingTreatment` always sets it on its `optimizing`
2189
+ * branch). An absent value here therefore means the wiring between the two
2190
+ * broke, and falling back to the default would silently add a dwell to a
2191
+ * capture that was never supposed to have one. Zero makes that failure a
2192
+ * pass-through instead of a 1.5s regression.
2193
+ */
2194
+ optimizingDwell: ({ context }) => context.optimizingDwellMs ?? 0,
2195
+ /**
2196
+ * IDC-05: how much longer the machine waits for WASM's commit once the phase
2197
+ * has had its full dwell (`optimizing.released`). Past this, `onCapture` —
2198
+ * the phase's normal exit — is not coming for the window this still belongs
2199
+ * to, and the phase is abandoned.
2200
+ *
2201
+ * A constant, not `dwell + margin`: the dwell has already been spent in
2202
+ * `holding` by the time this arms, so the total from phase entry is
2203
+ * unchanged. It also means the timer stays armed on a partially-resolved
2204
+ * payload, where `optimizingDwell` above collapses to zero.
2205
+ */
2206
+ optimizingGrace: OPTIMIZING_WATCHDOG_MARGIN_MS
1925
2207
  }
1926
2208
  }).createMachine({
1927
2209
  id: "idCapture",
@@ -1932,6 +2214,8 @@ const idCaptureMachine = setup({
1932
2214
  return {
1933
2215
  config: input.config,
1934
2216
  captureFeedbackMode: input.captureFeedbackMode ?? "countdown",
2217
+ captureWindowMs: input.captureWindowMs,
2218
+ optimizingDwellMs: input.optimizingDwellMs,
1935
2219
  currentMode,
1936
2220
  selectedDocumentType: void 0,
1937
2221
  stream: void 0,
@@ -1965,6 +2249,7 @@ const idCaptureMachine = setup({
1965
2249
  frameRect: void 0,
1966
2250
  detectionArea: void 0,
1967
2251
  previewImageUrl: void 0,
2252
+ candidateImageUrl: void 0,
1968
2253
  uploadProgress: void 0,
1969
2254
  manualCaptureTriggered: false,
1970
2255
  deepsightService: void 0,
@@ -2294,7 +2579,7 @@ const idCaptureMachine = setup({
2294
2579
  guard: "needsDeepsightInit"
2295
2580
  },
2296
2581
  {
2297
- target: "detecting",
2582
+ target: "choosingDetectionStrategy",
2298
2583
  guard: "hasInitializedCamera"
2299
2584
  },
2300
2585
  { target: "initializing" }
@@ -2329,7 +2614,7 @@ const idCaptureMachine = setup({
2329
2614
  cameraFallbackLevel: context.cameraFallbackLevel
2330
2615
  }),
2331
2616
  onDone: {
2332
- target: "detecting",
2617
+ target: "choosingDetectionStrategy",
2333
2618
  actions: "setStreamAndCapturer"
2334
2619
  },
2335
2620
  onError: [{
@@ -2341,13 +2626,20 @@ const idCaptureMachine = setup({
2341
2626
  actions: assign({ error: ({ event }) => String(event.error) })
2342
2627
  }]
2343
2628
  } },
2629
+ choosingDetectionStrategy: {
2630
+ entry: ["reportCaptureOptimizingAssignment"],
2631
+ always: [{
2632
+ target: "detectingWithOptimization",
2633
+ guard: and(["isCaptureOptimizingMode", "supportsOptimizingStatus"])
2634
+ }, { target: "detecting" }]
2635
+ },
2344
2636
  detecting: {
2345
2637
  always: [{
2346
2638
  target: "manualCaptureWaiting",
2347
- guard: ({ context }) => context.manualCaptureTriggered,
2639
+ guard: "hasManualCaptureTriggered",
2348
2640
  actions: assign({ detectionStatus: () => "manualCapture" })
2349
2641
  }],
2350
- entry: [assign({ detectionStatus: () => "detecting" }), "reportCaptureResultPreviewAssignment"],
2642
+ entry: [assign({ detectionStatus: () => "detecting" })],
2351
2643
  invoke: [{
2352
2644
  id: "startRecording",
2353
2645
  src: "startRecording",
@@ -2398,6 +2690,113 @@ const idCaptureMachine = setup({
2398
2690
  ORIENTATION_CHANGE: { actions: "setOrientation" }
2399
2691
  }
2400
2692
  },
2693
+ /**
2694
+ * IDC-05 treatment arm: same detection as `detecting` above (kept in sync by
2695
+ * hand — see the comment there), plus the two-phase searching -> optimizing
2696
+ * phase.
2697
+ *
2698
+ * Two *distinct* state nodes (this one and `detecting`) rather than one
2699
+ * compound state, deliberately: `snapshot.matches({ capture: 'detecting' })`
2700
+ * keeps meaning exactly what it always meant. Folding the phase into
2701
+ * `detecting` as a substate would make every such selector silently true
2702
+ * while the user is being told the capture succeeded — a hazard no type or
2703
+ * test would catch.
2704
+ */
2705
+ detectingWithOptimization: {
2706
+ entry: assign({ detectionStatus: () => "detecting" }),
2707
+ always: [{
2708
+ target: "manualCaptureWaiting",
2709
+ guard: "hasManualCaptureTriggered",
2710
+ actions: assign({ detectionStatus: () => "manualCapture" })
2711
+ }],
2712
+ invoke: [{
2713
+ id: "startRecording",
2714
+ src: "startRecording",
2715
+ input: ({ context }) => ({
2716
+ config: context.config,
2717
+ stream: context.stream,
2718
+ existing: context.recordingSession,
2719
+ currentMode: context.currentMode
2720
+ }),
2721
+ onDone: { actions: assign({ recordingSession: ({ context, event }) => {
2722
+ return event.output ?? context.recordingSession;
2723
+ } }) },
2724
+ onError: { actions: () => void 0 }
2725
+ }, {
2726
+ id: "runDetection",
2727
+ src: "runDetection",
2728
+ input: ({ context }) => ({
2729
+ frameCapturer: context.frameCapturer,
2730
+ provider: context.provider,
2731
+ config: context.config,
2732
+ currentMode: context.currentMode,
2733
+ detectionArea: context.detectionArea ?? context.config.detectionArea,
2734
+ optimizingAfterMs: context.captureWindowMs
2735
+ })
2736
+ }],
2737
+ on: {
2738
+ DETECTION_SUCCESS: {
2739
+ target: "capturing",
2740
+ actions: assign({ qualityElements: ({ event }) => event.qualityElements })
2741
+ },
2742
+ DETECTION_RESET_READY: { actions: assign({ resetDetection: ({ event }) => event.reset }) },
2743
+ ID_TYPE_CHANGE: { actions: "setIdType" },
2744
+ ORIENTATION_CHANGE: { actions: "setOrientation" },
2745
+ MANUAL_CAPTURE: { target: "#idCapture.capture.capturingManual" },
2746
+ SWITCH_TO_MANUAL_CAPTURE: {
2747
+ target: "#idCapture.capture.manualCaptureWaiting",
2748
+ actions: assign({
2749
+ detectionStatus: () => "manualCapture",
2750
+ manualCaptureTriggered: () => true
2751
+ })
2752
+ }
2753
+ },
2754
+ initial: "searching",
2755
+ states: {
2756
+ searching: { on: {
2757
+ DETECTION_UPDATE: { actions: "setDetectionStatus" },
2758
+ ID_SIDE_CHANGE: { actions: assign({ detectionStatus: ({ context, event }) => getDetectionStatusFromSideChange(context, event.side) }) },
2759
+ BARCODE_DECODED: [{
2760
+ target: "#idCapture.capture.capturingBarcode",
2761
+ guard: "canRunBarcodeFastPath",
2762
+ actions: ["storeDecodedBarcode", "setBarcodeFastPathAttempted"]
2763
+ }, { actions: "storeDecodedBarcode" }],
2764
+ DETECTION_OPTIMIZING: {
2765
+ target: "optimizing",
2766
+ guard: "hasCandidateFrame",
2767
+ actions: "setCandidateFrame"
2768
+ }
2769
+ } },
2770
+ optimizing: {
2771
+ exit: "clearOptimizingPhase",
2772
+ on: {
2773
+ BARCODE_DECODED: { actions: "storeDecodedBarcode" },
2774
+ SWITCH_TO_MANUAL_CAPTURE: {
2775
+ guard: "hasStashedCommit",
2776
+ target: "#idCapture.capture.detectingWithOptimization.optimizing.released"
2777
+ },
2778
+ MANUAL_CAPTURE: {
2779
+ guard: "hasStashedCommit",
2780
+ target: "#idCapture.capture.detectingWithOptimization.optimizing.released"
2781
+ }
2782
+ },
2783
+ initial: "holding",
2784
+ states: {
2785
+ holding: {
2786
+ after: { optimizingDwell: { target: "released" } },
2787
+ on: { DETECTION_SUCCESS: { actions: "stashCommittedFrame" } }
2788
+ },
2789
+ released: {
2790
+ entry: "releaseStashedCommit",
2791
+ after: { optimizingGrace: {
2792
+ target: "#idCapture.capture.detectingWithOptimization.searching",
2793
+ actions: assign({ detectionStatus: () => "detecting" })
2794
+ } }
2795
+ }
2796
+ }
2797
+ }
2798
+ }
2799
+ },
2401
2800
  manualCaptureWaiting: { on: { MANUAL_CAPTURE: { target: "capturingManual" } } },
2402
2801
  capturing: {
2403
2802
  entry: [
@@ -2410,14 +2809,6 @@ const idCaptureMachine = setup({
2410
2809
  target: "submittingBarcode",
2411
2810
  guard: and(["hasCapturedImage", "shouldSubmitBarcode"])
2412
2811
  },
2413
- {
2414
- target: "resultPreview",
2415
- guard: and([
2416
- "hasCapturedImage",
2417
- "isCaptureResultPreviewMode",
2418
- "supportsCaptureResultPreview"
2419
- ])
2420
- },
2421
2812
  {
2422
2813
  target: "uploading",
2423
2814
  guard: "hasCapturedImage"
@@ -2431,7 +2822,6 @@ const idCaptureMachine = setup({
2431
2822
  }
2432
2823
  ]
2433
2824
  },
2434
- resultPreview: { on: { RESULT_PREVIEW_COMPLETE: { target: "uploading" } } },
2435
2825
  submittingBarcode: { invoke: {
2436
2826
  id: "submitBarcode",
2437
2827
  src: "submitBarcode",
@@ -2467,7 +2857,7 @@ const idCaptureMachine = setup({
2467
2857
  target: "success",
2468
2858
  actions: ["storeBarcodeResponse", "markBarcodeFastPathCaptured"]
2469
2859
  },
2470
- onError: { target: "detecting" }
2860
+ onError: { target: "choosingDetectionStrategy" }
2471
2861
  } },
2472
2862
  capturingManual: {
2473
2863
  entry: [
@@ -2477,11 +2867,11 @@ const idCaptureMachine = setup({
2477
2867
  ],
2478
2868
  always: [
2479
2869
  {
2480
- target: "resultPreview",
2870
+ target: "optimizingHold",
2481
2871
  guard: and([
2482
2872
  "hasCapturedImage",
2483
- "isCaptureResultPreviewMode",
2484
- "supportsCaptureResultPreview"
2873
+ "isCaptureOptimizingMode",
2874
+ "supportsOptimizingStatus"
2485
2875
  ])
2486
2876
  },
2487
2877
  {
@@ -2497,6 +2887,7 @@ const idCaptureMachine = setup({
2497
2887
  }
2498
2888
  ]
2499
2889
  },
2890
+ optimizingHold: { after: { optimizingDwell: { target: "uploading" } } },
2500
2891
  uploading: {
2501
2892
  entry: assign({ uploadProgress: () => 0 }),
2502
2893
  invoke: [{
@@ -2691,7 +3082,7 @@ const idCaptureMachine = setup({
2691
3082
  }
2692
3083
  },
2693
3084
  onDone: [{
2694
- target: "#idCapture.capture.detecting",
3085
+ target: "#idCapture.capture.choosingDetectionStrategy",
2695
3086
  guard: "hasStream",
2696
3087
  actions: "clearFlipPreview"
2697
3088
  }, {
@@ -2743,7 +3134,7 @@ const idCaptureMachine = setup({
2743
3134
  }
2744
3135
  },
2745
3136
  onDone: [{
2746
- target: "#idCapture.capture.detecting",
3137
+ target: "#idCapture.capture.choosingDetectionStrategy",
2747
3138
  guard: "hasStream",
2748
3139
  actions: "clearFlipPreview"
2749
3140
  }, {