-
fix(x): one hover companion — delete the summoned ask bar, no flash, no dead summons (#878)
发布于
2026-08-20 12:19:23 +00:00 - fix(x): hover companion — no flash of the old bar, no dead summons, one flow for every entry point
The ⌥⇧Space hover mode (quick access) had three real bugs:
- The first summon created the companion window and show()ed it while
the page was still loading: index.html painted a theme slab, then the
renderer's default summoned layout (the old text bar), then the
Skipper. Re-summons flashed the summoned layout for a frame too (the
hidden window keeps it, and show() fired before the re-render). - startHoverCall held its in-flight guard across the sticky screen
share; getDisplayMedia hangs forever when Screen Recording is denied,
so after the first summon every later chord was a silent no-op. - QuickAskBar called useMemo after conditional early returns — a pill ⇄
card switch (camera toggle) would throw React's hook-count error and
blank the window.
Fixes:
- Reveal protocol: quick-ask:mode carries a seq, the renderer acks
quickAsk:modeApplied two frames after committing that role, and main
orders the window in at opacity 0 and only makes it opaque / focuses on
the ack. Fold pushes first and shrinks on the ack. The renderer paints
nothing until its role is known; index.html gives #quick-ask and
#screen-pointer a transparent background from the very first paint. - startScreenShare is time-boxed (10s) and fails cleanly; the hover guard
is released right after startCall and the share is fire-and-forget; a
session that fails to start falls back to the text card. - ONE hover flow: chord, tray, tuck handle, toast "Try it", composer call
button and the "Share screen" preset all end in startHoverCall(). A
summon with no (or a still-loading) app window recreates it hidden and
re-relays on a quickAsk:appReady handshake; closing the app window
mid-call unpins the companion. Call-state cache survives fullscreen ⇄
popout (camera calls return as the pill) and is cleared by an explicit
idle push at call end. Hooks hoisted above the early returns. - [companion] mode-transition breadcrumbs in main; VIDEO_MODE.md updated.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
- fix(x): a summon right after app start waits for the voice probe instead of answering with the text card
The remaining "old quick access comes up instead of hover mode" case:
voiceAvailable/ttsAvailable start false and are filled by an async probe
(voice:getConfig + oauth:getState) at mount. A chord in the first seconds
after reopening the app read that not-yet-resolved false as "no voice
configured" and answered with quickAsk:show — the old text card, with no
Skipper ever arriving.Reproduced deterministically by delaying the probe: main logged
hidden → summoned (text card (fresh window)). With this change the same
run logshidden → pinned (call surface (summoned)).- App: mirror the probe result into refs (written inside the probe, so a
waiter doesn't depend on a re-render), keep the in-flight promise, and
have startHoverCall await it (capped at 4s, starting one if nothing has
probed yet) before deciding. The in-flight guard now covers that wait,
and a call started meanwhile just re-asserts its floating surface. A
genuinely unconfigured voice still falls back to the text card. - Bar: the tuck handle is optimistic until its own probe answers — the app
window owns the authoritative check and its own fallback, so an early
click relays instead of dying as a dimmed no-op.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
- fix(x): delete the summoned ask bar — the hover companion is the only surface
The old Spotlight-style quick-ask bar is gone, not just avoided. It was
only ever a fallback surface, and every "hover mode is glitchy" report was
that bar appearing where the Skipper belonged. With one role left there is
no second layout to flash, race, or get stuck in.Removed:
- main: the
summonedmode, its geometry (positionSummoned, FRAME_*,
BOTTOM_MARGIN), showSummonedCard/showQuickAsk/hideQuickAsk, the
blur-to-dismiss handler, and the trigger enum.toggleQuickAsk()now has
exactly one outcome: fold/unfold the Skipper's text when it's up, else
relay a summon. An unanswered relay is re-sent once by a watchdog and
then logged — never answered with a different surface. - IPC: quickAsk:show / quickAsk:hide / quickAsk:stop + quick-ask:stop /
quickAsk:state + quick-ask:state / quickAsk:setOptions +
quick-ask:set-options / quickAsk:optionsState + quick-ask:options-state /
quick-ask:summoned. CompanionMode is now 'hidden' | 'pinned'. - renderer: the bar layout, its answer panel, local dictation and the
⌥⇧Space hold-to-talk chord, the voice-out and share-without-a-call
toggles, dismiss/stage-click-to-dismiss, and the tuck handle's
start-a-call role. The window paints NOTHING unless it is pinned. - App: quickAskActiveRef / quickAskOptionsRef / quickAskStartedAtRef /
speakTurnRef and the answer-mirror effect. Speech now follows the
question's modality alone.
A summon that can't become a session (voice unconfigured, or the engine
failed to start) is explained in the APP window — brought to the front
with a toast that opens Settings — instead of silently becoming a
different floating thing.Verified on the dev build: earliest-possible summon after an app start
lands as the Skipper (no other layout at any frame), fold/unfold/end/
re-summon cycle clean, hidden window renders 0 bytes, and the no-voice
path shows the toast with the companion staying hidden.Co-Authored-By: Claude Fable 5 noreply@anthropic.com
- docs(x): retire the last references to the deleted ask bar
Comment- and copy-only follow-up to
5eeb56c9: several comments still
described the summoned bar's status line, voice toggle, destination chip
and geometry, and two user-facing strings still called the feature "Quick
Ask". Nothing behavioural.Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Co-authored-by: Claude Fable 5 noreply@anthropic.com
下载附件