The legend's Export path gets the busy state the HUD already had
Found by the bug-CLASS sweep, which is the point of doing one: the busy indicator was added to the selection HUD after a report that pressing its buttons showed no sign of work, and the legend's Export menu -- Poster and Packet -- had none at all. Same defect, different surface, and I fixed the reported instance without looking for its siblings. Both do the same slow work: two dynamic imports, a map-SVG fetch, a funnel fetch, then a multi-sheet render. onLegendExport now guards re-entry (a second click would run two renders over one print frame), sets busyAction so the control says what it is doing, and clears in finally on every exit path including the early return inside the try. The gate's busy assertion had two holes that let this through, and both are fixed. It counted setters by matching the literal strings 'packet' and 'poster', so a ternary assignment was invisible -- a regex that enumerates the shapes it knows about will always miss the next one, so it now matches the assignment and excludes the clear. And it only asserted a whole-file count, which "some handler somewhere sets it" satisfies; it now asserts PER FUNCTION for all three print paths, so a surface cannot be forgotten while the file as a whole looks fine. Mutation-tested: removing the busy handling from onLegendExport now fails naming that function, and dropping a finally fails naming its own. Also drops a stale "page 3" from a shipped CSS comment -- page order is variant-dependent since lightweight started omitting two sheets, so a fixed page number in the output is wrong even where no reader sees it.
This commit is contained in:
parent
2c5fdbd7ac
commit
c54a11d5aa
3 changed files with 49 additions and 10 deletions
|
|
@ -86,10 +86,30 @@ ok(adm1Cases === 0,
|
||||||
// from an unresponsive one, so the label has to change — and it must be CLEARED in `finally`, or an
|
// from an unresponsive one, so the label has to change — and it must be CLEARED in `finally`, or an
|
||||||
// error leaves the button reading "Working…" forever.
|
// error leaves the button reading "Working…" forever.
|
||||||
ok(/busyAction/.test(VUE), 'the busy indicator is gone — a slow packet build will look like a dead button again')
|
ok(/busyAction/.test(VUE), 'the busy indicator is gone — a slow packet build will look like a dead button again')
|
||||||
|
// ⚠ COUNT EVERY SETTER, NOT JUST THE LITERAL ONES. The first version of this matched
|
||||||
|
// `busyAction.value = 'packet'|'poster'` and so was blind to a ternary assignment — which is exactly
|
||||||
|
// what the legend path uses. A regex that enumerates the shapes it knows about will always miss the
|
||||||
|
// next shape; match the ASSIGNMENT and exclude the clear.
|
||||||
const finallys = (VUE.match(/finally\s*\{[^}]*busyAction\.value\s*=\s*''/g) || []).length
|
const finallys = (VUE.match(/finally\s*\{[^}]*busyAction\.value\s*=\s*''/g) || []).length
|
||||||
const setters = (VUE.match(/busyAction\.value\s*=\s*'(packet|poster)'/g) || []).length
|
const setters = (VUE.match(/busyAction\.value\s*=\s*(?!'')/g) || []).length - finallys
|
||||||
ok(finallys >= setters && setters >= 2,
|
ok(finallys >= setters && setters >= 3,
|
||||||
`busyAction is set ${setters} times but cleared in only ${finallys} finally blocks — a failed print would leave the button stuck on "Working…"`)
|
`busyAction is set ${setters} times but cleared in only ${finallys} finally blocks — a failed print would leave the surface stuck on "Working…"`)
|
||||||
|
|
||||||
|
// ★ EVERY SLOW PRINT PATH, NOT JUST THE ONE THAT WAS REPORTED. The busy state was added to the
|
||||||
|
// selection HUD after a report that its buttons showed no sign of work; the LEGEND's Export path
|
||||||
|
// (onLegendExport → printPrayerPacket / printPoster) does the same slow work — two dynamic imports,
|
||||||
|
// a map-SVG fetch, a funnel fetch, a multi-sheet render — and had no busy state at all. It was found
|
||||||
|
// by sweeping for the CLASS rather than the instance, and this pins it so the surface cannot be
|
||||||
|
// forgotten again. Asserted per FUNCTION, because "some handler somewhere sets it" is what let the
|
||||||
|
// legend path stay uncovered while the file as a whole looked fine.
|
||||||
|
for (const fn of ['onLegendExport', 'makeSelectionPacket', 'makeSelectionPoster']) {
|
||||||
|
const body = VUE.match(new RegExp(`async function ${fn}\\s*\\([^)]*\\)\\s*\\{[\\s\\S]{0,2600}?\\n\\}`))
|
||||||
|
if (!body) { fails.push(`${fn}() not found — this gate can no longer see the print paths it guards`); continue }
|
||||||
|
ok(/busyAction\.value\s*=/.test(body[0]),
|
||||||
|
`${fn}() does slow work with NO busy state — a user gets no sign anything is happening, which is the defect already reported on the other print surface`)
|
||||||
|
ok(/finally\s*\{/.test(body[0]),
|
||||||
|
`${fn}() sets a busy state without a finally — a throw would leave the surface permanently busy and the control dead`)
|
||||||
|
}
|
||||||
|
|
||||||
if (fails.length) {
|
if (fails.length) {
|
||||||
console.error('SELECTION-KINDS: FAIL')
|
console.error('SELECTION-KINDS: FAIL')
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de
|
||||||
.lc-grid{display:grid;grid-template-columns:repeat(52,1fr);grid-auto-rows:13px;gap:2px;width:100%}
|
.lc-grid{display:grid;grid-template-columns:repeat(52,1fr);grid-auto-rows:13px;gap:2px;width:100%}
|
||||||
.lc-c{display:block;width:100%;height:100%;border:1px solid #d8d3c4;border-radius:1px;background:#fff}
|
.lc-c{display:block;width:100%;height:100%;border:1px solid #d8d3c4;border-radius:1px;background:#fff}
|
||||||
.lc-on{background:#b9b2a0;border-color:#b9b2a0}
|
.lc-on{background:#b9b2a0;border-color:#b9b2a0}
|
||||||
/* The shaded share on page 3 is the REACHED share, so it takes the accent colour and the
|
/* The shaded share on the %-evangelical grid is the REACHED share, so it takes the accent colour and the
|
||||||
unshaded majority stays quiet — the visual weight belongs to what is still waiting. */
|
unshaded majority stays quiet — the visual weight belongs to what is still waiting. */
|
||||||
.lc-reached{background:#2f6d4f;border-color:#2f6d4f}
|
.lc-reached{background:#2f6d4f;border-color:#2f6d4f}
|
||||||
.lc-legend{display:flex;gap:34px;margin:20px 0 0;font:400 17px Georgia,serif;color:#413d35}
|
.lc-legend{display:flex;gap:34px;margin:20px 0 0;font:400 17px Georgia,serif;color:#413d35}
|
||||||
|
|
|
||||||
|
|
@ -1305,15 +1305,34 @@ provide('onLegendExport', onLegendExport)
|
||||||
// PosterWall.vue, so this surface could not reach it. It now lives in usePosterPrints
|
// PosterWall.vue, so this surface could not reach it. It now lives in usePosterPrints
|
||||||
// (printPoster) and BOTH surfaces call the one copy — which is what the card asked for:
|
// (printPoster) and BOTH surfaces call the one copy — which is what the card asked for:
|
||||||
// "reuse the existing three print actions rather than growing a fourth implementation."
|
// "reuse the existing three print actions rather than growing a fourth implementation."
|
||||||
|
// ★ THE LEGEND'S EXPORT PATH GETS THE BUSY STATE TOO. The selection HUD's buttons were given one
|
||||||
|
// after a report that pressing them showed no sign of work; THIS surface — the legend's Export ▾ →
|
||||||
|
// Poster / Packet — had none either and was never looked at. It does the same slow work: two dynamic
|
||||||
|
// import()s, a map-SVG fetch, a funnel fetch, then a multi-sheet render. Found by sweeping for the
|
||||||
|
// CLASS of the reported bug rather than the instance, which is the whole point of doing that.
|
||||||
|
// `busyAction` is declared below, and that is safe: provide() at :1276 only passes the reference,
|
||||||
|
// and this body runs on a click long after setup has finished.
|
||||||
async function onLegendExport(kind) {
|
async function onLegendExport(kind) {
|
||||||
const zoneId = selectedKey.value;
|
const zoneId = selectedKey.value;
|
||||||
if (!zoneId) return; // the button only shows with a selection
|
if (!zoneId) return; // the button only shows with a selection
|
||||||
if (kind === 'packet') { await printPrayerPacket(zoneId); return; }
|
// Guard re-entry the way the HUD does: a second click while a print is building would run two
|
||||||
// The legend selects by KEY, while the print actions label by item — hand over the zone
|
// renders over one print frame.
|
||||||
// object so the print dialog's title says the list's name rather than its slug.
|
if (batchBusy.value) return;
|
||||||
// Same resolution printPrayerPacket uses, so both print actions name the list identically.
|
batchBusy.value = true;
|
||||||
const zone = allLists().find((z) => z.id === zoneId) || { id: zoneId };
|
busyAction.value = kind === 'packet' ? 'packet' : 'poster';
|
||||||
await packetPrints.printPoster(zone);
|
try {
|
||||||
|
if (kind === 'packet') { await printPrayerPacket(zoneId); return; }
|
||||||
|
// The legend selects by KEY, while the print actions label by item — hand over the zone
|
||||||
|
// object so the print dialog's title says the list's name rather than its slug.
|
||||||
|
// Same resolution printPrayerPacket uses, so both print actions name the list identically.
|
||||||
|
const zone = allLists().find((z) => z.id === zoneId) || { id: zoneId };
|
||||||
|
await packetPrints.printPoster(zone);
|
||||||
|
} finally {
|
||||||
|
// Cleared on EVERY exit path including the early `return` inside the try — a throw would
|
||||||
|
// otherwise leave the surface permanently busy and the menu dead.
|
||||||
|
batchBusy.value = false;
|
||||||
|
busyAction.value = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build and print a zone's prayer packet without leaving the page.
|
// Build and print a zone's prayer packet without leaving the page.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue