mirror of
https://github.com/abue-ammar/tinycast.git
synced 2026-10-02 08:14:38 +08:00
main
101
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4935e937b0 |
Install extensions from GitHub; search only the Raycast Store (#1262)
* 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 |
||
|
|
37daebac16 |
Add window management shortcut presets (#1261)
* 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. |
||
|
|
9f8bb1f738 | Show leading think blocks from OpenAI-compatible providers as reasoning (#1236) | ||
|
|
a008987d29 |
Render LaTeX math in AI chat and Quick AI replies (#1238)
* 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. |
||
|
|
22f7ea8e98 |
Let a snippet take its own global shortcut (#1229)
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. |
||
|
|
e14a3ba36e |
Paste history one entry at a time with Paste Sequentially (#1221)
* 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. |
||
|
|
6fc6aa1b90 |
Stop child processes from hanging their caller after they exit (#1188)
`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. |
||
|
|
1d7d22663f |
Add an opt-in settings.json mirror, and custom Snippets and Notes folders (#1186)
* 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 |
||
|
|
a300cfecd3 |
Walk into a project's windows with Rooms (#1155)
* 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. |
||
|
|
4533c15d1e |
Copy the text in a clipboard image with ⇧⌘T (#1030) (#1147)
* 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> |
||
|
|
f2a27d7d5a |
Keep SwiftUI's named keys out of the ASCII recovery (#1106)
* 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 |
||
|
|
75974f86fb |
Add an AI Chat window with a conversations sidebar (#1047)
* feat(ai): add an AI Chat window with a conversations sidebar A Raycast-style AI Chat window: saved conversations on the left, the open one on the right, and a composer that picks the model, reasoning effort and tools per chat. The palette's chat becomes Quick AI; ⌘J moves a Quick AI conversation into the window. - Each chat keeps its model, effort and tool scope; reopening it restores them - Reasoning shows as collapsible thinking blocks, one per stretch of thinking - Image paste and attachments for vision-capable models (Claude, Codex) - Model lists come from each CLI's own catalog, so new Claude models appear - Context gauge with a hover card: history, budget, files, tools, tokens - Choices render as buttons above the composer; a bare "choices" list counts - Sources list with citation numbers at the end of the citing sentence - Find in Chat (⌘F) steps word by word and marks each match in place - A reply's text is one selectable NSTextView, so a drag spans paragraphs, lists, code and tables - Chats are titled by their harness as soon as the first question is sent - Several Codex chats can answer at once, each on its own thread - Actions menu, pin, rename, copy and delete from the sidebar * Refactor AI chat components and enhance functionality - Updated `ChatTitle` to improve string sanitization. - Modified `InstalledAI` to enhance name extraction logic. - Renamed database tables from `conversation_meta` to `conversation_details` and `message_meta` to `message_details` for clarity. - Added export functionality for chat transcripts in `AIChatCoordinator`. - Improved UI elements in `AIChatDetailView` and `AIChatSidebarView` for better user experience. - Enhanced `AIChatWindowChrome` to streamline toolbar actions and improve navigation. - Updated keyboard shortcuts for new chat, settings, and other actions across various components. - Improved chat transcript rendering in `ChatMarkdownText` and `ChatTranscriptView`. - Enhanced `QuickAICoordinator` to support regeneration of responses. - Updated documentation to reflect changes in database structure and new features. * feat(ai): enhance AI chat sidebar and history functionality with new continue action * feat(ai): update AI chat window and sidebar dimensions for improved layout --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
e307213c23 |
Offer Tinycast's MCP servers on the Codex and Claude routes (#1081)
* Offer Tinycast's MCP servers on the Codex and Claude routes * Give Codex's local servers their own variable names, and run each once Codex forwards a variable only under the name it already has, so a local server read TC_MCP_<HANDLE>_<KEY> instead of its own key. A server with variables now starts through /bin/sh, which moves each value to the name the server reads and execs it; the script carries names, never values. While Codex or Claude is the chat model, Tinycast keeps no connection of its own to a local server, since the CLI starts its own copy. AppCore re-applies that whenever the chat model changes. * Name Codex's MCP variables by position, so no two secrets share one The derived TC_MCP_<HANDLE>_<KEY> upper-cased and flattened both halves, so github-x + TOKEN and github + X_TOKEN, token and TOKEN, a remote header and a local key, or two non-Latin handles of one length all met in one variable, and one server received another's secret. Each value now lives under TC_MCP_<server>_<key>, positions in the launch's own list, and a name that repeats anyway refuses the launch. A key the shell cannot export is no longer forwarded under a name its server never reads. * Ask before every Claude MCP call, whatever the reader's settings allow The armed Claude turn relied on the CLI's own permission system to raise can_use_tool, and the reader's settings could answer first: an allow rule for their own server of the same name, or a bypassPermissions default, ran the tool with Ask Each Chat never asked. The turn now pins --permission-mode default and passes --settings with a permissions.ask rule for every armed server, which outranks an allow rule from any source while leaving the rest of the reader's settings in force. * Give Tinycast's servers their own names on Codex, and disable all of yours -c sets single keys, so a Tinycast server named like one of the reader's own Codex servers merged into it: a local one inherited their env table, literal secrets included, their cwd and any per-tool approval_mode that skips consent, and a remote one over their stdio one made Codex refuse the whole config. Tinycast's servers now go by tinycast-<handle>, every one of the reader's is disabled with no exception, and a launch whose armed name the reader already uses is refused. Elicitations and tool-call items map back to a handle only through that prefix, so a question about any other server is declined without asking. * Refuse to start Codex when the reader's MCP servers cannot be kept out A failed or unparseable `codex mcp list --json` launched the app-server with nothing disabled, so every server in the reader's Codex config started inside the Tinycast thread: the boundary failed open. It now fails closed with an error that says why, and so does a reader's server whose name holds a dot or an equals sign, which `-c` splits and so cannot switch off. Names with spaces or non-Latin letters are addressable and stay as they were. * Launch the Codex app-server once for concurrent starts A status refresh racing a turn, or two quick sends, each passed the isRunning check, read the list and launched; the second overwrote the first's process and pipe, and when the first exited its handler tore down the live one and failed the turn. A launch is now one shared Task that every caller for the same list awaits, handshake included, a stop that lands while the list is read keeps the launch from starting after it, and an exit is acted on only when it is the current process's. * Cover the Codex relaunch on a changed server list Two turns with the same list run in one app-server; a third whose server carries a different secret, as a refreshed token would, starts a second one with the new value in its environment. * Ask about one CLI tool call at a time Each Codex elicitation and each Claude can_use_tool is answered on its own Task, and DialogController refuses a second dialog while one is up, so a call arriving during the first question was told the reader had declined it. AIToolServerSession now asks one question at a time, in arrival order; the next is decided only after the dialog before it closes, so it sees the grant that dialog made. Questions still waiting when their turn ends are cancelled rather than asked. * Offer credential-free HTTP servers to the CLIs, and lend tokens as Authorization MCPServer.toolServer dropped any HTTP server without a header value, so one that needs no credential worked on the API route and was silently missing on Codex and Claude. And a lent OAuth token went out under the stored header name, so a server switched to OAuth from X-Api-Key sent "X-Api-Key: Bearer <token>" and got a 401. Only an OAuth server with no session is left out now; a Header server with an empty value is offered with no header, which both encoders omit, and a lent token always goes as Authorization, as Tinycast's own transport sends it. * Delete a crashed turn's private files at the next launch A Claude turn's MCP configuration, which carries the servers' secrets, and Grok's prompt file were removed only when the turn ended, so a crash mid-turn left them in the workspace indefinitely. InstalledAIManager now removes any tinycast-mcp-*.json and tinycast-prompt-*.txt older than the launch when it starts. * Stop the Codex helper when a server it runs is withdrawn The helper re-reads its server list only on the next turn or after ten idle minutes, so switching MCP off, removing a server, setting it to Never Allow or signing out of it left that server's process, and any token lent to it, running inside the helper until then. MCPCoordinator now tells ChatGPTSubscriptionManager which servers are still offered, and a helper launched with any other stops if no turn is running. * Route a Claude tool name at its first separator ClaudeMCPLaunch.route split mcp__<handle>__<tool> at the last "__" on the premise that a handle may hold one. It cannot: MCPSlug emits only letters, digits and "-". A tool name can, so mcp__files__read__file routed to a handle "files__read", permit found no server, and the call was refused without a question. * Escape every control character in Codex's TOML strings quoted() escaped only backslash, quote, \n, \r and \t, matching each as a Swift Character, and CRLF is one Character, so it matched neither and went through raw; so did every other control character. Codex then refused the whole config. Each Unicode scalar is now considered, and everything below U+0020 and U+007F goes out as \uXXXX, which tomllib and the real codex both read back as the original. * Refresh an OAuth token before lending it when under ten minutes remain A token lent to Codex or Claude had only to last 60 seconds past the turn's start, since that is when Tinycast's own requests refresh, but the CLI holds it for the whole turn and cannot ask for another. Lending now refreshes within ten minutes of expiry; a token with no refresh token, or whose refresh cannot be served, is lent as it is. * Name a Codex consent question after its own call The runner named the tool in an elicitation from the latest mcpToolCall item started on that server, which is another call whenever two run at once. CodexElicitation now keeps _meta.tool_name, and the question uses it whenever Codex sends one. A stub turn with two calls started and asked about together pins both names, and that the second question waits for the first. * Answer a Claude control request that is not a tool question A control_request whose subtype Tinycast does not know, or a can_use_tool for a tool on none of its servers, decoded to nothing and was never answered, so the CLI waited on it for the rest of the turn. Each now gets the SDK's error control_response. * Create Claude's per-turn MCP file private from the start FileManager.createFile writes its data to a temporary file at the default 0644 and applies the 0600 attribute afterwards, so the file carrying the servers' secrets was briefly readable by other accounts; only the 0700 workspace stood in the way. It is now opened with O_CREAT | O_EXCL at 0600 and written through that descriptor. * Keep each comment the CLI routes added to one line The CLI-routes change added 32 runs of two or more comment lines and several over the 100-character cap. Each is now one line, and the reasoning they carried lives in docs/features/mcp.md: why the listing and the launch share their flags, why a check keeps the running list, why the Claude file is per turn, and what the consent channel is. That paragraph also stops overstating the --allowedTools fallback: an allow list denies nothing by itself, --permission-mode dontAsk does. * Say which servers a CLI route is not handed, and what argv can carry The MCP doc promised the same server list on every route and that no secret reaches argv. An OAuth server nobody is signed into is left out on Codex and Claude, and a credential typed into a server's URL is part of the URL, which Codex takes as a launch argument; both are now said. * Tell readers what is different about MCP on Codex The website said the servers on the CLI routes are the same as everywhere else and that only adding, removing or re-authorizing a server restarts Codex's helper. It now also says that an OAuth server nobody is signed into is left out, that moving between an @handle message and an unaddressed one restarts the helper too, that withdrawing a server stops the helper, and which environment variable names reach a server Codex starts. * Run Codex and Claude with no round cap on Unlimited The CLI routes took their cap from AIToolRounds.rawValue, which is -1 for Unlimited: Claude would have been passed --max-turns -1, and Codex interrupted at its first call saying it stopped after -1 rounds. AIToolServerSession.rounds is now optional and comes from toolRounds.limit. On Unlimited, Claude is given no --max-turns, since it has no cap without one, and Codex counts no calls, so only the model or Stop ends the turn. A step still stops both, and the sentence names it. A turn with no tool servers stays a separate case. Claude with nothing to call still passes --max-turns 1, and the sentence for a cap Claude reports now names the number it was actually given; a max-turns result under no cap says Claude could not finish the response instead of naming one Tinycast never set. Codex with no session keeps its cap of one call. * Let the two-call consent test accept either order of questions * Tell readers that Codex reads a server's resources without asking * Read every forwarded value before exporting any in the Codex MCP shim * Let a Codex status check join a turn's pending launch instead of relaunching without its servers * Refactor code for improved readability and maintainability - Adjusted formatting in various Swift files to enhance code clarity. - Updated MCP OAuth handling to improve error messaging and flow. - Enhanced UI components for better user experience in settings and chat transcripts. - Improved handling of asynchronous tasks in CodexAppServerClient and InstalledCLITurnRunner. - Refined documentation for AI features and MCP integration. --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
3463f33299 |
Add OAuth authentication for HTTP MCP servers (#1077)
* Add OAuth authentication for HTTP MCP servers * Escape a plus sign in the OAuth authorization URL * Drop an issuer's terminating slash before inserting its well-known path * Keep the issuer comment under the 100-character cap * Keep a saved server signed in when Test Connection tries an edited URL * Let a token refresh finish when the server editor closes or saves * Say why an MCP server was kept when Remove cannot delete its credentials * Cover supplied OAuth client credentials in the harness |
||
|
|
d93a09baac |
Rebuild root search ranking and add Suggestions (#1057)
* Rebuild root search ranking and add Suggestions Root search priced each match as a cell of a role-by-tier table plus a boost learned per submitted query, and indexed Spotlight's alternate names, which merge every language a bundle ships. On an English Mac `ll` and `sap` found Safari, `settings` ranked Tinycast's own command above System Settings, and loose subsequence hits crowded the list. Each field now gets an alignment score that rewards word starts, Search sensitivity decides how loose a hit may be and still show, and an ordered comparator settles two entries by exact hits, past search terms, match score, frecency and kind. Learning is one frecency record per entry: an open adds 100 to a score that halves every ten days. With the field empty, a Suggestions section offers fresh installs, what the user opens most and a few built-in commands; Show suggestions turns it off. The old learned table does not decode, so learned ranking starts empty. * Enhance fuzz-test with a seeded random number generator for reproducibility; update ExtensionCatalog and ExtensionManager to include installation date; refine AppIndex to track usage order and improve launcher documentation. |
||
|
|
c3a54c71ac |
Add Globe hotkey bindings (#1010)
* Add Globe hotkey bindings * Fix Globe hotkey monitoring and cancellation * Label hotkey permission warning for accessibility * Drop Input Monitoring from modifier-only hotkeys * Unify modifier-only bindings behind one trigger map --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
a27fd41638 |
Run an extension's menu bar command, and draw its item natively (#963)
* Prepare extension runtime for menu bar commands * Host Raycast menu bar commands with transient runtimes * Stabilize extension menu layout and lifecycle * Restore menu dismissal and release runtime temporaries * Preserve pending menu actions and explicit launches * Render menu actions before loading icons * Promote menu interactions and retry returning icons * Keep cached menu actions responsive during reload * Prepare extension menus before opening from bottom edge * Match native status menu spacing on every display * Release extension HTTP sessions and reuse private transport * Record extension HTTP retention investigation and validation * Keep background refreshes out of the foreground runtime Rebasing onto main brought in scheduled no-view refresh, which shares the one JSContext with the palette. Four ways that went wrong: - A refresh that finished before its waiter registered recorded a timeout, so a fast command backed its own schedule off as though it had failed. - Boot and bundle reads suspend. A foreground launch in that gap aborted the refresh, but the refresh then started inside the context that replaced it. - Disabling extensions left a running "Refresh Now" alive, and a queued one could still start afterwards. - A scheduled command could not launch a sibling without naming its extension, since ownership resolved only from the foreground session. ExtensionBackgroundSession now owns one run: the outcome is buffered rather than signalled, and the first writer wins, so neither an early finish nor a late callback can be lost or overwritten. A preempted run is cancelled rather than recorded, leaving its schedule where it found it, and every suspension point rechecks session identity before touching the runtime. Also drops the duplicate ExtensionLaunchType the merge left in ExtensionBootConfig. All 64 harnesses pass; Debug builds with no new warnings. Verified against the real OpenCodex Usage and Port Manager menu commands. * Let AppKit own the extension status menu Clicking a second extension menu bar item while one was open only closed the first: the button drove a manual popUp, whose modal tracking loop swallowed the click instead of handing it to the other item. Two native menus hand off, and ours did not. Attach the menu to the status item and let AppKit position and track it, which restores that handoff along with Escape, outside clicks and click-to-close. The manual anchor maths and its two spacing constants go with it, since AppKit places the menu itself. The menu is attached once at init rather than per snapshot, so the first click opens it before any render has arrived. * Key menu icons by value instead of scanning for them A menu rebuilds every row on each React commit, and each row searched two arrays for its icon and a third for the failures. Small menus make that cheap, but it is linear work on a path that runs several times a second while a menu is open. RenderValue and RenderNode gain Hashable, so the cache becomes a dictionary and the failure list a Set. Same behaviour, no per-row scans. Also restores the guard that a menu bar extra with no rows detaches its menu, which the switch to AppKit tracking had dropped: without it an extension rendering nothing would open an empty menu. * Cut every menu bar comment back to one line * Drop the menu memory investigation log and table the shortcut keys The benchmark file recorded how one HTTP retention bug was found, which the feature doc already states as a rule. No other investigation is kept this way, so it goes rather than starting a convention. The named-key switch becomes the table it always was. * Reuse the existing refresh, metadata and icon paths for menu bar commands The branch was written against an older main and grew its own copies of machinery main already ships. Menu-bar activation and the saved button now live on the command's own ExtensionCommandMetadata record, so ExtensionMenuBarStore and extension-menu-bars.json go away and the writes coalesce on the metadata store's debounce instead of hitting the disk on every React commit. Menu-bar refresh cadence comes from ExtensionRefreshPolicy.nextDue, which adds the failure backoff and the per-command phase the hand-rolled scan never had. ExtensionBackgroundSession and its rewrite of the manager's background internals are gone: main's continuation path does the same work, so the scheduler is main's again. Manifest intervals parse once through ExtensionRefreshPolicy.parse, which takes a floor and now rejects an amount that overflows to infinity. Menu-bar icons load through ExtensionImage.load rather than restating its four bitmap cases, which also fixes fileIcon to draw fitted like every other icon here. Settings reads the manager directly, mirroring the background-refresh row, so the coordinator no longer threads through four views. * Reach the menu bar toggle through the extension coordinator A feature action belongs on its coordinator, with AppCore only locating it. The Show in menu bar toggle read and wrote ExtensionManager directly, past the coordinator the enable switch two hundred lines above already goes through. |
||
|
|
d721ca3d44 |
Draw the oklch() swatches Color Picker states its colours in (#917)
Organize Colors builds each tile as a bare {color} swatch whose string comes
from getPreviewColor(), which formats in oklch() while the row label keeps the
user's hex preference. The extensions layer carried its own colour parser that
read #rrggbb and nothing else, so every swatch resolved to nil and fell through
to the icon placeholder: correct hex beside a grid of grey boxes.
ColorValue is already the one CSS parser, and ColorSpaces already held the
Oklab matrices one way round, so the inverse initialiser lands there and
parseFunctional gains an oklch case that reuses its own argument, percentage
and angle helpers. ExtensionImage now calls that parser and drops its hex
reader, which also gives extension tints rgb() and hsl() for free, and lets the
clipboard read back the oklch() it could already write.
|
||
|
|
00516759c4 |
Hand an app-picker preference to an extension as an Application (#907)
* Hand an app-picker preference to an extension as an Application
An appPicker preference reached JS as the bare path it is stored as, but
Raycast hands one over as an Application — { name, path, bundleId }. Project
Manager reads vscodeApp?.name.replace(...) at module scope, so the command
threw before its first render.
ExtensionPreferenceValue gained an application case whose JSON form is that
object, resolved from the bundle at the path — the shape
ExtensionHostBridge.describe(application:) already sends for getApplications().
ExtensionPreferenceSchema.runtimeValue converts app pickers only, and returns
nothing for an empty path, so an unset picker arrives as an absent key and the
extension's own optional chain short-circuits instead of throwing. Storage is
untouched: the picker still writes a path, so Settings, backups and the
required-preference check read what they always did.
* Cover an app-picker preference surviving a storage reload
Also applies swift-format to the new ext-test checks.
---------
Co-authored-by: abue-ammar <iabueammar@gmail.com>
|
||
|
|
ca3da2ee4e |
Add the WebSocket and mDNS support Home Assistant needs (#901)
* Run extensions that speak WebSocket A bundled `ws` handshakes through `http.request` and wants a raw socket back, which the fetch-backed shim had none of. Sockets are `URLSessionWebSocketTask` now, and the upgrade path hands `ws` one that re-frames RFC 6455 both ways. Home Assistant is the reference case. * Resolve a .local host without joining a multicast group Home Assistant's default `homeassistant.local` is resolved by the extension itself with multicast-dns, which died on `dgram.createSocket`. `dgram` now answers an address query out of `getaddrinfo` — mDNSResponder handles `.local` — so nothing multicasts and no entitlement is needed. * Harden the WebSocket and dgram shims A rejected host call poisoned the send queue, so every later send and the close after it rejected too; the queue now recovers while the caller still sees the failure. A ping is answered by the peer through `URLSessionWebSocketTask`, not by a pong the adapter invents. An upgrade no listener claims destroys the socket, the way Node does, so the native task goes with it. `dgram` answers address questions only. And a socket id that is not a whole number falls back instead of trapping. |
||
|
|
eb32d8fe4a |
Collect a custom command's arguments inline in root search (#903)
* Collect a custom command's arguments inline in root search A custom command that declares arguments now shows its fields beside the search field when its row is selected, the way a quicklink already does, instead of replacing the screen with a one-at-a-time form. The form, `PaletteMode.customCommandArguments` and `CustomCommandArgumentSession` are gone. Handled the way Raycast handles script-command arguments: - At most three. `CustomCommandArgument.sanitized` enforces it on every path in, so a stored command carrying more keeps its first three; the editor's Add stops there. - ↵ with a required field empty focuses that field rather than running. - A hotkey, favorite slot or Run Again with values missing opens root search onto that one row, seeded with its name, first empty field focused. The row is listed even when hidden from the launcher, since its shortcut still has to be answered. Fields are keyed by position (`$1`–`$3`), not name, because two arguments may share a name. `runCustomCommand(id:values:)` stays the one funnel and `positionalValues(from:)` restores `$n` order, so values still reach zsh as positional parameters and never as command text. The field strip moves from Quicklinks to `DesignSystem/InlineArgumentFields` so both features draw the same control rather than a copy of it. * Carry a clicked row's inline values into its launch Clicking a launcher row launched it without the values typed into its inline fields, so a custom command reopened its prompt empty and a quicklink lost what was typed. The click now goes through `argumentValues(for:)`, as ↵ already did. Also names what `argumentKey`'s second half is for each feature. |
||
|
|
141a7a918e | Let raycast-width and raycast-height shrink Detail markdown images (#891) | ||
|
|
9a9d8d2a9b |
Render Markdown in Notes (#880)
* Parse Markdown notes into ranged lines * Add the Notes Markdown edit planner NoteMarkdownEditing turns a NoteEditAction into a single NoteEditPlan: one range, one replacement, one resulting selection. Every list, indent and inline-style gesture resolves through it, so each is one undo step and none of it needs a text view to test. NoteRevealPolicy decides which lines show their raw syntax for a given selection, widened to whole fenced blocks. * Render Markdown in the Notes editor * Edit Markdown notes with shortcuts, tasks and links * Add a Render Markdown setting to Notes * Document Markdown rendering in Notes * Add code block and quote edit planning Extends NoteMarkdownEditing with fenced code and block quote toggles. NoteFormatting is the report the caret's context produces, decided by the same span and line rules the toggles use, so a lit button always undoes. * Add code block and quote shortcuts to Notes * Add a formatting bar to Notes * Simplify the Notes Markdown engine Scan inline spans on demand instead of storing them on every parsed line: only the styler, the editing rules and NoteTitle read them, and each reads one line at a time. NoteMarkdown keeps its UTF-16 units and vends inlines(of:) on request. Adopt NSTextStorageDelegate in NoteMarkdownRenderer, which reports the edited range and length delta for every mutation including undo and marked text. That replaces a full shadow copy of the document and the prefix and suffix diff run against it on every edit, selection change and read. Emit the trailing empty line as a real zero-length line when the source ends in a terminator, so the caret after a final newline sits on a line like any other. Five restatements of that special case go away. Delete NoteTask, superseded by NoteMarkdownParser and no longer referenced. At 100,000 characters typing drops from 6.8 to 5.6 ms at the end of the note, 5.2 to 4.0 in the middle and 2.8 to 1.6 at the start. * Move the Notes formatting bar to the trailing edge Mirror the band under the editor: the character count leads, the formatting capsule trails. The expand transition anchors trailing so the buttons grow out of the round button leftwards, and the tooltip alignments swap with it. Anchor the heading menu to the heading button's own frame, reported by the laid-out view, rather than re-deriving the capsule's geometry in AppKit. The old anchor guessed from the window edge and a capsule height composed from two tokens, which put the menu under the wrong end of the bar once the capsule moved. * Keep indented rules literal and renumber wide markers A four-space indent already keeps a heading and a quote literal, but the rule check ran before that guard, so ` ---` drew a horizontal rule and hid its own text. An ordered marker's stored number drops leading zeros, so `007.` was read as one digit. Continuing that list took the second zero for its delimiter and renumbering rewrote only the first digit. Both now take the digit run from the source. Also corrects two doc lines the formatting bar's move left behind. * Drop Carbon from the Notes editor's chords NoteTextView only needed HIToolbox for the digit key codes that keep the list and heading chords working on a non-US layout. Those are seven constants, so it states them itself. Carbon stays where it earns its place: the global hotkey registration and the TIS input-source APIs. |
||
|
|
91d8b9c20b |
Define Words (#848)
* Add native dictionary define fallback * Make dictionary lookup a core launcher command * feat(dictionary): add dictionary functionality with lookup and display - Introduced DictionaryEntry model to parse and store dictionary entries. - Implemented DictionaryProvider to fetch definitions from Dictionary Services. - Created DictionaryCoordinator to manage dictionary-related actions. - Developed DictionaryScreen to display definitions in the palette. - Added fallback command for "define" to trigger dictionary lookups. - Updated UI components to integrate dictionary features, including copy and open actions. - Enhanced documentation to cover new dictionary functionality and usage. * feat(dictionary): enhance dictionary command and fallback functionality * Render Define Word entries as a structured dictionary page Read the record's XHTML through Dictionary Services' record calls, resolved with dlsym so a macOS without them falls back to the public plain text. DictionaryMarkup turns the span classes into headword, part of speech, numbered senses, notes and sections, and DictionarySession looks terms up off the main actor. --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
e25bd13bc0 |
Add search to action menus (#864)
* Add search to action menus * Fix action menu keyboard handling * Refactor symbol name resolution to simplify handling of dark mode and improve code clarity * Close the palette when a menu action opens a window An action ran before its menu closed, so a window it opened took key while the menu's callbacks were still live and `menuOpen` was still mirrored true a cycle later. Both dismissal guards then bailed and the palette stayed on screen behind Settings, About and Support. Close the menu first, and state `menuOpen` in `open`/`closeMenus` rather than mirroring it from `onChange`, so the window delegate reads it within the same turn. Also tidies the header menu symbol initializer's formatting. * Fix action menu presentation * Refactor menu height calculations for improved clarity and consistency * Refactor menu animation durations for improved responsiveness --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
92a5d15bce |
Add interactive Markdown task lists to Notes (#823)
* feat: Add interactive Markdown checkboxes to notes * fix: Add spacing between note tasks * fix: Update note tasks incrementally |
||
|
|
4bc84e2ab8 |
Add custom window sizes (#819)
* Add custom window sizes (#734) User-defined window commands: a name, a width and height in points or percent, and a 3x3 position, applied to the focused window on its own display. Listed with the window commands, bindable to a global shortcut, undone by Restore, and carried in settings backups. * Address review on custom window sizes Fold imported names without a locale, matching the store's own duplicate check. Reach the coordinator through the environment instead of AppCore, and let it decide between add and update. Drop formatting-only edits to unrelated files that slipped into the first commit. * Drop unrelated formatting edits from the custom sizes branch |
||
|
|
f51f1c5786 |
Keep SemVer prerelease tags in installed CLI versions (#811)
`opencode --version` prints `opencode2 v0.0.0-beta-19271`, and the row showed `Version 0.0.0`. The version match now includes the SemVer prerelease and build suffixes. |
||
|
|
d4e788fb58 |
Search and run Apple Shortcuts from the launcher (#756)
Shortcuts from the Shortcuts app become their own launcher section, read through Apple's /usr/bin/shortcuts tool on every launcher open and run headless with `shortcuts run <uuid>`. Each row carries an alias, a global hotkey and a hide checkbox, like any launcher item. The feature ships off behind one switch in Settings > Apple Shortcuts. AppleShortcutCoordinator.run(id:) is the single funnel for rows and hotkeys. A successful, non-empty read sweeps the hotkey, alias, visibility, favorite and ranking of any shortcut no longer listed; a failed or empty read frees nothing. LauncherItemsSection's table half is now LauncherItemsList, shared with the new pane, and ToolRunner accepts a nil timeout. |
||
|
|
423fcfac8f |
Enhance and polish the Emoji and Symbols picker (#733)
* Polish the emoji and symbol picker * Split the emoji observers out of the palette's state chain * Give each header menu its own width and simplify emoji pins and zoom - Replace fitted menu widths with a stated width per header menu - Route emoji zoom chords through the panel's command shortcut path - Count pin positions over the pins the catalog can show - Move PinnedEmojiStore into its own file - Restore SwiftUI menu symbols and leave the extension chevron untouched --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
dd281ff975 | Run extension commands from raycast:// and tinycast:// deeplinks (#674) | ||
|
|
b7822817a9 |
Let palette screens answer their own shortcuts (#678)
* Let palette screens answer their own shortcuts RootPaletteView had grown back to 1,513 lines, and much of that was feature code: ten key handlers that cast `screen` to a concrete type, the AI model menus, and views other files use. - `PaletteShortcut` recognises each row chord (⌘⌫, ⌃X, ⇧⌘C, ⌘Y, ⇧⌘F, ⌘R, …) and carries its compact-bar and open-menu guards; a screen acts on it through `PaletteScreen.perform(_:at:)`. One handler replaces the ten, and ⌘. and ⌘1…⌘0 go through the same call. - The AI model and reasoning menus move to `AIModelMenu`, next to the rest of AI. - `ArmedHover`, `EmptyResults`, `PaletteBackground` and `CompactFavoritesRow` get their own files. - The rule for what saving an AI connection does to its Keychain key moves out of the view into `AIConnectionKeyPolicy`, and the connection editor sheet gets its own file. No behaviour change. RootPaletteView is 1,204 lines; AISettingsView is 640. * Refactor AppSettings to improve readability of palettePositions assignment |
||
|
|
47fbedd546 |
Show test progress and timings, and stop harnesses from hanging the suite (#644)
run-tests.sh now numbers each result, prints each harness's run and compile time, names what is still running during a quiet stretch, kills a harness that passes TINYCAST_TEST_TIMEOUT (default 300s), and ends on a clear PASSED or FAILED line. custom-command-test hung forever in a real terminal: an interactive zsh found the inherited controlling terminal and was stopped by SIGTTOU. The harness now calls setsid(), matching the app, which has no terminal. fuzz-test runs its 100k-query property loop across cores over queries drawn in one seeded sequence, cutting it from ~25s to ~1.4s. clipboard-worker-test is removed: it compiled fixtures with xcrun at run time and hung in 9 of 40 runs, stuck in Process.waitUntilExit after the helper had exited. |
||
|
|
79144fc5aa | Improve emoji keyword matching and search ranking (#627) | ||
|
|
9102d32da5 |
Play media the moment Quick Look opens it, and stop one from stretching the panel (#622)
* Play media the moment Quick Look opens it, and stop one from stretching the panel File Search's ⌘Y overlay now starts a movie or a track as it appears. `FileSearchSurface` carries one `autoplays` parameter; the preview pane leaves it off, because arrow-keying a list must not start a movie, while opening Quick Look on one is the ask itself. The clipboard's media preview asked for a fixed 260 pt whatever the pane had. With the Information block's 175 pt under it the column wanted 435 pt of a 359 pt content area, and the overflow pushed the bottom bar out and the panel taller than it is anywhere else. `clipboardMediaHeight` is now the cap it should always have been, so the player shrinks the way the image and text previews beside it already do. * Add keyboard focus handling and related tests for media playback * Refactor drag handling for empty text fields in the palette search field |
||
|
|
7caa286ed0 |
Turn on the hardened runtime, and teach the updater the identity it will switch to (#620)
* Turn on the hardened runtime, and teach the updater the identity it will switch to Notarization needs two things Tinycast doesn't have: the hardened runtime, and an Apple Developer ID signature. This does the first and prepares for the second. The updater compares signatures before it installs, byte-for-byte against the leaf the running app carries. A Developer ID leaf is a different certificate, so switching identities outright would make every installed copy reject every future update. `BundleSignature` therefore learns the Developer ID requirement now, while releases are still signed with `Tinycast Self-Signed` — the code that trusts the new identity has to reach people before the first build carrying it does. The requirement pins the team, not the certificate, so a renewal strands nobody. It omits the `notarized` keyword on purpose: that resolves a ticket through syspolicyd or the network, and the updater verifies inside a cache directory Gatekeeper has never assessed, so an offline Mac would refuse a bundle the chain already proves is ours. Hardened runtime needs two entitlements. JavaScriptCore compiles every extension command, and without `allow-jit` it falls back to the interpreter. Without `automation.apple-events` every Apple event is refused with -1743 and no prompt, which silently kills Get Info, the Finder selection extensions read, and the System Events-driven system actions. Nothing else is required: the only dlopen is Apple's own IOBluetooth, so library validation stays on. The flag is not part of the designated requirement, so no Accessibility grant is lost here. `project.yml` keeps signing with `Tinycast Self-Signed`, so nothing changes for a contributor building locally. `Scripts/verify-signature.sh` asserts what notarization will check — the runtime flag on the app and on the embedded helper, an intact nested seal, and no get-task-allow. Both release jobs run it before packaging, because a nested binary missing the runtime flag is the most common notarization rejection there is. * Keep the hardened runtime out of Debug, where library validation refuses the debug dylib Hardened runtime turns on library validation, and a Debug build links `Tinycast Dev.debug.dylib`. The self-signed identity carries no Team ID, so the loader sees a team mismatch and aborts at launch — every local Debug build died with a DYLD "Library missing" termination. Notarization only ever sees Release, so the flag belongs in that config alone. |
||
|
|
5d8dbb77ab |
Give Search Files a preview pane, a type filter and an Actions menu worth opening (#612)
* feat: Enhance file search functionality with type filtering and Quick Look support - Introduced FileSearchFilter to allow users to filter search results by type (All Types, Folders, Documents, Images, Audio, Video, Archives). - Updated FileSearchScreen to display Recently Used files when the search query is empty. - Implemented Quick Look functionality within the file search panel, allowing users to preview files without leaving the search interface. - Enhanced the UI to include a preview pane alongside search results, displaying relevant file information. - Added keyboard shortcuts for file actions (copy, paste, trash) and Quick Look toggle. - Improved the handling of empty states and error messages during file searches. - Updated documentation to reflect new features and usage instructions. * Read one attribute per Spotlight result, not four Opening Search Files sat for half a second before its Recently Used rows appeared, and a broad query took the best part of a second. Neither was Spotlight: `MDQueryExecute` returns in 15–75 ms. It was `MDItemCopyAttribute`, which costs about half a millisecond per attribute per result — reading the content type, the invisible flag and both date stamps over the ~430 candidates a recents query matches was 430 ms of the 550. `kMDItemPath` is the exception: `MDQuery` hands it back from its own cache, so a thousand of them read in 2.8 ms. Everything else a row needs — is it a folder, is it hidden, is it an application — now comes from one `resourceValues` stat, taken only for the candidates the ignore list did not already drop. Two hundred URLs stat in 13 ms where two hundred metadata fetches cost 200 ms. Recents no longer date every candidate either. Spotlight sorts on one attribute, so the service runs one sorted query per stamp and merges their heads; only the first twenty rows of each list can reach the merged one, and only those are dated. The sort attribute has to be named in `MDQueryCreate` — set afterwards through `MDQuerySetSortOrder` it is ignored, which is why the first version had to sort locally. The blank screen also skips the typing debounce, having no next keystroke to coalesce with. Measured on the developer home, release-optimized: recents 41 ms on a repeat against ~550 ms, and the five benchmark queries 54–107 ms against 192–831 ms. Building the expressions moved with the queries into the service, where the policy that shapes them already is; the session now owns only when a search runs. Move to Trash takes ⌃X, the chord the clipboard's delete already uses, and Paste File moves up beside Copy File in the Actions menu. * Let the preview play, in the pane as well as the overlay The overlay's dismissing tap gesture was laid over the whole card, preview included, so the click that should have hit a movie's play button closed the overlay instead — the transport drew, took the press and never saw it. Only the margin around the card dismisses now; anything over the preview belongs to the preview. The pane beside the list shows the file itself for the same reason it was worth having Quick Look at all: a still says nothing a movie or a long document needs said. `QuickLookSurface` moves out of the overlay into its own file and mounts in both, and the pane's copy waits 180 ms for the selection to hold, so arrow-keying a list of two hundred rows opens no preview it is about to drop. The thumbnail stands in until then. The live view is torn down whenever the palette is ordered out, the overlay covers it, or a folder is selected — one key over all three, so no `onChange` races the task, and never two preview extensions running for one file. * Give the preview its own player, 16:9, and an Escape AVKit cannot eat Three things the preview pane still got wrong. A movie never played. `QLPreviewView` draws a first frame and hands out a transport, but inside a non-activating panel it does not play, so movies and audio now go to an `AVPlayerView` of File Search's own. It is a copy of the shape the clipboard's preview uses rather than a share of it: that pane is a different surface with its own sizing and its own lifetime, and forty lines of teardown is the cheaper trade against coupling the two. The stage is 16:9 and sized before the block beneath it. Without the layout priority the aspect ratio fits itself into whatever height the Information rows left over, which is backwards — it shrank the preview to 328×173 instead of filling the pane's width at 436×234. The rows now scroll in what is left, through the plain scroll view the clipboard's preview uses, since a thin scrollbar over a document is chrome on chrome. Escape did not close Quick Look once a player was in it: a focused `AVPlayerView` answers the key window's Escape before SwiftUI's handler ever sees it. `PalettePanel.sendEvent` is the one place ahead of the responder chain, so the panel now owns that press while the overlay is up, and `PaletteEscapeAction` goes back to the shape it had. The Close chip is a button now too, for the pointer. * feat: Add FileSearchFileView for unified file preview handling in search results * feat: Replace FileSearchFileView with FileSearchSurface for unified file handling in search results * Hand the preview another file rather than building it a new one Clicking a row went blank before it went live. The stage gated the whole surface on a flag that flipped false on every selection, so each move dismantled the `QLPreviewView`, closed it, showed `Color.clear`, then built a fresh one — a teardown and a rebuild to show the next file. The surface now outlives the selection: only the settled URL changes, so a move hands the same view another item. The settle drops to 80 ms, which still coalesces a held arrow key and no longer reads as a wait on a click. Measured against the real QuickLook machinery: the first load in a process is ~130 ms and every load after it ~10 ms, so what a click waits for is the settle, not the preview. Text goes back to QuickLook with it. Drawing it here fixed the wrong thing — its scroll view is configured exactly like the palette's own, overlay style and autohiding, and QuickLook renders a document better than a monospaced `Text` in a `ScrollView` does. * Say in the feature doc what the preview surface now does * Answer a click on the press, and drop the timer in front of the preview The second a click took was SwiftUI waiting: `.onTapGesture(count: 2)` cannot deliver the single tap until the system's double-click interval has passed without a second press, so the selection — and the preview that follows it — sat still for that whole window. The clipboard's rows never felt this because they select in `mouseDown`. File Search now does the same, through an `onRowClick` catcher beside the right-click one already in `DesignSystem`: select on the press, open when the press is the second. The settle goes with it, and the state it needed. It was guarding a cost that is not there: measured against the real QuickLook machinery inside a panel shaped like the palette's — borderless, floating, non-activating, never key — handing a live `QLPreviewView` another file paints in about 8 ms, and the first load in a process in about 130 ms. Debouncing 8 ms of work bought nothing and spent 80. What is left is a surface mounted while it should be on screen and unmounted when it should not, which is also the whole of its teardown: no duration, no flag, no task. |
||
|
|
2a93f66ed8 |
Reach a window or a menu bar item from the launcher (#602) (#603)
Adds a Navigation feature: one settings pane, one switch, two commands that move you somewhere rather than changing something. Switch Windows sweeps every regular app's standard windows over AX, minimized ones included, orders them most-recently-used and raises the chosen one. Recency comes from a single CGWindowListCopyWindowInfo call for per-app front rank — public API, no Screen Recording grant and no long-lived activation observer. Search Menu Bar Items is today's Search Menu Items, moved out of Settings > Commands into the new pane and renamed. Its raw id is unchanged, so recorded shortcuts, aliases and visibility keys survive. It also gains a Disabled Applications list: MenuSearchTarget.classify answers .excluded before the menu-bar test, so an excluded app starts no walk at all rather than having a read menu filtered afterwards. The exclusion list is the Clipboard section lifted into a shared DisabledApplicationsSection, and AppPickerPopover moves to Features/Launcher/Settings/ where its three consumers already pointed. |
||
|
|
e2e3bf6113 |
Drag a clipboard entry out into another app (#596)
* Spell out the Array conversion in the OCR search tail Left open, the type checker reads .prefix as the Sequence overload, types the result as PrefixSequence and stops resolving the whole + chain. The Swift 6.2.1 toolchain in the Command Line Tools rejects the line outright, which takes clipboard-test, clipboard-search-test and every other harness that compiles the store down with it. Behaviour is unchanged. * Drag an image or file out of clipboard history An image or file row becomes a drag source for the file it already is, so reaching another app costs one gesture instead of Reveal in Finder plus a second drag. The drag is AppKit rather than SwiftUI's onDrag for one reason: imagesDir sits on the boot volume, where a file-URL drop defaults to a move, and a move carries the blob out of the history and strands its row. Only an NSDraggingSource can answer sourceOperationMaskFor, so ClipDragView answers .copy everywhere. The handle claims mouse-down the way WindowDragHandle does, since the hosting view eats the click first, and forwards anything under 4pt of slop as the click it was. Text rows are untouched: dragURL is nil for them, so no overlay is installed and their gestures stay as they were. * Drag a link and plain text out too, not only a file A text row had no payload and so no overlay, which left every entry that is not an image or a file undraggable. dragPayload now answers for all three: the file URL for anything on disk, and for a text row the classification the store already derives. A link writes two pasteboard types from one item. A browser reads public.url, a text field reads the string, and neither has to settle for the other's flavour. A bare domain gets https:// so the URL is one a browser accepts, which is the same assumption the address bar makes. The drag preview is now the row as drawn, since a text row has no thumbnail to fall back on. * Fix the drag preview, and move the payload off the store Four things the review caught. The row snapshot never drew anything. SwiftUI renders into layers, so cacheDisplay hands back a transparent bitmap and the drag carried no image at all. Previews are drawn per payload now: the cached tile for a file, a rounded text tile for a link or a copy. The dragging frame is sized to that image and centred on the cursor, and a refused drop animates back, so a drag that achieved nothing says so. dragPayload touched no store state and pushed ClipboardStore past 1000 lines. It is a computed property on ClipboardItem now, in its own Model file beside the ClipDragPayload it returns, which is where textForm and colorValue already live. The bespoke bare-domain helper is gone. QuicklinkDestination.detect already parses schemes, network shares and deeplinks. textForm stays the one answer to whether an entry is a link, so the drag and the type filter cannot disagree; the detector only builds the URL. A vanished file is reported rather than dragged out as a dead path, which is what Reveal and Open already do. The payload resolves on mouse-down instead of on every row render, so the stat costs one call per drag. Also deletes the row's onTapGesture and double-tap gesture. The overlay claims mouse-down on every row, so both were unreachable duplicates of the closures the handle already calls. * Rewrite the drag docs against the code that shipped The section still described dragURL on the store, a nil payload for text and the onTapGesture the overlay replaced, all three of which went in the last two commits. It now covers what is there: dragPayload on the item, the mouse-down resolution and its stat, the QuicklinkDestination reuse, and why previews are drawn rather than snapshotted. * Let a right click through, and drop the deprecated lockFocus Three things from the review. The drag overlay sat above the actions catcher and answered every event it was offered, right-mouse included. NSView forwards an unhandled rightMouseDown up the superview chain, never to a sibling, so the catcher underneath was unreachable and the row's actions menu silently stopped opening. ClipDragView now declines right events in hitTest, the mirror of what RightClickCatcher already does with the left button, so neither overlay can claim what the other needs. Verified against a real event loop: the right click lands on the catcher and the left one still starts the drag. lockFocus and unlockFocus are deprecated, and the SDK names NSImage(size:flipped:drawingHandler:) as the replacement. The text tile draws through that instead. The comments were stacked two and three lines deep. Each is one line now, and the ordering invariant the overlay depends on is written down in the feature doc rather than argued in the file. --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
9ad7f376ce |
Scale the palette with an Interface Size setting (#597)
Tinycast rendered every surface at one fixed size, which reads small on a large display and fine on a laptop. General ▸ Appearance now carries an Interface Size control — Default, Large, Larger — that uniformly zooms the palette and the surfaces that float with it, at 1.0 / 1.1 / 1.2. The setting reaches the palette, the ⌘K menu, the extension list panel, Quick Actions, the snippet prompt, dialogs and HUDs. Settings, Onboarding, Support, Update, About and Notes never scale: a zoom there only breaks their layout. `InterfaceMetrics` stores a scale and nothing else, deriving every value from the `Theme` literal, so `Theme` stays the one place a number is written down. It reaches views through an `@Entry` environment key defaulting to `.standard`, which is why `BarButton`, `KeyCapChip`, `PopoverMenu` and the rest render unscaled in Settings without being forked — the scope is the injection, not the component. An AppKit site reads `settings.interfaceSize.metrics` where it computes its frame, so no second owner of the value exists. Two things in this are not obvious from the diff. A scaled font is rebuilt from that style's own `NSFontDescriptor` at the scaled point size, never reconstructed as `.system(size:weight:)` from a written-out weight table. On macOS `Font.headline` resolves to `.SFNS-Bold` and `Font.caption2` to `.SFNS-Medium`, so a table lightens both the moment the user leaves the default size. The same helper yields the `NSFont` twins the caret width and the AI chips are measured against, which have to move in lock-step with what SwiftUI renders or the caret detaches from the text. And the palette's environment is pushed by a `ViewModifier` rather than a stored `.environment(_:_:)` value. `PaletteWindowController` builds the hosted tree once and reuses the panel, so a stored value would have frozen at build time and the setting would never have taken effect — not even on the next summon. A length measured against the screen does not scale; a length measured against our own content does. So `hairline`, `paletteTopMarginFraction`, `paletteSnapDistance`, `paletteMinimumVisible`, the drop-guide dashes, `hudEdgeOffset` and every row *count* stay on `Theme`. Scaling rounds to whole points once, at the leaf accessor, and a derived token composes already-scaled parts rather than scaling the derived result, so an AppKit frame can never disagree with the SwiftUI view inside it by a point. A size change re-enters through `AppCore.track` → `applyInterfaceSize()`, which drops the cached anchor and re-resolves it — one rule, the summon's. An untouched palette re-centres at the new width; a dragged one keeps its stored top-left unless the wider bar no longer leaves `paletteMinimumVisible` on any display, in which case it falls home. `PalettePanel`'s stale `750, 475` literal is gone; it reads the tokens. `ExtensionFormMetrics` becomes a struct taking a scale, staying inside `Features/Extensions/` and Foundation-only. `EdgeDissolve` derives its bands from the metrics and resolves to the same 86 and 80 it draws today. |
||
|
|
69aacc188b |
Expand snippets inside a Note (#592)
Snippet keywords did nothing in a Note, and the reason was the destination rather than the delivery: `TextInjector` resolved its target from `NSWorkspace.frontmostApplication`, but every Tinycast panel is non-activating, so with the Note key the frontmost app is still the one behind it. The expansion was being posted as events at that app. The destination is now an `InjectionTarget`, resolved from `NSApp.keyWindow` first, and one of our own editors is written in process — undoable in the editor's own UndoManager, with no Accessibility grant, pasteboard lease, app activation or event posting. A key window of ours that is not an `InjectableTextView` resolves to no target at all, so a keyword typed in the palette's search field no longer expands into whatever sits behind it either. The tap is a `headInsertEventTap`, so it fires before AppKit hands the keystroke to our own view: the first look at the text storage is always one character stale, and `.pending` only covers a document shorter than the keyword. This tier therefore leads with one convergence interval before it inspects, then polls the shared budget — in practice the keyword has landed after a single 5 ms pass. |
||
|
|
992c3dcdfc |
Search Menu Items for the frontmost app (#587)
* Add Search Menu Items for the frontmost app Fuzzy-searches the frontmost application's menu bar from the palette and activates the chosen item, via the Search Menu Items launcher command or its bindable (unbound by default) global hotkey. The bar is walked once per show, off-main, into plain MenuTreeNode values: 20 levels deep, 4,000 items, 200 direct leaves per submenu, 1-second budget, every cap truncating silently. Attribute reads are batched per leaf and only enabled, visible, pressable leaves become rows, each with its full menu path and shortcut glyph (Shift/Option/ Control bits decoded live, unrenderable keys degrade to path alone, private-use scalars mapped to arrow and page glyphs). Filtering reuses the launcher's fuzzy tiers over the frozen snapshot, ranked once per query change and capped at 200 rows. Activation re-resolves the row by path, re-checks it, and presses via AXPress after dismissing the palette and reactivating the frozen target; failures report through the dialog controller. Accessibility gates show and activate with an Open Settings recovery. No new AppEntry.Kind, no visibility gate, no frecency learning, and hiding the palette resets the session. Invariants and internals are documented in docs/features/menu-search.md. Originally submitted as #586. Rebased onto main, formatted, and documented in a feature doc while folding in a small dedupe of the shortcut glyph ordering. Co-authored-by: abue-ammar <iabueammar@gmail.com> * Drop derivative framing from comments and docs Removes the wording that described Tinycast's own design as derived from another launcher: "a port of", "-style", "as Raycast does", "exactly as in Raycast", "Raycast's own shape/form". Tinycast's surfaces are its own, and the comments now say what they do and why rather than what they were measured against. Every reference where Raycast names something Tinycast actually reads, parses, fetches or must stay API-compatible with is left exactly as it was: the extension API and runtime, raycast:// URLs, raycast-* colour keywords, the .rayconfig importer and RAYCFG3 container, script-command headers, store endpoints, on-disk install layout and accepted token spellings. Those name a real dependency, and deleting them would delete the reason that code exists. The measured comparisons in docs/features/uninstall.md stay too; they state where Tinycast deliberately differs. Comments and prose only; no behaviour changes. Two stacked-comment and three line-length violations are fixed in passing. --------- Co-authored-by: joaogsleite <joaogsleite@gmail.com> |
||
|
|
4cc3385632 |
Cycle halves across displays (#563)
* Cycle halves across displays Adds a Cycling picker to Window Management, matching Raycast's own setting: None, Cycle ½ ⅓ and ⅔, or Cycle displays. Under Cycle displays every display contributes two half-slots to one strip, ordered left-to-right. Left and Top walk it backwards, Right and Bottom forwards, both wrapping, so one shortcut sweeps the whole desktop in one direction. A single display makes the mode a quiet no-op, the same call Next Display already makes. A private Half type in the engine carries the (axis, edge) pair, so a slot's edge decides which side a third hugs and the four halves stop being four hand-written fraction cases. WindowActionMemory now takes a cycleLength rather than a Bool: a length of 1 covers both cycling off and a command that never cycles, so it no longer reads the catalog. Closes #552 * Default the cycling picker to None, not the size cycle `windowManagementCycleOnRepeat` defaulted to false, so a repeat press re-applied the same half on every install that never opened the setting. Renaming the key to `windowManagementCycleMode` means nobody holds it on first launch after the update, and a `.sizes` fallback would have turned size cycling on for that whole population — a visible behaviour change for users who never asked for one. `.off` keeps the shipped default. Choosing a mode stays a deliberate act, which is also what makes `Cycle displays` discoverable rather than something a window starts doing on its own. Brings the website docs along: the Settings table row, the reference table and the Cycling section all still described the old checkbox. |
||
|
|
f80db5187f |
Custom quick actions (#555)
* Split QuickAction into the shipped four and the user's own QuickAction becomes a sum over BuiltInQuickAction and a CustomQuickAction record, so every surface downstream keeps taking one type. Custom actions are name, SF Symbol and instructions, stored in their own file-backed CustomQuickActionStore under Application Support, and never in a backup, like the instruction overrides they resemble. The untrusted-input boundary is prepended to a custom prompt and cannot be edited away: an override on a built-in replaces it by design, but a whole prompt written by the reader has nothing else standing between the model and the text it is handed. * Give Quick Actions their own launcher kind, section and shortcut AppEntry.Kind.quickAction is the new section; the four built-ins move out of Commands into it, keeping their CommandIDs so no shortcut or preference key changes. A custom action reaches the launcher through the same kind and binds a chord through HotKeyAction.quickAction(id:). allowsHotKey now gates a command on the kind its entry actually reports, so both halves of the feature answer to one switch rather than two. * Own the custom action store from AppCore, and run its actions The store loads before hotKeys.start so a binding whose action was deleted while Tinycast wasn't running is pruned. Deleting confirms through DialogController and unwinds the shortcut, favorite, alias, visibility and ranking the row held, only once the record itself is gone. * Add, edit and remove custom actions in the Quick Actions pane Custom rows sit below the shipped four in one Actions list, each with the same pencil, recorder, Replace/Preview picker and launcher checkbox. A plus in the section header opens a blank editor. * Register the new sources and cover custom actions in the harnesses * Update the Quick Actions and launcher docs for custom actions The quick-actions doc said there were four actions and that a fifth was one case in QuickAction. Both stopped being true. It now covers the sum type, the record and its store, why Replace/Preview lives on the record, why a custom prompt cannot drop the untrusted-input boundary, why the pane draws its own alias field, and why nothing is saved until it is on disk. The launcher doc gains the shared Quick Actions kind, the quick-action entry ids, the new hotkey action, why a Quick Action command goes through allowsHotKey ungated, and the note that Enable Commands no longer stops the shipped four. * Widen the Settings window for the Quick Actions alias field Every Quick Actions row now draws an AliasField, which put the widest row's trailing controls at roughly 430pt and left about 35pt of slack at 860. 900 gives back the room a long custom action name needs. --------- Co-authored-by: overflowy <overflowy@riseup.net> |
||
|
|
ddbf87b3f5 |
Add opt-in OCR search for clipboard images and PDFs (#522)
* Add opt-in OCR search for clipboard images and PDFs * Fix clipboard OCR scheduling, retries and retained memory * Refactor async extraction calls and improve error handling in clipboard tests * Tighten clipboard OCR wiring, release guards and docs The helper's read loop and `waitUntilExit` both block, and they ran inline on the cooperative pool, so a stuck helper could hold a pool thread for the whole 60-second deadline. They now run on their own `DispatchQueue`, which is what `ShellCommandRunner` already does and says why. `AppCore` no longer keeps a transition task to wait for the indexer's teardown before starting another. The indexer's own `defer` already reschedules after a cancel-while-enabled, and `start`, `stop` and that `defer` are all MainActor-isolated with no suspension between them, so the wait bought nothing. One indexer now lives for the app's life, and `clipboard-text-test` covers the un-waited stop-then-start that relies on it — breaking the reschedule fails the harness. Both release jobs asserted architecture slices on `Contents/MacOS` alone, which a bundled helper now sits beside. A thin helper inside a universal build is the quiet form of the bug that guard exists to prevent: the app still boots on Intel, `posix_spawn` fails with `EBADARCH`, the indexer burns three retries per item into private OSLog, and clipboard OCR simply never answers. Both jobs check both binaries. In the store, `insertExtractedText` hand-rolled a prepare/finalize the store already has a helper for, so it folds into `setExtractedText`. The 200-row result cap appeared in four places and is now named, and an `OFFSET 999` that silently duplicated `memoryWindow` derives from it. `extractionGeneration` and `textSearchEnabled` are `@ObservationIgnored` — no view reads either — which leaves `searchRevision` as the single observed search signal, and it now says why it is read for its own sake. `reinsert` lost its doc comment and the note about why the array ops are separate while it was being rewritten; both are back, and the new one records that a delete there would take the row's recognized text with it. `docs/performance/` is deleted: nothing referenced it, `main` has no such tree, and per-run measurement dumps belong in a PR rather than the repo. The feature and testing docs lose the PR-report prose that came with them, and the claim that re-enabling waits for cancellation, which is no longer how it works. --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
e3019fffe5 |
Render an extension's search-bar dropdown, and report its opening choice (#515)
`List.Dropdown` and `Grid.Dropdown` reached `ExtensionScreen.searchBarAccessory`
and were read by nobody, so a command that filters its rows from the dropdown
drew an empty list — the Visual Studio Code extension's Search Recent Projects
holds `useState<EntryType | null>(null)` and its predicate rejects everything
until `onChange` fires. Raycast reports the opening choice itself, so two
things were missing, not one: the control, and the first dispatch.
The dropdown becomes an `OpenMenu` case, the same shape the clipboard type
filter and the two AI menus already have. Arrows, ↵, Escape, the click-away
catcher and the ⌘K suppression all come from the one menu path, so no second
key handler exists to disagree with it, and `ExtensionShortcutKeys` is gated on
`menuOpen` for free — an extension's own single-key shortcuts can no longer
fire while its filter list is up.
Swift owns the selection, because the runtime keeps `makeSearchDropdown`
hook-free so an extension may call `List.Dropdown({…})` directly:
`accessoryValues` keys the pick by render-node id, so a pushed screen and the
one under it keep their own. A `value` prop makes the dropdown controlled —
the extension holds it, nothing is seeded and nothing is reported back — which
is what removes any need to remember what was last dispatched.
`storeValue` parks the pick in `ExtensionStorage.accessoryValues`: host UI
state, outside the `LocalStorage` namespace JavaScript reads, and dropped with
the extension on uninstall. `Store` now decodes section by section, because
adding a non-optional field to a file read with `try?` would have reset every
installed extension's store on first launch, losing its `LocalStorage` and its
saved preferences alike.
Everything extension-shaped stays in `Features/Extensions/`, including the
list's own 240pt width — a form picker's 360 belongs to the field it sits
under, not to a header chip. The palette learns only that the running command
may offer a header filter; `PaletteFilterAction` carries the ⌘P decision as a
pure type beside `PaletteEscapeAction` and `PaletteTabAction`, so the one hunk
that touches an existing path is covered by a harness rather than argued.
`extensionCommandScreen` gates on the mode before the cast. Reaching it costs
the running mode's own list build, and the header reads it on every render, so
an unguarded cast would have charged the launcher a second `LauncherScreen` per
keystroke to answer a question only an extension can ever say yes to.
Closes #511
|
||
|
|
f5502036ae |
Bound clipboard URL decoding for large file selections (#513)
* Bound clipboard URL decoding by the capture limit * Simplify the bounded reader to a single pass The batched reader is replaced by one loop that filters while it decodes, and `urls(on:)` becomes that reader with no limit, so the rule that a modern file URL suppresses the legacy fallback is written once rather than twice. Head to head the two are a wash: batching is ~6% faster once files are accepted and ~7% slower on a rejected prefix, both ~70x better than the base, and the difference is 0.05ms on a 0.5s poll. macOS synthesizes `public.file-url` items for every `NSFilenamesPboardType` write, so the raw legacy fixture tested a branch the system never reaches; it and its runtime class swizzling are dropped. The limit boundaries, the predicate call counts and modern/legacy precedence are kept, and an uncapped-reader control guards the attachment path against the delegation. * Give every harness under Tests/ editor flags Five files in Tests/ had no .compile entry, so every symbol in them was unresolved in an editor. Two separate gaps caused it. The four hand-compiled benchmarks never reach run-tests.sh, which is also what emits editor flags. `run index <name> <source...>` registers one for --index without queueing it, so they earn flags and the suite still reports 62. A helper compiled into another harness, ext-list-key-test.swift, was never claimed either: an entry claimed only its own harness. Claiming everything under Tests/ fixes it and stays safe, since the app compiles nothing there. * Refactor JSON construction and improve code formatting in tests --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
2364cd1164 |
Background refresh for no-view extension commands (#495)
* Refresh no-view extension commands in the background on their manifest interval * Keep command metadata out of the extension data file Drawing a launcher row read every command's metadata, and that metadata lived in the same file as the extension's LocalStorage and Cache, so publishing the launcher entries faulted every installed extension's whole store in synchronously on the main actor and held it there. Give the metadata its own small file, outside extension-data so the cleanup sweep doesn't read it as one extension's own. ExtensionStorage returns to what it was, which retires the tolerant decoder it had grown for the key that is no longer there. The store is Observable, so the manual metadataRevision counter goes with it. A cancelled tick no longer records itself as a timeout: cancelling the loop preempts the run the way a manual launch does, and only an elapsed timeout counts as a failure. The Settings toggle now gates on the parsed interval rather than the raw string, so an unparseable one gets no switch it can never honour, and a failure is cut to its headline before it reaches AppEntry rather than at render. * Say what the extensions-stay-inside rule actually forbids "Never shared with another feature" reads as a ban on any other feature rendering an extension-owned view, which LauncherScreen has done with ExtensionArgumentsAccessory since extensions landed. The rule is about where a view is owned, not which file renders it: the danger is an extension's shape reaching DesignSystem or Theme and forcing a change on a palette surface, not a launcher row embedding a box it never looks inside. --------- Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
1cce6ff750 |
Reduce Quick Actions diff matrix memory (#503)
The LCS matrix is the largest allocation a Quick Actions diff makes: at the 4,000-token cap it is 4,001 x 4,001 cells, and an Int cell spends eight bytes holding a number that never exceeds 4,000. Store the lengths in UInt16 instead, which drops peak process RSS for a capped diff from about 141 MB to 42 MB. No LCS length can exceed maxTokens, so the narrower cell cannot overflow; text-diff-test asserts that relationship and the exact chunks at the cap, one token under it and one token over. Tokenization, recurrence, traceback, tie-breaking, coalescing and the over-cap fallback are unchanged. Co-authored-by: abue-ammar <iabueammar@gmail.com> |
||
|
|
e3b9f43eb8 |
Make Escape walk back through the screens it opened (#504)
* feat: Implement Escape Key Behavior settings and navigation enhancements - Added EscapeKeyBehavior enum to define actions for the Escape key in the palette. - Updated AIChatCoordinator to use palette.replace() for chat navigation. - Modified SettingsBackup to include escapeKeyBehavior property. - Enhanced PaletteState to manage back navigation and screen states. - Updated PaletteCoordinator to handle navigation logic for existing screens. - Improved RootPaletteView to reflect new Escape key behavior and navigation logic. - Added tests for palette navigation to ensure correct behavior of back steps and screen states. - Updated documentation to reflect changes in Escape key functionality and navigation. Co-authored-by: notsoshu <319949436+notsoshu@users.noreply.github.com> * feat: Add CommandEscapeTap to handle ⌘⎋ key chord and update tests for navigation and escape behavior Co-authored-by: notsoshu <319949436+notsoshu@users.noreply.github.com> * feat: Add hover effect and back navigation button to enhance user experience in the palette --------- Co-authored-by: notsoshu <319949436+notsoshu@users.noreply.github.com> |