Skip to content

Add Launcher home-screen plugin#22

Merged
kasunben merged 4 commits into
mainfrom
feat/launcher-plugin
Jun 14, 2026
Merged

Add Launcher home-screen plugin#22
kasunben merged 4 commits into
mainfrom
feat/launcher-plugin

Conversation

@kasunben

@kasunben kasunben commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds plugins/launcher/ — the platform home screen (LCH-01–05) — and makes / serve the configured root plugin in place. The Launcher is the default root_plugin_id, so / and /launcher now both render the Launcher (URL stays /).

SRS: LCH-01–LCH-05, PLT-12, PLT-14, docs/plugins/launcher.md.

What's in here

The Launcher plugin — a responsive tile grid with a main section, an admin-only Admin section, and an empty state. Each tile is a plugin icon (monogram), name, and description, linking to the plugin's routePrefix.

How it gets its plugin list — the SDK boundary rule forbids a plugin importing the runtime registry, and sdk.db isn't wired until 0.5.05. So the Launcher reads a new session-gated route GET /api/plugins (forwarding the caller's cookie; middleware injects x-sovereign-user-role). Filtering is a pure selectLauncherPlugins(plugins, disabledIds, role): excludes the three chrome plugins (CHROME_PLUGIN_IDS), excludes disabled plugins, hides adminOnly plugins from non-admins.

/ serves the root plugin in place (revises the 0.4.04 redirect) — the middleware rewrites / to the configured plugin's routePrefix rather than 307-redirecting, so the URL stays /. It stays request-time and respects the configurable root_plugin_id (CON-11): a new admin-key-guarded GET /api/admin/root-plugin returns the prefix (or null when the configured plugin isn't a valid root), which the Edge middleware fetches (it can't read the DB — same pattern as the disabled-plugins check). (platform)/page.tsx keeps its redirect() as a fallback for when that fetch fails.

Sidebar — the middle monogram section now excludes chrome plugins (PLT-12); full root-plugin-first ordering (PLT-11–15) stays separate.

Notable decisions / deviations from the spec

  • Components under app/_components/ (not a sibling components/) — the generate script composes only each plugin's app/ tree.
  • Monogram tiles — no icon-serving pipeline yet (launcher.md Q3); matches the sidebar's monogram approach.
  • /api/plugins over sdk.db — pragmatic v0.1 path; migrates to sdk.db in 0.5.05.
  • In-place serve via rewrite, not redirect — keeps / as the canonical home URL while honoring configurable root.

Tests

Pure logic extracted and unit-tested; vitest.config.ts now also includes plugins/** source tests. Full suite green (101 tests).

  • selectLauncherPlugins — 7 (chrome exclusion, admin vs non-admin, disabled exclusion, adminOnly flag, description default, all-chrome → empty)
  • monogram — 5. These caught a real bug: the original initials || name.slice(0,2) never fell through for a single-word name (a one-letter initials is truthy), so "Tasks" would have rendered "T" not "TA", and whitespace-only names leaked spaces. Fixed.
  • resolveRootRoutePrefix — 4 (valid → prefix; not-installed / disabled / admin-only → null)

format:check, lint, typecheck all green.

Verified live (real session)

Check Result
/ (authenticated) 200 — Launcher served in place, URL stays /
/launcher (authenticated) 200, direct
/api/plugins (authenticated) {"plugins":[]} — only chrome plugins installed
/api/admin/root-plugin (admin key) {"routePrefix":"/launcher"}
/ and /api/plugins (no cookie) 307 → login (gated)

Not exercisable live yet: the populated grid and Admin section need a non-chrome plugin to exist (first is Tasks, a separate repo/task), and the admin path needs an admin session. Both are covered by the selectLauncherPlugins unit tests — the natural point for the browser click-through once a non-chrome plugin lands.

Versions

runtime → 0.3.0; new plugins/launcher at 0.1.0.

Draft

Marked draft pending review / browser pass.

🤖 Generated with Claude Code

kasunben and others added 4 commits June 14, 2026 08:51
Adds plugins/launcher/ — the platform home screen (LCH-01–05) that
serves /launcher and, as the default root_plugin_id, now backs /. This
makes the Task 0.4.04 root redirect resolve to a real plugin for the
first time (/ → /launcher).

Because the SDK boundary rule forbids a plugin importing the registry,
the Launcher reads its tiles from a new session-gated runtime route
GET /api/plugins (forwarding the caller's cookie; middleware injects
x-sovereign-user-role). The route role-filters via a pure
selectLauncherPlugins() in runtime/src/launcher-plugins.ts: excludes the
three chrome plugins (CHROME_PLUGIN_IDS), excludes disabled plugins, and
hides adminOnly plugins from non-admins. The page renders a main grid, an
admin-only "Admin" section, and an empty state.

The sidebar middle section now also excludes chrome plugins (PLT-12);
full root-plugin-first ordering (PLT-11–15) stays separate.

Notes: plugin components live under app/_components/ since the generate
script composes only each plugin's app/ tree; tiles use a monogram
pending an icon-serving pipeline (launcher.md Q3).

Verified live with a real session: / → 307 → /launcher, /launcher → 200
(empty state), /api/plugins → {plugins:[]} (only chrome installed), and
unauthenticated gating (307). selectLauncherPlugins has 7 unit tests.

runtime → 0.3.0; new plugins/launcher at 0.1.0.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Extracts the tile monogram into a pure app/_components/monogram.ts and
adds unit tests (vitest now also includes plugins/** source tests).

The tests caught a real bug in the shipped helper: `initials || name.slice`
never fell through for a single-word name (a one-letter `initials` is
truthy), so "Tasks" rendered as "T" instead of "TA"; the whitespace-only
fallback also used the untrimmed name and returned spaces. Reworked to
take the first letter of the first two words, else the first two
characters of the trimmed name, else empty.

Docs: bring the launcher.md body in line with what was built — the
directory tree now shows app/_components/ + launcher.module.css +
package.json, and Data model / SDK dependencies document the interim
session-gated /api/plugins read (replacing sdk.db until 0.5.05).

Co-Authored-By: Claude Code <noreply@anthropic.com>
…ting

Previously / 307-redirected to the configured root plugin's routePrefix
(e.g. /launcher), changing the URL. Now the middleware rewrites / to that
routePrefix so the plugin renders in place — the URL stays /, and the
plugin remains reachable at its own prefix (so / and /launcher both serve
the Launcher).

Resolution stays request-time and respects the configurable root_plugin_id
(CON-11): a new admin-key-guarded GET /api/admin/root-plugin returns the
prefix (or null when the configured plugin isn't a valid root), which the
Edge middleware fetches — the same round-trip pattern as the disabled-
plugins check, since middleware can't read the DB. resolveRootRoutePrefix()
in runtime/src/root-plugin.ts encapsulates the logic (reuses
validateRootPlugin) and is unit-tested (4 cases). (platform)/page.tsx keeps
its redirect() as a fallback for when the resolution fetch fails.

Verified live with a real session: / -> 200 serving the Launcher in place
(URL unchanged), /launcher -> 200 directly, / unauthenticated -> 307 to
login. Revises PLT-14 (redirect -> in-place serve); docs updated.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Updates SRS PLT-14 + the root-plugin paragraph and launcher.md sidebar
section from "redirects to" to "serves in place" (the runtime rewrites /
to the root plugin's routePrefix; URL stays /). Adds an explicit Next
pointer to Task 0.4.06 (Account plugin) in CLAUDE.md's Status section.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@kasunben kasunben marked this pull request as ready for review June 14, 2026 07:53
@kasunben kasunben merged commit 85b3bc0 into main Jun 14, 2026
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.

1 participant