186 lines
8.0 KiB
Markdown
186 lines
8.0 KiB
Markdown
# On the Level — v1 Product & Technical Brief
|
||
|
||
**Status:** Ready for final Claude review before implementation
|
||
**Date:** 2026-07-11
|
||
**Companion audit:** [`AUDIT.md`](AUDIT.md)
|
||
|
||
## Product decision
|
||
|
||
On the Level is an offline-first Android pocket-tool app for quick, trustworthy
|
||
level and angle work. It should feel like a responsive physical instrument—not
|
||
a novelty simulation—while keeping displayed measurements stable and honest.
|
||
|
||
v1 is deliberately small: a level, an angle meter, a calibrated screen ruler,
|
||
and the settings necessary to make them dependable. There is no account,
|
||
backend, advertising, camera/AR measurement, cloud sync, analytics dependency,
|
||
or subscription in v1.
|
||
|
||
## Audience and promise
|
||
|
||
For everyday DIY, hanging pictures, shelving, small home jobs, and quick rough
|
||
measurements. The app is not a substitute for a certified or safety-critical
|
||
instrument. Screen-ruler copy must state that it is for short, rough
|
||
measurements only.
|
||
|
||
## Navigation and layout
|
||
|
||
Use a simple three-destination bottom navigation: **Level**, **Angle**, and
|
||
**Tools**. The app is portrait-locked in v1. The UI is edge-to-edge and must
|
||
correctly handle system insets and back gestures.
|
||
|
||
### 1. Level — default destination
|
||
|
||
- Header: `LEVEL`, live/calibrated status, calibration/settings action.
|
||
- Explicit mode selector: **Surface** | **Edge**. Do not auto-switch modes in
|
||
v1; unexpected switching undermines confidence while positioning the phone.
|
||
- **Surface:** phone lying screen-up on a surface; use a two-axis cross-vial
|
||
visualization with meaningful pitch and roll readings.
|
||
- **Edge:** phone held upright on its long edge against a wall, shelf, or
|
||
picture frame; use a single, prominent vial/readout for that plane.
|
||
- The central visual is tactile: its bubble has restrained spring/damping,
|
||
settles naturally, and responds immediately to device movement.
|
||
- A locked state is a brief, accessible success reaction with a debounced
|
||
system-respecting haptic/audio cue. It is not the persistent normal UI.
|
||
- Keep the screen on while this destination is visible.
|
||
|
||
### 2. Angle
|
||
|
||
- Large primary angle readout.
|
||
- Secondary pitch, roll, and percent-grade readings.
|
||
- **Hold to zero** sets a relative reference and is always free.
|
||
- Percent grade is `tan(pitch) × 100`; define a sensible cap or infinity
|
||
presentation near a vertical surface.
|
||
- Target-angle alerts and saved named references are Pro features.
|
||
|
||
### 3. Tools
|
||
|
||
- **Screen Ruler**: a Pro tool with a clear preview and calm, intentional
|
||
paywall—not an interruption in the free level/angle flow.
|
||
- Calibration entry point.
|
||
- Units, haptic/audio, and motion/accessibility preferences.
|
||
|
||
## Visual and interaction direction
|
||
|
||
Use the concept board's deep graphite base, warm amber bubble, and lime level
|
||
lock as direction rather than a literal spec. Numeric measurements remain
|
||
near-white and must be readable in bright conditions. Never convey accuracy,
|
||
state, or purchase status by color alone.
|
||
|
||
The visual vial may be expressive, but it must never contradict the numeric
|
||
reading. Provide an in-app reduced-motion option and respect disabled system
|
||
animations. Respect device haptic settings; do not force vibration.
|
||
|
||
## Measurement modes and calibration
|
||
|
||
Only promise calibration for the two physical orientations explicitly supported
|
||
in v1:
|
||
|
||
1. **Surface** — screen-up, flat contact plane.
|
||
2. **Long Edge** — upright, long-edge contact plane.
|
||
|
||
Store a reference orientation for each supported mode—not a scalar angle offset.
|
||
The calibration flow uses two samples: place the device, record; rotate it 180°
|
||
on the same plane, record; derive the reference/bias from both samples. This
|
||
reduces fixed device bias without requiring the user to start on a known-level
|
||
surface. Calibration UI must give exact, mode-specific placement instructions.
|
||
|
||
Screen-ruler calibration is separate from sensor calibration. Offer calibration
|
||
against a standard credit-card width (85.60 mm) and a conventional ruler option.
|
||
Store scale with the active display/configuration characteristics and request
|
||
recalibration whenever a material display change makes the prior scale suspect.
|
||
|
||
## Sensor and measurement architecture
|
||
|
||
Use a small native Android/Kotlin Compose application. Keep one app module for
|
||
v1, with a manual application container rather than premature modularization or
|
||
Hilt.
|
||
|
||
```text
|
||
app/
|
||
core/design/ theme, typography, tactile animation primitives
|
||
core/sensors/ sensor source, device-axis mapping, measurement math
|
||
core/settings/ DataStore preferences and calibration persistence
|
||
core/billing/ entitlement abstraction and Play Billing implementation
|
||
feature/level/ state and UI for Surface / Edge level modes
|
||
feature/angle/ relative-zero logic and UI
|
||
feature/ruler/ ruler calibration and UI
|
||
feature/tools/ tools and settings UI
|
||
```
|
||
|
||
Require an accelerometer in the manifest. Select sensors in this order:
|
||
|
||
1. `TYPE_GAME_ROTATION_VECTOR`
|
||
2. `TYPE_GRAVITY`
|
||
3. A low-pass-filtered `TYPE_ACCELEROMETER`
|
||
|
||
The app does not need north/heading. Map device-frame sensor values explicitly
|
||
for the portrait-locked UI and for each measurement mode. Handle unavailable
|
||
sensors with a clear unsupported-device state.
|
||
|
||
Maintain three distinct values:
|
||
|
||
1. **Raw sensor value** — received from the selected sensor source.
|
||
2. **Stable calibrated measurement** — reference-orientation calibration plus
|
||
a defined EMA/low-pass filter. This is the sole input to numeric readouts
|
||
and level-lock detection.
|
||
3. **Animated display value** — a critically damped spring derived from the
|
||
stable value and used only to move the vial/bubble.
|
||
|
||
Readouts use 0.1° resolution and a display deadband to prevent flickering. Lock
|
||
behavior starts at no more than 0.2°, exits at at least 0.35°, has a short dwell
|
||
time, and has a multi-second haptic debounce. Exact constants remain tunable,
|
||
but lock and readout must always use the same stable calibrated measurement.
|
||
|
||
Register sensors only while a relevant screen is foregrounded; unregister them
|
||
when backgrounded.
|
||
|
||
## Monetization
|
||
|
||
Use one permanent, non-consumable **On the Level Pro** Google Play product. Do
|
||
not use a subscription. The free product is complete:
|
||
|
||
- Surface and Edge level modes
|
||
- Angle measurement, pitch/roll, degree/grade display
|
||
- Hold-to-zero
|
||
- Calibration and basic feedback/unit preferences
|
||
|
||
Pro adds deliberate, additive measurement tools:
|
||
|
||
- Calibrated Screen Ruler
|
||
- Target-angle haptic/audio alerts
|
||
- Saved and named angle references
|
||
|
||
Expose entitlement as `StateFlow<Entitlement>` from `core/billing`. Billing
|
||
initialization must be asynchronous/lazy: free tools render immediately and
|
||
never block on Play services. Cache a previously Play-confirmed entitlement for
|
||
offline job-site use, refresh owned purchases when possible (including resume),
|
||
and never grant from an arbitrary preference value or a pending purchase.
|
||
|
||
## Testability and release gates
|
||
|
||
Keep all math—device-axis mapping, calibration, smoothing, hysteresis, grade,
|
||
and lock transitions—in pure Kotlin. Place sensor I/O behind an injected
|
||
`SensorSource` so tests can use synthetic and recorded real-device traces.
|
||
|
||
Before release, verify:
|
||
|
||
- Surface and Edge readings across supported orientations and screen rotation
|
||
conditions.
|
||
- Two-sample calibration improves repeatability and remains isolated per mode.
|
||
- Numeric readings, vial animation, and lock feedback stay consistent.
|
||
- Haptic debounce, system haptic settings, reduced motion, and bright-light
|
||
readability.
|
||
- Sensor absence, app backgrounding, and screen wake behavior.
|
||
- Pro purchase, restore, pending purchase, offline cached entitlement, and
|
||
billing-unavailable behavior without degrading free tools.
|
||
- Ruler calibration, display/configuration changes, and honest rough-measure
|
||
wording.
|
||
|
||
## Explicitly out of scope for v1
|
||
|
||
- Camera-assisted or AR distance measurement
|
||
- Accounts, server verification, cloud storage, sharing, or collaboration
|
||
- Ads, subscription plans, consumable credits, or trial metering
|
||
- Automatic flat/edge mode switching
|
||
- Support claims for every device edge, case, or external/foldable display
|