This commit is contained in:
mikecarper
2026-09-16 21:35:54 +00:00
parent b830d6807d
commit 6ba1e3340c
4 changed files with 61 additions and 18 deletions
+37 -13
View File
@@ -13,6 +13,8 @@
7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250, 500,
]);
const SCHEDULE_HORIZON_MS = 0x7fffffff;
const EARLY_JOIN_MS = 60 * 60 * 1000;
const CLOCK_RESET_COMMAND = "clkreboot";
class PresetTestError extends Error {}
@@ -121,6 +123,10 @@
return Math.max(0, Math.ceil((config.endMs - nowMs) / 60000));
}
function immediateAvailable(config, nowMs) {
return nowMs >= config.startMs - EARLY_JOIN_MS && nowMs < config.endMs;
}
function scheduleAvailability(config, nowMs) {
if (nowMs >= config.startMs) {
return { available: false, reason: "The start time has passed; use the immediate option." };
@@ -302,6 +308,7 @@
setCommand(root, "stock-cancel-during", staticCommands.stockCancelDuring);
setCommand(root, "companion-cancel-before", staticCommands.companionCancelBefore);
setCommand(root, "companion-cancel-during", staticCommands.companionCancelDuring);
setCommand(root, "reset-clock", CLOCK_RESET_COMMAND);
setCommand(
root,
"share-url",
@@ -321,16 +328,28 @@
function render() {
const nowMs = Date.now();
const phase = phaseAt(config, nowMs);
const immediateOpen = immediateAvailable(config, nowMs);
const status = root.querySelector('[data-role="status"]');
const schedule = scheduleAvailability(config, nowMs);
const commands = commandsFor(config, nowMs);
status.dataset.state = phase;
if (phase === "before") {
status.textContent = "Scheduled";
setText(root, '[data-role="countdown-label"]', "Starts in");
setText(root, '[data-role="countdown"]', formatCountdown(config.startMs - nowMs));
setText(root, '[data-role="countdown-detail"]', "Do not use immediate TempRadio yet.");
if (immediateOpen) {
status.textContent = "Setup window open";
setText(root, '[data-role="countdown-label"]', "Test starts in");
setText(root, '[data-role="countdown"]', formatCountdown(config.startMs - nowMs));
setText(root, '[data-role="countdown-detail"]', "Immediate TempRadio commands are enabled.");
} else {
status.textContent = "Scheduled";
setText(root, '[data-role="countdown-label"]', "Commands open in");
setText(
root,
'[data-role="countdown"]',
formatCountdown(config.startMs - EARLY_JOIN_MS - nowMs)
);
setText(root, '[data-role="countdown-detail"]', "The setup window opens one hour before the test.");
}
} else if (phase === "active") {
status.textContent = "Test live";
setText(root, '[data-role="countdown-label"]', "Ends in");
@@ -343,25 +362,27 @@
setText(root, '[data-role="countdown-detail"]', "Temporary radios should be back on saved settings.");
}
if (phase === "active") {
if (immediateOpen) {
setCommand(root, "stock-now", commands.stockNow);
setCommand(root, "companion-now", commands.companionNow);
} else {
const unavailable = phase === "before"
? "Available when the test starts — use Option 2 to schedule now."
? "Available one hour before the test — use Option 2 to schedule now."
: "Test window ended — do not start TempRadio.";
setCommand(root, "stock-now", unavailable);
setCommand(root, "companion-now", unavailable);
}
setCommandEnabled(root, "stock-now", phase === "active");
setCommandEnabled(root, "companion-now", phase === "active");
setMaterialCommandCopyEnabled(root, "stock-now", phase === "active");
setMaterialCommandCopyEnabled(root, "companion-now", phase === "active");
setCommandEnabled(root, "stock-now", immediateOpen);
setCommandEnabled(root, "companion-now", immediateOpen);
setMaterialCommandCopyEnabled(root, "stock-now", immediateOpen);
setMaterialCommandCopyEnabled(root, "companion-now", immediateOpen);
setText(
root,
'[data-role="stock-now-note"]',
phase === "before"
? "Available when the test starts; schedule it now with Option 2."
phase === "before" && !immediateOpen
? "Available one hour before the test; schedule it now with Option 2."
: phase === "before"
? "The setup window is open; the timeout includes the hour before the official start."
: phase === "active"
? "The final argument is the live minutes remaining until the common end."
: "The test window has ended."
@@ -380,7 +401,7 @@
render();
if (typeof global.MutationObserver === "function") {
const materialCopyObserver = new global.MutationObserver(function () {
const enabled = phaseAt(config, Date.now()) === "active";
const enabled = immediateAvailable(config, Date.now());
setMaterialCommandCopyEnabled(root, "stock-now", enabled);
setMaterialCommandCopyEnabled(root, "companion-now", enabled);
});
@@ -393,10 +414,13 @@
DEFAULTS: DEFAULTS,
VALID_BANDWIDTHS: VALID_BANDWIDTHS,
SCHEDULE_HORIZON_MS: SCHEDULE_HORIZON_MS,
EARLY_JOIN_MS: EARLY_JOIN_MS,
CLOCK_RESET_COMMAND: CLOCK_RESET_COMMAND,
PresetTestError: PresetTestError,
configFromSearch: configFromSearch,
phaseAt: phaseAt,
remainingMinutes: remainingMinutes,
immediateAvailable: immediateAvailable,
scheduleAvailability: scheduleAvailability,
commandsFor: commandsFor,
formatCountdown: formatCountdown,
+4
View File
@@ -163,6 +163,10 @@
background: var(--md-default-bg-color);
}
.preset-test-card--wide {
grid-column: 1 / -1;
}
.preset-test-card h3 {
margin-top: 0;
}
+19 -4
View File
@@ -2892,6 +2892,20 @@ clock</code></pre>
clock</code></pre>
<button type="button" data-copy-command="set-clock">Copy current clock command</button>
</article>
<article class="preset-test-card preset-test-card--wide">
<h3>If the clock is ahead and cannot move backward</h3>
<p>
If setting the correct time returns <code>ERR: clock cannot go backwards</code>,
run the command below. It resets the clock to an older fallback date and
reboots the node immediately, so it sends no reply. After the node reconnects,
copy and run the fresh local clock command above, verify with <code>clock</code>,
and then add the <code>tempradioat</code> schedule again. The reboot clears any
pending scheduled entries.
</p>
<pre><code data-command="reset-clock">clkreboot</code></pre>
<button type="button" data-copy-command="reset-clock">Copy clock reset command</button>
</article>
</div>
<p class="preset-test-note">
@@ -2902,11 +2916,12 @@ clock</code></pre>
</section>
<section aria-labelledby="join-now-title">
<h2 id="join-now-title">Option 1: switch when the test begins</h2>
<h2 id="join-now-title">Option 1: switch during the setup window</h2>
<p>
Open this page after the start time. Its timeout shrinks so every node
returns at the same end time. These copy buttons remain disabled before
the window to prevent an early switch.
The immediate commands become available one hour before the official
start. Their timeout shrinks so every node still returns at the same end
time. Before that setup window, the command text and copy controls stay
unavailable.
</p>
<div class="preset-test-grid">
File diff suppressed because one or more lines are too long