Add Launcher home-screen plugin#22
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
plugins/launcher/— the platform home screen (LCH-01–05) — and makes/serve the configured root plugin in place. The Launcher is the defaultroot_plugin_id, so/and/launchernow 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.dbisn't wired until 0.5.05. So the Launcher reads a new session-gated routeGET /api/plugins(forwarding the caller's cookie; middleware injectsx-sovereign-user-role). Filtering is a pureselectLauncherPlugins(plugins, disabledIds, role): excludes the three chrome plugins (CHROME_PLUGIN_IDS), excludes disabled plugins, hidesadminOnlyplugins from non-admins./serves the root plugin in place (revises the 0.4.04 redirect) — the middleware rewrites/to the configured plugin'sroutePrefixrather than 307-redirecting, so the URL stays/. It stays request-time and respects the configurableroot_plugin_id(CON-11): a new admin-key-guardedGET /api/admin/root-pluginreturns the prefix (ornullwhen 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.tsxkeeps itsredirect()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
app/_components/(not a siblingcomponents/) — the generate script composes only each plugin'sapp/tree./api/pluginsoversdk.db— pragmatic v0.1 path; migrates tosdk.dbin 0.5.05./as the canonical home URL while honoring configurable root.Tests
Pure logic extracted and unit-tested;
vitest.config.tsnow also includesplugins/**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 originalinitials || name.slice(0,2)never fell through for a single-word name (a one-letterinitialsis 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,typecheckall green.Verified live (real session)
/(authenticated)//launcher(authenticated)/api/plugins(authenticated){"plugins":[]}— only chrome plugins installed/api/admin/root-plugin(admin key){"routePrefix":"/launcher"}/and/api/plugins(no cookie)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
selectLauncherPluginsunit tests — the natural point for the browser click-through once a non-chrome plugin lands.Versions
runtime→ 0.3.0; newplugins/launcherat 0.1.0.Draft
Marked draft pending review / browser pass.
🤖 Generated with Claude Code