Skip to content

feat: live plugin ops β€” outdated badges, streamed install/update, doctor copy-fix, leaner nav - #7

Merged
0xLeif merged 1 commit into
mainfrom
0xleif/feat/outdated-streaming-doctor
May 6, 2026
Merged

0xLeif merged 1 commit into
mainfrom
0xleif/feat/outdated-streaming-doctor

Conversation

@0xLeif

@0xLeif 0xLeif commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Push toward "best tool for fledge" β€” make the hub feel alive and tighten its scope around plugin management. Four threads in one PR.

1. Outdated detection

Every installed plugin now reveals whether a newer release exists, with a one-click update.

  • src/semver.ts + 12 unit tests β€” lenient comparator (handles v prefix, missing patch segments, pre-release suffixes).
  • src/github.ts fetchLatestVersion(owner, repo) β€” tries /releases/latest then falls back to most-recent tag. 404s cached so missing-release repos don't hammer GitHub.
  • GET /api/plugins/outdated β€” runs lookups in parallel, returns [{ name, source, current, latest, outdated }].
  • Frontend lazily fetches outdated info after the initial render so neither Installed nor Store is blocked by GitHub. Cards get a blue accent stripe + "v0.6.0 β†’ v0.7.0" badge; Update button becomes primary.

2. SSE streaming for plugin operations

You can now watch installs / updates / removes happen live instead of waiting on a blocking POST.

  • src/fledge.ts spawnStream / fledgeStream β€” pumps stdout/stderr line-by-line from a Bun.spawn process.
  • Three new SSE endpoints: `GET /api/plugins/{install,update,remove}/stream`. Source/name params validated against allowlist regex (`/^[A-Za-z0-9_./@:-]+$/` and `/^[A-Za-z0-9_-]+$/` respectively).
  • Old non-streaming POST endpoints (`/plugins/install|update|remove`) deleted β€” only consumer was the hub itself.
  • New fixed-bottom ops console drawer in the UI: opens automatically on each operation, shows colour-coded stdout/stderr/error lines, status pulse β†’ success/failure dot. Collapsible and dismissable.

3. Doctor: copy + rerun

Fix strings are arbitrary shell commands, not safely auto-runnable β€” so we don't try.

  • One-click Copy button on every fix command (with secure-context fallback for non-HTTPS).
  • Re-run button + last-run timestamp at the top of the page.

4. Navigation cleanup

Earlier conversation: the hub is the plugin hub, not a project IDE. Cut what didn't earn its place.

  • Lanes page removed from Global β€” Overview already shows project lanes; the standalone page was duplicate data living under the wrong section. `/api/lanes` endpoint gone.
  • Installed trimmed to plugins only. The Templates tab (which actually called `templates search`, not "installed") moves implicitly to Store. The Commands tab (full `fledge introspect` tree) was redundant β€” plugin commands already render as badges. `/api/templates` endpoint gone.
  • Stats grid + section-tabs CSS dropped.

Test plan

  • `bun test` β€” 29 pass / 0 fail (12 new semver tests added).
  • `bun run typecheck` clean.
  • Open `/` β†’ Installed. Confirm outdated badges appear after a brief delay; Update button labels include the target version.
  • Click Update β€” ops console drawer opens, stdout streams live, finishes with success dot. Plugin list refreshes; outdated badge clears.
  • Repeat for Install (from Store) and Remove. Bad inputs (`source=foo;bar`) return 400.
  • Doctor β†’ Re-run reloads checks; Copy button on a fix line copies the exact command and flashes "Copied".
  • Sidebar shows just Project / Overview + Global / Store / Installed / Config / Doctor.
  • Navigate to Project Overview β€” lanes still render there from `/api/project`.

πŸ€– Generated with Claude Code

@corvid-agent

Copy link
Copy Markdown
Collaborator

πŸ‘‹ CorvidAgent is looking into this.

@0xLeif
0xLeif merged commit d541d51 into main May 6, 2026
6 checks passed

@corvid-agent corvid-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good PR β€” four coherent threads tied together well. The streaming migration, outdated detection, and nav cleanup all land cleanly. A few notes:

Semver (src/semver.ts) β€” clean implementation, good test coverage. One pedantic note: the regex (?:[-+](.+))? conflates build metadata (+build.5) with pre-release (-rc1). Per semver spec, build metadata should be ignored for precedence, so 1.0.0+build.5 and 1.0.0 should compare equal β€” but the code treats the former as less than the latter. Unlikely to bite in practice for fledge plugins, but worth knowing.

Unhandled rejections β€” refreshOutdated() in both the store and installed loaders fires as a .then() without a .catch(). If the /plugins/outdated endpoint 500s or the network drops, that's a silent unhandled promise rejection. Easy fix: .catch(() => {}) like you already do inside streamPluginOp.

Input validation β€” VALID_SOURCE and VALID_NAME regexes look correct for keeping metacharacters out of argv. SSE endpoints being GET means they're theoretically CSRF-reachable, but this is a localhost dev tool so it's fine.

Streaming β€” pumpLines + opsConsole.attach handles the EventSource lifecycle well. The done β†’ close β†’ error race is handled correctly by nulling this.current before the error handler fires.

Minor β€” the timeout kill in spawnStream doesn't surface a "timed out" message to the client; the process just dies and the exit code reflects the signal. Not blocking but a future nice-to-have.

Tests pass, types check, removals are clean. Ship it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants