Add authoritative v1 product & technical brief (Codex)
This commit is contained in:
@@ -0,0 +1,185 @@
|
|||||||
|
# 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
|
||||||
Reference in New Issue
Block a user