Skip to content

Style Reference

This page is a living reference for all available documentation components and styling. It is not part of the public documentation structure.


The following examples are temporary and exist solely to verify styling across all documentation elements.

Configure your activation hotkey in ~/.config/torchsnap/settings.json. The default is Option + Space.

Use the gadget-sdk crate’s prelude module to import all common types: use gadget_sdk::prelude::*;. The SearchResult struct requires a title: String and an action: Action.

Press Cmd + K to open the command palette, Esc to dismiss, or to execute.

Permission Description Risk Level
clipboard.read Read clipboard contents Low
clipboard.write Write to clipboard Low
http.fetch Make outbound HTTP requests Medium
fs.read Read files from specified paths Medium
fs.write Write files to specified paths High
shell.execute Run shell commands on the host Critical

Torchsnap’s gadget system is designed around the principle of least privilege. Every host capability requires explicit opt-in through the manifest, and users are prompted before granting sensitive permissions.

Unordered:

  • Catalog mode: fuzzy match against a finite, pre-built list
    • App launcher uses this for installed applications
    • Clipboard history uses this for recent entries
  • Query mode: every keystroke is forwarded to the gadget
  • Prefix-routed mode: a single character prefix routes exclusively
    • = for calculator
    • ! for bangs

Ordered:

  1. Create a new Rust project with cargo init --lib
  2. Add gadget-sdk as a dependency
  3. Implement the Gadget trait
  4. Write your gadget.toml manifest
  5. Build for wasm32-wasip2
  6. Package with torchsnap gadget pack
Stable Experimental Deprecated Default
  1. Install the WASM target

    Terminal window
    rustup target add wasm32-wasip2
  2. Create a new gadget project

    Terminal window
    cargo init --lib my-gadget
    cd my-gadget
    cargo add gadget-sdk
  3. Implement the Gadget trait

    src/lib.rs
    use gadget_sdk::prelude::*;
    pub struct MyGadget;
    impl Gadget for MyGadget {
    fn search(&self, query: &str) -> Vec<SearchResult> {
    vec![]
    }
    }
  4. Build and package

    Terminal window
    cargo build --target wasm32-wasip2 --release
    torchsnap gadget pack --manifest gadget.toml
Terminal window
brew install torchsnap

Catalog Search

Fuzzy-match against a finite, pre-built list of items. Best for app launchers and clipboard history.

Query Search

Every keystroke is forwarded to the gadget’s search function. Best for calculators and web searches.

Prefix Routing

A single character prefix routes input exclusively to one gadget. Best for bangs and quick commands.

Background Tasks

Gadgets can declare cron-scheduled background tasks for periodic data refreshes.

  • Directorymy-gadget/
    • Directorysrc/
      • lib.rs
    • Cargo.toml
    • gadget.toml
    • README.md
Advanced: Custom icon resolution

Gadgets can provide custom icons by including them in the .torchsnap archive. Icons are resolved in the following order:

  1. Inline data: URI in the SearchResult.icon field
  2. Named icon from the gadget’s bundled asset directory
  3. System icon lookup by application bundle ID
  4. Default fallback icon

The following examples are temporary and exist solely to verify syntax highlighting across languages.

src/lib.rs
use gadget_sdk::prelude::*;
#[derive(Debug, Clone)]
pub struct Calculator {
history: Vec<String>,
}
impl Gadget for Calculator {
fn search(&self, query: &str) -> Vec<SearchResult> {
let result = match evaluate(query) {
Ok(value) => value,
Err(_) => return vec![],
};
vec![SearchResult {
title: format!("{result}"),
subtitle: Some(format!("{query} = {result}")),
icon: Icon::named("calculator"),
action: Action::copy(result.to_string()),
}]
}
}
fn evaluate(expr: &str) -> anyhow::Result<f64> {
// Recursive descent parser for basic arithmetic
let tokens: Vec<char> = expr.chars().filter(|c| !c.is_whitespace()).collect();
let (result, _) = parse_expression(&tokens, 0)?;
Ok(result)
}
src/launcher/search.ts
interface SearchResult {
title: string;
subtitle?: string;
icon: string;
score: number;
}
async function queryGadgets(
input: string,
gadgets: Map<string, GadgetHandle>,
): Promise<SearchResult[]> {
const results = await Promise.all(
[...gadgets.values()].map(async (gadget) => {
try {
return await gadget.search(input);
} catch (err) {
console.error(`Gadget ${gadget.id} failed:`, err);
return [];
}
}),
);
return results
.flat()
.sort((a, b) => b.score - a.score)
.slice(0, 20);
}
export type { SearchResult };
gadget.toml
[gadget]
id = "com.example.calculator"
name = "Calculator"
version = "0.1.0"
description = "Evaluate math expressions inline"
[search]
mode = "query"
debounce_ms = 50
[permissions]
clipboard = "write"
Building a gadget
# Build the gadget for the WASM target
cargo build --target wasm32-wasip2 --release
# Package into a .torchsnap archive
torchsnap gadget pack \
--manifest gadget.toml \
--wasm target/wasm32-wasip2/release/calculator.wasm \
--output calculator.torchsnap
settings.json
{
"launcher": {
"hotkey": "Option+Space",
"max_results": 12,
"theme": "system"
},
"gadgets": {
"com.example.calculator": {
"enabled": true,
"prefix": "="
}
}
}
theme.css
@theme {
--color-accent: var(--color-orange-500);
--color-accent-hover: var(--color-orange-600);
--color-surface-sidebar: #f8f5ef;
}
[data-theme="dark"] {
--color-surface: #1c1c1e;
--color-surface-sidebar: #222224;
--color-border: #3a3a3c;
}