India's whole-country poster stays withheld everywhere except an explicit PREVIEW context

It is not released yet, and the pilot syncs to DX Life (and the partner handoff) within minutes. So WITHHELD.IND is
{ poster: true, rows: true, preview: true }: posterWithheld() is false ONLY when the host page sets
globalThis.DXM_PREVIEW_UNRELEASED === true (the Plugin Studio on the pilot, where it is reviewed); never by a URL
parameter. Every other host keeps the withheld sentence on India's card, and Country Posters' India tab exists only
under that flag. The rows (list, packet, cards) stay withheld everywhere.
check:country-sheet-one-path arm G proves (a) withheld by default and (b) printing at 66x66 with the flag (only
=== true opens it), rows withheld in both, the India tab gated, with a poison for each; check:poster-section arm C
expects the sentence by default.
This commit is contained in:
Doxa Maps 2026-10-02 07:53:41 +00:00
parent 2992f5b941
commit f804bd8bcc
4 changed files with 47 additions and 24 deletions

View file

@ -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) }

View file

@ -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.

View file

@ -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(', ')}`)

View file

@ -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)