Split app.js and add experimental Android integration#45
Open
kasnder wants to merge 27 commits into
Open
Conversation
…dules, migration Review fixes: - Only advance the URL-check throttle timestamp on successful extraction so null extractions no longer delay website blocking by up to 500ms - Cache getAppLabel/shouldSkipPackage fallbacks on exception so failing PackageManager lookups aren't re-run on every accessibility event - Surface fs scope for read/write_text_file so blocklist export/import works from the save/open dialogs - Deduplicate hasApp/hasWebsiteBlockingCandidates, drop redundant toString() calls and the dead blocklistId match in the friction gate Dead code removal: - Remove the unused get_blocking_state command chain (JS/Rust/Kotlin/ permissions) and the never-called Schedules.toggle/isScheduleActive - Remove the legacy temporary_unlock escape hatch and the overrideAndroidScheduleId fallback; unknown friction-gate ids now log and bail instead of silently no-oping in Kotlin - Revert the platform-neutral blocklist auto-selection change and the single-use DEFAULT_UI_ZOOM_MOBILE constant Pause/resume on Android (battery-preserving, restores ReEnableWorker): - Sync paused schedules/blocks with isPaused + pauseEndTimestampMs; Kotlin stores them disabled and arms a one-off WorkManager re-enable at the expiry, so blocking resumes even if the app process is dead - Missing pauseEndTime means paused indefinitely (legacy-disabled schedules); UI now renders indefinite pauses as Paused with a working Resume button - Manual blocks whose duration elapses mid-pause are not resurrected One-shot (non-repeating) schedules now enforce on Android via absolute activeFrom/activeUntil windows, mirroring the iOS one-off entries. Legacy migration hardening: - Run migration before any set_schedules sync (both write the same legacy "routines" prefs key) and regardless of accessibility grant - Stamp repeatType on migrated schedules (they were misclassified as non-repeating and silently excluded from sync) - Map legacy disabledUntil onto timed pauses so mid-temporary-unlock schedules auto-resume instead of sticking disabled - Import legacy MANUAL schedules as blocklists only (deliberate) - Normalize www. prefixes in Kotlin website matching Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…st-utils, utils, i18n) Pure mechanical extraction — code moved verbatim, function names unchanged. Cross-module mutable vars (appData, selectedBlocklistId, platform flags, helperAvailable, screentimeAuthorized, androidPermissionsGranted, isMicrosoftStorePackage, userExplicitlyDeselected) now live on the shared state object in state.js since ES import bindings are read-only. window.__REDDBLOCK_INTERNALS__ contract unchanged; in-app blocking tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…me modules Mechanical moves; temporary imports from './app.js' for functions that will move to their final module in later phases. In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shared time-picker/schedule-mode state moved to state.js. In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First import cycles appear here (render <-> app.js); safe because all cross-module calls are hoisted function declarations invoked at runtime, never during module evaluation. Cross-module mutable vars (lastBlockedDomains, activatedBlockIds, pauseScheduleData, isScheduleMode) moved to state.js. In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…odules Schedule segments/repeat/overlay-selection state moved to state.js. In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Modal/challenge session state (18 vars) moved to state.js. Fixed 13 rename collisions inside functions whose local parameter is named 'state' (onboarding/migration browser-state objects) via an appState alias. In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Migration/onboarding banner state (12 vars) moved to state.js. In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
App-blocking warning overlay, Android/iOS blocking init, friction gate, warning coordinator, handset modal screens, platform detection. In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
app.js is now ~3.4k lines of entry wiring (DOMContentLoaded init sequence, setupEventListeners, i18n-apply glue). window.__REDDBLOCK_INTERNALS__ moved to dev-internals.js with identical keys and appData getter/setter semantics. architecture.md updated with the new frontend module map and conventions. In-app tests pass (132/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
openInstalledAppsPicker referenced the blocklist modal's selected-apps
array via bare 'modalApps', which only resolved through the implicit
global set by setupModalListeners ('window.modalApps = modalApps').
Reference the window property explicitly, matching the existing
window.lockedApps usage, and document the in-place-mutation contract.
No behavior change; ESLint no-undef is now clean across src/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add docs/android-build.md covering the CLI build recipe: required env vars (ANDROID_HOME/NDK_HOME/JAVA_HOME, which npm install does not set), debug-APK flags (--apk true --aab false --debug --target aarch64), output paths, the adb install/logcat loop, the Android Studio callback requirement, release signing caveat, and troubleshooting. Link it from the README build section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings in main's 5 fixes (one-shot schedule enforcement + voice-recording mic fixes). The Rust changes (native_host.rs, diagnostics.rs, lib.rs) merged cleanly. main's app.js changes were re-applied by hand to the new module homes created by the app.js split on this branch: - Schedule enforcement (0c21646): buildResolvedOneShotSegment / buildResolvedOneShotSegments / buildPersistedSchedule / buildPersistedAppData now live in schedule-engine.js; saveData() in persistence.js persists via buildPersistedAppData() so non-repeating schedules carry resolvedSegments. - Voice-recording fixes (7ba37dc, ff5c87f, a49ddd9, 28b2c6b): applied to schedule-overlay.js — finishScheduleOverlayRecorderKeepMic (hold mic until modal close), meter-stream clone, single-file recording (no timeslice / requestData). In-app tests pass (132/132); vite build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tauri.android.conf.json pinned the Android app at version 2.4.0 while the canonical version (tauri.conf.json / package.json / Cargo.toml) is 3.5.0 — so Android builds produced a stale 2.4.0 / versionCode 2004000. Remove the Android-specific 'version' override so the Android platform inherits the base version, eliminating the duplicate source of truth that drifted. Android versionCode now derives as 3005000 (> the old 2004000, and > any installed build), so upgrades remain monotonic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
siteNameForDisplay returned the second-to-last host label, so bbc.co.uk / guardian.co.uk rendered as "co" in the card summary. Recognise common second-level public suffixes (co.uk, com.au, etc.) and take the registrable label instead (bbc, guardian). theguardian.com etc. are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two issues surfaced by an on-device migration from the legacy native app: - Phantom "Distractions": loadData creates a default space when blocklists is empty, which on Android is always the case at startup (native routines migrate later, post-onboarding), so migrating users got the default plus their imported spaces. Defer the default on Android until migrateAndroidNativeSchedules runs; it creates the default itself only when there's no legacy data. Default-blocklist creation is extracted into a shared createDefaultBlocklist() in persistence.js. - App labels: blocklist cards showed raw package ids (e.g. "App.vanadium.browser"). The installed-apps cache (package -> friendly label) was preloaded only on desktop; Android now preloads it in initializeAndroidBlockingState. The unknown-app fallback also no longer title-cases package-style ids. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
libredd_block_lib.so had 4 KB-aligned LOAD segments, triggering the Android 15+ 16 KB page-size compatibility warning (and blocking Play Store uploads from Nov 2025). Emit -Wl,-z,max-page-size=16384 for Android targets from build.rs via cargo:rustc-link-arg — the Tauri Android build sets RUSTFLAGS itself, so a .cargo/config.toml rustflags entry is ignored; rustc-link-arg (already used here for macOS rpaths) is honoured. NDK r27 needs the opt-in; r28+ defaults to it. Verified: all LOAD segments now align to 0x4000. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collaborator
Author
|
Rust integration led for me to a crash though, see #46. Need bumping of Tauri to newer version.. |
- redd-focus-web/ is a separate git checkout in the working tree, not part of this project. - src/main/assets/blocked is copied in at build time (identical to the canonical src-tauri/blocked), matching the already-ignored tauri.conf.json. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The legacy native app's always-on protection is a MANUAL schedule that is toggled on (isEnabled + a live session). migrateAndroidNativeSchedules imported MANUAL schedules as blocklist-only, so the always-on block was silently dropped on upgrade — the first set_schedules sync then deletes the legacy schedule/session and blocking stops. Carry a running MANUAL schedule over as an always-on active block (isAlwaysOn, ALWAYS_ON_END_TIME) and start its Kotlin session via start_manual_block after the post-migration sync, mirroring proceedWithBlock. Idle MANUAL schedules still import as blocklist-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Migrate the Android blocking engine into the Tauri app so Android can share the same web UI as desktop and iOS. This is a cleaned-up replacement for #41 (rebased on the latest
main, with theapp.jsmodule split and several follow-up fixes squashed in).Keep Android enforcement in native Kotlin rather than moving it into Rust: the Rust plugin is a thin IPC bridge, while blocking decisions, Accessibility events, WorkManager jobs, package/app integration, and device-protected preferences remain on the Android side. This preserves Android lifecycle integration and avoids introducing a long-running Rust enforcement loop.
Changes
tauri-plugin-android-blockerto bridge frontend/Rust IPC to Kotlin blocking code.net.kollnig.reddblockandroidfor migration continuity.Additional work beyond #41
app.jsinto ~20 ES modules (leaf modules, schedule engine, render hubs, onboarding, enforcement, etc.).Validation
node --check src/app.jsgit diff --checkFull desktop
cargo checkwas not completed locally because the macOS Safari bridge build step failed before Rust checking due to local Swift/SDK/cache setup.