Explain display setup in Settings, stop iOS form zoom, lock the page behind the drawer
CI / verify (pull_request) Successful in 1m12s
CI / infrastructure-smoke (pull_request) Successful in 49s
CI / package-release (pull_request) Successful in 47s
CI / migration-smoke (pull_request) Successful in 2m18s
CI / image-build (pull_request) Successful in 1m3s
CI / deploy-smoke (same-origin) (pull_request) Successful in 49s
CI / package-publication (pull_request) Successful in 54s
CI / deploy-smoke (split) (pull_request) Successful in 3m38s
CI / verify (pull_request) Successful in 1m12s
CI / infrastructure-smoke (pull_request) Successful in 49s
CI / package-release (pull_request) Successful in 47s
CI / migration-smoke (pull_request) Successful in 2m18s
CI / image-build (pull_request) Successful in 1m3s
CI / deploy-smoke (same-origin) (pull_request) Successful in 49s
CI / package-publication (pull_request) Successful in 54s
CI / deploy-smoke (split) (pull_request) Successful in 3m38s
A second real-iPhone review of the Stage 1 Dashboard found three defects. This fixes them, and ADR 0065 records the decisions. - Settings -> Displays now sets up a screen in one place. A "Set up a new screen" panel shows the platform display page to open on the screen, a Copy link button, and Add display directly beneath it; the step-by-step version is folded into a <details>. The addresses come from a new GET /api/v1/devices/setup, which requires core.devices.manage and lists each running display module's /display/<module-id> page. Nothing names Signage. With no display module installed, the panel says so and offers no Add display. - iOS Safari zoomed into the pairing field because the shared .ek-control rendered at 14px. On phones and touch screens it now renders at max(1rem, 16px). The viewport is untouched, so zoom stays available, and a test guards that. The shared focus trap now returns focus with preventScroll, so closing a dialog no longer moves the page under the phone header. - While the phone drawer is open, the page behind it no longer scrolls. The root's overflow is hidden, a non-passive touchmove listener cancels drags outside the drawer's list and overscroll past its ends, and root overscroll is turned off. The list itself still scrolls. Closing restores the exact scroll position unless another page opened. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5f9b536bc6
commit
906c015ca8
@@ -13,6 +13,7 @@ import {
|
||||
requireTrustedOrigin,
|
||||
} from "./authorization-middleware.js";
|
||||
import { CORE_MUTATION_MAX_BODY_BYTES, platformBodyLimit } from "./body-limits.js";
|
||||
import { moduleDisplayPageUrl } from "./module-display-path.js";
|
||||
|
||||
/**
|
||||
* Host-owned device identity transport: pairing a display, and managing what has been paired
|
||||
@@ -315,6 +316,32 @@ export function createDeviceRoutes(options: DeviceRoutesOptions): Hono {
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* How a new screen starts pairing (ADR 0065): the platform display page for every running module
|
||||
* that shows content on screens, for Settings → Displays to tell an administrator what to open on
|
||||
* the screen. Each page is the generic display host at `/display/<module-id>` (ADR 0033), which
|
||||
* shows a pairing code until the screen is approved.
|
||||
*
|
||||
* Administrative, like the device list beside it: `core.devices.manage`, and never anonymous, so
|
||||
* this does not become a way to enumerate what is installed — an unpaired screen still learns
|
||||
* nothing beyond the page it was told to open. It answers names and root-relative page paths only;
|
||||
* the browser resolves them against the API origin, which is where the display host lives in both
|
||||
* topologies (ADR 0045). An empty list is a real answer: no installed module uses displays yet.
|
||||
*/
|
||||
routes.get(`${DEVICES_PREFIX}/setup`, requireManage, (c: Context) => {
|
||||
noStore(c);
|
||||
return c.json(
|
||||
{
|
||||
displays: options.moduleUi.listDisplays().map((display) => ({
|
||||
moduleId: display.moduleId,
|
||||
name: display.name,
|
||||
url: moduleDisplayPageUrl(display.moduleId),
|
||||
})),
|
||||
},
|
||||
200,
|
||||
);
|
||||
});
|
||||
|
||||
routes.get(DEVICES_PREFIX, requireManage, async (c: Context) => {
|
||||
try {
|
||||
const devices = await options.devices.listDevices();
|
||||
|
||||
@@ -419,6 +419,27 @@ describe.sequential("display device pairing, end to end", () => {
|
||||
expect((await device.request("/display/announcements")).status).toBe(200);
|
||||
expect((await device.request("/api/v1/module-displays/announcements")).status).toBe(403);
|
||||
|
||||
// How an administrator learns which page to open on that screen (ADR 0065): the platform
|
||||
// display page of every module that uses displays, named, and only for an administrator. The
|
||||
// screen itself — still anonymous — is refused, so setup never enumerates what is installed.
|
||||
const setup = await adminRequest(
|
||||
running.origin,
|
||||
running.ownerCookie,
|
||||
"/api/v1/devices/setup",
|
||||
);
|
||||
expect(setup.status).toBe(200);
|
||||
expect(setup.headers.get("cache-control")).toBe("no-store");
|
||||
expect(await setup.json()).toEqual({
|
||||
displays: [
|
||||
{
|
||||
moduleId: "announcements",
|
||||
name: "Announcements fixture",
|
||||
url: "/display/announcements",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect((await device.request("/api/v1/devices/setup")).status).toBe(403);
|
||||
|
||||
// 2. It asks to be paired and receives a code plus an HttpOnly claim cookie.
|
||||
const requested = await device.post("/api/v1/device-pairing/requests", {
|
||||
moduleId: "announcements",
|
||||
@@ -501,6 +522,7 @@ describe.sequential("display device pairing, end to end", () => {
|
||||
});
|
||||
expect((await device.request("/api/v1/dashboard/navigation")).status).toBe(403);
|
||||
expect((await device.request("/api/v1/devices")).status).toBe(403);
|
||||
expect((await device.request("/api/v1/devices/setup")).status).toBe(403);
|
||||
|
||||
// 9. The administrator revokes it.
|
||||
const devices = (await (
|
||||
|
||||
Reference in New Issue
Block a user