Documentation everywhere a module is chosen or run. The module page has a
Documentation tab (after Overview) rendering the package's docs in the
console language (marked + DOMPurify; external links open in a new tab), a
catalog card has a Documentation button, and the install page's Grants
step links the same dialog — so the guide is readable before anything is
installed. A version published before the rule shows an empty state.
The platform review queue is /platform/modules (a sid-less top-level route,
meta.platformOnly; linked from the Modules page for platform admins):
one card per in_review version with its report, notes, egress and
Open in sandbox for its ui/ pages; Approve / Reject (notes required
to reject), Check again, Upload to catalog, and — master only — Yank a
version.
The Modules area. Modules is its own area on the console's rail (the
seventh, before Settings). Its pane has two system pages — Catalog
(/store/:sid/modules, visible to a manager or to any member whose
develop capability is readable; mutations need a manager) and Upload
package (/store/:sid/modules/upload, platform admins only for now) — and
an Installed block: one row per installed module (icon, name, a health
dot: ok / not configured / failing; disabled ones dimmed) linking to its
detail. The Modules page lists installed modules as a table and the catalog
as cards grouped by category behind a chip row, each with a UiChip status
(not configured / unverified / verified {date} / failing / disabled); a
catalog card's Install opens the upload page in install mode
(modules/install/:key), whose stepper shows only Grants → Install —
one component, two entry points, so "grants before any store is touched" has
exactly one implementation.
The upload page (UploadPackageView, four steps: package → check →
grants → install / update). Pick the zip; the browser pre-flights it
(archive listing, validateManifest, size caps — the same row ids the server
emits); the bytes go through the console proxy to POST /system/module/upload
with progress; the Check step renders the ModuleCheckReportCard — an
error keeps the page there, a warn is shown and the flow continues; the
Grants step shows the access ∩ MODULE_MAX_ACCESS and scope trees
read-only (the diff against the installed version on an update, via
ModuleGrantsDiff: grants widened, schema fields removed, egress added) with
the explicit accept; the last step is one button — Install on {store},
Update {store} to {version}, or Submitted for review (platform target,
later phase). A managed module (§8.7) shows Runs in: your space cloud
on the Grants step instead of an endpoint, and its install / update ends on a
Deploying card (the state chip, the function id, the project) that polls
/api/module/deploy/status every 15 s until the install lands at the target version,
or shows the deploy's error with Try again; a 409 deploy_unavailable is a
toast naming the reason. The detail page's About shows Deployed function
(URL · project · version) in place of the endpoint.
The detail page (/store/:sid/modules/:id, ModuleView): a header with
the module icon, name, the status chip, the hosting chip and the version, an
Enabled switch; tabs Overview · Elements · Gallery · Documentation · Data ·
Logs · Grants (Elements only for a module with a kit or routes.web;
Gallery, the Elements page's specimen wall narrowed to the module's own
elements, only for a module with a kit). Overview holds
the Update available card (release notes, the grant diff, Review and
update → the upload page's Grants step), the Configuration card
(ModuleConfigCard: non-secret fields, write-only secrets with a set
indicator, a public toggle per field, Verify, Save) and the Health
table (one row per point: last ok, consecutive failures, timeout, fallback;
outbox pending / dead in the foot). The right rail: About (vendor, category
- tags, source + registry key, installed by / at, grants accepted, endpoint,
the drift chip), Data it owns (content collections → the collection view,
records collections,
state/key count, the{entity}.modules.{id}fields it may write), Pages & widgets (one link permanifest.admin.pages[]entry →/store/:sid/modules/:id/pages/:page,ModuleFrame— the module's own page in a sandboxed cross-origin iframe with the session-token handshake of §4.2; shown for aconnectedmodule that declares pages), Identity (the managed token's note, the hook entry id, the three keys, Reroll keys) and the Danger zone (Uninstall · keep data / Uninstall · purge). Logs lists the outbox rows; Grants shows the installed version's trees read-only with a note on whatMODULE_MAX_ACCESSdenies. Every mutation is a manager verb; adevelop: readeditor sees the page.
The Elements tab (components/modules/ModuleElements.vue, one
POST /api/module/elements call): a table of the module's kit elements
— name + id, a state chip (As shipped / Customized / Conflict /
Missing), the version the body is based on — with Edit (opens the
library at /store/:sid/elements?element={id}), Duplicate (a derived
copy), Reset to module version on a customized row, Replace with the
module's element on a conflict (force) and Restore on a missing one
(the last three are manager actions; edits need the web capability, not
this route's develop). Under the table a live preview
(ModuleElementsPreview.vue): a tab per element that is in the library,
drawn on the store's design with the module's runtime and the store's real
data — its /m/{id}/** requests go to the module read-only, nothing is
written (a row's Preview selects it; admin/docs/elements.md "Module
elements"). Below it Your elements using this module (the
derived and custom ones, "based on {element} · {version}"), a runtime line
(size, disabled, or no runtime marker yet), a note when the install is
not restamped (stamped: false), and the primary action Build a new
element → /store/:sid/elements?module={id}, which opens the library's
new element dialog pre-bound to the module. The update card links here
("customized elements are kept"); the disable and uninstall confirmations
say what happens to placements. Elsewhere in the console: the element
library shows a module chip per row, hides delete on owned elements, offers
Duplicate, and shows a note with the computed state on the selected
element; the element picker shows the chip, does not offer elements of a
disabled module, never embeds a module element and warns on a placement
whose module is off (admin/docs/elements.md). Runtime docs are hidden from the
library and the picker — only this tab reports them.