Bump the contract for the new gate, and close the ways it could read as open
CI / full-gate (push) Successful in 2m45s

This commit is contained in:
2026-09-02 08:36:16 +02:00
parent a17ed96b54
commit bf146c07e7
19 changed files with 57 additions and 44 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ const clients = on("oauth2-clients");
const pluginSettings = on("plugin-settings");
export default definePlugin({
apiVersion: "0.3.0", // the host contract this was built against — a literal, never HOST_API_VERSION
apiVersion: "0.4.0", // the host contract this was built against — a literal, never HOST_API_VERSION
nav: [ADMIN_NAV],
-4
View File
@@ -26,10 +26,6 @@ What it demonstrates:
The plugin holds **no state** — data lives upstream (README → *Stateless*). Handlers are thin and
`fetch` is injectable, so they unit-test as pure functions (`shifts.test.ts`).
The shifts list and "My shifts" repeat a little view-model and markup rather than sharing a
parameterised one: an example is read far more often than it is changed, and each page is meant to be
followed top to bottom on its own.
## Upstream
Set `PLUGIN_SETTING_SCHEDULING_UPSTREAM` to your backend's base URL. The dev compose points it at a tiny in-memory
+1 -3
View File
@@ -12,7 +12,7 @@ let upstreamUrl = "";
const upstream = createUpstream(() => upstreamUrl);
export default definePlugin({
apiVersion: "0.3.0", // the host contract this was built against — a literal, never HOST_API_VERSION
apiVersion: "0.4.0", // the host contract this was built against — a literal, never HOST_API_VERSION
// onBoot runs after discovery, before the server listens — where a plugin receives its resolved
// settings. A malformed URL already failed the boot by then; the host validated the declared type.
@@ -39,8 +39,6 @@ export default definePlugin({
{ description: "Create and edit shifts", name: WRITE },
],
// Mounted under /scheduling; `permission` gates before the handler runs. The overview is `public`
// (anyone may reach /scheduling, signed in or not); the rest need a permission.
routes: [
{ handler: overview(), method: "GET", path: "/", public: true },
{ handler: myShifts(upstream), method: "GET", path: "/mine", session: true },
+2 -1
View File
@@ -218,7 +218,8 @@ export function buildMineModel(opts: { chrome: PageChrome; email: string; error?
table: {
caption: t("scheduling.mine.title"),
columns: [{ label: t("scheduling.table.shift") }, { label: t("scheduling.table.start") }, { label: t("scheduling.table.end") }],
emptyText: t("scheduling.mine.empty", { email: opts.email }),
// Only when the upstream answered: a failed read knows nothing about what is assigned.
...(opts.error === undefined ? { emptyText: t("scheduling.mine.empty", { email: opts.email }) } : {}),
rows: opts.shifts.map((s) => ({ cells: [{ rowHeader: { text: s.title } }, s.start, s.end], name: s.title })),
},
title: t("scheduling.mine.title"),
+1 -2
View File
@@ -1,6 +1,5 @@
<%#
Scheduling · the visitor's own shifts (reference plugin). Reached behind `session: true`, so
ctx.user is always set by the time this renders.
Scheduling · the visitor's own shifts (reference plugin).
Data: chrome, title, breadcrumbs, count, table, error?
%><%
const navHtml = include("partials/nav-tree", { nodes: chrome.nav });