Skip to content

Fix invisible strokes from Ink API timestamp bug#1

Open
jdkruzr wants to merge 23 commits intojshph:mainfrom
jdkruzr:fix/ink-api-timestamps
Open

Fix invisible strokes from Ink API timestamp bug#1
jdkruzr wants to merge 23 commits intojshph:mainfrom
jdkruzr:fix/ink-api-timestamps

Conversation

@jdkruzr
Copy link

@jdkruzr jdkruzr commented Mar 15, 2026

Summary

  • toInkStroke() was passing per-point dt deltas directly as elapsedTimeMillis instead of accumulating them into cumulative timestamps. The Jetpack Ink API rejects strokes where consecutive points share both position and elapsed time (INVALID_ARGUMENT: Inputs must not have duplicate position and elapsed_time), causing strokes to silently fail to render despite being saved to the database.
  • Particularly severe on lower-powered SoCs (e.g. Snapdragon 750G in the Palma 2 Pro) where the stylus produces more duplicate sample points at the same millisecond.

Test plan

  • Draw rapid strokes on a lower-powered Boox device (e.g. Palma 2 Pro) — strokes that previously vanished should now render
  • Verify existing strokes that were saved but invisible now appear on page load
  • Confirm no rendering regressions on higher-powered devices (e.g. Tab Ultra C Pro)

🤖 Generated with Claude Code

jshph and others added 23 commits March 8, 2026 22:08
Replace quick pages with an Inbox Capture template that renders a
frontmatter zone (auto-filled created date, handwritable tags area),
a divider line, and a lined content area below. This is the first step
toward syncing handwritten notes to Obsidian as markdown files.

- Add ML Kit Digital Ink Recognition dependency (19.0.0)
- Add "inbox" native background type with drawInboxBg() renderer
- Change quick page creation to use inbox background
- Add InkTestView debug screen for testing recognition (~111ms on NoteAir5C)
- Add CLAUDE.md with project setup and build instructions
- Add docs/inbox-capture.md with full feature specification
- Add IS_NEXT=false to gradle.properties for local builds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ault

When leaving an inbox capture page, strokes are classified by zone
(tags vs content), recognized via ML Kit Digital Ink, and written as
a markdown file with YAML frontmatter to the Obsidian inbox folder.
The Notable page is deleted after successful sync.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…plicit save

Replace the handwritten frontmatter zones with a Compose UI toolbar at top:
tag pills from vault (ranked by frequency×recency), text input with
autocomplete filtering, and explicit Save & Exit / Back buttons. Pen toolbar
moves to the bottom on inbox pages.

- VaultTagScanner parses tags from inbox folder markdown frontmatter
- Tags are cached on app start for instant loading
- InboxSyncEngine now accepts tags from UI instead of stroke recognition
- Frontmatter uses Obsidian format: created: "[[YYYY-MM-DD]]"
- Settings page shows Inbox Capture section with configurable folder path
- Double-sync guard prevents duplicate saves

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ne, clean library

- Fix non-writable area: setupSurface now excludes both top (inbox toolbar)
  and bottom (pen toolbar) for capture pages, preventing pen input dead zones
- Make tag section collapsible: starts collapsed showing "Capture" toggle with
  tag count, expands to show search + tag pills. Drawing surface updates dynamically
- Simplify library: remove folders/notebooks/import, show clean grid of captures
  with prominent "New Capture" card
- Rename "Inbox Capture" to "Capture" throughout UI and settings
- Remove background template selector (all pages are captures now)
- Add sync overlay ("Saving to vault...") during capture save
- Default pen changed to fountain

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pre-context

ML Kit assumes single-line input, so multi-line captures were poorly
recognized. Now strokes are clustered into lines by vertical position,
each line is recognized independently with its bounding box as the
WritingArea, and the last 20 chars feed forward as pre-context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RecognitionContext.builder() requires setPreContext() to be called.
Always pass it (empty string for the first line).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the signing process with env vars in CLAUDE.md.
Add *.jks to .gitignore to prevent committing keystores.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VaultTagScanner.cachedTags is now Compose mutableStateOf so the
InboxToolbar automatically picks up tags when refreshCache() runs
(either at startup or after changing the vault path in settings).

Also add release build best practices to CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tency

Three interconnected bugs prevented the fountain pen from working correctly
as the default nib:

1. Race condition in pen initialization: setupSurface() calls openRawDrawing()
   which resets the Onyx SDK stroke style to its default. Because
   updatePenAndStroke() ran synchronously in surfaceCreated while
   updateActiveSurface() ran in a coroutine, the pen style was always
   overwritten before the first stroke. Fix: updatePenAndStroke() now runs
   inside updateActiveSurface() after setupSurface() completes.

2. Fountain pen bitmap replay rendered as ballpoint: The NeoFountainPenV2
   SDK wrapper returned null PenPathResults during offline replay, producing
   uniform-width strokes that lost all pressure variation on any screen
   redraw (menu open, undo, dropdown). Fix: switched fountain pen replay
   to the custom drawFountainPenStroke renderer which explicitly varies
   stroke width per-point based on pressure. Deleted the now-unused
   NeoFountainPenV2Wrapper.

3. Light pressure produced invisible strokes: The linear pressure
   normalization (pressure/maxPressure) meant a gentle touch at ~200/4096
   mapped to 0.05, barely visible. Fix: applied sqrt curve so the same
   touch maps to ~0.22, making light strokes clearly visible while
   preserving full range at heavy pressure.

Refactoring:
- Centralized pen defaults into Pen.DEFAULT and Pen.DEFAULT_SETTINGS
- Added Pen.strokeStyle property, replacing standalone penToStroke() function
- Extracted calculateExcludeHeights() from updateActiveSurface()
- Removed redundant updatePenAndStroke() calls from observers that already
  call updateActiveSurface()
- Pen.fromString() now falls back to Pen.DEFAULT instead of hardcoded BALLPEN

Also adds a "Clear all pages" button in settings with confirmation dialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stroke points now store the actual delta time (dt) from the Onyx SDK's
TouchPoint.timestamp instead of leaving it null. Previously, the ML Kit
recognizer fell back to synthetic 10ms-apart timestamps, losing all real
pen dynamics (writing speed, pauses between letters, stroke duration).
This data is critical for ML Kit to disambiguate characters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move pen toolbar toggle to InboxToolbar at the top of the screen so
it's away from the writing hand. When hidden on inbox pages, the bottom
toolbar is fully removed (no leftover button to ghost-tap).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Save & Exit now returns to the library immediately instead of
blocking while sync runs. A SyncState singleton with its own
coroutine scope handles background sync, and the library shows
a "Syncing..." overlay on pages still processing. Pages are no
longer deleted after sync — they stay in the library.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove ML Kit Digital Ink Recognition dependency and all related code.
Handwriting recognition now uses the Boox firmware's built-in MyScript
engine via AIDL IPC, which produces significantly better results.

- Add AIDL interface for com.onyx.android.ksync HWR service
- Add OnyxHWREngine with protobuf encoding and service binding
- Simplify InboxSyncEngine to use only OnyxHWR (no fallback)
- Remove InkTestView debug screen and its navigation wiring
- Fix CountDownLatch reuse bug on service reconnection
- Make Context non-nullable in sync path (always available)
- Remove redundant DB query in sync flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Swap out manual Path-based pen rendering (ballpen, fountain, brush,
marker, pencil) and Onyx Neo*PenWrapper calls with androidx.ink's
CanvasStrokeRenderer. Removes ~200 lines of custom drawing code.
Onyx SDK live input path is unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move all editor tools (pen, eraser, lasso, line, undo/redo, image, home, menu)
  from bottom toolbar to a vertically-centered left-edge sidebar
- Pen picker flyout with size/color options appears to the right of sidebar
- Eraser flyout with pen/select eraser and scribble-to-erase toggle
- Add wiki link [[]] and tag # annotation buttons to sidebar
- Annotation mode: tap [[ or #, then draw a box over text to create an annotation
- Annotations render as semi-transparent colored overlays (blue for wikilink, green for tag)
- New Annotation entity in Room DB (v35) with type, bounding box, and page FK
- Annotation data flows through PageDataManager, PageView, and AppRepository
- One-shot annotation: mode resets after each box is drawn
- Remove PositionedToolbar and bottom toolbar from EditorView

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move sidebar outside canvas SurfaceView (Row layout) so finger taps
  work even when Onyx SDK raw drawing is active
- Add dashed stroke preview in annotation color (blue/green) when
  annotation mode is active, with e-ink refresh after box creation
- Increase annotation overlay visibility (alpha 130, border 4px)
- Add annotation mode observer to update pen stroke style immediately
- Eraser now removes annotation boxes in addition to strokes
- HWR sync recognizes annotated text inline: strokes inside [[]] boxes
  become [[wiki links]], strokes inside # boxes become #tags
- Add e-ink refresh helper to sidebar for button state updates
- Skip scribble-to-erase when annotation mode is active

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nc accuracy

- Replace solid rectangle overlays with typographic indicators: wiki links
  show [[ ]] brackets flanking content, tags show # prefix, both with light
  color wash, rounded corners, and subtle underline
- Expand canvas clip and e-ink refresh regions to include bracket/hash glyphs
  that extend beyond the annotation bounding box
- Fix HWR annotation wrapping by diffing full-page vs non-annotation stroke
  recognition instead of recognizing annotation strokes in isolation (which
  had worse accuracy due to less context, e.g. "pkm" recognized as "plan")
- Add 10s timeout to OnyxHWR calls to prevent infinite hang on service disconnect

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace upstream Ethran README with one that reflects what this fork
actually is: a handwriting capture surface for Obsidian on Boox tablets.
Covers the capture workflow, HWR pipeline, vault sync, editor changes,
and the UX principles visible across the commit history.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ck, simplify CI workflows

- Annotation create/delete now tracked in undo/redo history
- Expand redraw area to include bracket/hash glyph visual bounds, fixing partial clipping
- InboxSyncEngine: fall back to per-annotation HWR when diff-based approach has count mismatch
- Simplify CI workflows: update action versions, use GITHUB_TOKEN, trigger release on tag push

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…estamps

toInkStroke() was passing per-point dt deltas directly as elapsed_time
instead of accumulating them. This caused the Jetpack Ink API to reject
strokes with INVALID_ARGUMENT ("duplicate position and elapsed_time"),
making them invisible despite being saved to the database. Particularly
severe on the Palma 2 Pro where the slower SoC produces more duplicate
sample points.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants