@incodetech/core 0.0.0-dev-20260820-e7c67ad4 → 0.0.0-dev-20260821-af317971

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 (134) hide show
  1. package/dist/{BrowserDiagnosticsCryptoProvider-pKKLCGRk.esm.js → BrowserDiagnosticsCryptoProvider-C0MCKC1X.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-DyXOc0Ha.esm.js → apiError-DINhZF1k.esm.js} +1 -1
  8. package/dist/authentication.esm.js +2 -2
  9. package/dist/{authenticationManager-C_DUIrb5.esm.js → authenticationManager-Ckwq7pZi.esm.js} +3 -3
  10. package/dist/{authenticationStateMachine-N-u23M0G.esm.js → authenticationStateMachine-CScvURND.esm.js} +4 -4
  11. package/dist/certificate-issuance.esm.js +2 -2
  12. package/dist/{certificateIssuanceManager-buCaNcxx.esm.js → certificateIssuanceManager-DFTukqtI.esm.js} +2 -2
  13. package/dist/{certificateIssuanceStateMachine-COd7_l2T.esm.js → certificateIssuanceStateMachine-D_R92TYu.esm.js} +2 -2
  14. package/dist/consent.d.ts +1 -1
  15. package/dist/consent.esm.js +2 -2
  16. package/dist/{consentManager-uhDPdU6i.d.ts → consentManager-BhYFeKiG.d.ts} +8 -8
  17. package/dist/{consentManager-DyBXdkFn.esm.js → consentManager-BxjpLZAG.esm.js} +2 -2
  18. package/dist/{consentStateMachine-WD-S-Yhs.esm.js → consentStateMachine-D9D4ON8Z.esm.js} +2 -2
  19. package/dist/cross-document-data-match.esm.js +1 -1
  20. package/dist/{crossDocumentDataMatchManager-pNkutndd.esm.js → crossDocumentDataMatchManager-CGT0V6RV.esm.js} +2 -2
  21. package/dist/curp-validation.esm.js +2 -2
  22. package/dist/{curpValidationManager-DEesnLee.esm.js → curpValidationManager-6Nmkn5u1.esm.js} +2 -2
  23. package/dist/{curpValidationStateMachine-9KwnqeoB.esm.js → curpValidationStateMachine-Ds7KMacK.esm.js} +2 -2
  24. package/dist/custom-fields.esm.js +1 -1
  25. package/dist/custom-watchlist.esm.js +2 -2
  26. package/dist/{customFieldsManager-Dgy3Hi0q.esm.js → customFieldsManager-BjTF6dRi.esm.js} +2 -2
  27. package/dist/{customWatchlistManager-BlJTcPKK.esm.js → customWatchlistManager-SFTkKkcs.esm.js} +2 -2
  28. package/dist/{customWatchlistStateMachine-B_ey3TFj.esm.js → customWatchlistStateMachine-YWSgKLbF.esm.js} +2 -2
  29. package/dist/{deepsightLoader-CJ-pplPZ.esm.js → deepsightLoader-B97yrkGH.esm.js} +3 -3
  30. package/dist/document-capture.esm.js +2 -2
  31. package/dist/{documentCaptureManager-C1UC1WYh.esm.js → documentCaptureManager-CV-B3ttS.esm.js} +2 -2
  32. package/dist/{documentCaptureStateMachine-CBHmgyM7.esm.js → documentCaptureStateMachine-DyNnjqKZ.esm.js} +2 -2
  33. package/dist/dynamic-forms.esm.js +2 -2
  34. package/dist/{dynamicFormsManager-DZmrn3Gs.esm.js → dynamicFormsManager-DhL8CAti.esm.js} +2 -2
  35. package/dist/{dynamicFormsStateMachine-DJGGCw-_.esm.js → dynamicFormsStateMachine-C58R6XYG.esm.js} +3 -3
  36. package/dist/ekyb.esm.js +3 -3
  37. package/dist/{ekybManager-DvNzYUtw.esm.js → ekybManager-DhFCnPQH.esm.js} +2 -2
  38. package/dist/{ekybStateMachine-C4F-25c-.esm.js → ekybStateMachine-DLGDsiyt.esm.js} +4 -4
  39. package/dist/ekyc.esm.js +2 -2
  40. package/dist/{ekycManager-CasdX1Ou.esm.js → ekycManager-DgT48ky6.esm.js} +2 -2
  41. package/dist/{ekycStateMachine-DHvksnrr.esm.js → ekycStateMachine-CQwqiR-R.esm.js} +3 -3
  42. package/dist/electronic-signature.esm.js +1 -1
  43. package/dist/{electronicSignatureManager-B8ewPeEV.esm.js → electronicSignatureManager-BNR6fi3d.esm.js} +3 -3
  44. package/dist/email.esm.js +2 -2
  45. package/dist/{emailManager-NLzaP12q.esm.js → emailManager-BY0jeYwR.esm.js} +2 -2
  46. package/dist/{emailStateMachine-Bm894MHB.esm.js → emailStateMachine-Crw8QVmc.esm.js} +2 -2
  47. package/dist/extensibility.d.ts +5 -5
  48. package/dist/extensibility.esm.js +34 -34
  49. package/dist/face-match.esm.js +2 -2
  50. package/dist/{faceCaptureManagerFactory-BltEV2Xt.esm.js → faceCaptureManagerFactory-BfuYyPH6.esm.js} +2 -2
  51. package/dist/{faceCaptureSetup-BGG17dLI.esm.js → faceCaptureSetup-DVMnvRbV.esm.js} +4 -4
  52. package/dist/{faceMatchManager-DEVhhdf4.esm.js → faceMatchManager-Ri4UbNz0.esm.js} +2 -2
  53. package/dist/{faceMatchStateMachine-CJiO2yLO.esm.js → faceMatchStateMachine-20DdZ3jg.esm.js} +2 -2
  54. package/dist/{feature-management-ZXt938Hq.esm.js → feature-management-DBuXnUWY.esm.js} +1 -1
  55. package/dist/feature-management.esm.js +1 -1
  56. package/dist/field-comparison.esm.js +1 -1
  57. package/dist/{fieldComparisonManager-BRYsIi34.esm.js → fieldComparisonManager-Cg6kPQQx.esm.js} +2 -2
  58. package/dist/fiscal-qr.esm.js +1 -1
  59. package/dist/{fiscalQrManager-7UH-ZMea.esm.js → fiscalQrManager-C_mDF2xK.esm.js} +2 -2
  60. package/dist/flow.d.ts +1 -1
  61. package/dist/flow.esm.js +5 -5
  62. package/dist/{flowCompletionService-BGairB36.esm.js → flowCompletionService-DthD-ObU.esm.js} +1 -1
  63. package/dist/{flowServices-DifI1jyA.esm.js → flowServices-BARG219v.esm.js} +4 -4
  64. package/dist/geolocation.esm.js +2 -2
  65. package/dist/{geolocationManager-BnB4QqVU.esm.js → geolocationManager-CHxv2c-F.esm.js} +2 -2
  66. package/dist/{geolocationStateMachine-CfI-8eaD.esm.js → geolocationStateMachine-C9dzQ0YZ.esm.js} +3 -3
  67. package/dist/government-validation.esm.js +2 -2
  68. package/dist/{governmentValidationManager-DnVUyppQ.esm.js → governmentValidationManager-CQBcGMBa.esm.js} +2 -2
  69. package/dist/{governmentValidationStateMachine-CNzJ5UQb.esm.js → governmentValidationStateMachine-DgxxQTbM.esm.js} +2 -2
  70. package/dist/home.esm.js +1 -1
  71. package/dist/id-ocr.esm.js +2 -2
  72. package/dist/id-selfie-flow.esm.js +8 -8
  73. package/dist/id-verification.esm.js +2 -2
  74. package/dist/id.d.ts +2 -2
  75. package/dist/id.esm.js +5 -5
  76. package/dist/{idCaptureManager-DQEUuleg.d.ts → idCaptureManager-DR0ra4Us.d.ts} +20 -10
  77. package/dist/{idCaptureManager-5p43Mmyv.esm.js → idCaptureManager-Ji6vFlsJ.esm.js} +12 -16
  78. package/dist/{idCaptureServices-D5TXcSQQ.esm.js → idCaptureServices-0lE7kEdA.esm.js} +143 -17
  79. package/dist/{idCaptureStateMachine-DF1WRVP9.esm.js → idCaptureStateMachine-DG7t_39B.esm.js} +445 -54
  80. package/dist/{idOcrManager-D_2k-6Vv.esm.js → idOcrManager-BIQYQXXj.esm.js} +2 -2
  81. package/dist/{idOcrStateMachine-Bq_e3l4E.esm.js → idOcrStateMachine-cSxzHpHt.esm.js} +2 -2
  82. package/dist/{idVerificationManager-DdlrWSSB.esm.js → idVerificationManager-JpV_SAsl.esm.js} +2 -2
  83. package/dist/{idVerificationStateMachine-GEQWUPU2.esm.js → idVerificationStateMachine-nPeUNyFq.esm.js} +2 -2
  84. package/dist/identity-reuse.d.ts +1 -1
  85. package/dist/identity-reuse.esm.js +2 -2
  86. package/dist/{identityReuseManager-CzLZNgdh.esm.js → identityReuseManager-kNCg9AbD.esm.js} +2 -2
  87. package/dist/{identityReuseManager-BFDI7iFK.d.ts → identityReuseManager-v2TH5g7e.d.ts} +4 -4
  88. package/dist/{identityReuseStateMachine-AubRPhIi.esm.js → identityReuseStateMachine-DyulxDjP.esm.js} +2 -2
  89. package/dist/index.esm.js +1 -1
  90. package/dist/mandatory-consent.d.ts +1 -1
  91. package/dist/mandatory-consent.esm.js +2 -2
  92. package/dist/{mandatoryConsentManager-CEIkNukS.esm.js → mandatoryConsentManager-B4Niyg_u.esm.js} +2 -2
  93. package/dist/{mandatoryConsentManager-DvR2XzUH.d.ts → mandatoryConsentManager-CaTcQLK6.d.ts} +4 -4
  94. package/dist/{mandatoryConsentStateMachine-zroQGzwb.esm.js → mandatoryConsentStateMachine-B4-1u-L8.esm.js} +2 -2
  95. package/dist/{onDeviceFaceResults-BeHb0Zet.esm.js → onDeviceFaceResults-BqLGnM8A.esm.js} +3 -3
  96. package/dist/personhood.esm.js +5 -5
  97. package/dist/phone.esm.js +2 -2
  98. package/dist/{phoneManager-B_03mKyi.esm.js → phoneManager-FsJ0W2Ei.esm.js} +2 -2
  99. package/dist/{phoneStateMachine-E1fUUcNM.esm.js → phoneStateMachine-Ba0wKHyX.esm.js} +2 -2
  100. package/dist/qe-signature.esm.js +1 -1
  101. package/dist/{recordingService-DKb3A23J.esm.js → recordingService-BNUTYip6.esm.js} +1 -1
  102. package/dist/redirect-to-mobile.esm.js +2 -2
  103. package/dist/{redirectToMobileManager-Bgo6eqri.esm.js → redirectToMobileManager-y4VQB11M.esm.js} +3 -3
  104. package/dist/{redirectToMobileStateMachine-C9SYCJcH.esm.js → redirectToMobileStateMachine-Cyv2x0PP.esm.js} +2 -2
  105. package/dist/{runChildModule-DsFARqqY.esm.js → runChildModule-CyrA4IkE.esm.js} +3 -3
  106. package/dist/{sdkVersion-C5UD9rLq.esm.js → sdkVersion-C2N5d9MD.esm.js} +1 -1
  107. package/dist/selfie.esm.js +6 -6
  108. package/dist/{selfieManager-B-wIgrPB.esm.js → selfieManager-CCl2CgNV.esm.js} +3 -3
  109. package/dist/{selfieStateMachine-DXwqWYTH.esm.js → selfieStateMachine-OD6m5Pnh.esm.js} +4 -4
  110. package/dist/{session-khwoLlR8.esm.js → session-CK-eOrL7.esm.js} +4 -4
  111. package/dist/session.esm.js +4 -4
  112. package/dist/{sessionInitializer-BVjQZWxu.esm.js → sessionInitializer-CVrazqxw.esm.js} +4 -4
  113. package/dist/{setup-CSYGLqEQ.esm.js → setup-Cl3qY4N6.esm.js} +5 -5
  114. package/dist/signature.esm.js +2 -2
  115. package/dist/{signatureManager-DoACIR02.esm.js → signatureManager-Ca43yhwk.esm.js} +2 -2
  116. package/dist/{signatureStateMachine-Cf73DGWO.esm.js → signatureStateMachine-K1LMRrxG.esm.js} +2 -2
  117. package/dist/trueSight-ClQ2RLmN.esm.js +1 -0
  118. package/dist/{trueSightService-cimVc012.esm.js → trueSightService-CTM6xlHG.esm.js} +2 -2
  119. package/dist/trust-graph.esm.js +1 -1
  120. package/dist/{types-B_PW_0Wq2.d.ts → types-DQtm9Esn2.d.ts} +69 -13
  121. package/dist/video-selfie.esm.js +5 -5
  122. package/dist/{videoSelfieCameraWarmer-fvzaTVJ4.esm.js → videoSelfieCameraWarmer-DVzRMMaV.esm.js} +2 -2
  123. package/dist/wasm.esm.js +1 -1
  124. package/dist/watchlist-for-business.esm.js +2 -2
  125. package/dist/watchlist.esm.js +2 -2
  126. package/dist/{watchlistForBusinessManager-Bo08PkKo.esm.js → watchlistForBusinessManager-MEWKeR3h.esm.js} +2 -2
  127. package/dist/{watchlistForBusinessStateMachine-BAg1B4HF.esm.js → watchlistForBusinessStateMachine-Dszp35m2.esm.js} +2 -2
  128. package/dist/{watchlistManager-CPBZnFYj.esm.js → watchlistManager-LoNZH9qm.esm.js} +2 -2
  129. package/dist/{watchlistStateMachine-Bl7Wy9Cm.esm.js → watchlistStateMachine-DIKQDoUT.esm.js} +2 -2
  130. package/dist/workflow.esm.js +36 -36
  131. package/dist/{xstate-actors.esm-CtI5S0f4.esm.js → xstate-actors.esm-BXeQ-Ucz.esm.js} +1 -1
  132. package/dist/{xstate.esm-Cf4e0Cfd.esm.js → xstate.esm-BuFLFuzz.esm.js} +1 -1
  133. package/package.json +1 -1
  134. package/dist/trueSight-DZJ12lrB.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-ZXt938Hq.esm.js";
2
+ import { s as exposeExperimentOnce } from "./feature-management-DBuXnUWY.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-cimVc012.esm.js";
5
+ import { c as trueSightHintTransition, l as trueSightLog, r as noteTrueSightCaptureActivity } from "./trueSightService-CTM6xlHG.esm.js";
6
6
  import { n as eventModuleNames, s as recordExperimentAssignmentOnce } from "./events-DYiyoSlx.esm.js";
7
- import { t as getDisableIpify } from "./sessionInitializer-BVjQZWxu.esm.js";
8
- import "./trueSight-DZJ12lrB.esm.js";
7
+ import { t as getDisableIpify } from "./sessionInitializer-CVrazqxw.esm.js";
8
+ import "./trueSight-ClQ2RLmN.esm.js";
9
9
  import { t as addDeviceStats } from "./stats-B0nFKawG.esm.js";
10
10
  import { t as isAndroid } from "./platform-Q4r1nvWX.esm.js";
11
- import "./session-khwoLlR8.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-CK-eOrL7.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-Dgtg_4Lv.esm.js";
15
- import { _ as StreamCanvasProcessingSession } from "./onDeviceFaceResults-BeHb0Zet.esm.js";
15
+ import { _ as StreamCanvasProcessingSession } from "./onDeviceFaceResults-BqLGnM8A.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-DmNprTMV.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-DJNYwEOq.esm.js";
22
22
  import { r as flagIdManualReview, t as imageFileToDataUrl } from "./imageFile-DutiOU7n.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-D5TXcSQQ.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-0lE7kEdA.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) {
@@ -737,6 +737,20 @@ const hasAttemptsRemainingGuard = ({ context }) => context.attemptsRemaining > 0
737
737
  const hasCapturedImageGuard = ({ context }) => {
738
738
  return context.provider?.getCapturedCanvas() !== null;
739
739
  };
740
+ const hasCandidateFrameGuard = ({ event }) => {
741
+ return event.type === "DETECTION_OPTIMIZING" && Boolean(event.candidateCanvas);
742
+ };
743
+ /**
744
+ * IDC-05: whether WASM's commit arrived while the optimizing phase was still
745
+ * spending its dwell, and is therefore parked in `optimizing.holding` waiting
746
+ * for the release.
747
+ *
748
+ * Gates the phase's own `SWITCH_TO_MANUAL_CAPTURE` / `MANUAL_CAPTURE` handlers:
749
+ * while a commit is parked those escapes end the dwell early instead of leaving
750
+ * the phase, so WASM's committed frame is honored rather than dropped on the way
751
+ * out. False leaves both events to the arm parent, the documented escape hatch.
752
+ */
753
+ const hasStashedCommitGuard = ({ context }) => context.stashedCommit !== void 0;
740
754
  const hasUploadValidationErrorGuard = ({ context }) => {
741
755
  if (!context.uploadResponse) return false;
742
756
  return validateUploadResponse(context.uploadResponse, {
@@ -781,16 +795,31 @@ const shouldContinueToBackGuard = ({ context }) => {
781
795
  const shouldContinueToFrontGuard = ({ context }) => context.config.usSmartCapture === true && context.currentMode === "back" && context.uploadResponse?.forceFrontIdCapture === true;
782
796
  const hasMandatoryConsentGuard = ({ context }) => context.uploadResponse?.showMandatoryConsent === true;
783
797
  /**
784
- * Capture-result-preview feedback mode, resolved once per session by
785
- * `resolveCaptureResultPreviewTreatment` at actor creation.
798
+ * IDC-05 arm, as resolved once per session by
799
+ * `resolveCaptureOptimizingTreatment` at actor creation.
786
800
  */
787
- const isCaptureResultPreviewModeGuard = ({ context }) => context.captureFeedbackMode === "result-preview";
801
+ const isCaptureOptimizingModeGuard = ({ context }) => context.captureFeedbackMode === "optimizing";
788
802
  /**
789
- * Whether the rendering layer opted into handling `resultPreview` and
790
- * advancing it with `manager.nextStep()`. Headless consumers that omit this
791
- * capability retain the direct capture-to-upload path.
803
+ * IDC-05 rendering-layer capability: whether the consumer opted into rendering
804
+ * `captureStatus === 'optimizing'`. Consumers that omit it retain the direct
805
+ * capture-to-upload path. Nothing is advanced by the host — core owns the phase's
806
+ * timing on both paths (WASM's commit on auto capture, `optimizingDwell` on
807
+ * manual), so there is no acknowledgement call.
808
+ *
809
+ * Paired with `isCaptureOptimizingModeGuard` at both branch sites
810
+ * (`choosingDetectionStrategy`, `capturingManual`). Through
811
+ * `createIdCaptureActor` the two can never disagree — that path derives
812
+ * `captureFeedbackMode` from `resolveCaptureOptimizingTreatment`, which
813
+ * already requires this capability — so the pairing looks redundant, and for
814
+ * SDK-built sessions it is.
815
+ *
816
+ * It is NOT redundant on the public surface: `idCaptureMachine` is exported from
817
+ * `@incodetech/core/id` and its `input.captureFeedbackMode` is settable, so a
818
+ * host wiring the machine up directly could otherwise reach the phase without
819
+ * declaring the capability. This guard is what makes the documented opt-in true
820
+ * for every route into the machine, not just the factory.
792
821
  */
793
- const supportsCaptureResultPreviewGuard = ({ context }) => context.config.supportsCaptureResultPreview === true;
822
+ const supportsOptimizingStatusGuard = ({ context }) => context.config.supportsOptimizingStatus === true;
794
823
  const isDeepsightEnabledGuard = ({ context }) => context.dependencies !== void 0;
795
824
  const isDeepsightReadyGuard = ({ context }) => context.deepsightService !== void 0;
796
825
  const needsDeepsightInitGuard = ({ context }) => context.dependencies !== void 0 && context.deepsightService === void 0 && !context.deepsightInitAttempted;
@@ -879,6 +908,7 @@ const isBackModeInMultiSideFlowGuard = ({ context }) => context.currentMode ===
879
908
  * max-attempts/OCR cap (ENG-46592 / re-respects ENG-45666).
880
909
  */
881
910
  const wasExpiredRedirectedGuard = ({ context }) => context.expiredRedirected === true;
911
+ const hasManualCaptureTriggeredGuard = ({ context }) => context.manualCaptureTriggered;
882
912
  //#endregion
883
913
  //#region src/modules/id/idCaptureHelpers.ts
884
914
  /**
@@ -987,6 +1017,13 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
987
1017
  let isCapturing = false;
988
1018
  const timer = BrowserTimerProvider.getInstance();
989
1019
  let notificationTimeout = null;
1020
+ let optimizingBoundaryTimeout = null;
1021
+ /**
1022
+ * IDC-05 latch for the boundary clock: whether a boundary is currently
1023
+ * pending. `onCapturing` is WASM's "aggregation window opened" callback and is
1024
+ * the clock's only T0; nothing in WASM enforces that it fires once per window.
1025
+ */
1026
+ let isOptimizingBoundaryArmed = false;
990
1027
  let currentNotificationStatus = null;
991
1028
  let lastReportedSide = null;
992
1029
  const NOTIFICATION_DURATION_MS = 500;
@@ -1005,6 +1042,50 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1005
1042
  currentNotificationStatus = null;
1006
1043
  }
1007
1044
  };
1045
+ const clearOptimizingBoundary = () => {
1046
+ if (optimizingBoundaryTimeout) {
1047
+ timer.clearTimeout(optimizingBoundaryTimeout);
1048
+ optimizingBoundaryTimeout = null;
1049
+ }
1050
+ isOptimizingBoundaryArmed = false;
1051
+ };
1052
+ /**
1053
+ * IDC-05 boundary clock — the actor's *only* timer, and the only piece of the
1054
+ * optimizing phase it owns. WASM's `CapturingManager` opens the
1055
+ * frames-aggregation window at `onCapturing` and closes it with `onCapture` at
1056
+ * `T0 + framesAggregationInterval` (already resolved to
1057
+ * `captureWindowMs + optimizingDwellMs`). The phase begins at
1058
+ * `T0 + captureWindowMs`, so it is timed from the very callback that defines
1059
+ * T0; how long it then lasts is the machine's business.
1060
+ *
1061
+ * No best frame yet at that point means the session simply behaves like the
1062
+ * countdown arm — no event, no phase — which mirrors iOS's "auto capture with
1063
+ * no best crop yet" fallback.
1064
+ *
1065
+ * At most one boundary pending at a time — see `isOptimizingBoundaryArmed`.
1066
+ */
1067
+ const scheduleOptimizingBoundary = () => {
1068
+ if (input.optimizingAfterMs == null || isOptimizingBoundaryArmed) return;
1069
+ isOptimizingBoundaryArmed = true;
1070
+ optimizingBoundaryTimeout = timer.setTimeout(() => {
1071
+ optimizingBoundaryTimeout = null;
1072
+ isOptimizingBoundaryArmed = false;
1073
+ enterOptimizing();
1074
+ }, input.optimizingAfterMs);
1075
+ };
1076
+ /**
1077
+ * Reports the boundary with the candidate frame to freeze. Silent when there
1078
+ * is no candidate — no event means no phase, and the session behaves like the
1079
+ * countdown arm (see `scheduleOptimizingBoundary`).
1080
+ */
1081
+ const enterOptimizing = () => {
1082
+ const candidateCanvas = bestCanvas?.clone();
1083
+ if (!candidateCanvas) return;
1084
+ sendBack({
1085
+ type: "DETECTION_OPTIMIZING",
1086
+ candidateCanvas
1087
+ });
1088
+ };
1008
1089
  const sendNotification = (status) => {
1009
1090
  if (isReportedSideWrong()) return;
1010
1091
  if (notificationTimeout && currentNotificationStatus !== status) return;
@@ -1092,6 +1173,7 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1092
1173
  },
1093
1174
  onSwitchToManualCapture: () => {
1094
1175
  isCapturing = false;
1176
+ clearOptimizingBoundary();
1095
1177
  sendBack({ type: "SWITCH_TO_MANUAL_CAPTURE" });
1096
1178
  },
1097
1179
  onCapturing: () => {
@@ -1103,6 +1185,7 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1103
1185
  type: "DETECTION_UPDATE",
1104
1186
  status: "capturing"
1105
1187
  });
1188
+ scheduleOptimizingBoundary();
1106
1189
  },
1107
1190
  onBestFrame: (blur, glare, orientation) => {
1108
1191
  if (currentCanvas) {
@@ -1119,16 +1202,24 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1119
1202
  },
1120
1203
  onCapture: () => {
1121
1204
  isCapturing = false;
1122
- if (bestCanvas) sendBack({
1205
+ if (isOptimizingBoundaryArmed) {
1206
+ clearOptimizingBoundary();
1207
+ enterOptimizing();
1208
+ }
1209
+ if (!bestCanvas) {
1210
+ const clonedCanvas = currentCanvas?.clone();
1211
+ if (clonedCanvas) sendBack({
1212
+ type: "DETECTION_SUCCESS",
1213
+ canvas: clonedCanvas,
1214
+ qualityElements: {}
1215
+ });
1216
+ return;
1217
+ }
1218
+ sendBack({
1123
1219
  type: "DETECTION_SUCCESS",
1124
1220
  canvas: bestCanvas,
1125
1221
  qualityElements: storedQualityElements
1126
1222
  });
1127
- else if (currentCanvas) sendBack({
1128
- type: "DETECTION_SUCCESS",
1129
- canvas: currentCanvas.clone(),
1130
- qualityElements: {}
1131
- });
1132
1223
  },
1133
1224
  onIdTypeChange: (idType) => {
1134
1225
  sendBack({
@@ -1179,6 +1270,7 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1179
1270
  notificationTimeout = null;
1180
1271
  currentNotificationStatus = null;
1181
1272
  }
1273
+ clearOptimizingBoundary();
1182
1274
  currentCanvas = null;
1183
1275
  bestCanvas = null;
1184
1276
  storedQualityElements = {};
@@ -1197,6 +1289,7 @@ const runDetectionActor = fromCallback(({ input, sendBack }) => {
1197
1289
  });
1198
1290
  return () => {
1199
1291
  clearNotificationTimeout();
1292
+ clearOptimizingBoundary();
1200
1293
  session?.dispose();
1201
1294
  noteTrueSightCaptureActivity(false);
1202
1295
  };
@@ -1284,7 +1377,7 @@ const startRecordingActor = fromPromise(async ({ input }) => {
1284
1377
  });
1285
1378
  const initializeDeepsightSessionActor = fromPromise(async ({ input }) => {
1286
1379
  if (!input.dependencies) return;
1287
- const { loadDeepsightSession } = await import("./deepsightLoader-CJ-pplPZ.esm.js");
1380
+ const { loadDeepsightSession } = await import("./deepsightLoader-B97yrkGH.esm.js");
1288
1381
  return loadDeepsightSession({
1289
1382
  ds: input.ds,
1290
1383
  storage: input.dependencies.storage,
@@ -1322,11 +1415,11 @@ function performResetForFrontCapture(context) {
1322
1415
  function performResetDetection(context) {
1323
1416
  context.resetDetection?.();
1324
1417
  }
1325
- function performReportCaptureResultPreviewAssignment(context) {
1326
- if (context.config.supportsCaptureResultPreview !== true) return;
1327
- const assignment = exposeExperimentOnce(CAPTURE_RESULT_PREVIEW);
1418
+ function performReportCaptureOptimizingAssignment(context) {
1419
+ if (context.config.supportsOptimizingStatus !== true) return;
1420
+ const assignment = exposeExperimentOnce(CAPTURE_OPTIMIZING);
1328
1421
  if (!assignment) return;
1329
- recordExperimentAssignmentOnce(eventModuleNames.idCapture, CAPTURE_RESULT_PREVIEW.storeParam, assignment);
1422
+ recordExperimentAssignmentOnce(eventModuleNames.idCapture, CAPTURE_OPTIMIZING.storeParam, assignment);
1330
1423
  }
1331
1424
  function performStopMediaRecording(context) {
1332
1425
  if (context.recordingSession) stopRecording(context.recordingSession);
@@ -1392,6 +1485,7 @@ function getResetContextValues(context) {
1392
1485
  idType: void 0,
1393
1486
  qualityElements: void 0,
1394
1487
  previewImageUrl: void 0,
1488
+ ...getClearOptimizingValues(context),
1395
1489
  uploadProgress: void 0,
1396
1490
  currentMode,
1397
1491
  selectedDocumentType: void 0,
@@ -1407,6 +1501,7 @@ function getClearUploadFailureValues(context) {
1407
1501
  uploadError: void 0,
1408
1502
  detectionStatus: "idle",
1409
1503
  previewImageUrl: void 0,
1504
+ ...getClearOptimizingValues(context),
1410
1505
  uploadProgress: void 0
1411
1506
  };
1412
1507
  }
@@ -1426,6 +1521,39 @@ function storeCapturedCanvasInProviderLogic(context, event) {
1426
1521
  const canvasHeight = canvas.height();
1427
1522
  if (!canvasWidth || !canvasHeight) return;
1428
1523
  const originalCanvas = canvas;
1524
+ const frameRect = resolveCaptureFrameRect(context, canvasWidth, canvasHeight);
1525
+ const transformedCanvas = context.provider.transformPerspective(originalCanvas, frameRect);
1526
+ context.provider.setCapturedCanvases(originalCanvas, transformedCanvas);
1527
+ }
1528
+ /**
1529
+ * JPEG quality for the IDC-05 optimizing phase's display-only still.
1530
+ *
1531
+ * `updateBlob()` is synchronous and expensive: `toDataURL` → `atob` → a per-byte
1532
+ * loop building the blob (`IncodeCanvas.base64ToBlob`). At the default quality of
1533
+ * 1.0 on a 1080p frame that is tens of milliseconds of main-thread work — and on
1534
+ * this path it runs inside an `assign`, in the same tick that starts the phase
1535
+ * animation, with the 30fps WASM detection loop still live. Both halves of the
1536
+ * cost scale with the encoded byte count, so dropping quality cuts the encode and
1537
+ * the byte loop together.
1538
+ *
1539
+ * Treatment-arm only, deliberately: the committed frame keeps quality 1.0, since
1540
+ * it is what uploads (`getStoreCapturedImageValues` → `getBase64Image(1.0)`) and
1541
+ * its encode happens on the analyzing screen with no animation to jank. This
1542
+ * still is on screen for one dwell at roughly card size and never leaves the
1543
+ * device.
1544
+ */
1545
+ const CANDIDATE_FRAME_JPEG_QUALITY = .8;
1546
+ /**
1547
+ * The document's rectangle within a camera frame, in canvas coordinates —
1548
+ * the input to `transformPerspective`.
1549
+ *
1550
+ * Shared by the commit path (`storeCapturedCanvasInProviderLogic`) and the
1551
+ * IDC-05 optimizing phase (`getCandidateFrameValues`) so the still shown
1552
+ * mid-capture is cropped and warped exactly like the frame that later uploads.
1553
+ * Deriving it twice is what made the phase show an uncropped full frame that then
1554
+ * visibly jumped to a cropped one on the analyzing screen.
1555
+ */
1556
+ function resolveCaptureFrameRect(context, canvasWidth, canvasHeight) {
1429
1557
  let frameRect;
1430
1558
  const { innerWidth: viewportWidth, innerHeight: viewportHeight } = getWindowDimensions();
1431
1559
  if (context.detectionArea) {
@@ -1457,7 +1585,7 @@ function storeCapturedCanvasInProviderLogic(context, event) {
1457
1585
  h: context.frameRect.h / scale
1458
1586
  };
1459
1587
  } else {
1460
- const quadValue = (context.provider.getLastProcessResult?.())?.quad;
1588
+ const quadValue = (context.provider?.getLastProcessResult?.())?.quad;
1461
1589
  const hasQuad = !!quadValue;
1462
1590
  const quadSize = quadValue?.size ? quadValue.size() : quadValue?.length ?? 0;
1463
1591
  if (hasQuad && quadSize >= 4 && quadValue.get) {
@@ -1488,8 +1616,7 @@ function storeCapturedCanvasInProviderLogic(context, event) {
1488
1616
  };
1489
1617
  }
1490
1618
  }
1491
- const transformedCanvas = context.provider.transformPerspective(originalCanvas, frameRect);
1492
- context.provider.setCapturedCanvases(originalCanvas, transformedCanvas);
1619
+ return frameRect;
1493
1620
  }
1494
1621
  function getStoreCapturedImageValues(context, event) {
1495
1622
  if (!context.currentMode) return {
@@ -1523,7 +1650,79 @@ function getStoreCapturedImageValues(context, event) {
1523
1650
  }
1524
1651
  return {
1525
1652
  capturedImages,
1526
- previewImageUrl
1653
+ previewImageUrl,
1654
+ ...getClearOptimizingValues(context)
1655
+ };
1656
+ }
1657
+ /**
1658
+ * IDC-05: freezes WASM's best-scoring frame *so far* for the optimizing phase.
1659
+ * Display-only — the committed frame arrives later, from `onCapture`, and
1660
+ * `storeCapturedImage` puts that one on `previewImageUrl` while clearing this
1661
+ * one in the same assign, so the rendering layer's
1662
+ * `candidateImageUrl ?? previewImageUrl` falls through to the frame that
1663
+ * actually uploads for the crossfade.
1664
+ *
1665
+ * Revokes any previous URL rather than leaking one per retry.
1666
+ */
1667
+ function getCandidateFrameValues(context, event) {
1668
+ if (event.type !== "DETECTION_OPTIMIZING" || !event.candidateCanvas) return { candidateImageUrl: context.candidateImageUrl };
1669
+ const candidateCanvas = event.candidateCanvas;
1670
+ const canvas = cropCanvasForPreview(context, candidateCanvas);
1671
+ canvas.updateBlob(CANDIDATE_FRAME_JPEG_QUALITY);
1672
+ const url = canvas.getBlobData()?.url;
1673
+ if (!url) {
1674
+ releasePreviewCanvases(candidateCanvas, canvas, { shouldReleasePreview: true });
1675
+ return { candidateImageUrl: context.candidateImageUrl };
1676
+ }
1677
+ releasePreviewCanvases(candidateCanvas, canvas, { shouldReleasePreview: false });
1678
+ if (context.candidateImageUrl) revokeObjectURL(context.candidateImageUrl);
1679
+ return { candidateImageUrl: url };
1680
+ }
1681
+ /**
1682
+ * Reclaims the full-frame canvases the phase allocated. At 1080p each is ~8MB of
1683
+ * backing store, and `release()` frees it now instead of at the next GC — which
1684
+ * is what matters on iOS Safari, where canvas memory is its own budget.
1685
+ *
1686
+ * `source` — the clone the detection actor snapshotted — is always dead by the
1687
+ * time this runs: its pixels live on in `preview`'s blob.
1688
+ *
1689
+ * `preview` is the caller's decision, because `release()` also revokes
1690
+ * `blobData.url`. On the success path that URL is the very thing being handed to
1691
+ * the machine as `candidateImageUrl`, so releasing it would blank the phase's
1692
+ * own still; pass `shouldReleasePreview: true` only where no URL survives. Either way
1693
+ * the identity guard covers `transformPerspective` returning the canvas it was
1694
+ * given (no provider, or a WASM failure), so the aliased canvas is never
1695
+ * released twice or released out from under a live URL.
1696
+ */
1697
+ function releasePreviewCanvases(source, preview, { shouldReleasePreview }) {
1698
+ if (preview === source) {
1699
+ if (shouldReleasePreview) source.release();
1700
+ return;
1701
+ }
1702
+ if (shouldReleasePreview) preview.release();
1703
+ source.release();
1704
+ }
1705
+ function cropCanvasForPreview(context, canvas) {
1706
+ const canvasWidth = canvas.width();
1707
+ const canvasHeight = canvas.height();
1708
+ if (!context.provider || !canvasWidth || !canvasHeight) return canvas;
1709
+ const frameRect = resolveCaptureFrameRect(context, canvasWidth, canvasHeight);
1710
+ return context.provider.transformPerspective(canvas, frameRect);
1711
+ }
1712
+ /**
1713
+ * Drops the IDC-05 best-so-far still at every attempt/side boundary. Unlike
1714
+ * `previewImageUrl` it is never preserved into `flipPreview` (it was never a
1715
+ * committed frame), so it can always be revoked outright.
1716
+ *
1717
+ * Clears `stashedCommit` alongside it: a commit parked for the phase's dwell is
1718
+ * only meaningful inside that phase, and holding the canvas reference past it
1719
+ * would keep a full-res frame alive for the rest of the attempt.
1720
+ */
1721
+ function getClearOptimizingValues(context) {
1722
+ if (context.candidateImageUrl) revokeObjectURL(context.candidateImageUrl);
1723
+ return {
1724
+ candidateImageUrl: void 0,
1725
+ stashedCommit: void 0
1527
1726
  };
1528
1727
  }
1529
1728
  function getDetectionStatusFromUpdate(context, event) {
@@ -1658,6 +1857,7 @@ const idCaptureMachine = setup({
1658
1857
  qualityElements: void 0,
1659
1858
  frameRect: void 0,
1660
1859
  previewImageUrl: void 0,
1860
+ ...getClearOptimizingValues(context),
1661
1861
  flipPreview: context.previewImageUrl ? {
1662
1862
  url: context.previewImageUrl,
1663
1863
  orientation: context.orientation,
@@ -1688,6 +1888,7 @@ const idCaptureMachine = setup({
1688
1888
  qualityElements: void 0,
1689
1889
  frameRect: void 0,
1690
1890
  previewImageUrl: void 0,
1891
+ ...getClearOptimizingValues(context),
1691
1892
  flipPreview: context.previewImageUrl ? {
1692
1893
  url: context.previewImageUrl,
1693
1894
  orientation: context.orientation,
@@ -1717,6 +1918,7 @@ const idCaptureMachine = setup({
1717
1918
  qualityElements: void 0,
1718
1919
  frameRect: void 0,
1719
1920
  previewImageUrl: void 0,
1921
+ ...getClearOptimizingValues(context),
1720
1922
  uploadProgress: void 0,
1721
1923
  manualCaptureTriggered: false,
1722
1924
  attemptsRemaining: context.config.captureAttempts
@@ -1739,8 +1941,8 @@ const idCaptureMachine = setup({
1739
1941
  trackTutorialId: () => {},
1740
1942
  trackContinue: () => {},
1741
1943
  trackCameraId: ({ context }) => {},
1742
- reportCaptureResultPreviewAssignment: ({ context }) => {
1743
- performReportCaptureResultPreviewAssignment(context);
1944
+ reportCaptureOptimizingAssignment: ({ context }) => {
1945
+ performReportCaptureOptimizingAssignment(context);
1744
1946
  },
1745
1947
  preloadRecordingProvider: ({ context }) => {
1746
1948
  preloadIdRecordingProvider(context.config);
@@ -1782,6 +1984,53 @@ const idCaptureMachine = setup({
1782
1984
  } }),
1783
1985
  setCurrentMode: assign({ currentMode: ({ event, context }) => getCurrentModeFromEvent(context, event) }),
1784
1986
  storeCapturedImage: assign(({ context, event }) => getStoreCapturedImageValues(context, event)),
1987
+ setCandidateFrame: assign(({ context, event }) => getCandidateFrameValues(context, event)),
1988
+ /**
1989
+ * IDC-05: parks WASM's commit while the optimizing phase spends the rest of
1990
+ * its dwell. The frame travels in context only for that stretch —
1991
+ * `releaseStashedCommit` re-raises it and `getClearOptimizingValues` drops it
1992
+ * on every other exit from the phase.
1993
+ */
1994
+ stashCommittedFrame: assign({ stashedCommit: ({ event }) => {
1995
+ const commit = event;
1996
+ return {
1997
+ canvas: commit.canvas,
1998
+ qualityElements: commit.qualityElements
1999
+ };
2000
+ } }),
2001
+ /**
2002
+ * IDC-05: hands a parked commit back to the machine as the very event that
2003
+ * would have carried it, so `capturing`'s entry actions stay event-driven.
2004
+ * Re-raised rather than read from context by `capturing` because
2005
+ * `storeCapturedCanvasInProvider` falls back to the *live* frame when the
2006
+ * entering event has no canvas — a timer transition into `capturing` would
2007
+ * stage a live frame for upload instead of the one WASM committed.
2008
+ */
2009
+ releaseStashedCommit: enqueueActions(({ context, enqueue }) => {
2010
+ const stashed = context.stashedCommit;
2011
+ if (!stashed) return;
2012
+ enqueue(raise({
2013
+ type: "DETECTION_SUCCESS",
2014
+ canvas: stashed.canvas,
2015
+ qualityElements: stashed.qualityElements
2016
+ }));
2017
+ }),
2018
+ /**
2019
+ * IDC-05: releases everything the optimizing phase allocated — the parked
2020
+ * commit AND the frozen still's blob URL. Bound to `optimizing.exit`, so it
2021
+ * covers every route out of the phase, not just the two that already clear
2022
+ * up after themselves (`getStoreCapturedImageValues` on the commit,
2023
+ * `optimizingGrace`'s own action on the abandon path). Before this it
2024
+ * cleared only `stashedCommit`, and an exit through the manual-capture
2025
+ * escape leaked the URL until the next attempt/side boundary.
2026
+ *
2027
+ * Safe to revoke here on the commit path: exit and `capturing`'s entry run
2028
+ * in the same macrostep, and `capturing`'s eventless `always` means the UI
2029
+ * only ever observes the resulting `uploading` snapshot — by which point
2030
+ * `previewImageUrl` is set, so `candidateImageUrl ?? previewImageUrl` never
2031
+ * resolves to nothing (see `getStoreCapturedImageValues`).
2032
+ */
2033
+ clearOptimizingPhase: assign(({ context }) => getClearOptimizingValues(context)),
1785
2034
  storeBarcodeResponse: assign(({ context, event }) => getBarcodeResponseValues(context, event)),
1786
2035
  storeDecodedBarcode: assign({ decodedBarcode: ({ event }) => event.decoded }),
1787
2036
  setBarcodeFastPathAttempted: assign({ barcodeFastPathAttempted: true }),
@@ -1849,13 +2098,15 @@ const idCaptureMachine = setup({
1849
2098
  hasInitializedCamera: hasInitializedCameraGuard,
1850
2099
  hasAttemptsRemaining: hasAttemptsRemainingGuard,
1851
2100
  hasCapturedImage: hasCapturedImageGuard,
2101
+ hasCandidateFrame: hasCandidateFrameGuard,
2102
+ hasStashedCommit: hasStashedCommitGuard,
1852
2103
  hasUploadValidationError: hasUploadValidationErrorGuard,
1853
2104
  isFrontMode: isFrontModeGuard,
1854
2105
  shouldContinueToBack: shouldContinueToBackGuard,
1855
2106
  shouldContinueToFront: shouldContinueToFrontGuard,
1856
2107
  hasMandatoryConsent: hasMandatoryConsentGuard,
1857
- isCaptureResultPreviewMode: isCaptureResultPreviewModeGuard,
1858
- supportsCaptureResultPreview: supportsCaptureResultPreviewGuard,
2108
+ isCaptureOptimizingMode: isCaptureOptimizingModeGuard,
2109
+ supportsOptimizingStatus: supportsOptimizingStatusGuard,
1859
2110
  isDeepsightEnabled: isDeepsightEnabledGuard,
1860
2111
  isDeepsightReady: isDeepsightReadyGuard,
1861
2112
  needsDeepsightInit: needsDeepsightInitGuard,
@@ -1871,6 +2122,7 @@ const idCaptureMachine = setup({
1871
2122
  shouldSkipProcessId: shouldSkipProcessIdGuard,
1872
2123
  shouldSubmitBarcode: shouldSubmitBarcodeGuard,
1873
2124
  canRunBarcodeFastPath: canRunBarcodeFastPathGuard,
2125
+ hasManualCaptureTriggered: hasManualCaptureTriggeredGuard,
1874
2126
  isProcessingExpiredExhausted: isProcessingExpiredExhaustedGuard,
1875
2127
  shouldProcessAfterExhaustion: shouldProcessAfterExhaustionGuard,
1876
2128
  forceFinishAfterProcessing: forceFinishAfterProcessingGuard,
@@ -1879,6 +2131,36 @@ const idCaptureMachine = setup({
1879
2131
  isManualUploadExhausted: ({ event }) => event.output?.result === "exhausted",
1880
2132
  isDigitalUploadExhausted: ({ event }) => event.output?.result === "exhausted",
1881
2133
  isDigitalUploadSuccessful: ({ event }) => event.output?.result === "success"
2134
+ },
2135
+ delays: {
2136
+ /**
2137
+ * IDC-05: how long the optimizing phase lasts, on BOTH paths — the auto
2138
+ * path's `optimizing.holding` and the manual path's `optimizingHold`. One
2139
+ * value, read from context rather than hardcoded, so the experiment payload
2140
+ * governs the phase wherever it is played.
2141
+ *
2142
+ * `?? 0` rather than the default dwell, deliberately: neither state is
2143
+ * reachable outside the treatment arm, and the treatment arm is the only
2144
+ * thing that populates `optimizingDwellMs`
2145
+ * (`resolveCaptureOptimizingTreatment` always sets it on its `optimizing`
2146
+ * branch). An absent value here therefore means the wiring between the two
2147
+ * broke, and falling back to the default would silently add a dwell to a
2148
+ * capture that was never supposed to have one. Zero makes that failure a
2149
+ * pass-through instead of a 1.5s regression.
2150
+ */
2151
+ optimizingDwell: ({ context }) => context.optimizingDwellMs ?? 0,
2152
+ /**
2153
+ * IDC-05: how much longer the machine waits for WASM's commit once the phase
2154
+ * has had its full dwell (`optimizing.released`). Past this, `onCapture` —
2155
+ * the phase's normal exit — is not coming for the window this still belongs
2156
+ * to, and the phase is abandoned.
2157
+ *
2158
+ * A constant, not `dwell + margin`: the dwell has already been spent in
2159
+ * `holding` by the time this arms, so the total from phase entry is
2160
+ * unchanged. It also means the timer stays armed on a partially-resolved
2161
+ * payload, where `optimizingDwell` above collapses to zero.
2162
+ */
2163
+ optimizingGrace: OPTIMIZING_WATCHDOG_MARGIN_MS
1882
2164
  }
1883
2165
  }).createMachine({
1884
2166
  id: "idCapture",
@@ -1889,6 +2171,8 @@ const idCaptureMachine = setup({
1889
2171
  return {
1890
2172
  config: input.config,
1891
2173
  captureFeedbackMode: input.captureFeedbackMode ?? "countdown",
2174
+ captureWindowMs: input.captureWindowMs,
2175
+ optimizingDwellMs: input.optimizingDwellMs,
1892
2176
  currentMode,
1893
2177
  selectedDocumentType: void 0,
1894
2178
  stream: void 0,
@@ -1922,6 +2206,7 @@ const idCaptureMachine = setup({
1922
2206
  frameRect: void 0,
1923
2207
  detectionArea: void 0,
1924
2208
  previewImageUrl: void 0,
2209
+ candidateImageUrl: void 0,
1925
2210
  uploadProgress: void 0,
1926
2211
  manualCaptureTriggered: false,
1927
2212
  deepsightService: void 0,
@@ -2252,7 +2537,7 @@ const idCaptureMachine = setup({
2252
2537
  guard: "needsDeepsightInit"
2253
2538
  },
2254
2539
  {
2255
- target: "detecting",
2540
+ target: "choosingDetectionStrategy",
2256
2541
  guard: "hasInitializedCamera"
2257
2542
  },
2258
2543
  { target: "initializing" }
@@ -2287,7 +2572,7 @@ const idCaptureMachine = setup({
2287
2572
  cameraFallbackLevel: context.cameraFallbackLevel
2288
2573
  }),
2289
2574
  onDone: {
2290
- target: "detecting",
2575
+ target: "choosingDetectionStrategy",
2291
2576
  actions: "setStreamAndCapturer"
2292
2577
  },
2293
2578
  onError: [{
@@ -2299,13 +2584,20 @@ const idCaptureMachine = setup({
2299
2584
  actions: assign({ error: ({ event }) => String(event.error) })
2300
2585
  }]
2301
2586
  } },
2587
+ choosingDetectionStrategy: {
2588
+ entry: ["reportCaptureOptimizingAssignment"],
2589
+ always: [{
2590
+ target: "detectingWithOptimization",
2591
+ guard: and(["isCaptureOptimizingMode", "supportsOptimizingStatus"])
2592
+ }, { target: "detecting" }]
2593
+ },
2302
2594
  detecting: {
2303
2595
  always: [{
2304
2596
  target: "manualCaptureWaiting",
2305
- guard: ({ context }) => context.manualCaptureTriggered,
2597
+ guard: "hasManualCaptureTriggered",
2306
2598
  actions: assign({ detectionStatus: () => "manualCapture" })
2307
2599
  }],
2308
- entry: [assign({ detectionStatus: () => "detecting" }), "reportCaptureResultPreviewAssignment"],
2600
+ entry: [assign({ detectionStatus: () => "detecting" })],
2309
2601
  invoke: [{
2310
2602
  id: "startRecording",
2311
2603
  src: "startRecording",
@@ -2356,6 +2648,113 @@ const idCaptureMachine = setup({
2356
2648
  ORIENTATION_CHANGE: { actions: "setOrientation" }
2357
2649
  }
2358
2650
  },
2651
+ /**
2652
+ * IDC-05 treatment arm: same detection as `detecting` above (kept in sync by
2653
+ * hand — see the comment there), plus the two-phase searching -> optimizing
2654
+ * phase.
2655
+ *
2656
+ * Two *distinct* state nodes (this one and `detecting`) rather than one
2657
+ * compound state, deliberately: `snapshot.matches({ capture: 'detecting' })`
2658
+ * keeps meaning exactly what it always meant. Folding the phase into
2659
+ * `detecting` as a substate would make every such selector silently true
2660
+ * while the user is being told the capture succeeded — a hazard no type or
2661
+ * test would catch.
2662
+ */
2663
+ detectingWithOptimization: {
2664
+ entry: assign({ detectionStatus: () => "detecting" }),
2665
+ always: [{
2666
+ target: "manualCaptureWaiting",
2667
+ guard: "hasManualCaptureTriggered",
2668
+ actions: assign({ detectionStatus: () => "manualCapture" })
2669
+ }],
2670
+ invoke: [{
2671
+ id: "startRecording",
2672
+ src: "startRecording",
2673
+ input: ({ context }) => ({
2674
+ config: context.config,
2675
+ stream: context.stream,
2676
+ existing: context.recordingSession,
2677
+ currentMode: context.currentMode
2678
+ }),
2679
+ onDone: { actions: assign({ recordingSession: ({ context, event }) => {
2680
+ return event.output ?? context.recordingSession;
2681
+ } }) },
2682
+ onError: { actions: () => void 0 }
2683
+ }, {
2684
+ id: "runDetection",
2685
+ src: "runDetection",
2686
+ input: ({ context }) => ({
2687
+ frameCapturer: context.frameCapturer,
2688
+ provider: context.provider,
2689
+ config: context.config,
2690
+ currentMode: context.currentMode,
2691
+ detectionArea: context.detectionArea ?? context.config.detectionArea,
2692
+ optimizingAfterMs: context.captureWindowMs
2693
+ })
2694
+ }],
2695
+ on: {
2696
+ DETECTION_SUCCESS: {
2697
+ target: "capturing",
2698
+ actions: assign({ qualityElements: ({ event }) => event.qualityElements })
2699
+ },
2700
+ DETECTION_RESET_READY: { actions: assign({ resetDetection: ({ event }) => event.reset }) },
2701
+ ID_TYPE_CHANGE: { actions: "setIdType" },
2702
+ ORIENTATION_CHANGE: { actions: "setOrientation" },
2703
+ MANUAL_CAPTURE: { target: "#idCapture.capture.capturingManual" },
2704
+ SWITCH_TO_MANUAL_CAPTURE: {
2705
+ target: "#idCapture.capture.manualCaptureWaiting",
2706
+ actions: assign({
2707
+ detectionStatus: () => "manualCapture",
2708
+ manualCaptureTriggered: () => true
2709
+ })
2710
+ }
2711
+ },
2712
+ initial: "searching",
2713
+ states: {
2714
+ searching: { on: {
2715
+ DETECTION_UPDATE: { actions: "setDetectionStatus" },
2716
+ ID_SIDE_CHANGE: { actions: assign({ detectionStatus: ({ context, event }) => getDetectionStatusFromSideChange(context, event.side) }) },
2717
+ BARCODE_DECODED: [{
2718
+ target: "#idCapture.capture.capturingBarcode",
2719
+ guard: "canRunBarcodeFastPath",
2720
+ actions: ["storeDecodedBarcode", "setBarcodeFastPathAttempted"]
2721
+ }, { actions: "storeDecodedBarcode" }],
2722
+ DETECTION_OPTIMIZING: {
2723
+ target: "optimizing",
2724
+ guard: "hasCandidateFrame",
2725
+ actions: "setCandidateFrame"
2726
+ }
2727
+ } },
2728
+ optimizing: {
2729
+ exit: "clearOptimizingPhase",
2730
+ on: {
2731
+ BARCODE_DECODED: { actions: "storeDecodedBarcode" },
2732
+ SWITCH_TO_MANUAL_CAPTURE: {
2733
+ guard: "hasStashedCommit",
2734
+ target: "#idCapture.capture.detectingWithOptimization.optimizing.released"
2735
+ },
2736
+ MANUAL_CAPTURE: {
2737
+ guard: "hasStashedCommit",
2738
+ target: "#idCapture.capture.detectingWithOptimization.optimizing.released"
2739
+ }
2740
+ },
2741
+ initial: "holding",
2742
+ states: {
2743
+ holding: {
2744
+ after: { optimizingDwell: { target: "released" } },
2745
+ on: { DETECTION_SUCCESS: { actions: "stashCommittedFrame" } }
2746
+ },
2747
+ released: {
2748
+ entry: "releaseStashedCommit",
2749
+ after: { optimizingGrace: {
2750
+ target: "#idCapture.capture.detectingWithOptimization.searching",
2751
+ actions: assign({ detectionStatus: () => "detecting" })
2752
+ } }
2753
+ }
2754
+ }
2755
+ }
2756
+ }
2757
+ },
2359
2758
  manualCaptureWaiting: { on: { MANUAL_CAPTURE: { target: "capturingManual" } } },
2360
2759
  capturing: {
2361
2760
  entry: [
@@ -2368,14 +2767,6 @@ const idCaptureMachine = setup({
2368
2767
  target: "submittingBarcode",
2369
2768
  guard: and(["hasCapturedImage", "shouldSubmitBarcode"])
2370
2769
  },
2371
- {
2372
- target: "resultPreview",
2373
- guard: and([
2374
- "hasCapturedImage",
2375
- "isCaptureResultPreviewMode",
2376
- "supportsCaptureResultPreview"
2377
- ])
2378
- },
2379
2770
  {
2380
2771
  target: "uploading",
2381
2772
  guard: "hasCapturedImage"
@@ -2389,7 +2780,6 @@ const idCaptureMachine = setup({
2389
2780
  }
2390
2781
  ]
2391
2782
  },
2392
- resultPreview: { on: { RESULT_PREVIEW_COMPLETE: { target: "uploading" } } },
2393
2783
  submittingBarcode: { invoke: {
2394
2784
  id: "submitBarcode",
2395
2785
  src: "submitBarcode",
@@ -2425,7 +2815,7 @@ const idCaptureMachine = setup({
2425
2815
  target: "success",
2426
2816
  actions: ["storeBarcodeResponse", "markBarcodeFastPathCaptured"]
2427
2817
  },
2428
- onError: { target: "detecting" }
2818
+ onError: { target: "choosingDetectionStrategy" }
2429
2819
  } },
2430
2820
  capturingManual: {
2431
2821
  entry: [
@@ -2435,11 +2825,11 @@ const idCaptureMachine = setup({
2435
2825
  ],
2436
2826
  always: [
2437
2827
  {
2438
- target: "resultPreview",
2828
+ target: "optimizingHold",
2439
2829
  guard: and([
2440
2830
  "hasCapturedImage",
2441
- "isCaptureResultPreviewMode",
2442
- "supportsCaptureResultPreview"
2831
+ "isCaptureOptimizingMode",
2832
+ "supportsOptimizingStatus"
2443
2833
  ])
2444
2834
  },
2445
2835
  {
@@ -2455,6 +2845,7 @@ const idCaptureMachine = setup({
2455
2845
  }
2456
2846
  ]
2457
2847
  },
2848
+ optimizingHold: { after: { optimizingDwell: { target: "uploading" } } },
2458
2849
  uploading: {
2459
2850
  entry: assign({ uploadProgress: () => 0 }),
2460
2851
  invoke: [{
@@ -2656,7 +3047,7 @@ const idCaptureMachine = setup({
2656
3047
  }
2657
3048
  },
2658
3049
  onDone: [{
2659
- target: "#idCapture.capture.detecting",
3050
+ target: "#idCapture.capture.choosingDetectionStrategy",
2660
3051
  guard: "hasStream",
2661
3052
  actions: "clearFlipPreview"
2662
3053
  }, {
@@ -2708,7 +3099,7 @@ const idCaptureMachine = setup({
2708
3099
  }
2709
3100
  },
2710
3101
  onDone: [{
2711
- target: "#idCapture.capture.detecting",
3102
+ target: "#idCapture.capture.choosingDetectionStrategy",
2712
3103
  guard: "hasStream",
2713
3104
  actions: "clearFlipPreview"
2714
3105
  }, {