Dev-Only-Plugin-Routes
Design Log #171 — Dev-Only Plugin Routes
Written for AI agents. See Log Methodology Note below for details.
Background
Design Log #130 introduced plugin routes — full pages (jay-html + page component) declared in plugin.yaml and served by the Jay dev server. Plugins use them for admin dashboards, internal tools, and builder UIs that are not end-user site pages.
A short-lived embedOnly flag blocked direct HTTP access and hid routes from listRoutes(). That was the wrong model: dev-tooling routes should remain reachable on the dev server (direct URL, debugging, standalone tabs). The framework should expose metadata; each consumer (design tools, CLIs, production build) decides what to do with it.
Related: #130 (plugin routes), #128 (iframe embed mode / _jay_embed — client behavior only, not route access control).
Problem
- Plugin authors need a declarative way to mark routes as dev-server tooling, distinct from public site pages.
- Consumers of
DevServerService.listRoutes()needdevOnlyonRouteInfoto filter navigation UI without the framework hiding routes. - Production builds should eventually skip dev-only plugin routes (deferred — see backlog task in consuming projects).
- Plugin authors should know dev-only pages remain directly reachable on the dev server; standalone UX is the plugin's choice (documented in agent-kit
plugin-routes.md).
Questions and Answers
Q: Should dev-only routes block direct browser access on the dev server? A: No. Dev server serves them normally. Consumers may filter; the framework does not enforce access.
Q: Should devOnly auto-imply from other plugin metadata?
A: No. Plugin author sets devOnly: true explicitly on the routes[] entry. plugin-validator may warn in AIditor-specific contexts (see validator only — not core manifest).
Q: Does listRoutes() hide dev-only routes?
A: No. listRoutes() returns all routes with devOnly?: boolean set. Callers filter if needed.
Q: Production builds?
A: Deferred. Exclude devOnly routes from production manifest and bundles in a follow-up task.
Design
devOnly on plugin routes
# plugin.yaml
routes:
- path: /my-plugin/admin
jayHtml: ./lib/pages/admin/page.jay-html
component: adminPage
devOnly: true
description: Dev-server admin UI
| Layer | Behavior |
|---|---|
plugin.yaml |
Optional devOnly: boolean on routes[] |
JayRoute |
devOnly?: boolean propagated from manifest |
RouteInfo (DevServerService) |
Includes devOnly?: boolean; no filtering |
| Dev server HTTP | All routes served |
| Production server (today) | Still bundles dev-only routes (deferred) |
plugin-validator |
Validates devOnly is boolean; AIditor-specific template warnings live in validator only |
Type signatures
// compiler-shared PluginManifest routes[]
{ path: string; jayHtml: string; component: string; devOnly?: boolean; ... }
// route-scanner JayRoute
{ ..., devOnly?: boolean }
// dev-server RouteInfo
{ path: string; jayHtmlPath: string; compPath: string; devOnly?: boolean }
Rollback: remove embedOnly
Deleted in implementation:
embedOnlyfieldresolveRouteEmbedOnlyhelper- HTTP 404 gating on
_jay_embedin dev-server and productionfetch-page-handler listRoutes()filter that hid embed-only routes
DL#128 _jay_embed iframe client behavior (cookie, postMessage freeze) lives in generate-client-script.ts only — no server-side helper.
Plugin developer guidance (agent-kit)
Document in plugin-routes.md:
devOnly: true— route is dev-server tooling; future: excluded from production builds.- Direct access allowed — dev server serves the URL; plugin may handle standalone visitors (gate, redirect copy, or intentional standalone mode).
- Consumers — tools that build page pickers from
listRoutes()should filterdevOnlyroutes; tools that load a route by explicit path (e.g. embedded settings iframe) are unaffected.
Diagram
Implementation Plan
Phase 1 — Framework
devOnlyon manifestroutes[],JayRoute,RouteEntry- Propagate in
scanPluginRoutes(dev + production builder) RouteInfo.devOnlyinlistRoutes()/refreshRoutes()— no filtering- Remove
embedOnlyand HTTP embed gating plugin-validator: boolean check ondevOnly; AIditor settings template validation stays validator-only- Agent-kit
plugin-routes.md— dev-only semantics and standalone access
Phase 2 — Deferred
Production build excludes devOnly plugin routes from manifest and client bundles.
Examples
✅ Dev-only plugin route:
routes:
- path: /my-plugin/admin
jayHtml: ./lib/pages/admin/page.jay-html
component: adminPage
devOnly: true
✅ Consumer filters navigation (pattern — not framework code):
const navigableRoutes = routes.filter((route) => !route.devOnly);
❌ Framework blocking dev-only routes on HTTP:
if (route.devOnly) return res.status(404).end();
Trade-offs
- No HTTP gate on dev server — matches "dev tooling" semantics; production exclusion is the real ship boundary (later).
- Explicit flag — no inference from other metadata; plugin author opts in.
- Consumer responsibility —
listRoutes()is complete; each tool filters or not.
Verification Criteria
-
devOnly: trueroutes served at direct URL on dev server -
listRoutes()returns entries withdevOnly: truewhere declared - No
embedOnlyreferences remain in jay packages - Agent-kit
plugin-routes.mddocuments dev-only semantics and standalone access - Production exclusion tracked as separate backlog task
Consumer notes
AIditor-specific behavior (Pages dropdown filter, Project settings iframe host) lives in the AIditor contributor guide (aiditor-add-menu.md). Plugin author conventions for materializing settings discovery files and pairing them with devOnly routes are in Design Log #173 and agent-kit plugin/aiditor-settings-guide.md.
Log Methodology Note
Note: These design logs are written primarily for AI agents as part of the Design Log methodology and made accessible here for human readers. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation.