diff --git a/library/packet/countrySheet.js b/library/packet/countrySheet.js index b7432e9e7..a5c3150f1 100644 --- a/library/packet/countrySheet.js +++ b/library/packet/countrySheet.js @@ -89,11 +89,14 @@ const largeOf = (item) => (item && LARGE_SHEETS[String(item.iso3 || '').toUpperC * packet and cards with the same sentence. India's STATE and union-territory posters are separate cards * (`indiaState`) and are not affected. check:country-sheet-one-path fails if India's poster comes back unannounced. */ -// WHAT of a whole country is withheld: its POSTER, and/or its ROWS (its prayer list, packet and cards). India's -// whole-country poster ships (66x66 by default); its list, packet and cards (about a thousand pages) stay withheld. -export const WITHHELD = { IND: { poster: false, rows: true } } +// WHAT of a whole country is withheld: its POSTER, and/or its ROWS (its prayer list, packet and cards). +// India's whole-country poster is withheld EVERYWHERE BY DEFAULT (it is not released yet). It prints only in an +// explicit PREVIEW context: a host page that sets globalThis.DXM_PREVIEW_UNRELEASED = true (the Plugin Studio on the +// pilot, where it is reviewed). Never by a URL parameter. Its rows (about a thousand pages) stay withheld everywhere. +export const WITHHELD = { IND: { poster: true, rows: true, preview: true } } +export const previewUnreleased = () => typeof globalThis !== 'undefined' && globalThis.DXM_PREVIEW_UNRELEASED === true const withheldOf = (item) => (item && !item.indiaState && WITHHELD[String(item.iso3 || '').toUpperCase()]) || null -export function posterWithheld(item) { return Boolean(withheldOf(item)?.poster) } +export function posterWithheld(item) { const w = withheldOf(item); return Boolean(w?.poster) && !(w.preview && previewUnreleased()) } export function rowsWithheld(item) { return Boolean(withheldOf(item)?.rows) } /** The poster's withholding (the name Poster Design reads). */ export function sheetWithheld(item) { return posterWithheld(item) } diff --git a/plugin-designs/country-posters/country-posters-mfe.vue b/plugin-designs/country-posters/country-posters-mfe.vue index edccaa7e4..e1b5f6f09 100644 --- a/plugin-designs/country-posters/country-posters-mfe.vue +++ b/plugin-designs/country-posters/country-posters-mfe.vue @@ -35,6 +35,7 @@ import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pan import { useShadowStyles } from '@map/composables/useShadowStyles.js' import ViewSelect from '@map/components/ViewSelect.vue' import { countryName } from '@map/utils/countryName.js' +import { previewUnreleased } from '@map/packet/countrySheet.js' import { getRuntimeLocale, detectLocale } from '@map/i18n/index.js' import ShareButton from '@map/components/map-controls/ShareButton.vue' import { useI18n } from 'vue-i18n' @@ -70,9 +71,7 @@ const spec = computed(() => ({ // strip desynchronises from the scroll column. panes: [ { id: 'wall', label: t('tabs.countryPosters'), at: [0, 0], config: { profile: 'country-poster-wall' } }, - // INDIA: the whole-India poster (66x66 by default) on its own tab, the same wall filtered to India. Its label is - // India's own name in the reader's language (Intl), so no tab key is needed. - { id: 'india', label: countryName('IND', getRuntimeLocale() || detectLocale() || 'en') || 'India', at: [1, 0], config: { profile: 'country-poster-wall', countries: ['IND'] } }, + ...(previewUnreleased() ? [INDIA_PANE] : []), { id: 'map', label: t('tabs.map'), at: [2, 0], config: { profile: 'countries-map' } }, ], // The switcher lives in this element's own header, so the layout's "… below ↓" @@ -80,6 +79,12 @@ const spec = computed(() => ({ paneHint: false, })) +// INDIA: the whole-India poster (66x66 by default) on its own tab, the same wall filtered to India, labelled with +// India's own name in the reader's language (Intl). Shown ONLY in a preview context (countrySheet.js +// previewUnreleased(): the Plugin Studio on the pilot), because the poster is not released; everywhere else the tab +// is absent and India's card keeps the withheld sentence. +const INDIA_PANE = { id: 'india', label: countryName('IND', getRuntimeLocale() || detectLocale() || 'en') || 'India', at: [1, 0], config: { profile: 'country-poster-wall', countries: ['IND'] } } + const stage = ref(null) // DERIVED from the pane order, never a hardcoded id — the same trap upg-100-mfe // records: `ref('map')` silently became wrong the moment the panes were reordered. diff --git a/plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs b/plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs index 2be3d764d..b88a5690e 100644 --- a/plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs +++ b/plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs @@ -179,25 +179,36 @@ export function armF(srcs) { // ARM E: India is its OWN section, FIRST, counted ONCE. The India country card, the state cards and the none-listed // cards group together; the section's total is India's own count (not the card sum, which counts 855 twice); on that // true total it outranks every regional section. PosterWall's default (no sectionTotal) must still be the card sum. -// ARM G — WHAT OF A WHOLE COUNTRY IS WITHHELD (countrySheet.js WITHHELD). India: its whole-country POSTER ships (66x66 -// by default; largeSheet() names it), its ROWS (list, packet, cards: about a thousand pages) stay withheld with the -// sentence; a state card is never withheld. Each caller shows the sentence on the card only for a withheld POSTER and -// rejects the rows only for withheld ROWS. -export async function armG(cs, srcs) { +// ARM G — WHAT OF A WHOLE COUNTRY IS WITHHELD (countrySheet.js WITHHELD), and the PREVIEW guard. India: +// (a) BY DEFAULT (every public host: DX Life, the research landing, any embed) its whole-country POSTER is withheld +// (the sentence; largeSheet() null) and its ROWS are withheld; +// (b) only in a PREVIEW context (globalThis.DXM_PREVIEW_UNRELEASED === true: the Plugin Studio on the pilot) the poster +// prints, 66x66 by default; its rows stay withheld. A state card is never withheld. +// Each caller shows the sentence for a withheld POSTER and rejects the rows for withheld ROWS; Country Posters' India +// tab exists only under the preview flag. +export async function armG(cs, srcs, mfeSrc = '') { const fails = [] const ind = { id: 'country-ind', iso3: 'IND', label: 'India' } - if (!cs.WITHHELD || !cs.WITHHELD.IND) fails.push('countrySheet.js WITHHELD no longer lists India') - if (cs.posterWithheld(ind)) fails.push("India's whole-country poster is withheld again (it ships)") - if (!cs.rowsWithheld(ind)) fails.push("India's rows are no longer withheld: its list/packet/cards (~1,000 pages) would print") - const big = cs.largeSheet(ind) - if (!big || big.format !== '66x66') fails.push(`India's whole-country poster is not offered at 66x66 by default (${big && big.format})`) + const was = globalThis.DXM_PREVIEW_UNRELEASED + try { + delete globalThis.DXM_PREVIEW_UNRELEASED + if (!cs.posterWithheld(ind) || cs.largeSheet(ind)) fails.push("(a) India's whole-country poster is NOT withheld by default: it would ship to every host") + if (!cs.rowsWithheld(ind)) fails.push("India's rows are not withheld: its list/packet/cards (~1,000 pages) would print") + globalThis.DXM_PREVIEW_UNRELEASED = true + const big = cs.largeSheet(ind) + if (cs.posterWithheld(ind) || !big || big.format !== '66x66') fails.push(`(b) with the preview flag India's poster does not print at 66x66 (${big && big.format})`) + if (!cs.rowsWithheld(ind)) fails.push("(b) the preview flag also released India's rows") + globalThis.DXM_PREVIEW_UNRELEASED = 'true' + if (!cs.posterWithheld(ind)) fails.push('the preview flag is honoured for a non-boolean value (only === true opens it)') + } finally { if (was === undefined) delete globalThis.DXM_PREVIEW_UNRELEASED; else globalThis.DXM_PREVIEW_UNRELEASED = was } if (cs.sheetWithheld(ind) !== cs.posterWithheld(ind)) fails.push('sheetWithheld() no longer means the poster') if (cs.rowsWithheld({ iso3: 'IND', indiaState: true, id: 'india-state-gujarat' })) fails.push('an India STATE card has withheld rows (only the whole country may)') for (const [name, src] of Object.entries(srcs)) { if (!/posterWithheld\((?:c|item)\) \? t(?:Key)?\('messages\.countryPosterTooMany'\)/.test(src)) fails.push(`${name}: a withheld POSTER's card does not show messages.countryPosterTooMany`) if (!/rowsWithheld\((?:c|item)\) \? (?:withheld\(\)|Promise\.reject\(keyedError\('messages\.countryPosterTooMany')/.test(src)) fails.push(`${name}: withheld ROWS are not withheld (its list/packet/cards would print)`) - if (/sheetWithheld\(/.test(src)) fails.push(`${name}: still reads sheetWithheld() (ambiguous now: poster or rows?)`) + if (/sheetWithheld\(/.test(src)) fails.push(`${name}: still reads sheetWithheld() (ambiguous: poster or rows?)`) } + if (mfeSrc && !/\.\.\.\(previewUnreleased\(\) \? \[INDIA_PANE\] : \[\]\)/.test(mfeSrc)) fails.push('country-posters-mfe: the India tab is not gated on previewUnreleased()') return fails } @@ -240,6 +251,7 @@ async function main() { for (const p of listSources(PD)) files[relative(ROOT, p)] = readFileSync(p, 'utf8') const REG = readFileSync(join(ROOT, 'plugin-designs/regions-dashboard/regions-list.vue'), 'utf8') + const MFE = readFileSync(join(ROOT, 'plugin-designs/country-posters/country-posters-mfe.vue'), 'utf8') if (selfTest) { const wall = KNOWN_CALLERS[0] const cases = [ @@ -259,7 +271,10 @@ async function main() { ['the Regions list resolves [] for a phone-blocked card', () => armF({ wall: files[wall], regions: REG.replace("phoneBlocked(item) ? Promise.reject(keyedError('poster.phoneLimitAll'", "phoneBlocked(item) ? Promise.resolve([]) || (keyedError('poster.phoneLimitAll'") })], ['a locale loses a state name', () => armD(states, { ...areas, ro: { ...areas.ro, stateNames: Object.fromEntries(Object.entries(areas.ro.stateNames).filter(([k]) => k !== 'IN-GJ')) } }, meta)], ['the names lose their licence line', () => armD(states, areas, { ...meta, licence: '' })], - ['India\'s rows un-withheld', async () => armG({ ...CS, rowsWithheld: () => false }, { wall: files[wall], regions: REG })], + ['India\'s poster released by default', async () => armG({ ...CS, posterWithheld: () => false, largeSheet: () => CS.LARGE_SHEETS.IND }, { wall: files[wall], regions: REG }, MFE)], + ['the preview flag ignored', async () => armG({ ...CS, posterWithheld: () => true, largeSheet: () => null }, { wall: files[wall], regions: REG }, MFE)], + ['India\'s rows un-withheld', async () => armG({ ...CS, rowsWithheld: () => false }, { wall: files[wall], regions: REG }, MFE)], + ['the India tab shown without the preview flag', async () => armG(CS, { wall: files[wall], regions: REG }, MFE.replace('...(previewUnreleased() ? [INDIA_PANE] : []),', 'INDIA_PANE,'))], ['the wall prints the list of a country whose rows are withheld', async () => armG(CS, { wall: files[wall].replace('rowsWithheld(c) ? withheld() : ', ''), regions: REG })], ['a helper that mutates the shared ADM1', () => armC({ ...H, stateGeometry: (z, a, n) => { (a.features || []).forEach((f) => { if (f.properties.shapeName === n) f.properties.listId = z.id }); return H.stateGeometry(z, a, n) } }, states, JSON.parse(JSON.stringify(adm1Ind)))], ] @@ -279,7 +294,7 @@ async function main() { const callers = [] const E = armE(countries, states, files['plugin-designs/country-posters/country-poster-wall.vue'], readFileSync(join(ROOT, 'library/components/poster/PosterWall.vue'), 'utf8'), H) - const G = await armG(CS, { wall: files['plugin-designs/country-posters/country-poster-wall.vue'], regions: REG }) + const G = await armG(CS, { wall: files['plugin-designs/country-posters/country-poster-wall.vue'], regions: REG }, MFE) const fails = [...armA(files, callers), ...armB(seriesFor, countries, sheetFormatFor), ...armC(H, states, adm1Ind), ...G, ...E.fails, ...armD(states, areas, meta), ...armF({ wall: files['plugin-designs/country-posters/country-poster-wall.vue'], regions: REG })] const missing = [...KNOWN_CALLERS, `${STATE_CALLER}#state`].filter((k) => !callers.includes(k)) if (missing.length) fails.push(`known callers not found calling makeCountrySheet: ${missing.join(', ')}`) diff --git a/plugin-designs/country-posters/scripts/check-poster-section.mjs b/plugin-designs/country-posters/scripts/check-poster-section.mjs index 7e70647d7..7efd3b9d8 100644 --- a/plugin-designs/country-posters/scripts/check-poster-section.mjs +++ b/plugin-designs/country-posters/scripts/check-poster-section.mjs @@ -3,8 +3,8 @@ // A. the TYPE REGISTRY: 'country' is registered, an unknown type is null, a plugin can register its own. // B. SCOPES: DX Life's /regions slugs (africa … oceania, other) each resolve; every spelling of a region (its // slug, its API value, its English name) resolves to the same countries; the scopes cover all countries once. -// C. India through the type: its whole-country POSTER card is live; its withheld ROWS are rejected with the sentence, -// no records fetched. +// C. India through the type, by default: its whole-country POSTER card shows the withheld sentence; its ROWS are +// rejected with it, no records fetched. // D. the ELEMENT's attributes map to its config (collapsible on by default, collapsible="false", collapsed). // E. THE LIFT: every name PosterSection.vue's template uses is injected from the wall (a section never reads // something its wall does not provide). @@ -43,7 +43,7 @@ async function arms({ types, country, element, sectionSrc }) { else { let fetched = 0 const act = country.actions({ assetsBase: () => '', apiBase: () => { fetched++; return '' }, lang: () => 'en', profileConfig: null, t: (k) => k }) - if (act.unsupported(ind) !== null) fails.push("C: India's whole-country poster card is marked unsupported (its poster ships)") + if (act.unsupported(ind) !== 'messages.countryPosterTooMany') fails.push("C: India's whole-country poster card does not show the withheld sentence by default (it is not released)") let why = '' try { await act.records(ind); why = 'resolved' } catch (e) { why = e && (e.i18nKey || e.message) } if (!/countryPosterTooMany/.test(String(why))) fails.push(`C: withheld India's rows are not withheld (${why})`) @@ -93,7 +93,7 @@ try { } else { const f = await arms(real) if (f.length) { f.forEach((x) => console.log('FAIL ' + x)); code = 1 } - else console.log(`PASS check:poster-section — the 'country' type; ${DXL.length} DX Life scopes cover every country once (and their other spellings); India's poster live, its rows withheld; the element's attributes; PosterSection injects every name it uses`) + else console.log(`PASS check:poster-section — the 'country' type; ${DXL.length} DX Life scopes cover every country once (and their other spellings); India withheld by default; the element's attributes; PosterSection injects every name it uses`) } } finally { await server.close() } process.exit(code)