The preview read is DEV-only: no built bundle can open India's withheld poster from a global

previewUnreleased() is import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true, and the
India tab uses the same expression inline, so every BUILT bundle compiles both away. A page, an embed or a reader's
console setting the global can no longer print the withheld poster from production; only the Vite dev server (the
pilot's Plugin Studio, whose element bootstrap sets it) previews it. posterWithheld()/largeSheet() take the preview
state as an optional argument, so a gate can test an open preview directly.
check:country-sheet-one-path arm G: a global alone does NOT open the preview outside DEV; withheld by default; an
open preview prints 66x66; the India tab is gated on the DEV read; and the built tree mentions the flag 0 times
(the stale ungated build failed this check, as it should). A poison for each.
This commit is contained in:
Doxa Maps 2026-10-02 08:08:26 +00:00
parent eb4896d821
commit 0dc9765f77
3 changed files with 25 additions and 18 deletions

View file

@ -91,16 +91,18 @@ const largeOf = (item) => (item && LARGE_SHEETS[String(item.iso3 || '').toUpperC
*/
// 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.
// explicit PREVIEW context: the Vite DEV server (the pilot's Plugin Studio, where it is reviewed), whose element
// bootstrap sets globalThis.DXM_PREVIEW_UNRELEASED. The read is gated on import.meta.env.DEV too, so every BUILT bundle
// compiles it away: no page, embed or console can set a global and print it from production. 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
export const previewUnreleased = () => Boolean(import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true)
const withheldOf = (item) => (item && !item.indiaState && WITHHELD[String(item.iso3 || '').toUpperCase()]) || null
export function posterWithheld(item) { const w = withheldOf(item); return Boolean(w?.poster) && !(w.preview && previewUnreleased()) }
export function posterWithheld(item, open = previewUnreleased()) { const w = withheldOf(item); return Boolean(w?.poster) && !(w.preview && open) }
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) }
export function largeSheet(item) { return posterWithheld(item) ? null : largeOf(item) }
export function largeSheet(item, open = previewUnreleased()) { return posterWithheld(item, open) ? null : largeOf(item) }
// The sheet format a request gets: a large-sheet country prints at the large size ASKED FOR when it has one (66x66,
// 36x60), else at its default (60x96); a packet's page 1 stays a page; every other country gets exactly what it asked.
export function sheetFormatFor(item, format) {

View file

@ -35,7 +35,6 @@ 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'
@ -71,7 +70,8 @@ const spec = computed(() => ({
// strip desynchronises from the scroll column.
panes: [
{ id: 'wall', label: t('tabs.countryPosters'), at: [0, 0], config: { profile: 'country-poster-wall' } },
...(previewUnreleased() ? [INDIA_PANE] : []),
// the same DEV-gated read as countrySheet.js previewUnreleased(), INLINE so every build drops the pane entirely
...(import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true ? [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 ↓"

View file

@ -17,7 +17,7 @@
// absent from the data gets no series.
//
// Exit 0 pass, 1 fail, 75 not measured. `--self-test` plants each defect and requires its arm to fail.
import { readFileSync, readdirSync, statSync } from 'node:fs'
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
import { join, relative, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { parse as parseSfc, babelParse } from '@vue/compiler-sfc'
@ -189,18 +189,17 @@ export function armF(srcs) {
export async function armG(cs, srcs, mfeSrc = '') {
const fails = []
const ind = { id: 'country-ind', iso3: 'IND', label: 'India' }
// (a) by default: outside the Vite dev server (here: no import.meta.env.DEV) the preview is shut EVEN IF a page or a
// console sets the global; (b) an open preview (passed explicitly) prints the poster at 66x66.
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)')
if (cs.previewUnreleased()) fails.push('(a) the preview opens outside the dev server from a global alone: a page/console could print the withheld poster from production')
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")
} finally { if (was === undefined) delete globalThis.DXM_PREVIEW_UNRELEASED; else globalThis.DXM_PREVIEW_UNRELEASED = was }
if (!cs.rowsWithheld(ind)) fails.push("India's rows are not withheld: its list/packet/cards (~1,000 pages) would print")
const big = cs.largeSheet(ind, true)
if (cs.posterWithheld(ind, true) || !big || big.format !== '66x66') fails.push(`(b) an open preview does not print India's poster at 66x66 (${big && big.format})`)
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)) {
@ -208,7 +207,12 @@ export async function armG(cs, srcs, mfeSrc = '') {
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: poster or rows?)`)
}
if (mfeSrc && !/\.\.\.\(previewUnreleased\(\) \? \[INDIA_PANE\] : \[\]\)/.test(mfeSrc)) fails.push('country-posters-mfe: the India tab is not gated on previewUnreleased()')
if (mfeSrc && !/\.\.\.\(import\.meta\.env && import\.meta\.env\.DEV && globalThis\.DXM_PREVIEW_UNRELEASED === true \? \[INDIA_PANE\] : \[\]\)/.test(mfeSrc)) fails.push('country-posters-mfe: the India tab is not gated on the DEV-only preview read')
// the BUILT tree never mentions the flag (every read is compiled away)
try {
const pub = join(ROOT, 'public/dx-maps')
for (const b of readdirSync(pub)) { const f = join(pub, b, `${b}.js`); if (existsSync(f) && readFileSync(f, 'utf8').includes('DXM_PREVIEW_UNRELEASED')) fails.push(`the built ${b}.js mentions DXM_PREVIEW_UNRELEASED (a preview read survived the build)`) }
} catch (_) { /* no built tree here */ }
return fails
}
@ -273,8 +277,9 @@ async function main() {
['the names lose their licence line', () => armD(states, areas, { ...meta, licence: '' })],
['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)],
['the preview read without DEV (a global opens production)', async () => armG({ ...CS, previewUnreleased: () => globalThis.DXM_PREVIEW_UNRELEASED === true }, { 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 India tab shown without the preview flag', async () => armG(CS, { wall: files[wall], regions: REG }, MFE.replace('...(import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true ? [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)))],
]