4.4 KiB
Identity photo preview correction
Task #69, branch kyc-69.
Delivery: PR #71.
This frontend change builds on the identity verification journey
and is independent of the birthday controls in task #67.
Cause and correction
A synthetic valid PNG reproduced this Flutter 3.47.7 browser error:
TypeError: Instance of '_UnmodifiableUint8ListView': type
'_UnmodifiableUint8ListView' is not a subtype of type 'NativeUint8List'
The stack reached Uint8ListToJSUint8Array.toJS from CanvasKit's browser
image decoder through MemoryImage and ResizeImage. File selection had
successfully decoded the original typed bytes. The Photo constructor then
copied them into an unmodifiable view, which was passed directly into the
preview's web decoder. Native decoding accepted that view, hiding the bug in
native-only tests. The generic image error builder presented the failure as a
damaged document even though the selected bytes were valid.
PhotoPreview now creates a native Uint8List copy for MemoryImage once per
selected photo. The shared domain model stays immutable, and validation,
providers, services and transport contracts remain unchanged. The preview still
requests a decode width of at most 1000 pixels and evicts its decoded cache entry
on replacement or disposal. The extra encoded-byte copy belongs to the preview
and is released with its provider/cache references; no secure-erasure guarantee
is made.
An actual preview failure now says that the preview could not be displayed and suggests selecting the photo again. The English/Russian fallback keeps at least the preview's usual height, can grow for long text, and has a separate live-region semantic node. Existing replacement/removal controls stay available and selected input is retained. This is distinct from validation's corrupt-file message.
UX and measurement review
The outcome remains selecting readable ID and selfie photos, checking both on review, and submitting for confirmed service acceptance. Existing KYC journey attempts, retries, failure, submission and abandonment signals are reused. No control-level events, image bytes, filenames, form values or raw exceptions are added to telemetry. A selection success measures validation; it does not establish that pixels were painted. Preview-only rendering failures are not separately counted by existing telemetry. Server review/approval measurement and native screen-reader QA retain the limitations documented in the original journey.
Regression tests use synthetic JPEG/PNG content only. They start and await real
image decoding outside the widget test's fake async zone, inspect the actual
rendered images, and surface stream errors so an error fallback cannot conceal a
failure. Checks cover portrait/landscape decoding, model immutability, bounded
decode width, stable provider reuse, replacement/removal cache eviction, both
review slots, returning to review, localized fallback/replacement, a 320px view
at 1.6 text scale, and error announcements isolated from surrounding instructions.
The image package is already a KYC runtime dependency; its direct app test
dependency is used only to create synthetic fixtures in memory.
Validation and delivery
The original browser regression failed before the fix while its native equivalent passed. The complete task diff was reviewed against frontend architecture, UX, measurement, lifecycle and privacy requirements. Final validation passed:
- In
frontend/app,dart format --output=none --set-exit-if-changed lib/kyc/preview.dart test/preview_test.dart: two files, no changes. - In
frontend/app,flutter analyze --no-pub: no issues. - In
frontend/app,flutter test --platform chrome test/preview_test.dart --reporter expanded --no-pub --verbose: all six regression tests passed. - At the repository root,
make test-frontend: all 228 tests passed across the shared packages, theme example and app, including the six native preview regressions and existing KYC journey/analytics tests. - In
frontend/app,flutter build web --no-pub: release build and Wasm compatibility dry run succeeded.
Browser regression coverage uses Chrome; Safari and native assistive-technology
testing remain release checks. Generated localization and build code stays
ignored; the tracked app lockfile changes only image from transitive to direct
test dependency. The separate birthday worktree was preserved.