@@ -10740,23 +10740,26 @@ window.app.modules.filtering = {
}
}());
-// shared/elevation.js — admin elevation toggle.
+// shared/elevation.js — admin elevation via URL toggle.
//
-// Sudo-style model: admins behave as normal users by default; clicking
-// the header toggle elevates the session so admin escape hatches (WORM
-// bypass, .zddc edit authority, profile admin scaffolds) start firing.
-// State is carried in a `zddc-elevate=1` cookie that the server reads
-// via handler.ACLMiddleware → zddc.Principal{Elevated}.
+// Sudo-style model: admins behave as normal users by default; elevating
+// the session turns on admin escape hatches (WORM bypass, .zddc edit
+// authority, profile admin scaffolds). State is carried in a
+// `zddc-elevate=1` cookie that the server reads via handler.ACLMiddleware
+// → zddc.Principal{Elevated}.
//
-// Only renders the toggle when /.profile/access reports the caller has
-// some admin scope — a non-admin sees nothing, which keeps the chrome
-// quiet for the common case. The toggle fades in once access loads so
-// non-admins never even see the affordance flash.
+// Toggle is by URL query param — `?admin=true` to arm, `?admin=false`
+// (or the red banner's "Drop admin" button) to drop — so it's reachable
+// from ANY zddc-server page, not just ones that render a header control.
+// The cookie is the sticky state: it persists across navigation for its
+// Max-Age window, so the param need not stay in the URL (we strip it).
+// Arming is gated on /.profile/access `can_elevate`, so only real admins
+// can set it; a non-admin's ?admin=true is a silent no-op.
//
-// Click flow: set/clear the cookie, then reload the page so the server
-// sees the new state on the next render. The reload is intentional —
-// admin scaffolds in tool HTML are server-rendered for some tools, so
-// a soft state flip on the client alone wouldn't reach those.
+// Applying the cookie reloads to the cleaned URL so the server re-renders
+// under the new state (admin scaffolds in some tool HTML are server-
+// rendered, so a client-only flip wouldn't reach them). The red viewport
+// border + banner (applyArmedChrome) reflect the cookie on every load.
(function () {
'use strict';
@@ -10801,22 +10804,65 @@ window.app.modules.filtering = {
}
}
- function render(host, elevated) {
- host.classList.remove('hidden');
- host.innerHTML =
- ''
- + '';
- var cb = host.querySelector('#elevation-checkbox');
- cb.addEventListener('change', function () {
- setElevated(cb.checked);
- // Hard reload so server-rendered admin surfaces (profile
- // page scaffolds, hidden-entry listings) catch up. URL
- // and scroll state are preserved by the browser's normal
- // back-forward cache rules.
- window.location.reload();
- });
+ // ── URL toggle: ?admin=true | ?admin=false (typeable anywhere) ──────
+ //
+ // Admin mode is toggled via a URL query param rather than an on-screen
+ // checkbox, so it's reachable from any zddc-server page. The param only
+ // SETS the cookie; the cookie is the sticky state (it persists across
+ // navigation for its Max-Age window and is what the server reads), so
+ // there's no need to keep ?admin= in the URL once applied.
+
+ // adminParam returns true/false for a recognised ?admin= value, or null
+ // when absent / unrecognised (ignored).
+ function adminParam() {
+ try {
+ var v = new URLSearchParams(window.location.search).get('admin');
+ if (v === null) return null;
+ v = v.toLowerCase();
+ if (v === 'true' || v === '1' || v === 'on' || v === 'yes') return true;
+ if (v === 'false' || v === '0' || v === 'off' || v === 'no') return false;
+ return null;
+ } catch (_e) { return null; }
+ }
+
+ // urlWithoutAdmin returns the current URL with the admin param stripped
+ // (other params + hash preserved) — what we navigate/replace to so the
+ // dirty param isn't bookmarked and Back doesn't re-trigger it.
+ function urlWithoutAdmin() {
+ var u = new URL(window.location.href);
+ u.searchParams.delete('admin');
+ var qs = u.searchParams.toString();
+ return u.pathname + (qs ? '?' + qs : '') + u.hash;
+ }
+
+ // handleAdminParam applies a ?admin= request. Returns true when a
+ // navigation (reload) is underway so the caller can stop. Enabling is
+ // gated on can_elevate — a non-admin who types ?admin=true just gets
+ // the param stripped, never a misleading red border. Disabling is open
+ // (anyone may drop a cookie they somehow hold).
+ async function handleAdminParam() {
+ var want = adminParam();
+ if (want === null) return false;
+ var clean = urlWithoutAdmin();
+ if (want === isElevated()) {
+ // Already in the requested state — just clean the URL, no reload.
+ try { history.replaceState(history.state, '', clean); } catch (_e) {}
+ return false;
+ }
+ if (want === true) {
+ var access = await fetchAccess();
+ if (!access || !access.can_elevate) {
+ try { history.replaceState(history.state, '', clean); } catch (_e) {}
+ return false;
+ }
+ setElevated(true);
+ } else {
+ setElevated(false);
+ }
+ // Navigate to the clean URL (a real load, so the server re-renders
+ // under the new cookie) and replace history so Back is safe.
+ window.location.replace(clean);
+ return true;
}
// Page-wide affordances when elevation is active. The toggle alone
@@ -10858,26 +10904,16 @@ window.app.modules.filtering = {
}
async function init() {
- // Body chrome applies on every page load whether or not the
- // header has a toggle slot — the banner needs to surface in
- // tools / pages that don't host the toggle (e.g. iframed
- // classifier inside browse's grid mode), so the user can't
- // accidentally write through an elevated context elsewhere.
+ // Apply (or tear down) the red border + banner from the cookie on
+ // every page load — admin mode is toggled by URL, but the armed
+ // chrome must surface everywhere so the user can't accidentally
+ // write through an elevated context on a page they didn't toggle.
applyArmedChrome(isElevated());
- var host = document.getElementById('elevation-toggle');
- if (!host) return; // tool doesn't include the slot yet — no-op
- var access = await fetchAccess();
- if (!access) return; // anonymous / endpoint missing — no-op
- // Surface ONLY for users who have admin authority somewhere.
- // /.profile/access ships `can_elevate` as an elevation-
- // INDEPENDENT signal — true for any user named in any admin
- // list, regardless of current cookie state. The other flags
- // (is_super_admin, has_any_admin_scope) reflect EFFECTIVE
- // authority and would be false for an un-elevated admin
- // who hasn't toggled yet — so we can't gate on those.
- if (!access.can_elevate) return;
- render(host, isElevated());
+ // Honour ?admin=true|false typed into any zddc-server URL. There's
+ // no on-screen toggle anymore — the URL is the enable path and the
+ // red banner's "Drop admin" button is the one-click disable.
+ await handleAdminParam();
}
if (document.readyState === 'loading') {
diff --git a/zddc/internal/apps/embedded/browse.html b/zddc/internal/apps/embedded/browse.html
index db04d6a..976946d 100644
--- a/zddc/internal/apps/embedded/browse.html
+++ b/zddc/internal/apps/embedded/browse.html
@@ -2476,7 +2476,7 @@ body {
@@ -5967,23 +5967,26 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
window.zddc.menu = { open: open, close: close };
})();
-// shared/elevation.js — admin elevation toggle.
+// shared/elevation.js — admin elevation via URL toggle.
//
-// Sudo-style model: admins behave as normal users by default; clicking
-// the header toggle elevates the session so admin escape hatches (WORM
-// bypass, .zddc edit authority, profile admin scaffolds) start firing.
-// State is carried in a `zddc-elevate=1` cookie that the server reads
-// via handler.ACLMiddleware → zddc.Principal{Elevated}.
+// Sudo-style model: admins behave as normal users by default; elevating
+// the session turns on admin escape hatches (WORM bypass, .zddc edit
+// authority, profile admin scaffolds). State is carried in a
+// `zddc-elevate=1` cookie that the server reads via handler.ACLMiddleware
+// → zddc.Principal{Elevated}.
//
-// Only renders the toggle when /.profile/access reports the caller has
-// some admin scope — a non-admin sees nothing, which keeps the chrome
-// quiet for the common case. The toggle fades in once access loads so
-// non-admins never even see the affordance flash.
+// Toggle is by URL query param — `?admin=true` to arm, `?admin=false`
+// (or the red banner's "Drop admin" button) to drop — so it's reachable
+// from ANY zddc-server page, not just ones that render a header control.
+// The cookie is the sticky state: it persists across navigation for its
+// Max-Age window, so the param need not stay in the URL (we strip it).
+// Arming is gated on /.profile/access `can_elevate`, so only real admins
+// can set it; a non-admin's ?admin=true is a silent no-op.
//
-// Click flow: set/clear the cookie, then reload the page so the server
-// sees the new state on the next render. The reload is intentional —
-// admin scaffolds in tool HTML are server-rendered for some tools, so
-// a soft state flip on the client alone wouldn't reach those.
+// Applying the cookie reloads to the cleaned URL so the server re-renders
+// under the new state (admin scaffolds in some tool HTML are server-
+// rendered, so a client-only flip wouldn't reach them). The red viewport
+// border + banner (applyArmedChrome) reflect the cookie on every load.
(function () {
'use strict';
@@ -6028,22 +6031,65 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
}
}
- function render(host, elevated) {
- host.classList.remove('hidden');
- host.innerHTML =
- ''
- + '';
- var cb = host.querySelector('#elevation-checkbox');
- cb.addEventListener('change', function () {
- setElevated(cb.checked);
- // Hard reload so server-rendered admin surfaces (profile
- // page scaffolds, hidden-entry listings) catch up. URL
- // and scroll state are preserved by the browser's normal
- // back-forward cache rules.
- window.location.reload();
- });
+ // ── URL toggle: ?admin=true | ?admin=false (typeable anywhere) ──────
+ //
+ // Admin mode is toggled via a URL query param rather than an on-screen
+ // checkbox, so it's reachable from any zddc-server page. The param only
+ // SETS the cookie; the cookie is the sticky state (it persists across
+ // navigation for its Max-Age window and is what the server reads), so
+ // there's no need to keep ?admin= in the URL once applied.
+
+ // adminParam returns true/false for a recognised ?admin= value, or null
+ // when absent / unrecognised (ignored).
+ function adminParam() {
+ try {
+ var v = new URLSearchParams(window.location.search).get('admin');
+ if (v === null) return null;
+ v = v.toLowerCase();
+ if (v === 'true' || v === '1' || v === 'on' || v === 'yes') return true;
+ if (v === 'false' || v === '0' || v === 'off' || v === 'no') return false;
+ return null;
+ } catch (_e) { return null; }
+ }
+
+ // urlWithoutAdmin returns the current URL with the admin param stripped
+ // (other params + hash preserved) — what we navigate/replace to so the
+ // dirty param isn't bookmarked and Back doesn't re-trigger it.
+ function urlWithoutAdmin() {
+ var u = new URL(window.location.href);
+ u.searchParams.delete('admin');
+ var qs = u.searchParams.toString();
+ return u.pathname + (qs ? '?' + qs : '') + u.hash;
+ }
+
+ // handleAdminParam applies a ?admin= request. Returns true when a
+ // navigation (reload) is underway so the caller can stop. Enabling is
+ // gated on can_elevate — a non-admin who types ?admin=true just gets
+ // the param stripped, never a misleading red border. Disabling is open
+ // (anyone may drop a cookie they somehow hold).
+ async function handleAdminParam() {
+ var want = adminParam();
+ if (want === null) return false;
+ var clean = urlWithoutAdmin();
+ if (want === isElevated()) {
+ // Already in the requested state — just clean the URL, no reload.
+ try { history.replaceState(history.state, '', clean); } catch (_e) {}
+ return false;
+ }
+ if (want === true) {
+ var access = await fetchAccess();
+ if (!access || !access.can_elevate) {
+ try { history.replaceState(history.state, '', clean); } catch (_e) {}
+ return false;
+ }
+ setElevated(true);
+ } else {
+ setElevated(false);
+ }
+ // Navigate to the clean URL (a real load, so the server re-renders
+ // under the new cookie) and replace history so Back is safe.
+ window.location.replace(clean);
+ return true;
}
// Page-wide affordances when elevation is active. The toggle alone
@@ -6085,26 +6131,16 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
}
async function init() {
- // Body chrome applies on every page load whether or not the
- // header has a toggle slot — the banner needs to surface in
- // tools / pages that don't host the toggle (e.g. iframed
- // classifier inside browse's grid mode), so the user can't
- // accidentally write through an elevated context elsewhere.
+ // Apply (or tear down) the red border + banner from the cookie on
+ // every page load — admin mode is toggled by URL, but the armed
+ // chrome must surface everywhere so the user can't accidentally
+ // write through an elevated context on a page they didn't toggle.
applyArmedChrome(isElevated());
- var host = document.getElementById('elevation-toggle');
- if (!host) return; // tool doesn't include the slot yet — no-op
- var access = await fetchAccess();
- if (!access) return; // anonymous / endpoint missing — no-op
- // Surface ONLY for users who have admin authority somewhere.
- // /.profile/access ships `can_elevate` as an elevation-
- // INDEPENDENT signal — true for any user named in any admin
- // list, regardless of current cookie state. The other flags
- // (is_super_admin, has_any_admin_scope) reflect EFFECTIVE
- // authority and would be false for an un-elevated admin
- // who hasn't toggled yet — so we can't gate on those.
- if (!access.can_elevate) return;
- render(host, isElevated());
+ // Honour ?admin=true|false typed into any zddc-server URL. There's
+ // no on-screen toggle anymore — the URL is the enable path and the
+ // red banner's "Drop admin" button is the one-click disable.
+ await handleAdminParam();
}
if (document.readyState === 'loading') {
@@ -11742,8 +11778,8 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
// equivalent.
//
// Talks to the zddc-server history endpoints on the file's own URL:
-// GET ?history=1 → JSON [{ts, by, sha, prev, bytes, current}]
-// GET ?history= → that version's raw bytes
+// GET ?history=1 → JSON [{ts, by, id, bytes, current}]
+// GET ?history= → that version's raw bytes (id = snapshot filename)
// Restore re-PUTs a chosen version's bytes to , which the server
// records as a new version (forward-only; never destructive).
//
@@ -11803,8 +11839,8 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
return Array.isArray(data) ? data : [];
}
- async function fetchVersion(node, sha) {
- var resp = await fetch(histURL(node.url, sha), { credentials: 'same-origin' });
+ async function fetchVersion(node, id) {
+ var resp = await fetch(histURL(node.url, id), { credentials: 'same-origin' });
if (!resp.ok) throw new Error('HTTP ' + resp.status);
return await resp.text();
}
@@ -11907,17 +11943,17 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
cb.className = 'md-history-pick';
cb.addEventListener('change', function () {
if (cb.checked) {
- selected.push(ent.sha);
+ selected.push(ent.id);
// Keep at most two: drop the oldest selection.
if (selected.length > 2) {
var dropped = selected.shift();
var others = list.querySelectorAll('.md-history-pick');
others.forEach(function (o, i) {
- if (o !== cb && entries[i] && entries[i].sha === dropped) o.checked = false;
+ if (o !== cb && entries[i] && entries[i].id === dropped) o.checked = false;
});
}
} else {
- selected = selected.filter(function (s) { return s !== ent.sha; });
+ selected = selected.filter(function (s) { return s !== ent.id; });
}
syncDiffBtn();
});
@@ -11956,7 +11992,7 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
if (selected.length !== 2) return;
// Order oldest→newest by the entries' position (newest
// first in the list), so the diff reads old → new.
- var picks = entries.filter(function (e) { return selected.indexOf(e.sha) !== -1; });
+ var picks = entries.filter(function (e) { return selected.indexOf(e.id) !== -1; });
picks.sort(function (a, b) { return (a.ts < b.ts ? -1 : 1); });
renderDiff(modal, node, picks[0], picks[1], entries);
}
@@ -11973,7 +12009,7 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
body.innerHTML = '
Loading…
';
var text;
try {
- text = await fetchVersion(node, ent.sha);
+ text = await fetchVersion(node, ent.id);
} catch (e) {
body.innerHTML = '';
var err = document.createElement('p');
@@ -12010,8 +12046,8 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
body.innerHTML = '
@@ -9864,23 +9864,26 @@ X.B(E,Y);return E}return J}())
}
}());
-// shared/elevation.js — admin elevation toggle.
+// shared/elevation.js — admin elevation via URL toggle.
//
-// Sudo-style model: admins behave as normal users by default; clicking
-// the header toggle elevates the session so admin escape hatches (WORM
-// bypass, .zddc edit authority, profile admin scaffolds) start firing.
-// State is carried in a `zddc-elevate=1` cookie that the server reads
-// via handler.ACLMiddleware → zddc.Principal{Elevated}.
+// Sudo-style model: admins behave as normal users by default; elevating
+// the session turns on admin escape hatches (WORM bypass, .zddc edit
+// authority, profile admin scaffolds). State is carried in a
+// `zddc-elevate=1` cookie that the server reads via handler.ACLMiddleware
+// → zddc.Principal{Elevated}.
//
-// Only renders the toggle when /.profile/access reports the caller has
-// some admin scope — a non-admin sees nothing, which keeps the chrome
-// quiet for the common case. The toggle fades in once access loads so
-// non-admins never even see the affordance flash.
+// Toggle is by URL query param — `?admin=true` to arm, `?admin=false`
+// (or the red banner's "Drop admin" button) to drop — so it's reachable
+// from ANY zddc-server page, not just ones that render a header control.
+// The cookie is the sticky state: it persists across navigation for its
+// Max-Age window, so the param need not stay in the URL (we strip it).
+// Arming is gated on /.profile/access `can_elevate`, so only real admins
+// can set it; a non-admin's ?admin=true is a silent no-op.
//
-// Click flow: set/clear the cookie, then reload the page so the server
-// sees the new state on the next render. The reload is intentional —
-// admin scaffolds in tool HTML are server-rendered for some tools, so
-// a soft state flip on the client alone wouldn't reach those.
+// Applying the cookie reloads to the cleaned URL so the server re-renders
+// under the new state (admin scaffolds in some tool HTML are server-
+// rendered, so a client-only flip wouldn't reach them). The red viewport
+// border + banner (applyArmedChrome) reflect the cookie on every load.
(function () {
'use strict';
@@ -9925,22 +9928,65 @@ X.B(E,Y);return E}return J}())
}
}
- function render(host, elevated) {
- host.classList.remove('hidden');
- host.innerHTML =
- ''
- + '';
- var cb = host.querySelector('#elevation-checkbox');
- cb.addEventListener('change', function () {
- setElevated(cb.checked);
- // Hard reload so server-rendered admin surfaces (profile
- // page scaffolds, hidden-entry listings) catch up. URL
- // and scroll state are preserved by the browser's normal
- // back-forward cache rules.
- window.location.reload();
- });
+ // ── URL toggle: ?admin=true | ?admin=false (typeable anywhere) ──────
+ //
+ // Admin mode is toggled via a URL query param rather than an on-screen
+ // checkbox, so it's reachable from any zddc-server page. The param only
+ // SETS the cookie; the cookie is the sticky state (it persists across
+ // navigation for its Max-Age window and is what the server reads), so
+ // there's no need to keep ?admin= in the URL once applied.
+
+ // adminParam returns true/false for a recognised ?admin= value, or null
+ // when absent / unrecognised (ignored).
+ function adminParam() {
+ try {
+ var v = new URLSearchParams(window.location.search).get('admin');
+ if (v === null) return null;
+ v = v.toLowerCase();
+ if (v === 'true' || v === '1' || v === 'on' || v === 'yes') return true;
+ if (v === 'false' || v === '0' || v === 'off' || v === 'no') return false;
+ return null;
+ } catch (_e) { return null; }
+ }
+
+ // urlWithoutAdmin returns the current URL with the admin param stripped
+ // (other params + hash preserved) — what we navigate/replace to so the
+ // dirty param isn't bookmarked and Back doesn't re-trigger it.
+ function urlWithoutAdmin() {
+ var u = new URL(window.location.href);
+ u.searchParams.delete('admin');
+ var qs = u.searchParams.toString();
+ return u.pathname + (qs ? '?' + qs : '') + u.hash;
+ }
+
+ // handleAdminParam applies a ?admin= request. Returns true when a
+ // navigation (reload) is underway so the caller can stop. Enabling is
+ // gated on can_elevate — a non-admin who types ?admin=true just gets
+ // the param stripped, never a misleading red border. Disabling is open
+ // (anyone may drop a cookie they somehow hold).
+ async function handleAdminParam() {
+ var want = adminParam();
+ if (want === null) return false;
+ var clean = urlWithoutAdmin();
+ if (want === isElevated()) {
+ // Already in the requested state — just clean the URL, no reload.
+ try { history.replaceState(history.state, '', clean); } catch (_e) {}
+ return false;
+ }
+ if (want === true) {
+ var access = await fetchAccess();
+ if (!access || !access.can_elevate) {
+ try { history.replaceState(history.state, '', clean); } catch (_e) {}
+ return false;
+ }
+ setElevated(true);
+ } else {
+ setElevated(false);
+ }
+ // Navigate to the clean URL (a real load, so the server re-renders
+ // under the new cookie) and replace history so Back is safe.
+ window.location.replace(clean);
+ return true;
}
// Page-wide affordances when elevation is active. The toggle alone
@@ -9982,26 +10028,16 @@ X.B(E,Y);return E}return J}())
}
async function init() {
- // Body chrome applies on every page load whether or not the
- // header has a toggle slot — the banner needs to surface in
- // tools / pages that don't host the toggle (e.g. iframed
- // classifier inside browse's grid mode), so the user can't
- // accidentally write through an elevated context elsewhere.
+ // Apply (or tear down) the red border + banner from the cookie on
+ // every page load — admin mode is toggled by URL, but the armed
+ // chrome must surface everywhere so the user can't accidentally
+ // write through an elevated context on a page they didn't toggle.
applyArmedChrome(isElevated());
- var host = document.getElementById('elevation-toggle');
- if (!host) return; // tool doesn't include the slot yet — no-op
- var access = await fetchAccess();
- if (!access) return; // anonymous / endpoint missing — no-op
- // Surface ONLY for users who have admin authority somewhere.
- // /.profile/access ships `can_elevate` as an elevation-
- // INDEPENDENT signal — true for any user named in any admin
- // list, regardless of current cookie state. The other flags
- // (is_super_admin, has_any_admin_scope) reflect EFFECTIVE
- // authority and would be false for an un-elevated admin
- // who hasn't toggled yet — so we can't gate on those.
- if (!access.can_elevate) return;
- render(host, isElevated());
+ // Honour ?admin=true|false typed into any zddc-server URL. There's
+ // no on-screen toggle anymore — the URL is the enable path and the
+ // red banner's "Drop admin" button is the one-click disable.
+ await handleAdminParam();
}
if (document.readyState === 'loading') {
diff --git a/zddc/internal/apps/embedded/index.html b/zddc/internal/apps/embedded/index.html
index 831e6ff..121f8df 100644
--- a/zddc/internal/apps/embedded/index.html
+++ b/zddc/internal/apps/embedded/index.html
@@ -1536,7 +1536,7 @@ body {
@@ -2546,23 +2546,26 @@ body {
}
}());
-// shared/elevation.js — admin elevation toggle.
+// shared/elevation.js — admin elevation via URL toggle.
//
-// Sudo-style model: admins behave as normal users by default; clicking
-// the header toggle elevates the session so admin escape hatches (WORM
-// bypass, .zddc edit authority, profile admin scaffolds) start firing.
-// State is carried in a `zddc-elevate=1` cookie that the server reads
-// via handler.ACLMiddleware → zddc.Principal{Elevated}.
+// Sudo-style model: admins behave as normal users by default; elevating
+// the session turns on admin escape hatches (WORM bypass, .zddc edit
+// authority, profile admin scaffolds). State is carried in a
+// `zddc-elevate=1` cookie that the server reads via handler.ACLMiddleware
+// → zddc.Principal{Elevated}.
//
-// Only renders the toggle when /.profile/access reports the caller has
-// some admin scope — a non-admin sees nothing, which keeps the chrome
-// quiet for the common case. The toggle fades in once access loads so
-// non-admins never even see the affordance flash.
+// Toggle is by URL query param — `?admin=true` to arm, `?admin=false`
+// (or the red banner's "Drop admin" button) to drop — so it's reachable
+// from ANY zddc-server page, not just ones that render a header control.
+// The cookie is the sticky state: it persists across navigation for its
+// Max-Age window, so the param need not stay in the URL (we strip it).
+// Arming is gated on /.profile/access `can_elevate`, so only real admins
+// can set it; a non-admin's ?admin=true is a silent no-op.
//
-// Click flow: set/clear the cookie, then reload the page so the server
-// sees the new state on the next render. The reload is intentional —
-// admin scaffolds in tool HTML are server-rendered for some tools, so
-// a soft state flip on the client alone wouldn't reach those.
+// Applying the cookie reloads to the cleaned URL so the server re-renders
+// under the new state (admin scaffolds in some tool HTML are server-
+// rendered, so a client-only flip wouldn't reach them). The red viewport
+// border + banner (applyArmedChrome) reflect the cookie on every load.
(function () {
'use strict';
@@ -2607,22 +2610,65 @@ body {
}
}
- function render(host, elevated) {
- host.classList.remove('hidden');
- host.innerHTML =
- ''
- + '';
- var cb = host.querySelector('#elevation-checkbox');
- cb.addEventListener('change', function () {
- setElevated(cb.checked);
- // Hard reload so server-rendered admin surfaces (profile
- // page scaffolds, hidden-entry listings) catch up. URL
- // and scroll state are preserved by the browser's normal
- // back-forward cache rules.
- window.location.reload();
- });
+ // ── URL toggle: ?admin=true | ?admin=false (typeable anywhere) ──────
+ //
+ // Admin mode is toggled via a URL query param rather than an on-screen
+ // checkbox, so it's reachable from any zddc-server page. The param only
+ // SETS the cookie; the cookie is the sticky state (it persists across
+ // navigation for its Max-Age window and is what the server reads), so
+ // there's no need to keep ?admin= in the URL once applied.
+
+ // adminParam returns true/false for a recognised ?admin= value, or null
+ // when absent / unrecognised (ignored).
+ function adminParam() {
+ try {
+ var v = new URLSearchParams(window.location.search).get('admin');
+ if (v === null) return null;
+ v = v.toLowerCase();
+ if (v === 'true' || v === '1' || v === 'on' || v === 'yes') return true;
+ if (v === 'false' || v === '0' || v === 'off' || v === 'no') return false;
+ return null;
+ } catch (_e) { return null; }
+ }
+
+ // urlWithoutAdmin returns the current URL with the admin param stripped
+ // (other params + hash preserved) — what we navigate/replace to so the
+ // dirty param isn't bookmarked and Back doesn't re-trigger it.
+ function urlWithoutAdmin() {
+ var u = new URL(window.location.href);
+ u.searchParams.delete('admin');
+ var qs = u.searchParams.toString();
+ return u.pathname + (qs ? '?' + qs : '') + u.hash;
+ }
+
+ // handleAdminParam applies a ?admin= request. Returns true when a
+ // navigation (reload) is underway so the caller can stop. Enabling is
+ // gated on can_elevate — a non-admin who types ?admin=true just gets
+ // the param stripped, never a misleading red border. Disabling is open
+ // (anyone may drop a cookie they somehow hold).
+ async function handleAdminParam() {
+ var want = adminParam();
+ if (want === null) return false;
+ var clean = urlWithoutAdmin();
+ if (want === isElevated()) {
+ // Already in the requested state — just clean the URL, no reload.
+ try { history.replaceState(history.state, '', clean); } catch (_e) {}
+ return false;
+ }
+ if (want === true) {
+ var access = await fetchAccess();
+ if (!access || !access.can_elevate) {
+ try { history.replaceState(history.state, '', clean); } catch (_e) {}
+ return false;
+ }
+ setElevated(true);
+ } else {
+ setElevated(false);
+ }
+ // Navigate to the clean URL (a real load, so the server re-renders
+ // under the new cookie) and replace history so Back is safe.
+ window.location.replace(clean);
+ return true;
}
// Page-wide affordances when elevation is active. The toggle alone
@@ -2664,26 +2710,16 @@ body {
}
async function init() {
- // Body chrome applies on every page load whether or not the
- // header has a toggle slot — the banner needs to surface in
- // tools / pages that don't host the toggle (e.g. iframed
- // classifier inside browse's grid mode), so the user can't
- // accidentally write through an elevated context elsewhere.
+ // Apply (or tear down) the red border + banner from the cookie on
+ // every page load — admin mode is toggled by URL, but the armed
+ // chrome must surface everywhere so the user can't accidentally
+ // write through an elevated context on a page they didn't toggle.
applyArmedChrome(isElevated());
- var host = document.getElementById('elevation-toggle');
- if (!host) return; // tool doesn't include the slot yet — no-op
- var access = await fetchAccess();
- if (!access) return; // anonymous / endpoint missing — no-op
- // Surface ONLY for users who have admin authority somewhere.
- // /.profile/access ships `can_elevate` as an elevation-
- // INDEPENDENT signal — true for any user named in any admin
- // list, regardless of current cookie state. The other flags
- // (is_super_admin, has_any_admin_scope) reflect EFFECTIVE
- // authority and would be false for an un-elevated admin
- // who hasn't toggled yet — so we can't gate on those.
- if (!access.can_elevate) return;
- render(host, isElevated());
+ // Honour ?admin=true|false typed into any zddc-server URL. There's
+ // no on-screen toggle anymore — the URL is the enable path and the
+ // red banner's "Drop admin" button is the one-click disable.
+ await handleAdminParam();
}
if (document.readyState === 'loading') {
diff --git a/zddc/internal/apps/embedded/transmittal.html b/zddc/internal/apps/embedded/transmittal.html
index 756a834..8a4672e 100644
--- a/zddc/internal/apps/embedded/transmittal.html
+++ b/zddc/internal/apps/embedded/transmittal.html
@@ -2635,7 +2635,7 @@ dialog.modal--narrow {