You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An estimator's hands are busy: one on the mouse tracing, one on the keyboard for tool keys. Everything else — picking a condition, setting waste, naming a label, leaving a room note — steals the hands away from measuring. Voice fixes that: push-to-talk dictation that sets takeoff metadata while you keep tracing.
"carpet one, waste seven" → activates/creates condition CPT-1, sets waste 7% "label phase two" → arms the shape label "Phase 2" for subsequent traces "note: verify sheet vinyl in soiled utility with GC" → attaches a note
This is dictation, not conversation — v1 has no AI in the loop at all. It's a grammar over actions the app already exposes.
Hard constraint: nothing leaves the browser
OpenTakeoff's pledge is client-only. Audio must be processed on-device. Two candidate paths, and part of this RFC is picking one with benchmarks:
Whisper-class WASM (e.g., whisper.cpp compiled to WASM, tiny/base English models, lazy-loaded like fflate/pdf-lib already are). Pro: quality, consistency across browsers. Con: ~40–150 MB model download (must be opt-in + cached), CPU cost on low-end machines.
Web Speech API fallback. Pro: zero download. Con: on Chrome it ships audio to a speech service — that violates the pledge, so it could only ever be offered behind an explicit "uses your browser's cloud recognizer" consent, or not at all. Firefox/Safari support is inconsistent.
Recommendation to validate: WASM primary, no silent cloud fallback. If the model download is unacceptable, the feature simply isn't enabled — never a quiet privacy downgrade.
Scope (v1)
Push-to-talk (hold a key — proposal: V) with a live transcript chip near the HUD; release to commit, Esc to discard.
Grammar, not free text, parsed deterministically from the transcript:
<finish tag> [waste <n>] — activate (or offer to create) a condition. Tag vocabulary comes from the live conditions list + common Div-9 patterns (CPT/LVT/VCT/CT/RB/TR + number).
label <text> / clear label — the #111 shape-label vocabulary.
note <text> — freeform note attached to the active sheet (or selected shape).
waste <n> — active condition's waste %.
Everything routes through existing app actions — the parser produces the same calls the UI produces. No new mutation paths.
Explicit non-goals for v1: tool switching by voice, drawing by voice, conversational agent, always-on listening.
Why this belongs in OpenTakeoff
No takeoff tool on the market has hands-free metadata entry. It compounds with everything else here: conditions and labels are exactly the fields whose provenance the capture layer records, and a spoken transcript is honest evidence of intent. It also matters for accessibility — RSI and single-handed users get a first-class path.
Testing bar (a PR is reviewable only with ALL of this)
This feature touches microphone permissions, heavy WASM, and text that mutates takeoff data — the test bar is deliberately high:
Grammar parser: pure + exhaustively unit-tested. The transcript→intent parser must be a pure module (no DOM) with table-driven tests: every grammar production, ambiguous inputs ("carpet one seven" — tag+waste or tag only?), homophone traps ("waist seven"), numbers as words vs digits, rejected/garbage input (must produce no action, never a guess), and locale number formats.
Fixture audio corpus. A committed set of short WAV fixtures (multiple speakers/accents/noise profiles, recorded for this project — no third-party audio) with expected transcripts + expected intents. CI runs the WASM recognizer over the fixtures headlessly and asserts end-to-end intent accuracy ≥ an agreed floor; regressions fail the build.
Permission + lifecycle states, all tested manually and documented: mic denied, mic revoked mid-hold, no mic device, tab backgrounded mid-dictation, model still downloading when V is pressed (clear "not ready" affordance, never a silent drop), model download failure/retry, and cleanup on unmount (no orphaned audio contexts — verify with chrome://media-internals).
Cross-browser matrix documented in the PR: latest Chrome, Firefox, Safari (incl. one Apple-Silicon and one x86 run); WASM SIMD availability differences noted; graceful feature-absence (button hidden, not broken) where unsupported.
Performance budget: recognizer must not degrade canvas interaction — pan/zoom stays smooth while decoding (decode in a Worker, never the main thread); memory ceiling documented; model load lazy + cached (verify second-load path).
Privacy proof: a test (and a line in the PR description) demonstrating zero network requests during dictation — the existing "No telemetry" stance, extended to audio.
Mutation safety: voice-produced actions must be identical to their UI-produced equivalents (assert deep-equal on resulting state in tests) — no voice-only code path may touch shapes/conditions directly.
Where the seams are (for whoever picks this up)
Conditions + waste: the condition bar's activate/create/update paths in web/src/pages/TakeoffCanvas.jsx (addCondition, updateCond, activateCondition).
Shape labels: the #111 label vocabulary (shape_labels, assignShapeLabel, active-label state).
Lazy heavy deps precedent: web/src/lib/ingest.js (fflate/pdf-lib load on demand).
Design tokens for the transcript chip: web/src/styles/tokens.css.
Keyboard handling (for the push-to-talk key, mind existing single-letter tool keys): the keydown effects in TakeoffCanvas.jsx.
Slices
This RFC is deliberately sliced so each piece lands independently against the bar above.
The recognizer — WASM STT, fixture audio corpus, mic lifecycle, browser matrix, privacy proof. Claimed. This is the slice the testing bar was written for, and it does not land without all seven items.
Two-tier router — deterministic grammar on the fast path, falling through to the in-canvas agent loop for compound asks. Seam is in place, design not settled.
Slice 5 is open. Post a design comment before a PR.
The idea
An estimator's hands are busy: one on the mouse tracing, one on the keyboard for tool keys. Everything else — picking a condition, setting waste, naming a label, leaving a room note — steals the hands away from measuring. Voice fixes that: push-to-talk dictation that sets takeoff metadata while you keep tracing.
This is dictation, not conversation — v1 has no AI in the loop at all. It's a grammar over actions the app already exposes.
Hard constraint: nothing leaves the browser
OpenTakeoff's pledge is client-only. Audio must be processed on-device. Two candidate paths, and part of this RFC is picking one with benchmarks:
Recommendation to validate: WASM primary, no silent cloud fallback. If the model download is unacceptable, the feature simply isn't enabled — never a quiet privacy downgrade.
Scope (v1)
V) with a live transcript chip near the HUD; release to commit,Escto discard.<finish tag> [waste <n>]— activate (or offer to create) a condition. Tag vocabulary comes from the live conditions list + common Div-9 patterns (CPT/LVT/VCT/CT/RB/TR + number).label <text>/clear label— the #111 shape-label vocabulary.note <text>— freeform note attached to the active sheet (or selected shape).waste <n>— active condition's waste %.Why this belongs in OpenTakeoff
No takeoff tool on the market has hands-free metadata entry. It compounds with everything else here: conditions and labels are exactly the fields whose provenance the capture layer records, and a spoken transcript is honest evidence of intent. It also matters for accessibility — RSI and single-handed users get a first-class path.
Testing bar (a PR is reviewable only with ALL of this)
This feature touches microphone permissions, heavy WASM, and text that mutates takeoff data — the test bar is deliberately high:
chrome://media-internals).Where the seams are (for whoever picks this up)
web/src/pages/TakeoffCanvas.jsx(addCondition,updateCond,activateCondition).shape_labels,assignShapeLabel, active-label state).web/src/lib/ingest.js(fflate/pdf-lib load on demand).web/src/styles/tokens.css.Slices
This RFC is deliberately sliced so each piece lands independently against the bar above.
"carpet one, this room"— cursor position as the one-click seed) — landed in feat(voice): deixis — cursor-seeded one-click ("this room") per RFC #59 design #83, design record in the comments below.Slice 5 is open. Post a design comment before a PR.