Ship CLDR keyword packs for nine widely used languages as plain bundle files and merge the ones
matching Locale.preferredLanguages into the catalog's keywords at load. English stays indexed for
every user and keeps ranking first. EmojiIndex now folds each entry's text once at load through
FuzzyMatch.Candidate, so a keystroke folds only the query.
Closes#890
A running app's ⌘K menu gets Force Quit Application below Quit, also
bound to ⌃⌥⇧Q on the selected row. It force terminates every instance,
so the app gets no chance to save or refuse. Quit Application is no
longer shown in red, and neither is the new row.
The calendar menu bar menu now lists the upcoming events under day
headings, below the actions for the current event. Each row shows the
time range and title next to a ring in its calendar's colour, filled
while the meeting is in progress. Clicking a row joins the meeting, or
opens the event in Calendar when it has no link.
Include Tomorrow's Events becomes Days to Show (Today, Today and
Tomorrow, Next 7 Days) under Menu Bar. It still sets how far ahead
Tinycast reads, so My Schedule and launcher search follow the same
days. My Schedule groups by the same day headings as the menu.
Closes#1228
* Add a details page for meetings
Show Details, in a meeting's ⌘K menu, opens a read-only page with the
meeting's time, location, attendees and their responses, and its
description. Location, notes and attendees are read for that one
occurrence when the page opens, so the agenda snapshot stays small.
HTML descriptions show as plain text.
Closes#1170
Co-authored-by: Sebastian K <github@skroll.me>
* Add meeting shortcuts and update related UI components
- Introduced new shortcuts for opening meetings in their respective apps (⌘O) and showing meeting details (⌘I).
- Updated the MeetingActionsMenu to handle new actions and shortcuts.
- Enhanced the Launcher and Schedule screens to display meetings above suggestions.
- Modified documentation to reflect changes in meeting handling and shortcuts.
---------
Co-authored-by: Sebastian K <github@skroll.me>
* Install extensions from GitHub; search only the Raycast Store
Search now queries the Raycast Store alone: no GitHub search, no merged
results, and no registries or store toggle. A new "Install from GitHub"
row builds one extension from a repository or folder link with the
chosen package manager, keeps only the build, and deletes the source and
dependencies with the install's workspace. Closing the panel cancels a
running build.
* Refactor ExtensionStorePanel layout and improve StoreRow details
* Check store extensions for updates when Settings opens
Record the store's commit for each store install, and an unknown version
for each Raycast import that the first check adopts. Opening Settings ›
Extensions looks each tracked extension up by handle and name and offers
an Update per row and an Update All above the list. Folder and GitHub
installs are never checked. Uninstall moves out of the expanded settings
to a trash icon on the row, with Update to its left.
* Stop a cancelled install's whole process group and release its timeout
* Add window management shortcut presets
Settings › Window Management can now fill in the stock window shortcuts of
Rectangle / Magnet or Spectacle: pick one from the pop-up and click Apply.
Rectangle adopted Magnet's scheme wholesale, so the two share one entry.
A preset only fills in its own commands; every other shortcut stays as it
is. It asks for confirmation only when it would replace a shortcut the user
set, and never takes a key held by another action. Bindings are stored as
physical key codes, as the original apps register them, so non-QWERTY
layouts land on the same keys. Apply stays disabled while the chosen preset
is already fully applied.
Closes#730
* Re-plan a preset after its confirmation
The confirmation dialog suspends apply, and a settings.json reload can
change window bindings meanwhile. Rebuild the plan from the live bindings
once the user confirms, and ask again if it would now replace a shortcut
the dialog didn't list.
SwiftUI resolves a text field's tint into a fixed caret colour when the
field takes focus and never refreshes it. The search field keeps focus
across hide and show, so after a Dark-to-Light switch the caret stayed
white and could not be seen. The panel now re-colours its field editor
with the dynamic textPrimary on every focus change.
Also carries a formatting pass over the AI chat, extension settings and
quick action sources and their tests.
* Render LaTeX math in AI chat and Quick AI replies
Replies now typeset inline \(...\) and $...$ and display \[...\] and
$$...$$ math natively, in STIX Two Math through its OpenType MATH table,
with no dependency. Each formula is one attachment character in the
reply's single text view, so selection stays continuous, find and
citations keep their offsets, and copying gives back the LaTeX source.
Prices like "$5 and $10", escaped dollars and code spans stay prose.
An equation still streaming in, or one outside the supported subset,
shows as its source; a display equation outside it shows as a LaTeX
code block. A formula wider than a narrow Quick AI column scales down.
* Hold back an equation until it finishes streaming
A display equation still arriving showed as its source in a left-aligned
paragraph, then jumped to the centre once its closing delimiter landed.
Now, while a reply streams, a display equation at the very end is a
muted placeholder where the equation will sit, and an inline \( or \[
still open at the end is withheld until it closes.
Only the tail of text still arriving is touched: the last segment of a
streaming chat reply, and a Summarize result while it runs. An opener
the stream has moved past, or one in a finished reply, still shows as
source. A lone $ is never held back, since it may be a price. Find
parses the same way, so its matches stay on the drawn words.
* Document LaTeX math in AI replies
* Keep an equation held back when the stream has just sent a newline
A reply streaming "$$\nx = ...\n" ends on an empty line, which the
reader took for a blank line inside the equation and so for proof the
opener was stray: the half-written equation showed as source until its
closing $$ arrived. Mid-stream, a blank line now counts only once text
follows it, for display equations and for an inline one at the end.
* Expose each equation to VoiceOver as an image
The attachment's accessibility element carried the LaTeX as its label
but reached assistive apps with the role AXUnknown. It is now an image,
so VoiceOver meets it as one element named by its source.
* Fix Frequently Used emoji layout and recency
* Keep the emoji selection on its emoji as Frequently Used changes
Remap the selection whenever Frequently Used is rewritten, by a use, a zoom or a
default density change, instead of only on zoom. Count the two-row cap over the
catalog, and keep one tally per glyph on load and import.
---------
Co-authored-by: abue-ammar <iabueammar@gmail.com>
Each snippet's Settings row records a shortcut that runs the same expansion
funnel as its launcher row. Typed text now clears held modifiers, so the
shortcut's own keys no longer ride along on every character.
Empty Trash always asked first, even for someone who had switched off
Finder's "Show warning before emptying the Trash". That overrode a
choice the user had already made, for work that Finder itself does. It
now asks only while that box is on, and runs without a dialog once it is
off. Every other confirmed action still asks.
Finder writes `WarnOnEmptyTrash` only once the box is changed, so an
absent key counts as on: a Mac that was never touched sees the dialog it
always did. The preference is read only when Empty Trash runs, never for
the other actions.
* Paste text history one entry at a time with Paste Sequentially
Paste Sequentially is a new clipboard command. Bound to a shortcut, each
press pastes the next older text entry into the app in front, without
the palette. Copy A, B and C, and three presses paste C, B and A.
The walk takes its entries' ids at the first press and never promotes,
so it cannot repeat or skip an entry, and an entry deleted mid-walk is
skipped rather than pasted. A copy made since the last press, or a
minute without one, starts it over from the newest entry. Past the
oldest entry it says so instead of wrapping.
Each press drains the poller before it writes, so a copy made inside
the 0.5s poll interval reaches history instead of being overwritten.
* Hold back a Paste Sequentially press that comes before the last paste lands
A press writes the next entry to the pasteboard at once and posts ⌘V
50ms later, and the target reads the pasteboard only when it handles
that ⌘V. A second press inside that gap swapped the pasteboard first,
so one entry was pasted twice and another skipped.
A press within 0.25s of the last paste is now dropped. It is not
queued, since a queue would replay a held shortcut as a burst of
pastes. A dropped press does not move the walk, so nothing is skipped.
* Paste images and files with Paste Sequentially, not only text
The walk now takes every entry in history, newest first, so a run of
copies that mixes text and images pastes all of them in order. Paster
already writes each kind, so the filter was the only thing in the way.
An image or file gone from disk writes nothing. The same press now
moves on to the next entry, where before it did nothing at all.
* Give zlib buffered stream constructors, so PNG-writing extensions run
* Replace an extension toast with the next one, so a finished task clears its spinner
* Show AI providers as a list beside the selected provider's detail
The Providers panel was one long form: every installed tool and API
connection stacked as rows, with status, quota and actions squeezed into
each. It now follows Mail's Accounts: the on-device model, the installed
commands and the API connections sit in a list, each with a one-line
state and, for installed tools, its switch. The selected provider shows
its status, account, usage, command path and models beside it, with a
filter once a list runs long. API connections are added from the list's
plus menu, removed with its minus, and still edited in the existing
connection editor.
* Choose which of a provider's models the model picker lists
A route like OpenCode offers hundreds of models, and every one of them
landed in the chat's model picker. Each provider's model list is now a
set of checkboxes, with Show All and Hide All above it. A provider
nobody has trimmed keeps listing everything, including models it adds
later; once trimmed, it lists only what was ticked. The default model
always stays listed, since the picker has to show what it holds, and a
removed connection forgets its choices. The choice is per Mac and stays
out of settings backups.
The provider switch moves from the list into the selected provider's
header, so it no longer sits on the list's blue selection, and model rows
drop their effort levels, which the chat's own picker already shows.
* Give Grok, Cursor and OpenCode their own marks
Grok borrowed X's mark, Cursor a pointer symbol and OpenCode a terminal
symbol. Each now has its own, in the Providers list and in the chat's
model picker. OpenCode's inner block is drawn with `opacity`: the asset
compiler drops `fill-opacity` without a warning, which filled the block
solid and turned the mark into a window with a title bar.
* Name ChatGPT's newer plans
Pro now comes in two sizes, which the account reports as "pro" and
"prolite" and ChatGPT sells as Pro 20x and Pro 5x. Business, Enterprise
and Edu each gained plan types that read as "Account".
* List a provider's models in a table
A Form realizes every row it holds, and OpenCode offers over four hundred
models, so its list stuttered while scrolling. The checkboxes now sit in
an NSTableView inside one Form row, which builds only the rows on screen.
* Switch the on-device model or an API connection off in place
Only an installed tool could be switched off; an API connection had to be
removed, key and all, to leave the model picker. The on-device model and
every API connection now have the same switch. A route that is off stays
configured but leaves every picker, the default model moves to a route
still on, and asking for it anyway is refused with a message. Removing a
connection forgets that it was off, and the choice stays out of backups.
* Launch an installed tool from a set path, with set variables
Tinycast finds each installed tool by asking a login shell, which is
right until a Mac has two copies or a shell it cannot read. A tool can
now carry a command path that replaces the lookup, and variables set for
that tool each time it starts. Nothing sets them yet; the next commit
adds the fields.
A path with nothing to run fails the check and the turn, never falling
back to the lookup, since that would hide the mistake the path was set to
fix. The reader's variables lie over the app's own, but never over one
Tinycast sets to keep a tool inside the chat. Names are stored in
settings and values in the login Keychain; neither travels in a backup,
which must not decide what this Mac launches. Only the tool that was
edited is checked again.
* Add an Advanced section to an installed tool's detail
The command path has a field and a Choose… button whose open panel shows
hidden folders, since `~/.local/bin` is one. Variables are rows of a name
and a hidden value, added and removed one at a time. A name Tinycast sets
itself says its value is not used, and one that is not a variable name
says why. Each field saves as it is left.
* Name a Claude model by its display name when the description has no version
Claude Code used to lead a model's description with its version, "Opus 5.5
· Best for everyday…", and Tinycast named the model from that. A newer
CLI puts the version in the display name and leaves only the blurb in the
description, so every model in the picker read "Claude Best for everyday,
complex tasks". The version is now taken from the description only when
it is there, and from the display name otherwise.
* End an edit in the Providers panel with a click outside the field
A field holds the keyboard until something else takes it, and blank form
space takes nothing. The panel now releases it the way every other pane
does, which is also what saves a field that is saved as it is left.
* Show Claude's account and Codex's command, and keep Advanced while a tool is off
Claude's detail now names the account it is signed in with and its plan,
the address hidden until clicked as Codex's is. Both come from the answer
that already lists Claude's models, so nothing more is asked of the CLI.
Codex's detail names the command it ran, which the other tools already
did, and the empty command path field shows it too.
A tool that is switched off keeps its Advanced section, so a wrong path
can be fixed before it is switched on.
* Give a provider's detail its own pages
A provider's status, its models and its advanced settings shared one
scrolling form, so OpenCode's four hundred models buried everything under
them. The detail now has Overview, Models and Advanced pages behind a
segmented control, as Mail's Accounts has. An API connection has the first
two, and the on-device model, with one page, shows no control. The chosen
page is kept from one provider to the next.
* Keep the accent colour on the providers panel's page control
An editor panel wraps its content in Liquid Glass, and a segmented control
drawn on glass goes grey and bordered. The divider beside the selected
page is always hidden, so in grey the control read as missing one. The
providers panel now draws its glass behind the content, which leaves the
control its accent colour; every other editor panel is unchanged.
* Keep the MCP editor's connection control from resizing
The system segmented control opens tight around its labels and widens the
first time its selection changes, so the HTTP and Command control grew
under the pointer and stayed that wide. The editor now uses the AppKit
control with each segment pinned to its label plus the inset the control
settles at, and reports its own size from those widths, so it opens at the
size it would have ended up.
* Say Automatic in an empty command path field
The empty field showed the path the lookup had found, which read as a
path already set, and repeated the Command row on Overview. It now says
Automatic; the found path is on Overview alone.
* Credit the three new marks, and take two from the sources already credited
Cursor's mark now comes from Simple Icons and Grok's from Lobe Icons, the
two sources NOTICE already names; both look as they did. OpenCode's is
drawn after the one in its own repository, since the libraries carry the
frame without the block inside it, and its licence travels with it.
* Document the Providers panel, its pages and a tool's overrides
The feature doc gains the rules that are now invariants: a route that is
off is off everywhere, a picker lists what was ticked, a set command path
wins or fails, and a reader's variable never replaces one Tinycast sets.
It describes the panel's list and pages, and says why three more AI keys
stay out of backups and settings.json. The UI doc records the panel's
sizes and the two things learned about controls in an editor panel. The
website explains the pages, the switch, and the Advanced page.
* Keep a tool's variables when the Keychain cannot be read
A failed read loaded the variables' names with blank values, and leaving
any field in Advanced then saved the blanks over the stored values. The
read now throws, the page says it could not read them and saves only the
command path, and a save whose check read fails is refused.
Apple documents no retention for show(relativeTo:of:preferredEdge:), so
the sheet is held here rather than left to an unstated behaviour -- my
own test showed it surviving, but a comment is not a contract. The share
doc comment is one line now.
* Snap floating notes to the top-right on title-bar double-click
* Keep Notes traffic lights aligned after title updates
* Keep Notes traffic lights aligned after relayout
`waitUntilExit()` spins the calling thread's run loop, and on a GCD thread it
can miss the exit and block forever. About 3% of an extension's `execFile`
calls never settled, each holding a thread and two pipes until quit.
`Process.runObservingExit()` sets the termination handler before `run()` and
returns a `ProcessExit` that waits on a semaphore instead. Every
`waitUntilExit()` call site now uses it.
* Add an opt-in settings.json mirror for preferences and window management
* Let Snippets and Notes use a chosen folder, from Settings or settings.json
* Keep a Notes or Snippets folder change from reusing the old folder's contents
* format fix
undici touches four things at module scope that the runtime refused or
lacked, so Apple Mail and MyIP threw before their first render.
Give diagnostics_channel real channels, make worker_threads'
markAsUncloneable family no-ops so undici's `|| fallback` guard is moot,
add crypto.getHashes for the digests the host computes, and polyfill
Event, EventTarget, MessageChannel and MessagePort beside TextEncoder.
tracingChannel and the rest of worker_threads still refuse on use.
JavaScriptCore settles WebAssembly.compile/instantiate from a run-loop
timer on the thread that owns the VM, and the runtime's queue never
spins one, so the promises stayed pending forever. sql.js loads that
way: Zotero's Search Database sat on Loading… with no error.
Route compile, instantiate and the streaming forms through the
synchronous Module and Instance constructors, and cover them in
ext-test with a module that must instantiate both ways and an invalid
one that must reject with CompileError.
Co-authored-by: Richard Demann <richard.demann@gmail.com>
* Find a fish user's CLIs through fish, and read the lookup's answer past rc-file output
* Say which shells the CLI lookup speaks, and where it looks after them
* Mark the CLI lookup's answer so logout-file output cannot replace it
---------
Co-authored-by: abue-ammar <iabueammar@gmail.com>
* feat: Implement Room Management feature with UI components for room selection and preview
- Added RoomPickerList and RoomPickerScreen for selecting and managing room windows.
- Introduced RoomPreviewController and RoomPreviewView for displaying room previews.
- Created RoomsList and RoomsScreen for switching between rooms and managing layouts.
- Documented the Rooms feature, including layout options and usage instructions.
* Drop Show All Windows from rooms
Parked windows still come home on quit, when Window Management turns off
and at launch after a crash; entering another room returns any parked
window whose app it hides. The command, its Actions row, the Settings
button and its search entry go, with the docs that named them.
* refactor: Improve code readability by formatting return statement and frame modifier
* Address review on Rooms and trim comments
- Remember Arrangement no longer duplicates members.
- A bad stored room is dropped alone; an unknown layout resets to Auto.
- Switching the feature off unhides only the apps rooms hid.
- Settings reaches RoomCoordinator through the environment.
- The preview honours Reduce Motion on hide and card transitions.
- Window Switcher disarms a stale release monitor when it opens.
- Remove comments that restate the code.
* Copy the text in a clipboard image with ⇧⌘T (#1030)
An image in clipboard history can carry text nothing could reach: both
the ⇧⌘T chord and the Actions-menu row now offer Copy Text on it.
Eligibility is ClipboardItem.offersTextExtraction — a captured image
blob, or an image file copied in Finder — never a text entry and never a
PDF, which stays a background-indexing capability. The action closes the
palette, stats the file so a vanished row raises the HUD its kind
already uses, then shows a "Reading text…" pill while ClipboardTextWorker
spawns the bundled helper: no Vision runs in the app process, nothing
reads the item_text table, and nothing depends on the text-search
switch. The extracted text lands on the pasteboard unmarked, so the
copy enters history like Copy Path, and the pill is replaced by its
outcome — Copied text, No text found, or Couldn't read the text.
The clipboard and palette-shortcut harnesses cover the eligibility
answer and the new chord, and every harness that compiles
ClipboardStore now compiles ClipboardFileKind beside it.
* Read Copy Text screenshots in whole lines, once each
OCR tiled a bitmap into overlapping 2048-pixel squares, which was
harmless for search but wrong for a copy: a Retina screenshot's lines
came back cut at the tile edge with their tails appended at the end,
and a tall phone screenshot repeated every line in the overlap band.
Recognition now runs on full-width strips, and each strip keeps only
the lines centred in its half of an overlap, so every line is read
once, whole and in order. The pixel budget is unchanged: a larger one
measured worse, since Vision downsamples a wide input itself.
Copy Text stats the source off the main actor after the progress pill
shows, a newer trigger cancels the helper an older one is waiting on,
and a result never overwrites a copy made while the helper ran.
---------
Co-authored-by: mch <omitted@email.com>
Co-authored-by: abue-ammar <iabueammar@gmail.com>
* Promote feature enable controls in Settings
* Reuse the shared feature toggle label
* Keep Search Scopes available when Applications is off
* Restore search highlight on launcher settings lists
* Draw Detail markdown images at their own size instead of capping them at 220pt
* Let a height-only size hint widen a Detail markdown image past its own width
* Keep the CLI stubs ahead of Homebrew's CLIs in the harnesses' login shell
The stub fixtures point ZDOTDIR at an empty folder so the user's rc files
cannot put a real CLI ahead of the stub. /etc/zprofile still runs, and its
path_helper moves /etc/paths.d entries, Homebrew's included, in front of the
stub folder, so on a Mac with codex or opencode in /opt/homebrew/bin the
locator's login-shell lookup found the real CLI and codex-turn-test and
installed-ai-test drove it instead of the stub. Each fixture's folder now
saves PATH in .zshenv and restores it in .zprofile, after path_helper.
* Run every located CLI with its own folder on PATH, so a Finder launch can read Codex's servers
An npm or Homebrew codex is a `#!/usr/bin/env node` script. Launched from
Finder or the Dock, Tinycast's PATH is /usr/bin:/bin:/usr/sbin:/sbin, and
the `codex mcp list --json` read that decides which of the reader's servers
to disable ran under that PATH unchanged: env found no node, the read
failed, and Codex refused to start with "MCP isolation check failed". The
app-server launch already put the executable's folder and Homebrew's in
front; the read did not. The installed-CLI status probes had the same gap.
ExecutableLocator.environment(running:adding:inherited:) is now the one
rule for what a located CLI runs under: its own folder, /opt/homebrew/bin
and /usr/local/bin ahead of the inherited PATH, NO_COLOR set, and added
variables never overriding either. The Codex listing and launch, every
InstalledAIProbe run and request, InstalledCLIProvider and
MCPStdioTransport use it in place of their own copies.
codex-turn-test covers it with the stub codex, itself `env node`, read
under Finder's PATH through both the listing and a status probe; both
checks fail against the previous environments.
* Say what a located CLI runs under, and why a Finder launch needs it
A Mac set to zh-Hans-US never read zh-Hans.lproj, the folder most
third-party apps ship for Simplified Chinese, so WeChat could not be
found by 微信. The language list tried the full tag, a region form and
the bare language, but never the language-script form.
A tag with a script now also reads that form, before the region form.
zh-Hant and sr-Latn gain the same. Tags without a script, every
English one included, give exactly the same list as before.
* Refactor separator spacing in PopoverMenu and ExtensionActionsPanel to use listInset for consistent layout
* Refactor separator heights to use device pixel precision across PopoverMenu and ExtensionActionsPanel
Calendar's "Show in launcher" hid the meeting entries and also removed
all five calendar commands from search. With meetings off the root,
My Schedule could no longer be found by typing, which left no way to
open the meetings from the launcher.
The switch now controls only the meeting entries. The commands follow
the Calendar switch alone. Custom Commands' switch already works this
way, and each command's checkbox still hides that command.
The switch and the launcher limit now only republish the meeting
entries. Before, each change also restarted the store and reloaded
EventKit.
With pins, the list opened on the first pin, so the clip just copied
was one ↓ per pin away. With no query, every reset now lands on the
newest clip and centres it with the pins in view above; a typed query
still lands on its first match.
Every reset goes through RootPaletteView.land(), which reads the
screen's landingSelection (0 unless a screen says otherwise), so the
handlers that fire in one update agree whatever their order. onAppear
lands too: the first show builds the view after prepare has run.
ScrollIntent gains .center. SelectionFollowing brings an unbuilt row in
by id, centres it once its frame is measured, then lets go.
* Find aliases and camelCase names by any word, and stop matching pane identifiers
An alias only matched from its start, so `dark` missed an alias of
`toggle light / dark`. An alias that is neither an exact nor a prefix hit
now ranks as one more alternate title.
camelCase was no word boundary, so `stack` scored OrbStack as a mid-word
hit and High hid it. A lowercase-to-uppercase hump in an ASCII name now
starts a word; `code` still stays mid-word in Xcode.
Settings panes indexed their bundle names, so `text` found Trackpad through
`TrackpadExtension`. A name the bundle's own language table replaces is no
longer indexed, and an overridden pane skips the walk entirely.
* Enhance search scoring and matching logic in launcher
- Added new test cases for camelCase and acronym handling in search scoring.
- Updated documentation to clarify word start conditions in search queries.
- Refactored search sensitivity defaults to improve user experience.
* Stream spawned child output to extensions as it arrives
* Keep a Detail's markdown image on screen while it updates
* Arm spawn timeouts at launch and keep streamed UTF-8 characters whole
* Emit spawn before a child's output and stream it in the runtime fixtures
* Keep SwiftUI's named keys out of the ASCII recovery
* Cover the shortcut caller path, not just the recovery
* Skip the caller-path cases when the layout has no trap to spring
* Keep the recovery's contract in Platform, without the test seam
* Stack a list detail's metadata under its markdown, as Raycast does
* Drop metadata separators from a list detail, as Raycast does
* Align a list detail's metadata label with the first line of its value
* Render markdown tables in extension Detail views
* Draw extension markdown tables as a bordered full-width grid
* Keep escaped pipes inside extension markdown table cells
* Split table cells after escaped backslashes and unescape pipes in code spans
* Require a hyphen in extension markdown table separator cells
* End extension markdown tables at a blank line
* Buffer extension markdown table rows until the table ends
CameraPreviewController kept one AVCaptureSession for the whole launch.
Cancel freed the panel's preview layer and stopped the session; the next
join restarted it under a new preview layer, which stayed black. The
preview session has no output of its own, and a reused session like that
feeds a new preview layer no frames after a restart. Open Camera only
escaped because its photo output keeps the reused session feeding.
stop() now drops the session, so every open builds its own. configure()
starts on the camera last switched to, unless it has been unplugged, so
Open Camera still reopens on the camera the user picked.
* Report Grok failures instead of Claude's missing response
Grok 1.0.40 still exits 0 from grok models while signed out, and an error result omits result and puts the cause in errors. The shared Claude decoder then showed Claude could not finish the response.
* Test signed-out Grok discovery