Skip to content

Roadmap

tavlean edited this page Jul 26, 2026 · 14 revisions

Roadmap

Rewritten 2026-07-02 as the single authoritative plan. It consolidates the previous roadmap (2026-06-05) with a new feature push: menu bar command, per-server CPU/memory, and Raycast AI tools. Changes from the previous version: the Shopify start/restart section shipped in the 2026-06-10 release and now lives in Shopify Integration; the one-line "menu-bar mode" idea grew into Phase 1; kill-undo and default-folder kept their scoped notes as Phases 4 and 6; per-project script override moved to "Later, deliberately".

Current phase: 2 (CPU and memory per server). Phase 1.5 shipped 2026-07-26; the next update starts here, with Retry-on-a-failed-row and Phase 4's undo as the two smaller items queued alongside it. Update this line and the checkboxes as phases complete.

Vision

Project-first dev server control everywhere Raycast reaches: a dashboard for working with servers, a menu bar for glancing at and poking them without opening anything, and AI tools for doing it in plain language. Every surface reuses the same headless core (src/servers.ts), so a feature lands once and shows up everywhere.

Framework findings (Raycast API docs, checked 2026-07-02)

The plan relies on these; doc paths are from developers.raycast.com. Re-verify if the API version moves past ^1.104.

  • Menu bar lifecycle: mode: "menu-bar" commands are not long-lived. Raycast loads the command when the user opens the menu (or on background refresh), waits for isLoading to become false, then unloads it. Every menu open re-runs the command, so data can be fetched fresh on open; Cache gives an instant stale paint first. (/api-reference/menu-bar-commands)
  • Background refresh: add "interval": "1m" to the command manifest. The manifest page documents a 1m minimum while the background-refresh page says 10s; use 1m for store safety. Raycast auto-adds enable/disable preferences, prevents overlapping runs, and timing is inexact on battery. (/information/lifecycle/background-refresh, /information/manifest#command-properties)
  • Immediate refresh pattern: after a mutation elsewhere, launchCommand({ name: "menubar", type: LaunchType.Background }) is the docs-recommended way to refresh a menu bar command right away. (/api-reference/command)
  • Menu items: MenuBarExtra.Item gets onAction (arbitrary code: open(), shell, toasts, launchCommand), an Option-key alternate item, shortcuts, icons, subtitles. No ActionPanel, no search, no forms, no pagination inside the menu; sections and sparing submenus are the only structure. Items with identical titles at the same level can misfire onAction; disambiguate titles. (/api-reference/menu-bar-commands)
  • Root-search subtitle: updateCommandMetadata({ subtitle: "3 running" }) persists a subtitle under the command in root search. (/api-reference/command)
  • AI tools: declare tools in package.json; each maps to src/tools/<name>.ts whose default export is the tool function. Input is a single typed object; JSDoc on the type fields is what the AI reads. Tool.Confirmation<Input> (exported as confirmation) runs before the tool; Action.Style.Destructive for kills. Instructions and evals go in ai.yaml (recommended over inline package.json); evals run with npx ray evals and double as suggested prompts. Tools shipped in API 1.93.0. Users invoke them by @-mentioning the extension in Quick AI / AI Chat, which requires the user to have Raycast AI access. (/ai/learn-core-concepts-of-ai-extensions, /api-reference/tool, /ai/write-evals-for-your-ai-extension)
  • Frecency: useFrecencySorting(data, { namespace, key }) from @raycast/utils returns sorted data plus visitItem; rankings persist between runs and can be shared across commands via the same namespace and keys. It is a React hook, so it works in view and menu-bar commands but not in plain tool functions. (/utilities/react-hooks/usefrecencysorting)
  • No native process stats: no Raycast API exposes per-PID CPU/memory. Extending our existing ps call with extra columns is the documented-adjacent path. List accessories support colored tags/text with tooltips for display. (/api-reference/utilities, /api-reference/user-interface/list)
  • State sharing: Cache (10 MB default, string data) is shared across commands in the extension and is the right home for server snapshots; LocalStorage stays for durable small config (recents already live there). (/api-reference/cache, /api-reference/storage)
  • Menu bar commands are macOS-only; irrelevant today (extension is macOS-only) but it pins the Windows port even further out.

Foundations (designed once, consumed by several phases)

  • F1: Snapshot cache. Whichever surface runs fetchServers writes the result (plus a timestamp) to Cache under one key. The menu bar paints instantly from the snapshot, then fetches fresh. The dashboard keeps polling as today and just gains the write. Consumers: Phases 1, 2, 3.
  • F2: Stats in the scan. fetchServers's existing ps invocation gains ppid, %cpu, and rss columns; per-server stats roll up the listening process plus its descendants (dev servers are process trees: package manager → node → workers). One scan, no extra spawns. Consumers: Phases 2, 3 (and menu bar subtitles later if wanted).
  • F3: Frecency namespace. One useFrecencySorting namespace keyed by canonical cwd, shared by the menu bar start section and the Start picker. visitItem fires on every successful start regardless of surface. Consumers: Phases 1, 5.

src/servers.ts already has zero @raycast/api imports (verified 2026-07-02), so menu bar onAction handlers and AI tools can call fetchServers / killServer / restartServer / startDevServer directly. Keep it that way.

Phases

Each phase ships independently: code + README + CHANGELOG in one commit series, npm run lint clean.

  • Phase 1: Menu bar command (with F1 and F3). Shipped in the 2026-07-02 release, then polished in the 2026-07-09 release: project-name-first rows, real favicons, and pruning of deleted-folder recents. F1 (snapshot cache) lives in src/snapshot.ts; F3 (frecency) is wired in src/menubar.tsx and still awaits Phase 5 to reach the Start picker. Decisions documented in Menu Bar.
    • Manifest: "mode": "menu-bar", "interval": "1m". Title shows the running-server count (icon only when zero, so an empty menu bar stays quiet). Tooltip "Dev Servers".
    • Menu: one section per project (matching the dashboard's grouping), one submenu per server titled name · port with framework icon; inside: Open in Browser, Open Localhost URL, Restart, Kill, Copy URL, Copy Port, Open in Editor / Terminal (when the preference is set). A final section starts recent projects, frecency-sorted (F3), plus "Open Dashboard" and "Start Dev Server…" items that launchCommand the existing view commands.
    • Interaction decisions: kills and restarts run directly in onAction against servers.ts (no window opens), then background-relaunch the menu bar command to refresh itself and call updateCommandMetadata for the root-search subtitle. Starts launch the dashboard with the existing launchContext spawn flow instead of spawning inline: the dashboard owns the spawn state machine, bind-polling, and failure toasts (see Spawn Flow), and the user gets visible progress for an action whose outcome takes seconds.
    • Guard: launchCommand to a disabled command throws; wrap the dashboard→menubar refresh poke in try/catch so users who disable the menu bar item lose nothing.
    • Done when: menu shows live grouped servers with working kill/restart/open/copy; count stays fresh via interval + pokes; recents start from the menu; lint clean; README + CHANGELOG updated.
  • Phase 1.5: Pending start rows (specced 2026-07-25, shipped on pending-start-rows 2026-07-26)
    • Spawn feedback moved from the toast into the list: every start gets a spinning row that hands off to the real row on bind, or turns red in place with the remedies (View Startup Log, Copy Fix Command, Open in Terminal, Dismiss) when the 15s watchdog fires.
    • Grew past its spec during testing, all deliberate: restarts got rows too (a non-goal in the spec, taken on once the machinery existed), rows live in their project's section rather than a separate Starting one, list order became newest-first, and the spinner replaced the progress ring because a ring that vanishes at 20% reads as a cancelled job. Reasoning in Spawn Flow and UI Conventions.
    • Testing it also turned up four defects worth naming, all fixed on the branch: reparented workerd helpers posing as servers, those helpers surviving kills and piling up, orphaned helpers ignoring SIGTERM, and the auto-open bug that turned out to be a preference silently reset by a scope move (Auto-Open Investigation, Raycast Quirks).
    • Spec: Spec: Pending Start Rows.
  • Phase 2: CPU and memory per server (with F2)
    • Dashboard rows gain a stats accessory: 4.2% · 182 MB as secondary text, switching to an orange tag past attention thresholds (exact numbers tunable in the spec; start around 150% CPU / 1.5 GB RSS). Tooltip breaks down the process tree. Toggleable row-accessory preference like the existing ones, default on.
    • Menu bar server submenus show the same numbers as subtitles for free once F2 is in the snapshot.
    • Non-goal: historical graphs. This is a "which one do I kill" glance, not monitoring.
    • Done when: a busy Vite build visibly climbs, numbers match Activity Monitor within a poll interval, lint clean, docs updated.
  • Phase 3: AI extension tools
    • Tools: list-servers (no confirmation; returns project, framework, port, URL, uptime, stats), kill-server (destructive confirmation), restart-server (confirmation), start-server (resolves a project by name against recents + running set; confirmation shows what will be spawned where). All import from servers.ts / recents.ts only.
    • ai.yaml: instructions telling the AI to call list-servers first when a reference is ambiguous ("kill the storefront one"), plus evals with mocks for each tool (npx ray evals passing is part of done).
    • Start feedback: tools return text, so start-server reports "spawned, watching for the port" and a headless bind-wait (reusing the dashboard's timeout constants) upgrades the reply to the bound URL or a log-path failure within 15s.
    • Done when: "@dev-servers what's running?", "kill the vite server", and "start my shopify theme" work end-to-end in AI Chat; evals pass; store metadata (tool titles/descriptions) reads well.
  • Phase 4: Undo a kill (⌘Z) (rescoped 2026-07-26; supersedes the 2026-06-05 "kill toast with undo")
    • Killed the wrong server, ⌘Z brings it back. The undo respawns through startDevServer(cwd), so it is a fresh process rather than a true undo, which for a dev server is the same thing a restart gives you.
    • The undo lives on the dashboard, not on a toast. The 2026-06-05 scope hung it off a toast's primary action, which this update's own experience argues against: a toast's actions die with the toast, and with the Raycast window closed it degrades to an actionless HUD (the exact reasoning that moved start failures onto rows, see Spawn Flow). Right after a kill the user is looking at the dashboard, so that is where the affordance belongs.
    • Shape: an Undo Kill action in the row action panel, bound to ⌘Z, present only while an undo is available, titled with what it would bring back (Undo Kill (Explan), Undo Kill (3 servers)). The panel makes it discoverable and the chord makes it fast, which a bare hidden shortcut would not. A brief toast on kill (Killed Explan) can carry the hint, but must not be the only way to reach it.
    • Cheaper now than when it was first scoped: the pending-row machinery from Phase 1.5 means an undone kill comes back as a visible Starting… row in its project's section, with the failure path already handled.
    • Settled already: if the user started a replacement in the same cwd meanwhile, spawn alongside rather than killing it (no magic); bulk undo does not re-confirm; the kill handlers in src/index.tsx already snapshot their targets through mutate's optimistic update, which is the data an undo needs.
    • Verify before designing around it: ⌘Z is not on Raycast's documented reserved list (⌘K, ⌘W, ⌘Esc are) and z is a valid Keyboard.KeyEquivalent, but the docs do not promise the list is complete. Bind it in ray dev and confirm the action actually fires before building anything on top; fall back to ⌘⇧Z if it is swallowed.
    • Open questions: how long an undo stays available (a timer, or until the next kill supersedes it, or until the command unloads); whether the stack is one level (the last kill action, which may cover N servers) or deeper; and whether Kill All is undoable at all, or too big to hand to a single chord.
  • Phase 5: Frecency in the Start picker
    • Swap the picker's last-seen ordering for the shared F3 frecency sort (falling back to last-seen for never-started entries via sortUnvisited). Small; depends on Phase 1 landing F3.
  • Phase 6: Default folder for "Choose Folder…" (scoped 2026-06-05, unchanged)
    • defaultStartFolder directory preference on the start command; inject default location (POSIX file "<path>") into the existing choose folder AppleScript in pickFolderNative, escaping \ and ". statSync-check first and omit on any doubt: a stale preference must never break the picker. Scope: changes the dialog's starting directory only; never auto-treats the folder as a project.

Deferred from the pending-start-rows update

The branch closed 2026-07-26 with the user calling the update complete. Everything it set out to do shipped, plus the Finder-selection row and a quality pass over the whole extension. One item was deliberately left for the next update.

  • Retry on a failed row. A failed row explains the problem and hands over the fix, then makes the user leave the dashboard to apply it. Deliberately deferred in the original spec because the spawn flow is once-per-mount behind the spawnFlowFired ref, but the watch and watchdog effects both key on spawnState.phase, so setting the phase back to spawning re-arms them. No dependency-array surgery needed.
    • Open design question, settle before building: what happens when a second retry starts while the first is still in flight. The phase machine holds one batch, so a naive second retry clobbers the first's expecting map. Options are to merge into the existing map (the newcomer then inherits the incumbent's deadline, which is wrong but harmless), to refuse while one is in flight, or to give each pending entry its own deadline, which is the clean answer and the largest change.
    • Worth pairing with Phase 4's undo when it comes up: both are "the dashboard lets you correct what just happened without leaving it", and both re-enter the spawn flow from outside its once-only path.

Polish queue (noted by the user 2026-07-26, after the store merge)

Small, user-reported, none started. Raise them at the start of the next session; item 3 is a discussion before it is a change.

  • The Kill icon does not read as a trash can. The user's words: it looks like "a strange container with a cork on top". The icon choice is right; the fill is what breaks it. Confirm the surface first, because three different glyphs are in play: the dashboard's per-server Kill uses Icon.Stop (a rounded square, not a bin at all), project and all-server kills use Icon.Trash, and the menu bar uses the bundled assets/menubar-kill.svg tinted solid red, which is the likeliest culprit since a flat fill merges the lid into the body.
  • Menu bar fallback icons should take their color from the favicon, not the framework badge. With no favicon available, serverIcon renders tintedMenuIcon("server", toolColor(server.tool)), so every SvelteKit project is badge orange and every project of a given framework looks alike. The glyph is fine; the color should be the dominant color of that project's real favicon, which the dashboard already fetches and stores in recents (favicon / faviconRaster). Derive it once at fetch time, persist it beside the icon, and tint with it wherever the image itself cannot render. Point is fast visual scanning: color becomes identity per project rather than per framework.
  • Show the branch only when it disambiguates. Hide it when it is main; show it, main included, only when the project has more than one worktree, since otherwise every server of a project shares a branch and the tag says nothing. Priority is the menu bar, which prints the branch unconditionally in the submenu title (menubarTitle) and offers no way to turn it off. The dashboard already has the showBranch command preference (default on) and stays as-is pending that discussion; note its section header and per-row tooltip already carry the worktree, so the tag is doing less work there than it looks.

Later, deliberately

  • Non-Node runtime detection (Deno, Rails, Django, PHP, Go, …): the biggest audience expansion and the biggest detection matrix. Promote once Phases 1–3 ship and store feedback shows demand; design would extend the two-path detection in Framework Detection.
  • Kill Port command: "kill whatever holds 3000" as a no-view command with an argument. Deliberately Port Manager's turf (see README positioning); promote if users keep asking.
  • Per-project script override (from 2026-06-05 roadmap): pin a package.json script per canonical cwd for when pickDevScript guesses wrong. Needs a small pref store; promote on real user misses. See Script Picker.
  • Shopify follow-ups: open theme editor / copy preview link actions. See Shopify Integration.
  • Per-project Quicklinks: Action.CreateQuicklink for pinned one-keystroke starts. Cheap; promote on request.
  • QR code for the network URL: scan-to-open on a phone. Needs a bundled QR generator (offline); promote when mobile-testing users ask.
  • Tunnel sharing (cloudflared / ngrok): expose a dev server publicly from the action panel. New dependency + auth surface; promote on demand.
  • Browser-tab awareness: focus an already-open localhost tab instead of opening a duplicate, via the Browser Extension API (requires the user to install the Raycast Browser Extension). Spark stage.
  • Windows port: platform primitives stay isolated (see Process Detection); menu bar commands are macOS-only, which raises the port's cost further. Promote when Raycast-on-Windows matures.

For future executors

  • Assumptions to re-verify before building on them: servers.ts has no @raycast/api imports; the dashboard owns all spawn lifecycle (Spawn Flow); recents live in LocalStorage (Recents); API is ^1.104.
  • Verification: npm run lint and ray build -e dist; manual check via ray dev. No test suite exists.
  • Every shipped phase updates README, CHANGELOG, this page's checkbox and "Current phase" line, and any wiki page whose claims it changes.
  • Docs live in this wiki only; the extension repo must stay documentation-free apart from README/CHANGELOG (it is PR'd to the Raycast Store verbatim). Session context for AI-assisted work: .claude/PROJECT_BRIEF.md in the extension repo (gitignored).
  • Formatting: never the U+2014 long dash; the U+2013 short dash is fine, including ranges like 200–500ms.

Clone this wiki locally