feat(image-meter): native photo annotation with scaled measurements#85
feat(image-meter): native photo annotation with scaled measurements#85tstapler wants to merge 5 commits into
Conversation
JVM Load Benchmark (Desktop)Synthetic in-memory benchmark measuring load performance for the desktop (JVM) app.
Flamegraphs (this PR)**Allocation** — object allocation pressure (JDBC/SQLite churn)Alloc flamegraph not available CPU — method-level hotspots by on-CPU time CPU flamegraph not available Top SQL queries by total time (this PR)| table:operation | calls | p50 | p99 | max | total | |-----------------|-------|-----|-----|-----|-------| | `pages:select` | 2 | 1ms | 1ms | 1ms | 1ms |Top allocation hotspots (this PR)`68.3%` byte[]_[k] `4.3%` java.lang.String_[k] `3.7%` java.lang.Object[]_[k] `3%` java.lang.StringBuilder_[k] `1.8%` long[]_[k]Top CPU hotspots (this PR)`99.3%` /usr/lib/x86_64-linux-gnu/libc.so.6 `0.1%` pread `0%` jdk/internal/loader/Resource.cachedInputStream_[i] `0%` ConstMethod::localvariable_table_start `0%` kotlinx/coroutines/internal/DispatchedContinuationKt.safeDispatch_[0] |
Android Load BenchmarkInstrumented benchmark on an API 30 x86_64 emulator — 500-page synthetic graph. Comparing Graph Load
Interactive Write Latency (during Phase 3)
SAF I/O Overhead (ContentProvider vs direct File read)Measures Binder IPC cost added by ContentResolver per readFile() call.
|
There was a problem hiding this comment.
Pull request overview
This PR introduces the “image-meter” feature scaffolding across SteleKit’s KMP stack: first-class image_annotation blocks with sidecar-backed persistence, measurement annotation storage, gallery/editor navigation, and platform sensor/device integration stubs.
Changes:
- Adds image/measurement annotation persistence (SQLDelight schema + migration v4) and sidecar serialization/indexing utilities.
- Introduces UI plumbing for an image gallery + annotation editor entry points (navigation, block rendering hooks, settings UI).
- Adds cross-platform sensor + external measurement device abstractions (camera/motion/depth/ML, BLE/keyboard/USB stubs) and Google OAuth token storage interfaces/implementations.
Reviewed changes
Copilot reviewed 130 out of 130 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| kmp/src/wasmJsMain/kotlin/dev/stapler/stelekit/platform/sensor/WebCameraProvider.kt | WASM camera provider stub returning HardwareUnavailable. |
| kmp/src/jvmTest/kotlin/dev/stapler/stelekit/db/ImageSidecarManagerTest.kt | JVM tests for sidecar manager/indexer and filesystem failure paths. |
| kmp/src/jvmTest/kotlin/dev/stapler/stelekit/annotate/AnnotationExporterTest.kt | JVM integration tests for baking/encoding annotation overlays. |
| kmp/src/jvmMain/kotlin/dev/stapler/stelekit/ui/annotate/ImageEncoder.jvm.kt | Desktop JPEG encoding via ImageIO. |
| kmp/src/jvmMain/kotlin/dev/stapler/stelekit/platform/sensor/WebcamCameraProvider.kt | Desktop camera provider stub delegating to file picker. |
| kmp/src/jvmMain/kotlin/dev/stapler/stelekit/platform/sensor/DesktopFilePicker.kt | Swing-based image file picker for desktop imports. |
| kmp/src/jvmMain/kotlin/dev/stapler/stelekit/platform/google/JvmGoogleTokenStore.kt | Desktop Google token persistence via Preferences (not secure). |
| kmp/src/iosMain/kotlin/dev/stapler/stelekit/ui/annotate/ImageEncoder.ios.kt | iOS JPEG encoder stub. |
| kmp/src/iosMain/kotlin/dev/stapler/stelekit/platform/sensor/IOSMotionSensorProvider.kt | iOS motion sensor provider stub. |
| kmp/src/iosMain/kotlin/dev/stapler/stelekit/platform/sensor/IOSLidarDepthProvider.kt | iOS LiDAR depth provider stub + hardware check placeholder. |
| kmp/src/iosMain/kotlin/dev/stapler/stelekit/platform/sensor/IOSCameraProvider.kt | iOS camera provider stub. |
| kmp/src/iosMain/kotlin/dev/stapler/stelekit/platform/google/IosGoogleTokenStore.kt | iOS Google token store stub (in-memory). |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/repository/InMemoryMeasurementAnnotationRepositoryTest.kt | commonTest coverage for in-memory measurement repository. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/repository/InMemoryImageAnnotationRepositoryTest.kt | commonTest coverage for in-memory image annotation repository. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/sensor/SensorDataPropagationTest.kt | Tests for propagating sensor snapshots into imported annotations. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/sensor/NoOpCameraProviderTest.kt | Tests for NoOp camera provider behavior. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/sensor/MotionSensorProviderTest.kt | Tests for motion sensor provider contract and defaults. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/sensor/GpsTaggingTest.kt | Tests for GPS presence + tilt warning threshold logic. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/sensor/BearingAnnotationTest.kt | Tests for compass-bearing label formatting rules. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/measurement/NoOpBleScannerTest.kt | Tests for no-op BLE scanner and registry registration. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/measurement/MeasurementDeviceRegistryTest.kt | Tests for measurement device registry aggregation and reset. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/measurement/LeicaDistoProtocolTest.kt | Tests for Leica DISTO protocol parsing. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/measurement/KeyboardMeasurementParserTest.kt | Tests for keyboard measurement parsing (units, separators, edge cases). |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/platform/measurement/BoschGlmProtocolTest.kt | Tests for Bosch GLM protocol parsing. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/model/UnitConversionTest.kt | Tests for meter↔display unit conversions. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/model/ImageAnnotationModelTest.kt | Tests for ImageAnnotation defaults + validation. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/google/GoogleTokenStoreTest.kt | Tests for token store contract + expiry logic. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/error/DomainErrorTest.kt | Extends DomainError message coverage for SensorError variants. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/db/sidecar/SidecarSerializationTest.kt | Sidecar JSON round-trip tests and malformed JSON handling. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/db/sidecar/FakeFileSystem.kt | In-memory FileSystem for sidecar/image tests. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/db/ImageStoragePathResolverTest.kt | Tests for image asset path conventions. |
| kmp/src/commonTest/kotlin/dev/stapler/stelekit/db/ImageImportServicePathTest.kt | Tests for reservePath directory creation + UUID prefixing. |
| kmp/src/commonMain/sqldelight/dev/stapler/stelekit/db/SteleDatabase.sq | Adds image_annotations + measurement_annotations tables and queries. |
| kmp/src/commonMain/sqldelight/dev/stapler/stelekit/db/migrations/4.sqm | SQLDelight migration v4 for image/measurement tables + indexes. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/StelekitViewModel.kt | Adds navigation helpers for Gallery and AnnotationEditor screens. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/screens/PageView.kt | Wires callback to open annotation editor from page blocks. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/screens/JournalsView.kt | Adds annotation editor callback plumbing through journal block list. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/gallery/GalleryViewModel.kt | New gallery state + loading/filtering/sorting logic. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/drive/DriveFileBrowserViewModel.kt | New Drive file browser state and view model flow. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/Sidebar.kt | Adds Gallery navigation item. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/settings/SettingsDialog.kt | Adds Google Account settings category and wiring parameters. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/settings/GoogleAccountSettings.kt | New settings UI section for Google account connect/disconnect. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/SearchDialog.kt | Adjusts SearchResultRow signature (modifier) + wrapper overload. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/BlockRenderer.kt | Adds callback for opening annotation editor from rendered blocks. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/BlockList.kt | Passes onOpenAnnotationEditor through block list rendering. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/BlockItem.kt | Adds IMAGE_ANNOTATION rendering branch and editor open callback. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/AppState.kt | Adds Screen.Gallery and Screen.AnnotationEditor routes. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/App.kt | Routes Gallery/AnnotationEditor screens; wires view models. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/annotate/TagEditorPanel.kt | Tag editing UI for the annotation editor. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/annotate/MeasurementLabelOverlay.kt | Canvas overlay to render measurement labels with leader lines. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/annotate/LabelInputOverlay.kt | Floating overlay for label text entry tool. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/annotate/ImageEncoder.kt | expect/actual JPEG encoder abstraction. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/repository/SqlDelightMeasurementAnnotationRepository.kt | SQLDelight-backed measurement annotation repository. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/repository/MeasurementAnnotationRepository.kt | Repository interface for measurement annotations. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/repository/InMemoryMeasurementAnnotationRepository.kt | In-memory measurement annotation repository. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/repository/InMemoryImageAnnotationRepository.kt | In-memory image annotation repository. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/repository/ImageAnnotationRepository.kt | Repository interface for image annotations. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/repository/GraphRepository.kt | Adds image/measurement repositories to RepositorySet defaults. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/sensor/SensorModule.kt | Global wiring point for platform sensor providers/estimators. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/sensor/PlatformImageFile.kt | Represents captured/imported image file + EXIF/sensor snapshot. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/sensor/NoOpCameraProvider.kt | No-op camera provider returning HardwareUnavailable. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/sensor/MotionSensorProvider.kt | Motion sensor provider interface + NoOp implementation. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/sensor/DepthSensorProvider.kt | Depth sensor provider interface + NoOp implementation. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/sensor/CameraProvider.kt | Camera provider abstraction for capture pipeline. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/ml/MonocularDepthEstimator.kt | ML depth estimator abstraction + NoOp implementation. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/measurement/MeasurementDeviceRegistry.kt | Registry for aggregating measurement device factories. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/measurement/keyboard/KeyboardMeasurementParser.kt | Parser for typed measurement strings into meters. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/measurement/keyboard/KeyboardEmulationDeviceFactory.kt | Factory emitting a singleton “Keyboard” measurement device. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/measurement/keyboard/KeyboardEmulationDevice.kt | Keyboard-emulation measurement device emitting readings from text. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/measurement/ExternalMeasurementDevice.kt | Device interfaces/models for external measurement sources. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/measurement/ble/NoOpBleScanner.kt | No-op BLE scanner factory. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/measurement/ble/LeicaDistoProtocol.kt | Leica DISTO pure-Kotlin protocol parser. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/measurement/ble/BoschGlmProtocol.kt | Bosch GLM pure-Kotlin protocol parser. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/google/GoogleTokenStore.kt | Token store interface + expiry helper. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/google/GoogleTokenRefresher.kt | Ktor-based refresh-token exchange helper. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/platform/google/GoogleAuthManager.kt | Platform interface for initiating OAuth flow. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/model/Models.kt | Registers image_annotation as a valid block type. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/model/ImageAnnotation.kt | Adds image annotation domain models (calibration/sensor/source). |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/model/BlockTypes.kt | Adds BlockTypes.IMAGE_ANNOTATION constant. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/error/DomainError.kt | Adds SensorError variants + UI mapping. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/domain/MeasurementPropertySyncer.kt | Generates block properties + resolves {{measure: ...}} templates. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/db/sidecar/ImageSidecarSchema.kt | Defines sidecar JSON schema v1 for images/measurements. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/db/sidecar/ImageSidecarIndexer.kt | Rebuilds DB state from sidecar files. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/db/RestrictedDatabaseQueries.kt | Adds restricted write helpers for new image/measurement queries. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/db/ImageStoragePathResolver.kt | Resolves <graph>/assets/images/<date>-<uuidPrefix>.jpg storage path. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/calibration/CalibrationFallbackChain.kt | Implements calibration method fallback chain with logging. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/ui/annotate/ImageEncoder.android.kt | Android JPEG encoder using Bitmap.compress. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/platform/sensor/ARCoreDepthProvider.kt | ARCore depth provider stub. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/platform/sensor/AndroidPhotoPickerLauncher.kt | Photo Picker wrapper that copies selected content into temp file. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/platform/sensor/AndroidCameraProvider.kt | CameraX-based camera provider stub. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/platform/ml/OnnxMonocularDepthEstimator.kt | ONNX monocular depth estimator stub. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/platform/measurement/usb/AndroidUsbSerialFactory.kt | USB serial factory stub (delegates to no-op). |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/platform/measurement/ble/KableBleScanner.kt | Kable BLE scanner stub with permission guard. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/platform/measurement/ble/AndroidMeasurementForegroundService.kt | Foreground service stub for BLE connections. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/platform/google/AndroidGoogleTokenStore.kt | Secure Android token store using EncryptedSharedPreferences. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/platform/google/AndroidGoogleAuthManager.kt | Android OAuth flow stub launching browser with consent URL. |
| kmp/src/androidMain/AndroidManifest.xml | Adds permissions/features for GPS/BLE/USB and registers BLE service. |
| kmp/build.gradle.kts | Adds Android ExifInterface dependency for future capture pipeline. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private fun loadImages() { | ||
| scope.launch { | ||
| val tag = _state.value.selectedTag | ||
| val flow = if (tag != null) { | ||
| imageAnnotationRepository.getImageAnnotationsByTag(tag) | ||
| } else { | ||
| imageAnnotationRepository.getAllImageAnnotations() | ||
| } | ||
| flow.collect { result -> | ||
| result.fold( | ||
| ifLeft = { err -> | ||
| logger.error("Failed to load gallery images: ${err.message}") |
| is Screen.Gallery -> { | ||
| NavigationTracingEffect("Gallery") | ||
| val galleryViewModel = remember { | ||
| GalleryViewModel(repos.imageAnnotationRepository) | ||
| } | ||
| GalleryScreen( | ||
| viewModel = galleryViewModel, | ||
| onOpenAnnotationEditor = { uuid -> | ||
| viewModel.navigateToAnnotationEditor(uuid) | ||
| }, | ||
| onNavigateToPage = { pageUuid -> | ||
| viewModel.navigateToPageByUuid(pageUuid) | ||
| }, | ||
| ) | ||
| } |
| return withContext(Dispatchers.IO) { | ||
| try { | ||
| UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) | ||
| } catch (e: CancellationException) { | ||
| throw e | ||
| } catch (_: Exception) { | ||
| // Non-fatal: system L&F is cosmetic only | ||
| } | ||
|
|
||
| var chosenFile: File? = null | ||
| val done = java.util.concurrent.CountDownLatch(1) | ||
|
|
||
| SwingUtilities.invokeLater { | ||
| try { | ||
| val chooser = JFileChooser().apply { | ||
| dialogTitle = "Select Image" | ||
| fileSelectionMode = JFileChooser.FILES_ONLY | ||
| isMultiSelectionEnabled = false | ||
| fileFilter = FileNameExtensionFilter( | ||
| "Image files (JPEG, PNG)", | ||
| "jpg", "jpeg", "png" | ||
| ) | ||
| } | ||
| val result = chooser.showOpenDialog(null) | ||
| if (result == JFileChooser.APPROVE_OPTION) { | ||
| chosenFile = chooser.selectedFile | ||
| } | ||
| } catch (e: CancellationException) { | ||
| throw e | ||
| } catch (_: Exception) { | ||
| // Swallow; chosenFile stays null → user-cancel path | ||
| } finally { | ||
| done.countDown() | ||
| } | ||
| } | ||
|
|
||
| done.await() | ||
|
|
| override fun getLastModifiedTime(path: String): Long? = null | ||
| override fun readFileBytes(path: String): ByteArray? = files[path]?.encodeToByteArray() | ||
| override fun writeFileBytes(path: String, data: ByteArray): Boolean { | ||
| files[path] = data.decodeToString() | ||
| return true | ||
| } |
| * Secure storage interface for Google OAuth 2.0 tokens. | ||
| * | ||
| * Platform implementations: | ||
| * - androidMain: [AndroidGoogleTokenStore] via EncryptedSharedPreferences + Android Keystore | ||
| * - jvmMain: [JvmGoogleTokenStore] via java.util.prefs.Preferences (not production-grade) | ||
| * - iosMain: [IosGoogleTokenStore] via in-memory storage (stub; full Keychain impl deferred) | ||
| * | ||
| * SECURITY: Tokens must NEVER be stored in plaintext. Each platform implementation | ||
| * must use the strongest available secure storage. If secure storage is unavailable, | ||
| * propagate the error to the caller rather than falling back silently. | ||
| */ |
| /** | ||
| * JVM (Desktop) implementation of [GoogleTokenStore] using [java.util.prefs.Preferences]. | ||
| * | ||
| * NOTE: This is NOT production-grade secure storage. Java Preferences stores data in | ||
| * the OS user profile directory (e.g., ~/.java/.userPrefs or the Windows Registry) without | ||
| * encryption. Tokens stored here can be read by any process running as the same OS user. | ||
| * | ||
| * TODO: Replace with OS keyring integration: | ||
| * - Linux: libsecret / Secret Service DBus API | ||
| * - macOS: Keychain Services via JNA or the macOS Keychain Java bridge | ||
| * - Windows: Windows Credential Manager via JNA | ||
| * | ||
| * This implementation is sufficient for development and local testing only. | ||
| */ |
| val writers = ImageIO.getImageWritersByFormatName("jpeg") | ||
| if (!writers.hasNext()) return ByteArray(0) | ||
| val writer = writers.next() | ||
| val params: ImageWriteParam = writer.defaultWriteParam.apply { | ||
| compressionMode = ImageWriteParam.MODE_EXPLICIT | ||
| compressionQuality = quality / 100f | ||
| } | ||
| val out = ByteArrayOutputStream() | ||
| writer.output = ImageIO.createImageOutputStream(out) | ||
| writer.write(null, IIOImage(buffered, null, null), params) |
| @DirectRepositoryWrite | ||
| override suspend fun saveImageAnnotation(annotation: ImageAnnotation): Either<DomainError, Unit> { | ||
| val current = annotations.value.toMutableMap() | ||
| if (annotation.uuid in current) { | ||
| return DomainError.ValidationError.ConstraintViolation( | ||
| "ImageAnnotation with uuid ${annotation.uuid} already exists" | ||
| ).left() | ||
| } | ||
| current[annotation.uuid] = annotation | ||
| annotations.value = current | ||
| return Unit.right() | ||
| } |
| // Upsert: delete first (no-op if absent), then insert | ||
| imageAnnotationRepository.deleteImageAnnotation(annotation.uuid) | ||
| imageAnnotationRepository.saveImageAnnotation(annotation).onLeft { err -> | ||
| logger.warn("ImageSidecarIndexer: failed to upsert annotation ${annotation.uuid}: ${err.message}") | ||
| return@onLeft | ||
| } | ||
| measurementAnnotationRepository.deleteMeasurementsForImage(annotation.uuid) | ||
| measurementAnnotationRepository.saveMeasurements(annotation.uuid, measurements) | ||
| upserted++ |
| @Test | ||
| fun `isAuthenticated returns true after saveTokens`() = runTest { | ||
| store.saveTokens("access", "refresh", System.currentTimeMillis() + 3_600_000) | ||
| assertTrue(store.isAuthenticated()) | ||
| } |
Replaces ImageMeter app workflow end-to-end inside SteleKit:
- Annotated images are first-class `image_annotation` blocks in the
Logseq graph; measurement data stored as block properties and
queryable via the existing block system
- Compose Multiplatform annotation canvas (DrawScope + ZoomImage) with
line, polygon, angle, text-label, and grid-ref tools; all coordinates
stored in normalized [0,1] space; Path objects retained to hit 30fps
- Calibration fallback chain: BLE laser (±1mm) → manual reference →
ARCore depth (±8–10cm, explicit warning) → EXIF focal-length (±15%) →
Depth Anything V2 monocular ML (last resort)
- BLE laser rangefinder support: Leica DISTO and Bosch GLM protocol
implementations; Kable/USB-serial/HID-keyboard-emulation device
registry; Android ForegroundService for API 31+ BLE lifecycle
- Camera capture (Android/iOS CameraK), Android Photo Picker import
(post-March-2025 API — photoslibrary scopes revoked), Google Drive
import/export via Ktor REST v3, OAuth via Credential Manager
- Gallery view with tag filtering and sort; journal auto-insert on
camera capture; `{{measure: image.label}}` block math references
- Platform sensors: GPS tagging, compass bearing auto-annotation,
accelerometer tilt warning, iOS LiDAR depth stub
- SQLDelight migration 4: `image_annotations` + `measurement_annotations`
tables; JSON sidecar at `.stelekit/images/<uuid>.measure.json` as
portable ground truth (sidecar written before DB row for atomicity)
- 1582 tests passing, 0 detekt violations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…RCore, ONNX, iOS sensors - CameraX full capture: ProcessCameraProvider + ImageCapture bound to ProcessLifecycleOwner, suspendCancellableCoroutine bridge, ExifOrientationFixer applied post-capture, EXIF metadata extracted into PlatformImageFile.sensorData - Kable 0.32.0 BLE scanning: LeicaDistoKableDevice and BoschGlmKableDevice with real GATT connect, MTU negotiation (100 bytes), characteristic observe loop, GATT-133 exponential backoff (5 retries, 2–60s), mandatory peripheral.disconnect() in finally; iOS equivalents via IOSKableBleScanner - ARCore 1.46.0 depth: full ArSession lifecycle, DepthMode.AUTOMATIC, uint16→float depth map + confidence map extraction; optional AR feature in manifest - ONNX Runtime 1.20.0: DepthModelDownloader via Android DownloadManager (survives process death), OnnxMonocularDepthEstimator with NNAPI/CPU fallback, 518×518 ImageNet normalization, DepthEstimationPanel UI (Absent/Downloading/Ready/Failed) - iOS CMMotionManager: device motion at 10Hz (pitch/roll), CLLocationManager for GPS + compass heading, callbackFlow emission - iOS ARKit LiDAR: ARSession with sceneDepth semantics, CVPixelBuffer float32 extraction, confidence map from ARConfidenceLevel - macrobenchmark module: fixed pre-existing desugaring + resource-merge build gaps - CancellationException rethrow guards added across all new catch blocks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lan, ADRs) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
c0cfb5a to
4e8b23c
Compare
…nMain - Replace System.currentTimeMillis() with Clock.System.now().toEpochMilliseconds() in Google API classes - Replace .toByteArray(Charsets.UTF_8) with .encodeToByteArray() in DriveExportService - Remove @volatile annotations from SensorModule (Wasm/JS single-threaded) - Replace String.format() with custom formatDecimals() helper in AnnotationEditorScreen - Add missing wasmJsMain actual for ImageEncoder expect declaration - Add BleError and AttachmentError branches to DomainErrorTest exhaustive when Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eKitApplication) Conflict: both branches added imports to SteleKitApplication.kt — main added WriteBehindQueue, branch added SensorModule/ARCore/BLE wiring. Kept all imports from both sides. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
image_annotationblocks in the Logseq graph — no app-switching requiredcommonMain; Kable device registry; Android ForegroundService for API 31+; USB serial and HID keyboard-emulation fallbacksphotoslibraryscopes revoked); OAuth with EncryptedSharedPreferences + Android Keystore{{measure: image.label}}block math references; back-links between gallery and pagesimage_annotations+measurement_annotationstables; JSON sidecar at.stelekit/images/<uuid>.measure.jsonwritten before DB row for atomicityKey architectural decisions
image_annotationblockType extending existing Blockphotoslibrary.readonlyand related scopes were revoked March 31 2025; programmatic library browsing is impossibleNotable things NOT yet wired (require hardware/native SDK work)
com.juul.kabledependency added; protocol parsing is complete and tested)Test plan
FileSystem.writeFileBytesthrow → DB row not committed (TC-020)gatt.close()verified (TC-073/074)🤖 Generated with Claude Code