Skip to content

ci: typecheck the extension and run the Android unit tests - #282

Merged
johannesjo merged 7 commits into
mainfrom
claude/app-improvement-priorities-9jg2vp
Sep 4, 2026
Merged

johannesjo merged 7 commits into
mainfrom
claude/app-improvement-priorities-9jg2vp

Conversation

@johannesjo

Copy link
Copy Markdown
Owner

tsc --noEmit never ran in CI (#152) and did not pass: the ambient asset
declarations in global.d.ts sat in a file that a top-level import type
had turned into a module, so every scss/svg/mp3 import failed to resolve,
and without skipLibCheck third-party typings failed too. That noise hid a
handful of real errors: a timer id typed as NodeJS.Timeout in a browser
file, a manifest override read off an untyped JSON import, and the
background message listener's implicitly-any parameters.

  • keep global.d.ts a script (import() types instead of a top-level import)
  • tsconfig: skipLibCheck, resolveJsonModule, explicit include
  • fix the four real errors; the listener now narrows its message
  • add npm run typecheck and a CI step for it
  • run the Android JVM unit test suite as its own CI job (ci: run the Android unit test suite before shipping #151)

Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_012vfDHpx2YT8CkxPPATgigc

`tsc --noEmit` never ran in CI (#152) and did not pass: the ambient asset
declarations in global.d.ts sat in a file that a top-level `import type`
had turned into a module, so every scss/svg/mp3 import failed to resolve,
and without skipLibCheck third-party typings failed too. That noise hid a
handful of real errors: a timer id typed as NodeJS.Timeout in a browser
file, a manifest override read off an untyped JSON import, and the
background message listener's implicitly-any parameters.

- keep global.d.ts a script (import() types instead of a top-level import)
- tsconfig: skipLibCheck, resolveJsonModule, explicit include
- fix the four real errors; the listener now narrows its message
- add `npm run typecheck` and a CI step for it
- run the Android JVM unit test suite as its own CI job (#151)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vfDHpx2YT8CkxPPATgigc
…#196)

The fresh intervention already opens on the clock-matched loading sky with
the native sun blooming on it; what was left of #118 were the two sibling
overlays. The sleep wind-down window still fell back to the flat dark
ground, faded in over the blocked app for 300ms, forced a hardware layer
on its WebView (the very thing that makes an overlay WebView skip its
first composite), and had none of the first-frame nudges or the failed-
WebView teardown. The success sun window never opted out of system-bar
insets, so its gradient could leave an uncovered strip under the gesture
bar.

- CommonWindow: the initial shield is painted before addView through an
  overridable hook; the first-frame hardening (alpha nudge, invalidate
  pump) and the failed-WebView teardown move here so every WebView overlay
  shares one copy instead of re-deriving it.
- LoadingSky: the sky backdrop composable and the local-hour helper are
  shared rather than private to the intervention window.
- SleepWindDownOverlayWindow: instantly-opaque loading-sky shield,
  transparent WebView over it, no forced hardware layer, the shared
  hardening, full-screen insets.
- SuccessSunWindow: layout under the system bars and the cutout like its
  siblings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vfDHpx2YT8CkxPPATgigc
Detection lives inside the accessibility service and dies with it. The
permission checks catch the service being switched off, but not the
failure that actually happens in the field: the toggle still shows on
after an OEM kill or an update, nothing is bound, no intervention ever
fires, and a local-only app has no other way to tell the user it has
gone quiet.

- the service keeps a connected flag across onServiceConnected /
  onUnbind / onDestroy
- the main bridge exposes it with the settings state as DetectionHealth
  (JSON shape covered by a JVM test)
- the Android shell reads it on every refresh and shows one calm line
  when enabled-but-unbound; the tap opens the accessibility settings,
  where switching the service off and on re-binds it. A missing or
  malformed read counts as watching, so the line can only ever appear
  on a known fact - never on event silence, which an idle phone
  produces too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vfDHpx2YT8CkxPPATgigc
All data is local and every platform is an island: the extension's sync
storage is small and prunes the oldest answers when it fills, a lost
phone takes its answers with it, and nothing moves between the
extension and the app. The answer journal is the most personal thing
minded holds, so it gets a way out that doesn't depend on us - a plain
JSON file the user keeps, restorable on any platform.

- journalBackup.ts: the file format (answers + custom questions),
  tolerant parsing, and an additive merge by id that never overwrites
  an existing entry and is idempotent; unit-tested
- fileTransfer.ts: Blob download on the extension; on Android the
  bridge opens the system "save as" sheet (ACTION_CREATE_DOCUMENT), and
  a plain <input type="file"> is routed to the document picker through
  WebChromeClient.onShowFileChooser
- one "Your answers" section on both settings pages: Save a copy /
  Bring a copy back, with a single result line

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vfDHpx2YT8CkxPPATgigc
InteractionCommon.tsx and Sun.tsx are the files every audit flags as too
large, and both kept growing anyway - each polish pass landed inside
them. This moves the pieces that don't need the component into tested
modules next to them, and adds a ratchet test that pins both files at
their new size so a change can't quietly add to either.

- interactionCommonHelpers.ts: the widget-line match and the two DOM
  probes that were module-level in InteractionCommon
- interactionCornerSettle.ts: the Little Sun corner settle (arriving /
  departing, per platform, honouring Android's dragged rest centre) and
  the platform-sized phase settle
- sun/sunGeometry.ts: anchor point for a settle, rest scale, and the
  rendered-translate parse
- sun/sunGlow.ts: the rest/hover glow constants with their reasoning,
  the glow-axis temperature read, and the pointer-feedback scale
- the interaction's cleanup lists its frames and timeouts instead of
  eleven copies of the same if-block

Behaviour is unchanged; the extracted functions are covered by unit
tests, and the full suite, typecheck, and production build pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vfDHpx2YT8CkxPPATgigc
…lays

From an adversarial and a normal review of the branch:

- journal import on the extension: answers live in one browser sync item
  of a few kilobytes, so a phone's journal was rejected wholesale. The
  import now brings back the most recent imported answers that fit,
  never touching the device's own, and says so plainly; a non-quota
  failure gets a neutral message instead of a guessed cause. The quota
  check moves to a platform-neutral module so the app bundles don't pull
  in the extension's storage code.
- journal entries are rebuilt from known fields only; oversized picks
  are refused before parsing
- Android "save a copy": the export is staged in a cache file, so a
  process reclaimed behind the system picker can no longer leave the
  chosen document empty; a second tap while the sheet is up is ignored
- detection line: sits above the optional-permissions invite (which
  says "minded already works"), and the state is re-read shortly after
  mount so a cold-start bind race can't leave a stale line standing
- CommonWindow owns the loading-sky shield behind one flag instead of
  two copies in the intervention and wind-down windows
- Play internal and release builds run the Android unit suite before
  bundleRelease (#151)
- copy: drop the "stays on this device" claim (untrue on the extension);
  the size ratchet gets a few lines of headroom; README lists the new
  checks and no longer names a landing-page directory that isn't here

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vfDHpx2YT8CkxPPATgigc
The review-fix edit that staged the export in a cache file replaced the
whole span between saveTextFile and onCreate, taking the companion
object (widget intent extras, fade constants) and the intent-route
helpers with it. CI's first Kotlin compile of the branch caught it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vfDHpx2YT8CkxPPATgigc
@johannesjo
johannesjo merged commit 25ac9ed into main Sep 4, 2026
4 checks passed
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