Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c15043d
Add regression tests for the extrapolation-overlay clock-skew lift (#…
bmander Jul 2, 2026
4d46004
Merge pull request #1608 from bmander/fix-1604-extrapolation-clock-sk…
bmander Jul 2, 2026
97a55be
Drive map stop icons from an LRU cache, not each API fetch
bmander Jun 28, 2026
cb828bd
Dynamic stop sizes: collapse stops to dots at distant zoom + "more st…
bmander Jun 29, 2026
86eb488
Merge pull request #1607 from bmander/map-icon-cache
bmander Jul 2, 2026
e871f7b
Surface region-load failures and harden map-reveal/OTP-reset edges (#…
bmander Jul 2, 2026
95c680b
Merge pull request #1609 from bmander/fix-1602-region-failure-map-rev…
bmander Jul 2, 2026
a4eb00f
Merge pull request #1610 from bmander/dynamic-stop-sizes
bmander Jul 2, 2026
b54ffec
Remove travel-behavior residue
bmander Jul 2, 2026
b0364bd
Merge pull request #1611 from bmander/cleanup-travel-behavior-residue
bmander Jul 2, 2026
a908e64
refactor: extract duplicated inDarkMode() helper to ThemeUtils
bmander Jul 2, 2026
ac7f137
Filter "show vehicles on map" to the stop-relevant direction
bmander Jul 2, 2026
0076ff0
storage: retire the legacy SQLite ContentProvider; unify persistence …
bmander Jul 2, 2026
fe9b087
Fix HomeViewModelTest for ShowRouteRequest API change
bmander Jul 2, 2026
2be39b0
Use the server clock as the ETA baseline for server-timestamp deltas …
bmander Jul 2, 2026
ee550d8
Apply /simplify + comment-freshness cleanup to the server-clock chang…
bmander Jul 2, 2026
9c63885
storage: harden backup restore and ImportGate gating
bmander Jul 3, 2026
eb4f863
Merge pull request #1616 from bmander/filter-show-vehicles-by-direction
bmander Jul 3, 2026
c9bd707
Merge pull request #1614 from bmander/fix/1545-dark-mode-dedupe
bmander Jul 3, 2026
bea9587
Merge main into storage-room-migration
bmander Jul 3, 2026
e1e940c
Harden the server-clock change: stale-path projection, future-window …
bmander Jul 3, 2026
ee7a84e
Add CLAUDE.md guidance against unsanctioned heuristics; flag the one …
bmander Jul 3, 2026
6aefa73
Replace the seconds/millis guess with a deterministic conversion (#1612)
bmander Jul 3, 2026
bc59a2f
Drop the heuristics registry from CLAUDE.md; keep only the anti-heuri…
bmander Jul 3, 2026
1439d27
Restore active-window unit normalization; the field is polymorphic pe…
bmander Jul 3, 2026
80b3008
Apply code-review fixes: currentTime==0 fallback, alert-window normal…
bmander Jul 3, 2026
6f2cf37
Merge pull request #1615 from bmander/storage-room-migration
bmander Jul 3, 2026
cb9cf64
Merge main (storage-room-migration) into fix-1612-server-clock-eta-ba…
bmander Jul 3, 2026
4ad3374
Merge pull request #1618 from bmander/fix-1612-server-clock-eta-baseline
bmander Jul 3, 2026
cc0e159
feat: add prominent 'hide all' alerts banner above active alerts (#1534)
bmander Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,54 @@ Tests are in `onebusaway-android/src/androidTest/java/`. Key test classes:

CI runs on API level 33 emulator via GitHub Actions.

## Time domains: server clock vs device clock (#1612)

Any user-facing duration measured against a **server-provided timestamp** — ETAs ("N min"),
countdowns, "arriving now", vehicle age ("data updated N sec ago"), alert active-window checks — must
use the response's server clock as "now", **never** `System.currentTimeMillis()`. Mixing the two in a
single subtraction (server timestamp − device now) leaks device clock skew straight into the number.

- The server clock is on the response: `StopArrivals.currentTime`, `RouteTrips.currentTimeMs`,
`TripDetailsRepository.lastLoadedTime()`.
- Extrapolation deliberately stays on the **device** clock (`TripState.anchorLocalTimeMs`) because it
pairs each server time with the local receive time; cross back to the server clock with
`TripState.toServerClock(...)` before plotting against server-clock data.
- Purely local timers stay on the device clock: cache TTLs / recent-window cutoffs (compared against
locally-stamped `System.currentTimeMillis()` writes), poll scheduling (`SystemClock.elapsedRealtimeNanos`),
WorkManager/alarm scheduling, "updated Ns ago" against a locally-stamped `lastResponseTimeMs`.

Keep ETA/active-window helpers pure — pass the "now" in as a parameter (see `SituationUtils`,
`ArrivalInfo`); don't call the clock inside a helper. This is verified by `SituationUtilsTest` and
`ServerNowMsTest`.

## No unsanctioned heuristics

Do **not** introduce heuristics — magic thresholds, magnitude guesses, or "good enough" inference of
something the data should state explicitly. Examples of what counts: guessing whether a timestamp is
in seconds vs milliseconds from its size; inferring a unit, type, ID scheme, or intent from a value's
range; fuzzy string matching where an exact key exists; "if it looks like X, treat it as X." Heuristics
pass the happy path and misbehave silently at the edges, and they rot as the upstream data shifts.

Prefer resolving the fact at its source instead: normalize units/shape at the parse or wire boundary,
carry an explicit field, or fail loudly (throw / return an error) rather than guessing.

If a heuristic is genuinely unavoidable, it is a **human-sign-off gate**, not a judgment call an agent
makes alone:
1. Call it out explicitly in the PR description and get a human to approve it before merge.
2. Document it at the call site: the exact assumption, why no exact source exists, and its failure mode.
3. This applies equally to **pre-existing** heuristics you touch — reworking one re-opens the sign-off.

When you're tempted to guess a unit/type/intent, check the wire contract, the sample payloads, **and
the producer's source** first — the answer is usually knowable, and sometimes the honest answer is
"the field really is polymorphic," which is itself worth documenting. (Worked example: a legacy helper
guessed whether an alert active-window timestamp was seconds or millis by magnitude. Reading the OBA
**server** source settled it — GTFS-RT `active_period` is seconds per spec, but the server normalizes to
millis on ingestion via its own magnitude rule (`GtfsRealtimeAlertLibrary.toMillis`, threshold 1e12),
while older servers still emit seconds — so the field genuinely varies. The client normalization stays,
but it's now sanctioned by the server evidence and mirrors the server's exact threshold, rather than
being an invented guess — and it lives at the wire→domain adapter (`situationEpochToMillis`) so the
domain model is unambiguously millis. See `situationEpochToMillis` and `SituationWindow`.)

## Key Technical Details

- **Min SDK**: 23 (Android 6.0)
Expand Down
9 changes: 8 additions & 1 deletion onebusaway-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ android {
}
}

// Expose the exported Room schemas to instrumented tests so MigrationTestHelper can load them.
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas")
}

/**
* brand - how the name, look, and feel of each OneBusAway-based app is presented
* platform - Google
Expand Down Expand Up @@ -162,7 +167,8 @@ android {
}

/**
* Set brand BuildConfig.DATABASE_AUTHORITY, used in org.onebusaway.android.provider.ObaContract
* Set brand BuildConfig.DATABASE_AUTHORITY, used in org.onebusaway.android.ui.nav.DeepLinkUris (the
* content:// stop/route deep-link authority) — historically the ObaProvider ContentProvider authority.
*/
androidComponents {
onVariants(selector().all(), { variant ->
Expand Down Expand Up @@ -297,6 +303,7 @@ dependencies {
implementation "androidx.room:room-runtime:2.7.0"
ksp "androidx.room:room-compiler:2.7.0"
implementation "androidx.room:room-ktx:2.7.0"
androidTestImplementation "androidx.room:room-testing:2.7.0"
// Hilt
implementation "com.google.dagger:hilt-android:2.59.2"
ksp "com.google.dagger:hilt-android-compiler:2.59.2"
Expand Down
2 changes: 0 additions & 2 deletions onebusaway-android/flavors/agencyX.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ android.productFlavors {
buildConfigField "String", "FIXED_REGION_PAYMENT_ANDROID_APP_ID", "null"
buildConfigField "String", "FIXED_REGION_PAYMENT_WARNING_TITLE", "null"
buildConfigField "String", "FIXED_REGION_PAYMENT_WARNING_BODY", "null"
buildConfigField "boolean", "FIXED_REGION_TRAVEL_BEHAVIOR_DATA_COLLECTION", "false"
buildConfigField "boolean", "FIXED_REGION_ENROLL_PARTICIPANTS_IN_STUDY", "false"
buildConfigField "String", "FIXED_REGION_SIDECAR_BASE_URL", "null"
buildConfigField "String", "FIXED_REGION_PLAUSIBLE_ANALYTICS_SERVER_URL", "null"
}
Expand Down
2 changes: 0 additions & 2 deletions onebusaway-android/flavors/agencyY.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ android.productFlavors {
buildConfigField "String", "FIXED_REGION_PAYMENT_ANDROID_APP_ID", "\"co.bytemark.hart\""
buildConfigField "String", "FIXED_REGION_PAYMENT_WARNING_TITLE", "null"
buildConfigField "String", "FIXED_REGION_PAYMENT_WARNING_BODY", "null"
buildConfigField "boolean", "FIXED_REGION_TRAVEL_BEHAVIOR_DATA_COLLECTION", "false"
buildConfigField "boolean", "FIXED_REGION_ENROLL_PARTICIPANTS_IN_STUDY", "false"
buildConfigField "String", "FIXED_REGION_SIDECAR_BASE_URL", "null"
buildConfigField "String", "FIXED_REGION_PLAUSIBLE_ANALYTICS_SERVER_URL", "null"
}
Expand Down
2 changes: 0 additions & 2 deletions onebusaway-android/flavors/kiedybus.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ android.productFlavors {
buildConfigField "String", "FIXED_REGION_PAYMENT_ANDROID_APP_ID", "null"
buildConfigField "String", "FIXED_REGION_PAYMENT_WARNING_TITLE", "null"
buildConfigField "String", "FIXED_REGION_PAYMENT_WARNING_BODY", "null"
buildConfigField "boolean", "FIXED_REGION_TRAVEL_BEHAVIOR_DATA_COLLECTION", "false"
buildConfigField "boolean", "FIXED_REGION_ENROLL_PARTICIPANTS_IN_STUDY", "false"
buildConfigField "String", "FIXED_REGION_SIDECAR_BASE_URL", "null"
buildConfigField "String", "FIXED_REGION_PLAUSIBLE_ANALYTICS_SERVER_URL", "null"
}
Expand Down
2 changes: 0 additions & 2 deletions onebusaway-android/flavors/oba.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ android.productFlavors {
buildConfigField "String", "FIXED_REGION_PAYMENT_ANDROID_APP_ID", "null"
buildConfigField "String", "FIXED_REGION_PAYMENT_WARNING_TITLE", "null"
buildConfigField "String", "FIXED_REGION_PAYMENT_WARNING_BODY", "null"
buildConfigField "boolean", "FIXED_REGION_TRAVEL_BEHAVIOR_DATA_COLLECTION", "false"
buildConfigField "boolean", "FIXED_REGION_ENROLL_PARTICIPANTS_IN_STUDY", "false"
buildConfigField "String", "FIXED_REGION_SIDECAR_BASE_URL", "\"https://onebusaway.co\""
buildConfigField "String", "FIXED_REGION_PLAUSIBLE_ANALYTICS_SERVER_URL", "null"
}
Expand Down
Loading
Loading