Skip to content

Releases: NemSothea/CambodianAddressSDK

Release list

v1.4.1

Choose a tag to compare

@NemSothea NemSothea released this 05 Jun 16:10

What's Changed

Documentation

Added real screenshots and animated GIFs to the README — all five media slots previously marked as TODO are now filled:

Asset Description
picker-demo.gif Hero: full Province → District → Commune → Village picker flow
picker-screenshot.png CambodiaAddressPicker embedded in a SwiftUI Form
standalone-demo.gif AddressPickerView presented as a .sheet
uikit-screenshot.png CambodiaAddressPickerViewController (UIKit tab)
search-demo.gif Live offline search — typing "krang leav" with results updating in real time

All assets are captured automatically via DemoCapture.swift (a XCUITest target in the ExampleApp) and converted to GIF with ffmpeg. Re-run anytime to regenerate:

cd ExampleApp
xcodebuild test-without-building \
  -scheme CambodiaAddressExample \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -only-testing:CambodiaAddressExampleUITests/DemoCapture

No code or API changes — this is a docs-only patch.

v1.4.0

Choose a tag to compare

@NemSothea NemSothea released this 05 Jun 15:53

What's Changed

Bug Fixes & Hardening (code-review findings 5–8)

Finding 5 — Reliable payload size enforcement (BundledJSONDataSource)
Removed the unreliable url.resourceValues(forKeys: [.fileSizeKey]) pre-flight size check, which silently no-ops on sandboxed or virtual filesystems. The post-read data.count guard is now the sole, authoritative enforcement point.

Finding 6 — Query clamp at the correct altitude (AddressSearchEngine, KhmerNormalizer)
Moved the maxQueryLength truncation from the shared KhmerNormalizer utility into AddressSearchEngine.search() — the correct layer (user-query entry point). The normalizer is now a pure text utility with no search-specific concerns; the clamp no longer fires during index building.

Finding 7 — Consistent traversal contract (AddressStore)
districts(inProvince:), communes(inDistrict:), and villages(inCommune:) now throw AddressError.notFound for unknown parent codes instead of silently returning []. Callers can now reliably distinguish "valid empty list" from "unrecognised code" — consistent with selection(forVillageCode:) which has always thrown.

Finding 8 — Shared cache decode path + async file I/O (DatasetCache, DatasetDecoding, CachingDataSource)

  • Added DatasetDecoding.decodeDataset() for the domain-model cache schema (full property names, distinct from the wire format's short keys). DatasetCache.read() now routes through it for consistent AddressError.decodingFailed mapping.
  • Added DatasetCache.readAsync() which offloads blocking file I/O to DispatchQueue.global(qos: .userInitiated). CachingDataSource.bestAvailable(), version, and refresh() all use this path, keeping the Swift cooperative thread pool unblocked.

Tests

  • unknownParentReturnsEmptyunknownParentThrowsNotFound (reflects new throwing contract)
  • Clamp coverage moved from KhmerNormalizerTests to AddressSearchEngineTests.clampsOverlongQuery
  • 138 tests passing, zero warnings under Swift 6 strict concurrency