Skip to content

Roadmap

tavlean edited this page Aug 5, 2026 · 17 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.

Amended 2026-08-05: Phase 2 gained a toggleable detail pane, and a new Phase 2.5 ("Started by" attribution) follows it. Both ideas surfaced through Antonin Trognon's Port Watcher submission (raycast/extensions#29514); he reached out by email before that PR resolved, and the credit belongs in the CHANGELOG when Phase 2.5 ships.

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)
  • List detail pane (added 2026-08-05): isShowingDetail on List opens a right-hand pane per selected item; List.Item.Detail.Metadata renders a table of Labels, TagLists, Links, and Separators. Raycast suppresses row accessories while the pane is open, so glance mode and inspect mode do not fight; confirm that behavior in ray dev before relying on it. (/api-reference/user-interface/list)
  • 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 (revised 2026-08-01): kills run directly in onAction against servers.ts (no window opens), confirmed by a showHUD once the process has exited. Starts AND restarts launch the dashboard with the launchContext spawn flow instead of spawning inline: the dashboard owns the spawn state machine, bind-polling, and failure toasts (see Spawn Flow), the user gets visible progress for an action whose outcome takes seconds, and - discovered the hard way - the menu bar process does not survive a click long enough to spawn anything itself (see Raycast Quirks).
    • 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; detail pane added 2026-08-05)
    • 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.
    • Detail pane as inspect mode (added 2026-08-05, prompted by Port Watcher's layout in raycast/extensions#29514): a "Show Details" action on ⌘I toggles isShowingDetail. Glance mode keeps today's accessories; inspect mode swaps them for a List.Item.Detail.Metadata table: status, framework tags, folder, run command, port, network URL, PID, uptime, branch, the full process-tree stats breakdown (a better home for it than the tooltip), and Phase 2.5's "Started by" row once that ships. Persist the toggle across launches (small LocalStorage flag) so the mode is a preference expressed by use, not a per-session choice.
    • 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, the detail pane toggles and survives relaunch, lint clean, docs updated.
  • Phase 2.5: "Started by" attribution (added 2026-08-05; idea from Antonin Trognon's Port Watcher, raycast/extensions#29514 — credit him in the CHANGELOG when this ships)
    • What: every server answers who launched it — a terminal (iTerm, Terminal, Warp), an agent (Claude Code, Codex), Dev Servers itself, or nothing claimable. The question got real the day agent sessions started spinning up servers that look identical to the user's own.
    • Env-first, ancestry as fallback. A process copies its environment at spawn and keeps it after reparenting, so env outlives a closed terminal where a ppid walk goes dark. One batched ps eww -p <pids> -o pid=,command= over listener pids (raise maxBuffer; environments are ~1 KB each) and match markers: CLAUDECODE=1 for Claude Code, TERM_PROGRAM for terminals and editors (iTerm.app, Apple_Terminal, WarpTerminal, vscode). Fallback for unmarked processes: one ps -axo pid=,ppid=,comm= table walk, child-first lineage stopping below launchd, lineage of one dropped as noise. The fallback half mirrors Port Watcher's implementation; the env-first half is our improvement on it.
    • Stamp our own children: startDevServer exports a marker variable (e.g. DEV_SERVERS_LAUNCH=1) into the spawn env so our servers self-identify the same way, surviving Raycast restarts. Port Watcher's LAUNCH_MARK proves the pattern works.
    • Verify before building: what env Codex CLI actually stamps (check a live codex exec child before hardcoding anything); what the Raycast-spawned zsh environment already carries; ps eww only reveals env for same-user processes, which is fine because those are the only ones we manage.
    • Surfaces: the Phase 2 detail pane gets a "Started by" row showing the marker or the lineage chain; agent-started servers get a small accessory icon in glance mode; Phase 3's list-servers tool returns the field so "kill the server Claude started" resolves without guessing.
    • Done when: three servers started from iTerm, from a Claude Code session, and from Dev Servers each show the right source; closing the originating terminal does not blank the env-derived answer; lint clean; docs and CHANGELOG (with the credit) 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)

All three shipped 2026-08-01, in the same session that fixed the menu bar Restart (it raced Raycast's unload-on-menu-close and never respawned; see Raycast Quirks).

  • Kill icon (shipped 2026-08-01): the menu bar was the confirmed surface, and its glyph was Nucleo's stroked trash, whose outlined body under a floating handle read as "a container with a cork" once tinted flat red. Swapped for the filled sibling from the same set (solid body, handle fused into the lid), which keeps the trash silhouette under tinting. The dashboard's Icon.Stop / Icon.Trash are untouched.
  • Favicon-derived fallback tints (shipped 2026-08-01): the menu bar's fallback glyphs now take the dominant color of the project's cached SVG favicon, extracted at render time by svgFaviconTint; the framework badge color is the last resort. Decision notes in Favicons and Menu Bar. No new persistence: the SVG was already cached on recents.
  • Branch tags only when they disambiguate (shipped 2026-08-01, menu bar only): running rows show the branch, main included, only when the project's rows span more than one branch; Start rows only when two visible entries share a project name. Rule details in Menu Bar. The dashboard's showBranch preference stays as-is, still pending that discussion with the user.

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.
  • One restart implementation (consider, not committed): the dashboard currently restarts two ways - restart() behind the action panel, and the spawn flow's replace-in-place path used by the menu bar's delegated restart and the Start picker (both since 2026-08-01, see Spawn Flow). Folding restart() into the spawn flow would leave one implementation for every surface. Worth weighing, not urgent: both paths work and are tested by daily use, and the merge must preserve restart()'s own poll cadence and "Restarted" toast. Take it as a focused pass if the two ever drift or one grows a fix the other lacks.
  • 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