46 Commits

Author SHA1 Message Date
bf15db8334 Eliminate stroke commit flicker with pending path overlay
When the pen lifts, the in-progress path was cleared immediately but
the completed stroke didn't appear on the backing bitmap until the
async DB save completed — creating a visible flash where the stroke
vanished for several frames.

Fix: transfer the completed path to a "pending" overlay that stays
visible in the dynamic layer until addCompletedStroke() confirms the
stroke is on the backing bitmap. The stroke is now visible continuously
from pen-down through commit with no gap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:16:53 -07:00
73f35cd33d Navigate to editor on notebook creation, reduce flicker with double buffering
- Creating a notebook now auto-navigates to its first page editor
  (createNotebook returns ID via callback, triggers onNotebookClick)
- Double-buffered rendering: all layers composited to an off-screen
  bitmap before presenting to the display in a single drawBitmap call.
  Eliminates visible intermediate states (white flash between layers)
  that caused flicker on e-ink displays.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:59:56 -07:00
86a2ba0f6b Add sync UI: settings dialog, per-notebook sync, sync all
- SyncSettingsDialog: server URL, username, password fields
- Gear icon in library top bar opens sync settings
- "Sync to server" in notebook overflow menu
- "Sync all" button next to filter/sort bar
- Both check isSyncConfigured() and prompt for settings if needed
- Sync runs on Dispatchers.IO, shows Toast on success/error
- Sync settings stored in SharedPreferences via EngPadApp

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:41:48 -07:00
1b9a5d29e7 Rename app display name from eng-pad to Engineering Pad
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:34:36 -07:00
bedd3977b8 Fix protobuf setup: canonical proto, buf lint, Makefile proto target
- Restored canonical proto from eng-pad-server (PageData, StrokeData
  message names, not Notebook/Page/Stroke)
- Added java_package + java_multiple_files options
- Renamed service to EngPadSyncService (buf STANDARD lint compliance)
- Simplified build.gradle.kts: removed broken custom GenerateProtoTask,
  proto stubs generated via Makefile `make proto` and checked into git
- Generated stubs in app/src/main/java/gen/
- Fixed SyncClient/SyncManager to match canonical proto schema
- Updated dependency versions: protobuf 4.34.1, grpc 1.80.0, grpcKotlin 1.5.0
- Added buf.yaml with STANDARD lint rules and FILE breaking detection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:24:45 -07:00
8147ef2c11 Merge: protobuf + sync client skeleton 2026-03-24 20:56:17 -07:00
5fd1e241ec Add sync.proto (copied from eng-pad-server) 2026-03-24 20:56:05 -07:00
16de63972a Add protobuf Gradle setup and gRPC sync client skeleton
- Protobuf Gradle plugin with protoc, grpc-java, grpc-kotlin generators
- Dependencies: protobuf-kotlin-lite, grpc-kotlin-stub, grpc-okhttp,
  grpc-protobuf-lite
- Proto file at app/src/main/proto/engpad/v1/sync.proto
- SyncClient.kt: gRPC channel with TLS + credential metadata interceptor
- SyncManager.kt: notebook serialization to proto, sync/delete/list RPCs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:55:34 -07:00
e106d1ab76 Add notebook zip backup export
- BackupExporter: exports notebook as .engpad.zip containing
  notebook.json (metadata) and pages/NNN.json (strokes as JSON
  float arrays). Uses Dispatchers.IO for file I/O.
- Added "Export backup" to notebook overflow menu in library
- PdfExporter.sanitize changed from private to internal for reuse
- Shares via FileProvider + ACTION_SEND with application/zip MIME type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:46:42 -07:00
a7c57b56be Replace notebook long-press with overflow menu (rename/delete)
- Three-dot overflow button on each notebook card
- Dropdown menu with Rename and Delete options
- Rename dialog with title field (keyboard defaults to Title Case)
- Removed combinedClickable — tap opens notebook, menu for actions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 19:20:02 -07:00
1e13361e7e Fix page selection from page list, add library filter and sort
Page selection fix:
- Pass selected page ID as a route parameter (editor/{notebookId}?pageId=X)
  instead of savedStateHandle, which was unreliable
- Pop to notebook list then navigate to editor with explicit page ID
- Eliminates stale page selection bug

Library filter/sort:
- Text filter field filters notebooks by name (case-insensitive)
- Sort dropdown: Name, Last Edited, Last Opened
- Clicking the current sort field toggles asc/desc direction
- Defaults to Last Edited descending
- Shows "No matches" when filter has no results vs "No notebooks yet"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 19:08:32 -07:00
a74e0dce04 Restore both-edge swipe: left edge = prev page, right edge = next page
Swipe direction is determined by which edge the finger started on AND
the drag direction matching (right edge + swipe left = next, left edge
+ swipe right = prev). Both no-op at their respective boundaries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:42:44 -07:00
2cc445a30d Fix edge swipe and page list navigation
Edge swipe:
- Only detect from RIGHT edge (swipe left = next page)
- Left edge no longer triggers page nav (avoids conflict with system
  back gesture)
- Previous page navigation via binder dropdown or system back only

Page list navigation:
- Simplified selectedPageId handling — read and clear synchronously
  during recomposition instead of async StateFlow observation
- Fixes issue where clicking a page in view-all-pages went to last
  visited page instead of the selected one

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:38:43 -07:00
ed41907353 Move Library button to right side, rename notebook list title
- Page list: Library button moved to actions (right side) with titleLarge
- Notebook list: title changed to "Engineering Pad :: Library"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:31:48 -07:00
5276bda3de Rename page list close button from X to Library
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:28:44 -07:00
7d7073c3c9 Add close button and swipe-left-to-close on view all pages
- X button in top app bar navigates back to notebook list
- Swipe left gesture (>200px) on the page grid closes to notebook list
- Both clear the notebook and view-all-pages state in SharedPreferences

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:27:42 -07:00
7a94704c8c Fix CopyStrokesAction ID tracking and cache grid bitmap
- CopyStrokesAction: use insertAll return value (List<Long>) to get
  inserted IDs directly instead of fragile takeLast(n) re-fetch
- StrokeDao.insertAll now returns List<Long>
- Grid bitmap cached separately from stroke bitmap, redrawn only on
  zoom/pan/resize changes. Eliminates per-frame grid line rendering
  during active drawing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:06:25 -07:00
ba1571011a Fix critical bugs found in code audit
- MoveStrokesAction.undo(): was offsetting by (0,0), now restores
  original pre-move point data correctly
- Stroke commit flicker: new strokes render incrementally onto existing
  backing bitmap instead of triggering full redraw
- Selection state leak: view-side selection cleared on page navigation
  via onPageNavigated callback
- Initial page fallback: blank canvas prevented if initialPageId
  not found in DB — falls back to first page
- Arrow head Paint: preallocated reusable object, no per-call allocation
- Updated PROGRESS.md with audit findings and backup design notes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:35:06 -07:00
b18e77177e Fix page list: drag reorder, navigation, delete renumbering, state
Drag reorder:
- Separated long-press drag from tap/menu: long-press-and-drag reorders,
  tap opens page, overflow menu (three dots) for delete/export
- Fixed combinedClickable conflict that prevented drag from working

Page navigation:
- View-all-pages state persisted in SharedPreferences, restored on startup
- Cleared when navigating back to editor from page list

Delete renumbering:
- After deleting a page, remaining pages are renumbered sequentially

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:28:02 -07:00
2692f0eb0d Fix empty page list: use mutableStateListOf for Compose observability
The reorderable pages list was a plain mutableListOf which Compose
doesn't observe. Changed to mutableStateListOf so the grid recomposes
when pages are loaded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:22:59 -07:00
368351f9c6 Add drag-to-reorder pages and fix page navigation from page list
Drag-to-reorder:
- Added sh.calvin.reorderable library for LazyVerticalGrid drag support
- Long-press and drag page thumbnails to reorder
- Page numbers updated in DB on drop
- Visual feedback: slight scale + transparency while dragging

Page navigation fix:
- Clicking a page in "view all pages" now correctly opens that page
  (key(pid) forces EditorScreen recreation with the selected page)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:20:28 -07:00
984f19af06 Add page long-press menu (delete/export JPG) in view all pages
- Long-press a page thumbnail to show context menu with Delete and
  Export as JPG options
- Delete shows confirmation dialog
- Export renders page at 300 DPI and shares via intent
- FAB always creates new pages (no empty-page restriction in page list)
- Note: drag-to-reorder deferred — requires custom grid drag handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:11:23 -07:00
6a628d2435 Fix remaining flicker and prevent empty page creation on swipe
Flicker: removed View background and super.onDraw() call — canvas is
filled with white explicitly in onDraw before compositing the backing
bitmap, eliminating the clear-then-redraw flash.

Empty page: swiping right on the last page only creates a new page if
the current page has strokes. Empty pages don't spawn more empties.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:10:01 -07:00
a10d7febf9 Fix drawing flicker with screen-resolution backing bitmap
Completed strokes are now rendered to a backing bitmap at the view's
pixel resolution. The bitmap is only redrawn when strokes change or
zoom/pan changes. During active drawing, onDraw just composites the
cached bitmap + the in-progress stroke, eliminating flicker from
re-rendering all paths every frame.

Unlike the earlier 1/4-resolution bitmap that caused blur, this one
uses full screen resolution with the view matrix applied, so strokes
remain pixel-perfect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:07:15 -07:00
e4f55738de Add icons for eraser, select, and move tools
- Eraser: angled rectangle with dividing line (eraser tip)
- Select: dashed rectangle (marquee selection)
- Move: four-way arrow cross with arrowheads
All icons match the existing pen dot, line, and box icon style.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:02:36 -07:00
b902aaa721 Toolbar icon redesign: pen dot, line/box icons, undo/redo arrows
- Pen sizes consolidated into single button with dot icon showing
  current size; long-press for 0.38/0.5/0.6/0.7mm selection
- Line button shows thick line icon; long-press for style variants
- Box button shows rectangle outline icon
- Undo/redo replaced with curved arrow icons (gray when disabled)
- Added PenSize enum (MM_038, MM_050, MM_060, MM_070)
- Removed PEN_FINE/PEN_MEDIUM tools, replaced with single PEN tool
  that uses the selected PenSize from CanvasState

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:58:02 -07:00
fade0de21b Improve paste behavior: selection retention and copy offset
Cut + paste: inserts at same position, selects the pasted strokes.
Copy + paste: offsets pasted strokes by ~10mm diagonally to distinguish
from originals, selects the new copies.

Both operations leave the pasted strokes selected so they can be
immediately moved, copied again, or deleted. After a cut+paste,
subsequent pastes behave as copy+paste (no repeated cuts).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:44:24 -07:00
1f869d556c Fix line styles, move tool selection, stroke style storage
Line styles:
- Store stroke style in DB (style column, migration v2->v3)
- Dashed lines rendered with DashPathEffect on the paint
- Arrow heads drawn dynamically from stored two-point line data
- Line tool stores just two endpoints; style determines rendering
- Removed buildLinePoints — arrow heads no longer baked into point data

Move tool:
- If there's an active selection, Move drags the entire selection
- Otherwise, hit-tests a single stroke and drags it
- Selection stays highlighted after move completes

Line dropdown:
- Removed conflicting onClick from Surface, combinedClickable now
  correctly handles both tap (select tool) and long-press (style menu)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:39:14 -07:00
df08f8a5e5 Fix UI polish: snap, line dropdown, move tool, eraser button, title case
1. Line long-press dropdown: removed conflicting onClick from Surface,
   combinedClickable now works correctly for long-press style menu
2. Move tool: tap a stroke and drag to reposition it, with undo support
   and visual highlight during drag
3. Snap fix: timer now re-arms continuously — pausing at ANY point
   during a stroke (not just near origin) triggers snap. Checks pen
   stillness at the moment the timer fires (~3mm threshold)
4. EMR eraser: now checks BUTTON_STYLUS_PRIMARY in buttonState
   (side button) in addition to TOOL_TYPE_ERASER
5. Notebook title: keyboard defaults to Title Case capitalization
6. Tool order in enum matches toolbar: pen, line, box, eraser, select, move

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:27:53 -07:00
408ba57051 UI polish: toolbar redesign, clipboard, snap fix, startup state
Toolbar:
- Replace FilterChip with custom ToolButton (no ripple, instant response)
- Reorder tools: 0.38 0.5 Line Box Eraser Select
- DropdownMenus anchored to buttons via Box wrapper (drop below)
- Page indicator styled as OutlinedButton (visible affordance)
- Cut/Del/Copy/Paste operations (internal clipboard)

Line snap fix:
- Check pen position when timer fires instead of canceling on movement
- Handles EMR stylus micro-tremor correctly

EMR eraser button:
- TOOL_TYPE_ERASER events route to eraser handler regardless of active tool
- Physical eraser end of pen works as temporary eraser

App state:
- Startup navigates to last-visited notebook/page via SharedPreferences
- Closing notebook (X button) clears last notebook so app starts at list
- Updated app icon

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:15:58 -07:00
e81dd60f30 Overhaul navigation, add line tool and JPG export
Navigation:
- Notebooks remember last page (last_page_id column, migration v1->v2)
- Opening a notebook goes directly to last page's editor
- Edge swipe (finger from screen edge) navigates prev/next page,
  auto-adds pages at end, no-op on first page
- X button closes notebook back to list
- Binder dropdown: "View all pages" and "Go to page" dialog
- Page list shows stroke previews and page numbers below cards

Line tool:
- LINE tool: drag start-to-end for straight lines
- Long-press Line chip for style variants: plain, arrow, double arrow, dashed
- Arrow heads rendered as path segments, stored in stroke data
- Dashed lines use DashPathEffect

Export:
- JPG export at full 300 DPI resolution
- Export dropdown: PDF or JPG options
- Refactored PdfExporter with shared drawStrokes helper

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:30:51 -07:00
85a210c001 Update docs for DC-1 testing, fix toolbar clipping
- Updated DESIGN.md: rendering strategy (no backing bitmap, screen-space
  grid, viewport clamping), added line snap and box tool documentation
- Updated PROGRESS.md: DC-1 testing results and fixes applied
- Updated CLAUDE.md: key conventions for rendering
- Fix toolbar overlap: use clipToBounds on canvas AndroidView to prevent
  it from drawing over the toolbar area

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:13:25 -07:00
2b567aa038 Enforce minimum zoom so page always fills viewport
Dynamic min zoom computed from page and view dimensions — page can
never be smaller than the visible area. Combined with pan clamping
from previous commit, the page always covers the full canvas with
no non-drawable area visible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:09:57 -07:00
61aaa9ebde Fix grid evenness, line snap, and viewport clamping
Grid:
- Draw grid in screen space with pixel-snapped positions, fixing
  the uneven rectangles caused by sub-pixel positioning in canonical space
- 1px screen-space lines for uniform weight at any zoom level

Line snap:
- Track max distance from origin (not per-move), increase threshold
  to 60pt (~5mm) to handle EMR stylus hand tremor
- Snap timer stays active until pen moves significantly from start

Viewport:
- Clamp pan so page always covers the full viewport
- Background changed to white (no dark gray borders)
- Page centers when smaller than viewport (e.g., zoomed out landscape)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:08:58 -07:00
e8091ba081 Fix rendering quality and add line snap + box tool
Rendering fixes for e-ink displays:
- Remove backing bitmap (was 1/4 resolution, caused blurry strokes)
- Draw strokes directly as paths for pixel-perfect rendering
- Disable anti-aliasing on stroke and grid paint (no edge fading)
- Grid: darker color (rgb 180,180,180), 2pt stroke width for crispness

New features:
- Line snap: hold pen still for 1.5s to snap to straight line from origin
- Box tool: draw rectangles by dragging corner to corner

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:02:18 -07:00
39adcfaaa4 Implement Phase 10: e-ink polish and finalize project docs
- High-contrast theme with expanded color palette for e-ink readability
- Updated source trees in DESIGN.md
- Updated PROJECT_PLAN.md and PROGRESS.md — all phases complete
  (only on-device performance profiling remains)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:51:52 -07:00
351a7596be Implement Phase 9: PDF export via share intents
- PdfExporter: creates PdfDocument, scales 300 DPI canonical coords to
  72 DPI PDF points, renders strokes without grid
- Share via Intent.ACTION_SEND + FileProvider
- PDF button in editor toolbar exports current page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:49:32 -07:00
47b6ffc489 Implement Phase 8: multi-page navigation
- PageListScreen: adaptive grid of page cards with correct aspect ratio
- PageListViewModel: pages flow + add page
- NavGraph: pages route loads notebook metadata, shows PageListScreen,
  tap page navigates to editor with page size parameter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:46:48 -07:00
34ad68d1ce Implement Phase 7: rectangle selection with move, copy, delete
- Rectangle selection via stylus drag in select mode
- Visual feedback: dashed selection rect, blue highlight on selected strokes
- Operations: delete selection, drag-to-move, copy (toolbar buttons)
- Full undo support: DeleteMultipleStrokesAction, MoveStrokesAction,
  CopyStrokesAction
- Preallocated RectF to avoid draw-time allocations (lint)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:45:04 -07:00
5eeedff464 Implement Phase 6: undo/redo with command pattern
- UndoableAction interface with AddStrokeAction and DeleteStrokeAction
- UndoManager: undo/redo stacks (depth 50), canUndo/canRedo StateFlows
- EditorViewModel: stroke operations routed through UndoManager,
  visual callbacks sync canvas view without full DB reload
- Toolbar: undo/redo buttons with enabled state
- 9 unit tests for UndoManager

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:40:24 -07:00
7cf779934d Implement Phase 5: stroke-level eraser
- Eraser mode: stylus touch/drag hit-tests against stroke bounding boxes
  expanded by 42pt radius (~3.5mm), processes historical points
- Deletes hit strokes from canvas view and Room DB
- Backing bitmap rebuilt automatically on stroke removal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:36:35 -07:00
3fc9751fc4 Implement Phase 4: pinch-to-zoom and finger pan
- ScaleGestureDetector for pinch zoom (0.5x-4x) with focal-point anchoring
- Finger drag for pan with multi-pointer tracking
- Zoom/pan state managed locally in PadCanvasView for responsiveness,
  synced to EditorViewModel on gesture end

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:34:42 -07:00
a31e7e64d0 Upgrade to Gradle 9.4.1, AGP 9.1.0, Kotlin 2.3.20
- Gradle wrapper 8.14.2 -> 9.4.1
- AGP 8.10.1 -> 9.1.0 (built-in Kotlin, no separate kotlin.android plugin)
- Kotlin 2.1.20 -> 2.3.20
- KSP 2.1.20-1.0.32 -> 2.3.6 (new versioning scheme)
- settings.gradle.kts: removed @Suppress for dependencyResolutionManagement
- gradle.properties: added android.disallowKotlinSourceSets=false for KSP compat

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:30:15 -07:00
2fc4224f5a Implement Phase 3: canvas drawing with stylus input
- PadCanvasView: custom View with stylus event handling (historical points
  for smoothness), Path/Paint stroke rendering, backing bitmap at 1/4
  resolution, 60pt grid drawing, Matrix coordinate transform
- CanvasState: tool modes (fine/medium pen, eraser, select), zoom/pan state
- EditorViewModel: loads/saves strokes to Room on completion
- EditorScreen: Compose wrapper with AndroidView + FilterChip toolbar
- NavGraph: pages route auto-navigates to first page editor, page size
  passed through route params

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:21:17 -07:00
644b8a4732 Implement Phase 2: notebook list screen with navigation
- NotebookListScreen: lazy list, create dialog (title + page size),
  long-press delete with confirmation, empty state
- NotebookListViewModel: StateFlow-based, create/delete operations
- EngPadTheme: high-contrast light scheme for e-ink displays
- NavGraph: three routes (notebooks, pages stub, editor stub)
- MainActivity wired to NavHost with database injection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:15:48 -07:00
0b53023a25 Implement Phase 1: project skeleton and data layer
Android project with Kotlin, Jetpack Compose, and Room. Includes:
- Gradle build system with version catalog, foojay JDK resolver, lint config
- Room entities (Notebook, Page, Stroke) with packed float BLOB encoding
- DAOs and repositories for all entities
- Unit tests for blob roundtrip and PageSize enum (10 tests, all passing)
- Minimal Application class and stub MainActivity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:03:57 -07:00