From fe743ff48d4a7180d1d20e4e42d5e313d90d1d77 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Thu, 1 Oct 2026 22:18:31 +0000 Subject: [PATCH] The whole-India poster is withheld for now: "Too many people groups to fit on one poster legibly" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit India's whole-country poster does not ship yet; a solution is in progress. countrySheet.js gains WITHHELD (India) and sheetWithheld(item): - makeCountrySheet draws nothing for a withheld country, in any format, before fetching a single record. - largeSheet() is null for it, so the Country Posters wall and the Regions list show their existing messages.countryPosterTooMany ("Too many people groups to fit on one poster legibly — a solution is in progress.", all eleven locales) on the card instead of a poster. - Both callers also withhold its list, packet and cards with the same sentence (about a thousand pages, offered when the poster is). - India's STATE and union-territory posters are separate cards (indiaState) and are untouched; so are the India section and its first place. - sheetFormatFor keeps the large-sheet size rule itself (it returns with the poster). check:country-sheet-one-path arm G: India stays withheld (no sheet in any of 7 formats, 0 records fetched, its state cards never withheld, both callers show the sentence and reject its rows), with a poison for un-withholding it and one for a wall that prints its list. --- library/packet/countrySheet.js | 16 +++++++-- .../country-posters/country-poster-wall.vue | 9 +++-- .../scripts/check-country-sheet-one-path.mjs | 34 +++++++++++++++++-- .../regions-dashboard/regions-list.vue | 7 ++-- 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/library/packet/countrySheet.js b/library/packet/countrySheet.js index 594d72e57..73ae1d46e 100644 --- a/library/packet/countrySheet.js +++ b/library/packet/countrySheet.js @@ -80,11 +80,22 @@ export function seriesFor(countries, item) { * The data (country-posters.json) still records why no PAPER sheet fits; this table is what prints instead. */ export const LARGE_SHEETS = { IND: { format: '60x96', formats: ['60x96', '66x66', '36x60'], desktopOnly: true } } -export function largeSheet(item) { return (item && LARGE_SHEETS[String(item.iso3 || '').toUpperCase()]) || null } +const largeOf = (item) => (item && LARGE_SHEETS[String(item.iso3 || '').toUpperCase()]) || null +/** + * ★ WITHHELD: a whole-country poster that is NOT shipped yet, though the renderer can draw it. India's 855 groups do + * not fit one poster legibly yet; a solution is in progress. Everywhere a reader could print or preview it, it is + * withheld: makeCountrySheet draws nothing, largeSheet() is null (so every caller shows + * messages.countryPosterTooMany, from the country's own unsupportedReason), and the callers withhold its list, + * 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. + */ +export const WITHHELD = new Set(['IND']) +export function sheetWithheld(item) { return Boolean(item && !item.indiaState && WITHHELD.has(String(item.iso3 || '').toUpperCase())) } +export function largeSheet(item) { return sheetWithheld(item) ? 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) { - const big = largeSheet(item) + const big = largeOf(item) // the size rule itself, independent of withholding (it is what returns with the poster) if (!big || format === 'packet') return format return big.formats.includes(format) ? format : big.format } @@ -104,6 +115,7 @@ export function makeCountrySheet({ records, assetsBase, countries = null }) { return async function countrySheet(item, format) { const iso3 = item && item.iso3 if (!iso3) return '' // a selection or a row without a country: no sheet + if (sheetWithheld(item)) return '' // withheld (above): nothing is fetched or drawn // Lazy, like the zone sheet: the generator and the boundary loader are only pulled when a // reader actually prints, so they stay out of every wall's initial download. const { generateCountrySheet } = await import('@map/packet/posterSheets.js') diff --git a/plugin-designs/country-posters/country-poster-wall.vue b/plugin-designs/country-posters/country-poster-wall.vue index 6de2c4b8f..b86b94c6c 100644 --- a/plugin-designs/country-posters/country-poster-wall.vue +++ b/plugin-designs/country-posters/country-poster-wall.vue @@ -44,7 +44,7 @@ import { getRuntimeLocale, detectLocale, tStatic } from '@map/i18n/index.js' // library/. Generated by library/scripts/posters/emit-countries.mjs, which now writes there. import data from '@map/data/country-posters.json' import { resolveAssetsBase } from '@map/utils/embedBase.js' -import { makeCountrySheet, largeSheet } from '@map/packet/countrySheet.js' +import { makeCountrySheet, largeSheet, sheetWithheld } from '@map/packet/countrySheet.js' import { isPhonePrint } from '@map/composables/printInPlace.js' import { keyedError } from '@map/i18n/keyedError.js' // India's states and union territories: the shared table (fetched when the wall shows them) and their sheet. @@ -167,7 +167,10 @@ const phoneBlocked = (c) => !c.indiaState && ON_PHONE && Boolean(largeSheet(c)?. // A blocked card's list, packet and cards REJECT with the notice (never resolve [], which the print path would // report as "no people groups" on a card that has hundreds), before any record is fetched. const desktopOnly = () => Promise.reject(keyedError('poster.phoneLimitAll', 'This needs a desktop or laptop computer.')) -const countryRecords = (c) => (phoneBlocked(c) ? desktopOnly() : c.indiaState +// A WITHHELD whole-country poster (countrySheet.js WITHHELD: India) withholds its list, packet and cards too, with +// the same sentence the card shows (about a thousand pages, offered when the poster is). +const withheld = () => Promise.reject(keyedError('messages.countryPosterTooMany', 'Too many people groups to fit on one poster legibly — a solution is in progress.')) +const countryRecords = (c) => (sheetWithheld(c) ? withheld() : phoneBlocked(c) ? desktopOnly() : c.indiaState ? (c.slugs.length ? recordsFor({ type: 'pg_slugs', values: c.slugs }) : Promise.resolve([])) : recordsFor({ type: 'country', values: [c.iso3] })) // ★ GENERATED AT THE CLICK, through the ONE shared path (@map/packet/countrySheet.js, which the Regions @@ -241,7 +244,7 @@ const collapsedBy = (sec) => sec.total < 60 // unsupportedReason is an English note (and a flag), not display text. The card already states the // count above it, so the sentence needs no number and no plural agreement. // A requested country with no poster at all says so in regions-list.vue's words (the regions catalogue). -const unsupported = (c) => (c.noGroups ? t('countryPosters.indiaStateNoGroups') : c.noPoster ? tRegions('No poster has been generated for this country yet.') : phoneBlocked(c) ? t('poster.phoneLimitAll') : c.unsupportedReason && !largeSheet(c) ? t('messages.countryPosterTooMany') : null) +const unsupported = (c) => (c.noGroups ? t('countryPosters.indiaStateNoGroups') : c.noPoster ? tRegions('No poster has been generated for this country yet.') : sheetWithheld(c) ? t('messages.countryPosterTooMany') : phoneBlocked(c) ? t('poster.phoneLimitAll') : c.unsupportedReason && !largeSheet(c) ? t('messages.countryPosterTooMany') : null) // ★ THROUGH THE CATALOGUE, like the UPG-100 wall: the heading, the tag, the intro and the counts read // in the page's language. The intro keeps its one emphasis as three plain keys (A, the emphasis, B) 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 bcda80cca..d67c42897 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,6 +179,30 @@ 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 — A WITHHELD WHOLE-COUNTRY POSTER stays withheld (India: not shipped until it prints legibly). The library +// draws NOTHING for it in any format and fetches no record (0 big jobs); a state card is never withheld; and each +// caller shows messages.countryPosterTooMany on the card and rejects the country's rows with it (no list/packet/cards). +export async function armG(cs, srcs) { + const fails = [] + if (!(cs.WITHHELD instanceof Set) || !cs.WITHHELD.has('IND')) fails.push("countrySheet.js WITHHELD no longer holds India: its whole-country poster would ship") + let fetched = 0 + const sheet = cs.makeCountrySheet({ records: () => { fetched++; return Promise.resolve([{ name: 'x', latitude: 20, longitude: 78 }]) }, assetsBase: () => '', countries: [] }) + for (const f of ['3x3', '3x4', 'letter', 'packet', '60x96', '66x66', '36x60']) { + let out + try { out = await sheet({ id: 'country-ind', iso3: 'IND', label: 'India' }, f) } catch (e) { out = 'THREW ' + e.message } + if (out !== '') fails.push(`makeCountrySheet drew something for withheld India at ${f}`) + } + if (fetched) fails.push(`makeCountrySheet fetched records ${fetched}x for withheld India (a big job started)`) + if (cs.sheetWithheld({ iso3: 'IND', indiaState: true, id: 'india-state-gujarat' })) fails.push('an India STATE card is withheld (only the whole-country poster may be)') + if (cs.largeSheet({ iso3: 'IND' })) fails.push('largeSheet(India) is not null while withheld, so callers would offer its poster') + for (const [name, src] of Object.entries(srcs)) { + if (!/sheetWithheld\((?:c|item)\) \? t(?:Key)?\('messages\.countryPosterTooMany'\)/.test(src)) fails.push(`${name}: a withheld country's card does not show messages.countryPosterTooMany`) + if (!/sheetWithheld\((?:c|item)\) \? (?:withheld\(\)|Promise\.reject\(keyedError\('messages\.countryPosterTooMany')/.test(src)) fails.push(`${name}: a withheld country's rows are not withheld (its list/packet/cards would print)`) + if (/const withheld = /.test(src) && !/const withheld = \(\) => Promise\.reject\(keyedError\('messages\.countryPosterTooMany'/.test(src)) fails.push(`${name}: withheld() does not reject with messages.countryPosterTooMany`) + } + return fails +} + export function armE(countries, states, wallSrc, posterWallSrc, H) { const fails = [] if (!/:section-total=/.test(wallSrc)) fails.push('country-poster-wall.vue does not pass :section-total, so India is counted twice') @@ -200,7 +224,8 @@ export function armE(countries, states, wallSrc, posterWallSrc, H) { async function main() { const selfTest = process.argv.includes('--self-test') - const { seriesFor, sheetFormatFor } = await import(join(ROOT, 'library/packet/countrySheet.js')) + const CS = await import(join(ROOT, 'library/packet/countrySheet.js')) + const { seriesFor, sheetFormatFor } = CS const data = JSON.parse(readFileSync(join(ROOT, 'library/data/country-posters.json'), 'utf8')) const countries = data.countries || [] if (countries.length < 170) { console.log(`NOT MEASURED: country-posters.json parsed ${countries.length} countries (floor 170)`); process.exitCode = 75; return } @@ -236,11 +261,13 @@ 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: '' })], + ['the whole-India poster un-withheld', async () => armG({ ...CS, WITHHELD: new Set(), largeSheet: (it) => CS.LARGE_SHEETS[it?.iso3] || null, makeCountrySheet: (o) => async (it, f) => { await o.records({}); return '' } }, { wall: files[wall], regions: REG })], + ['the wall prints the list of a withheld country', async () => armG(CS, { wall: files[wall].replace("const countryRecords = (c) => (sheetWithheld(c) ? withheld() : ", "const countryRecords = (c) => ("), 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)))], ] let bad = 0 for (const [name, run] of cases) { - const f = run() + const f = await run() console.log(`${f.length ? '✓ caught' : '✗ MISSED'} ${name}${f.length ? ` -> ${f[0]}` : ''}`) if (!f.length) bad++ } @@ -254,7 +281,8 @@ 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 fails = [...armA(files, callers), ...armB(seriesFor, countries, sheetFormatFor), ...armC(H, states, adm1Ind), ...E.fails, ...armD(states, areas, meta), ...armF({ 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 }) + 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(', ')}`) if (fails.length) { fails.forEach((f) => console.log(`FAIL ${f}`)); process.exitCode = 1; return } diff --git a/plugin-designs/regions-dashboard/regions-list.vue b/plugin-designs/regions-dashboard/regions-list.vue index 6b1168024..11b5c104e 100644 --- a/plugin-designs/regions-dashboard/regions-list.vue +++ b/plugin-designs/regions-dashboard/regions-list.vue @@ -70,7 +70,7 @@ import PosterWall from '@map/components/poster/PosterWall.vue' import RegionSectionCard from '@map/components/regions/RegionSectionCard.vue' import posterData from '@map/data/country-posters.json' import { createPeopleGroupRecords } from '@map/lists/peopleGroupRecords.js' -import { makeCountrySheet, largeSheet } from '@map/packet/countrySheet.js' +import { makeCountrySheet, largeSheet, sheetWithheld } from '@map/packet/countrySheet.js' import { isPhonePrint } from '@map/composables/printInPlace.js' import { keyedError } from '@map/i18n/keyedError.js' import { resolveAssetsBase } from '@map/utils/embedBase.js' @@ -373,10 +373,11 @@ function postersFor(region) { // its card says so and offers no rows, rather than failing (the Country Posters wall applies the same rule, same table). const ON_PHONE = isPhonePrint() const phoneBlocked = (c) => ON_PHONE && Boolean(largeSheet(c)?.desktopOnly) -const posterUnsupported = (c) => (phoneBlocked(c) ? tKey('poster.phoneLimitAll') +const posterUnsupported = (c) => (sheetWithheld(c) ? tKey('messages.countryPosterTooMany') : phoneBlocked(c) ? tKey('poster.phoneLimitAll') : c.unsupportedReason && !largeSheet(c) ? tKey('messages.countryPosterTooMany') : null) // A blocked card's rows REJECT with the notice, never [] ("no people groups" on a card that has hundreds). -const posterRows = (item) => (phoneBlocked(item) ? Promise.reject(keyedError('poster.phoneLimitAll', 'This needs a desktop or laptop computer.')) : countryRecords({ type: 'country', values: [item.iso3] })) +// A withheld whole-country poster (countrySheet.js WITHHELD: India) withholds its list, packet and cards too. +const posterRows = (item) => (sheetWithheld(item) ? Promise.reject(keyedError('messages.countryPosterTooMany', 'Too many people groups to fit on one poster legibly — a solution is in progress.')) : phoneBlocked(item) ? Promise.reject(keyedError('poster.phoneLimitAll', 'This needs a desktop or laptop computer.')) : countryRecords({ type: 'country', values: [item.iso3] })) // One region's wall is already inside a region section, so it needs no second level of // collapsing and no grouping: everything in it is that one region.