Skip to content

Architecture

tavlean edited this page Jul 2, 2026 · 1 revision

Architecture — extension internals

File map, the two data paths, and the conventions that keep the Store reviewer happy. For how data reaches the extension at all, read Data Contract first.

File map

package.json           Raycast manifest: 2 commands, 4 tools, dataUrl preference
ai.yaml                AI instructions + evals (current docs: root ai.yaml, NOT package.json.ai)
assets/icon.png        512×512 site logo (own background — works light & dark)
src/
  search-models.tsx    Search Models command
  search-benchmarks.tsx Search Benchmarks command
  tools/               4 AI tools: search-models, get-model, rank-models-by-benchmark, compare-models
  lib/
    types.ts           Dataset / ModelRow / BenchmarkDef (+ index signatures — tolerate unknown fields)
    constants.ts       DEFAULT_DATA_URL, COMPOSITE_KEYS (fixed display order), SITE_ROOT
    data.ts            useDataset() — React hook for view commands (useFetch, SWR, failure toast)
    dataset.ts         loadDataset() — non-hook path for AI tools (fetch + Cache, 1 h TTL, stale fallback)
    collections.ts     deriveDatasetCollections(): benchmarksByKey / realBenchmarks / composites — ONE derivation, both paths use it
    resolve.ts         deterministic fuzzy resolution (models + benchmarks): exact key → exact name → substring → word prefix; ambiguity returns candidates
    ranking.ts         sortModelsByKey (honors ascending, missing always last), rankModelsForBenchmark
    format.ts          formatScore (percent=fraction×100 / integer / currency, missing="—"), formatCost

The two data paths (why there are two)

React hooks don't work in AI tools (plain async functions), so:

  • View commandsuseDataset() (data.ts): useFetch gives stale-while-revalidate — instant reopen, background refresh, keepPreviousData, failure toast, manual "Refresh Data" action calling revalidate().
  • AI toolsloadDataset() (dataset.ts): plain fetch + Cache (namespace "dataset"), skip refetch under 1 h, fall back to any-age cache when the network fails. Both share DEFAULT_DATA_URL, the dataUrl preference, and deriveDatasetCollections() — change data logic in ONE place.

Rendering rules

  • Never render empty: lists pass isLoading (no empty-flicker); the Reasoning Levels table renders only columns with ≥1 value and levels with ≥1 value, and is omitted entirely otherwise (defense in depth — the endpoint also filters empty levels, but stale caches exist).
  • Benchmark labels: name alone (see Data Contract "Rules that bite").
  • Detail panels: markdown tables for scores, Metadata for structured facts, Metadata.Link for links[] (objects {title, url} — NOT strings; this was a real bug once).
  • AI tools: outputs capped (limits clamped to 25), fuzzy matches echoed back precisely (benchmark: {name, key}) so misresolution is visible, ambiguity returns top-3 candidates instead of guessing.

Store conventions (learned from DevServers)

  • The extension repo ships to the Store verbatim — no docs, no .claude/ tracked, nothing extra. Docs live in this wiki.
  • .claude/PROJECT_BRIEF.md exists locally in the extension repo (untracked, gitignored) so Claude sessions get oriented; it points here.
  • Screenshots for the Store go in metadata/ at the extension root (3–6 PNGs, 2000×1250) — added at R4.
  • Validation: npm run build + npm run lint (ray CLI). Manual test: npm run dev opens it in Raycast. No test suite — same as DevServers.
  • Submission: npm run publish via the existing fork tavlean/raycast-extensions; author handle tavlean.

Clone this wiki locally