The home page capture box
How the fast-capture box on the home page behaves, and why.
A way out: cancel, and a Send button that unlocks (BL-453, 2026-09-10)
The owner's report, in his words. "There is nothing to cancel it. And it's stopping in the middle. Suppose at one point, user is changing mind and wanna cancel the whole process." And separately: "When I'm going back and forth between them, the program is going to stuck, a freeze, and it's messy."
Those are one piece of work, because the freeze is what made the missing cancel serious. The only way out of a stuck Send button was to reload the page — and reloading destroys a recording that has not been saved yet.
The freeze was the Send button, not the microphone
Recording and then pressing Send makes the page finish the recording first and re-send the form afterwards. While it finishes, every submit button in the form is disabled and relabelled "Please wait…". The re-sent form then took a different route through the code — the route that skips the step which re-enables the button. Nothing was left to unlock it, so the button sat disabled, wearing a spinner, until the page was reloaded.
Two details mattered for the fix. The lock overwrites the button's label as well as disabling it, so restoring only the disabled state would leave it reading "Please wait…" for ever; both halves are restored. And the unlock now happens on every way out of the submit handler, including the two that previously returned early.
Cancel, to the owner's design
The microphone button keeps one meaning — start and stop listening. Cancel is a separate ×, so a user who changes their mind does not have to reason about which press means what. It appears whenever there is something to lose: typed text, a recording, voice notes, attached files, or a save in flight.
- Nothing recorded — the box clears immediately. No dialog for an empty decision.
- A recording exists — a small confirm appears inside the box, his instruction being "a pop up letting user know that keep draft or cancel means delete it, then user make decisions." Keep as draft turns the recording into a draft Blink straight away, reviewable in Drafts. Delete recording removes it, and the wording says the deletion cannot be undone — because it cannot: the audio file itself is removed, so restoring the draft from the Recycle Bin would return a titled note with no sound in it. Never mind closes the confirm and changes nothing.
Cancel does not reload the page, deliberately. A reload would throw away the recording still held in the browser — the very thing the user might have just chosen to keep — and would leave them with an unexplained draft. The box is reset field by field instead, which also means it can say what happened.
The microphone is now released
The page used to take hold of the microphone and never let go until the tab closed. That is why the recording indicator could stay lit with nothing recording, and why a second microphone elsewhere on the page could fail to start. Cancelling now releases the device properly, and the next recording re-acquires it. Browsers remember the permission, so there is no second prompt.
For developers
The cancel control lives in the page's own markup and is hidden with
visibility rather than being created by script, so the tests that fake the box's state
by setting classes still see it. It is positioned inside the text field rather than beside
it: measured on a 375 px screen, adding it as a sibling left 117 px of typing room out of
a 280 px row — a quarter of the field gone — against 158 px when placed inside. A test
pins that floor at 150 px so a future control cannot quietly eat it.
Two faults here were found only by driving a real browser, and neither was visible to tests that read source code: cancelling a recording that had already stopped left a flag set, which then swallowed the next recording's chip; and saving straight after a cancel was being redirected to the plans page because the test's seeded plan was inactive. This is the case for the browser rig, in one paragraph.
Scope. This change adds a way out. It does not change which recording engine runs, or how switching between Catch a Blink and Talk to OrbitPilot picks one — that is the next piece of work, and it is the one that carries behavioural risk.
One microphone, one owner (BL-454, 2026-09-10)
The owner's instruction. "You said we have three mics, which is totally wrong. We should have just one mic." And, when asked how far that should reach: "Some of them should be independent and separate. But their working model could be the same… do not forget our target is going toward microservices, so we cannot combine all items from different sections."
So this is one owner, two backends, and no shared file between sections — not one microphone object that every part of the platform depends on. The bug reporter's recorder, which he is happy with, is deliberately untouched.
What was actually wrong
The capture bar chose which recording method to start by looking at the Catch a Blink / Talk to OrbitPilot toggle at the moment the microphone button was pressed — and switching that toggle stopped nothing at all.
So a recording started in one mode was abandoned rather than ended when the user switched to the other. It kept running, and because the page is built to treat a pause in speech as thinking rather than stopping, it kept restarting itself. It also kept hold of the microphone. That single fact explains three separate complaints that looked unrelated:
- "The mic is freezing and I cannot turn it off." The button now belonged to the other method, so pressing it started a second one instead of stopping the first.
- "It is not transcribing." The abandoned session held the microphone, so the recorder that was supposed to be capturing audio received silence.
- "The OrbitPilot mic cannot be turned on." Same cause, different door — the abandoned session was still holding the device when the assistant's own microphone asked for it.
What changed
- The button asks who is listening, not which mode is showing. If anything is listening, that is what stops — whichever method it is. Only when nothing is listening does the page choose a method, and it stays chosen for the whole of that recording.
- The toggle is a destination. Switching between Catch a Blink and Talk to
OrbitPilot now finishes the current recording first and then changes the page. What
was already captured is kept — finishing is not the same as cancelling, which is the
×. - The recorder announces its own state. The bar used to flip its own recording indicator on each press, which was correct only while every start and stop came from a click. It was wrong every time the recorder stopped by itself — a refused microphone permission, the automatic stop on a long recording, a recording with no sound in it, a failed upload, or switching away from the tab. The indicator now follows what the recorder reports.
- Sending a question to OrbitPilot releases the microphone first. Previously the page handed the question over while still holding the device, which is why the assistant's own microphone would then refuse to start.
- Voice notes are hidden while talking to OrbitPilot, and the "this was spoken" marker no longer survives a mode switch — which is why a typed note could previously be filed as a voice note.
Why there are still two recording methods
Not an oversight, and not a compromise. Signed-in people get the full recorder, which saves the audio itself so it can be transcribed later and re-transcribed if the first attempt was poor. People who are not signed in cannot have that — there is nowhere to put the audio and no account to attach it to — so for them the page uses the browser's own speech-to-text, which produces words and keeps no sound. Forcing one method would mean either giving anonymous visitors full audio recording or taking voice off the home page for everyone. Both were rejected. What there is exactly one of is the thing that decides who is listening.
For developers
The owner lives in static/js/capture-box.js as micOwner: an
engine bound once when a session starts and never re-read from
mode, plus a monotonic sessionId so a late asynchronous
continuation can tell it is stale. Sessions are released by identity — the shared reference is
nulled before stop() is called, so the dying session's own onend
fails its guard and cannot restart it. That guard appears in all four handlers and is counted by a
test; it is the same machine the OrbitPilot widget runs (BL-447).
Phase reporting is one of-voice-phase CustomEvent dispatched at the top of
setRecorderButtonMode() in templates/blink/_wink_voice_js.html — the top,
because every branch below it returns early. The Blink form ignores the event. Device coordination
across surfaces is a two-line event convention rather than a shared runtime module, deliberately:
a microphone object that errortracking, blink, messaging and
orbitpilot all import is the weld the owner ruled out and that
docs/architecture/FUTURE-PROOFING.md §1/§4b forbids independently.