diff --git a/internal/check-batch-selection-export.mjs b/internal/check-batch-selection-export.mjs index 237908801..55e549b08 100644 --- a/internal/check-batch-selection-export.mjs +++ b/internal/check-batch-selection-export.mjs @@ -256,9 +256,23 @@ const wired = { const body = (name) => { const at = vue.indexOf(`async function ${name}() {`); return at < 0 ? '' : vue.slice(at, vue.indexOf('\n}\n', at)) } const before = (name, call) => { const b = body(name); const g = b.indexOf('phoneTooBig('), p = b.indexOf(call); return g > 0 && p > g } return before('makeSelectionPacket', 'packetPrints.printPacket(') && before('makeSelectionCards', 'packetPrints.printCardDeck(') - && before('makeSelectionPoster', 'packetPrints.printPoster(') && /flashStatus\(t\('poster\.phoneLimitAll'\)/.test(vue) + && before('makeSelectionPoster', 'packetPrints.printPoster(') && /hudSay\(t\('poster\.phoneLimitAll'\), 6000\)/.test(vue) && /const ON_PHONE = isPhonePrint\(\);/.test(vue) })(), + // ★ ON A PHONE .upg-status IS display:none, so a batch message written only there is INVISIBLE (a reader tapped + // Prayer List and nothing visible happened). Every batch action's message, and the print's progress, reach the + // HUD's line (.upg-hud-flash, a polite live region that no rule hides) as well as the status badge. + 'batch messages and progress reach the visible HUD on a phone': (() => { + const body = (name) => { const at = vue.indexOf(`async function ${name}() {`); return at < 0 ? '' : vue.slice(at, vue.indexOf('\n}\n', at)) } + const actions = ['makeSelectionPacket', 'makeSelectionCards', 'makeSelectionPoster'].map(body) + const tooBig = vue.slice(vue.indexOf('function phoneTooBig('), vue.indexOf('function phoneTooBig(') + 300) + return /const hudSay = \(text, ms\) => \{ flashStatus\(text, ms\); flashRef\(copyFlash, text, ms\); \};/.test(vue) + && /hudSay\(t\('poster\.phoneLimitAll'\)/.test(tooBig) && !/flashStatus\(/.test(tooBig) + && actions.every((b) => b && !/flashStatus\(/.test(b)) + && /class="upg-hud-flash" role="status" aria-live="polite">\{\{ copyFlash \|\| hudProgress \}\}/.test(vue) + && /hudProgress\.value = printStatus;/.test(vue) + && !/\.upg-hud-flash\{[^}]*display:\s*none/.test(vue) && !/@media[^{]*\{[^}]*\.upg-hud-flash\{[^}]*display:\s*none/.test(vue) + })(), 'the regroup warning on Copy': /const warn = regroupable\(sel\) \? '' : t\('upgMap\.copyNotRegroupable'\)/.test(vue), // The packet / card-deck title goes through selectionTitleNames (arm A6 proves it), with the keyed empty case. 'the title names come from selectionTitleNames': /const names = selectionTitleNames\(zoneNames, selectedTerritories\.value, from,\s*\n\s*\(\) => keyedError\('wall\.hud\.noRecords'/.test(vue) diff --git a/plugin-designs/my-upg-100-list/upg-100-list.vue b/plugin-designs/my-upg-100-list/upg-100-list.vue index 8f9419a5b..c64d9ba64 100644 --- a/plugin-designs/my-upg-100-list/upg-100-list.vue +++ b/plugin-designs/my-upg-100-list/upg-100-list.vue @@ -1528,14 +1528,15 @@ const packetPrints = usePosterPrints({ // watcher wrote is remembered and cleared exactly — the same rule flashRef follows above. let printStatus = '' watch(packetPrints.exportState, (st) => { - if (!st) { if (printStatus && status.value === printStatus) status.value = ''; printStatus = ''; return; } + if (!st) { if (printStatus && status.value === printStatus) status.value = ''; printStatus = ''; hudProgress.value = ''; return; } if (st.phase === 'error') { - printStatus = ''; - flashStatus(t('messages.packetFailed', { reason: st.message }), 4000); + printStatus = ''; hudProgress.value = ''; + hudSay(t('messages.packetFailed', { reason: st.message }), 5000); return; } printStatus = st.phase === 'done' ? '' : `${st.message}…`; status.value = printStatus; + hudProgress.value = printStatus; }); // BUNDLE-SPECIFIC: who is in this list. ⚠ pg_slugs BYPASSES the shared evaluator — @@ -1576,6 +1577,14 @@ function flashRef(target, text, ms) { setTimeout(() => { if (target.value === text) target.value = ''; }, ms); } const flashStatus = (text, ms) => flashRef(status, text, ms); +// ★ A BATCH ACTION SPEAKS WHERE A PHONE CAN SEE IT. .upg-status is display:none below 768px (the legend sheet owns +// the bottom edge there), so a message written only to it was invisible on a phone: a reader tapped Prayer List +// and nothing visible happened. hudSay writes the same text to the batch HUD's line (.upg-hud-flash, a polite +// live region, visible at every width) as well as to the status badge. +const hudSay = (text, ms) => { flashStatus(text, ms); flashRef(copyFlash, text, ms); }; +// The print's progress ("fetching people groups…", "printing 905 people groups…"), mirrored into the HUD for the +// same reason; the HUD shows it whenever no flash is up. +const hudProgress = ref(''); // `variant` selects WHICH packet — 'full' (map, life calendar, %-evangelical grid, triangle, // profiles, sets, points, wheel) or 'lightweight' (map, triangle, profiles, points, wheel). @@ -2004,7 +2013,7 @@ async function exportSelectionCsv() { const ON_PHONE = isPhonePrint(); function phoneTooBig(count) { if (!overPhoneCeiling(count, ON_PHONE)) return false; - flashStatus(t('poster.phoneLimitAll'), 6000); + hudSay(t('poster.phoneLimitAll'), 6000); return true; } @@ -2031,7 +2040,7 @@ async function makeSelectionPacket() { funnel: funnel && lists.length === 1 ? funnel : (funnel ? { ...funnel, listCount: 0 } : null), }); } catch (e) { - flashStatus(t('messages.selectionPacketFailed', { reason: errorText(e, t).slice(0, 90) }), 4000); + hudSay(t('messages.selectionPacketFailed', { reason: errorText(e, t).slice(0, 90) }), 5000); } finally { batchBusy.value = false; busyAction.value = ''; } } @@ -2046,7 +2055,7 @@ async function makeSelectionCards() { if (phoneTooBig(sel.count)) return; await packetPrints.printCardDeck(sel); } catch (e) { - flashStatus(t('upgMap.selectionCardsFailed', { reason: errorText(e, t).slice(0, 90) }), 4000); + hudSay(t('upgMap.selectionCardsFailed', { reason: errorText(e, t).slice(0, 90) }), 5000); } finally { batchBusy.value = false; busyAction.value = ''; @@ -2074,13 +2083,13 @@ async function makeSelectionPoster() { // is exactly the wall's poster; with several, say so plainly instead of silently printing // the first zone's poster as though it covered the selection. if (lists.length > 1) { - flashStatus(t('messages.posterOneTerritory', { count: lists.length }), 5000); + hudSay(t('messages.posterOneTerritory', { count: lists.length }), 5000); return; } if (phoneTooBig(lists[0].count)) return; await packetPrints.printPoster(lists[0]); } catch (e) { - flashStatus(t('messages.selectionPosterFailed', { reason: errorText(e, t).slice(0, 90) }), 4000); + hudSay(t('messages.selectionPosterFailed', { reason: errorText(e, t).slice(0, 90) }), 5000); } finally { batchBusy.value = false; busyAction.value = ''; } } @@ -3410,7 +3419,7 @@ onBeforeUnmount(() => { @click="exportSelectionCsv">{{ busyAction === 'csv' ? t('buttons.working') : t('upgMap.exportCsv') }} -