From 5880e8461607802efe59cc7f0dbb07f163e3c871 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Mon, 28 Sep 2026 17:51:44 +0000 Subject: [PATCH] Cards per row on the shared grid, with Auto as the absence of a value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Driver asked for a top-right dropdown with Auto plus fixed overrides, remembered, and said plainly that the CSS already working must not break. So Auto writes NOTHING. The grid is repeat(auto-fill, minmax(240px, 1fr)), which already gives one column on a phone and three to five on a desktop by measuring its container; Auto leaves the stylesheet alone and only an explicit override sets grid-template-columns. An Auto that computed a number and wrote it back would look identical until the container resized, and then be silently wrong — the same picture, with the responsiveness removed. An override is honoured even when it is a poor idea: five columns on a 390px phone is a 62px card. That is deliberate. The reader asked for it, Auto is the default and one click away, and the control shows which is active. Clamping someone's explicit choice to what we think is sensible is how a control stops being trusted. It is remembered per PROFILE, not per bundle. Two grids on one page are two different lists, and useDashboardLayout already paid for that lesson when a shared key made one caller inherit another's saved layout. Blocked storage is caught and ignored: in private mode the default is the correct outcome, not an error to report. It sits in its own row rather than in #filters, which is gated on useSearch || useFilters — a host turning both off would otherwise lose a control that has nothing to do with either. It modifies the grid, so it lives on the grid. Both strings are authored in all eleven catalogues, in the snake_case this catalogue uses: a camelCase key here would have resolved to nothing and rendered its own name, which is the defect I shipped in a legend earlier today. --- library/i18n/locales/ar/grid.json | 4 +- library/i18n/locales/de/grid.json | 4 +- library/i18n/locales/en/grid.json | 4 +- library/i18n/locales/es/grid.json | 4 +- library/i18n/locales/fr/grid.json | 4 +- library/i18n/locales/hi/grid.json | 4 +- library/i18n/locales/it/grid.json | 4 +- library/i18n/locales/pt/grid.json | 4 +- library/i18n/locales/ro/grid.json | 4 +- library/i18n/locales/ru/grid.json | 4 +- library/i18n/locales/zh/grid.json | 4 +- .../people-groups-grid/people-groups-grid.vue | 48 ++++++++++++++++++- 12 files changed, 80 insertions(+), 12 deletions(-) diff --git a/library/i18n/locales/ar/grid.json b/library/i18n/locales/ar/grid.json index 6a8ec764f..7a25d556a 100644 --- a/library/i18n/locales/ar/grid.json +++ b/library/i18n/locales/ar/grid.json @@ -16,5 +16,7 @@ "no_results": "لا توجد شعوب مطابقة لهذا التصفية.", "type_to_search": "اكتب للبحث...", "load_failed": "تعذّر تحميل بيانات الشعوب. هذه مشكلة اتصال، وليست مشكلة في التصفية.", - "search_people_or_countries": "ابحث عن الشعوب أو الدول…" + "search_people_or_countries": "ابحث عن الشعوب أو الدول…", + "cards_per_row": "البطاقات في كل صف", + "cards_per_row_auto": "تلقائي" } diff --git a/library/i18n/locales/de/grid.json b/library/i18n/locales/de/grid.json index 64a3af865..1047904d0 100644 --- a/library/i18n/locales/de/grid.json +++ b/library/i18n/locales/de/grid.json @@ -16,5 +16,7 @@ "no_results": "Keine Volksgruppen entsprechen diesem Filter.", "type_to_search": "Zum Suchen tippen...", "load_failed": "Die Daten konnten nicht geladen werden. Das ist ein Verbindungsproblem, nicht Ihr Filter.", - "search_people_or_countries": "Volksgruppen oder Länder suchen…" + "search_people_or_countries": "Volksgruppen oder Länder suchen…", + "cards_per_row": "Karten pro Zeile", + "cards_per_row_auto": "Automatisch" } diff --git a/library/i18n/locales/en/grid.json b/library/i18n/locales/en/grid.json index 79b002cfe..9e2abf2b4 100644 --- a/library/i18n/locales/en/grid.json +++ b/library/i18n/locales/en/grid.json @@ -16,5 +16,7 @@ "no_results": "No people groups match this filter.", "type_to_search": "Type to search...", "load_failed": "People-group data could not be loaded. This is a connection problem, not your filter.", - "search_people_or_countries": "Search people groups or countries…" + "search_people_or_countries": "Search people groups or countries…", + "cards_per_row": "Cards per row", + "cards_per_row_auto": "Auto" } diff --git a/library/i18n/locales/es/grid.json b/library/i18n/locales/es/grid.json index 97c664c8d..1d3e0cd09 100644 --- a/library/i18n/locales/es/grid.json +++ b/library/i18n/locales/es/grid.json @@ -16,5 +16,7 @@ "no_results": "Ningún pueblo coincide con este filtro.", "type_to_search": "Escribe para buscar...", "load_failed": "No se pudieron cargar los datos de pueblos. Es un problema de conexión, no de tu filtro.", - "search_people_or_countries": "Buscar pueblos o países…" + "search_people_or_countries": "Buscar pueblos o países…", + "cards_per_row": "Tarjetas por fila", + "cards_per_row_auto": "Automático" } diff --git a/library/i18n/locales/fr/grid.json b/library/i18n/locales/fr/grid.json index f278627e9..444618491 100644 --- a/library/i18n/locales/fr/grid.json +++ b/library/i18n/locales/fr/grid.json @@ -16,5 +16,7 @@ "no_results": "Aucun peuple ne correspond à ce filtre.", "type_to_search": "Tapez pour rechercher...", "load_failed": "Les données n’ont pas pu être chargées. C’est un problème de connexion, pas votre filtre.", - "search_people_or_countries": "Rechercher des peuples ou des pays…" + "search_people_or_countries": "Rechercher des peuples ou des pays…", + "cards_per_row": "Cartes par ligne", + "cards_per_row_auto": "Automatique" } diff --git a/library/i18n/locales/hi/grid.json b/library/i18n/locales/hi/grid.json index 034903e72..b1f9c7c27 100644 --- a/library/i18n/locales/hi/grid.json +++ b/library/i18n/locales/hi/grid.json @@ -16,5 +16,7 @@ "no_results": "इस फ़िल्टर से कोई जनसमूह मेल नहीं खाता।", "type_to_search": "खोजने के लिए लिखें...", "load_failed": "जनसमूह डेटा लोड नहीं हो सका। यह कनेक्शन की समस्या है, आपके फ़िल्टर की नहीं।", - "search_people_or_countries": "जनसमूह या देश खोजें…" + "search_people_or_countries": "जनसमूह या देश खोजें…", + "cards_per_row": "प्रति पंक्ति कार्ड", + "cards_per_row_auto": "स्वचालित" } diff --git a/library/i18n/locales/it/grid.json b/library/i18n/locales/it/grid.json index 5e07e54f0..0dba610ed 100644 --- a/library/i18n/locales/it/grid.json +++ b/library/i18n/locales/it/grid.json @@ -16,5 +16,7 @@ "no_results": "Nessun popolo corrisponde a questo filtro.", "type_to_search": "Digita per cercare...", "load_failed": "Impossibile caricare i dati dei popoli. È un problema di connessione, non del filtro.", - "search_people_or_countries": "Cerca popoli o paesi…" + "search_people_or_countries": "Cerca popoli o paesi…", + "cards_per_row": "Schede per riga", + "cards_per_row_auto": "Automatico" } diff --git a/library/i18n/locales/pt/grid.json b/library/i18n/locales/pt/grid.json index afac097ee..238e72d52 100644 --- a/library/i18n/locales/pt/grid.json +++ b/library/i18n/locales/pt/grid.json @@ -16,5 +16,7 @@ "no_results": "Nenhum povo corresponde a este filtro.", "type_to_search": "Escreva para pesquisar...", "load_failed": "Não foi possível carregar os dados dos povos. É um problema de ligação, não do seu filtro.", - "search_people_or_countries": "Pesquisar povos ou países…" + "search_people_or_countries": "Pesquisar povos ou países…", + "cards_per_row": "Cartões por linha", + "cards_per_row_auto": "Automático" } diff --git a/library/i18n/locales/ro/grid.json b/library/i18n/locales/ro/grid.json index 7b1216371..6df766df7 100644 --- a/library/i18n/locales/ro/grid.json +++ b/library/i18n/locales/ro/grid.json @@ -16,5 +16,7 @@ "no_results": "Niciun popor nu corespunde acestui filtru.", "type_to_search": "Scrie pentru a căuta...", "load_failed": "Datele despre popoare nu au putut fi încărcate. Este o problemă de conexiune, nu a filtrului.", - "search_people_or_countries": "Caută popoare sau țări…" + "search_people_or_countries": "Caută popoare sau țări…", + "cards_per_row": "Carduri pe rând", + "cards_per_row_auto": "Automat" } diff --git a/library/i18n/locales/ru/grid.json b/library/i18n/locales/ru/grid.json index 9ba74ace0..7cafbbe6b 100644 --- a/library/i18n/locales/ru/grid.json +++ b/library/i18n/locales/ru/grid.json @@ -16,5 +16,7 @@ "no_results": "Нет народов, соответствующих этому фильтру.", "type_to_search": "Введите для поиска...", "load_failed": "Не удалось загрузить данные о народах. Это проблема соединения, а не фильтра.", - "search_people_or_countries": "Поиск народов или стран…" + "search_people_or_countries": "Поиск народов или стран…", + "cards_per_row": "Карточек в ряду", + "cards_per_row_auto": "Авто" } diff --git a/library/i18n/locales/zh/grid.json b/library/i18n/locales/zh/grid.json index 4e654e12a..ee056f184 100644 --- a/library/i18n/locales/zh/grid.json +++ b/library/i18n/locales/zh/grid.json @@ -16,5 +16,7 @@ "no_results": "没有符合此筛选条件的民族。", "type_to_search": "输入以搜索...", "load_failed": "无法加载民族数据。这是连接问题,而不是筛选问题。", - "search_people_or_countries": "搜索民族或国家…" + "search_people_or_countries": "搜索民族或国家…", + "cards_per_row": "每行卡片数", + "cards_per_row_auto": "自动" } diff --git a/plugin-designs/people-groups-grid/people-groups-grid.vue b/plugin-designs/people-groups-grid/people-groups-grid.vue index c0fd6b6f9..a4f3f596f 100644 --- a/plugin-designs/people-groups-grid/people-groups-grid.vue +++ b/plugin-designs/people-groups-grid/people-groups-grid.vue @@ -312,6 +312,37 @@ const selectUrl = computed(() => props.selectUrl || cfg.value.selectUrl || '' // that installs no i18n plugin — useI18n() there throws, and a thrown setup() removes the subtree // with no build error. Order: host `t` wins (it always did), then the catalogue, then the English // literal already at each call site, so a locale missing a key degrades to today's behaviour. +/** + * ★ CARDS PER ROW. "Auto" is not a value — it is the ABSENCE of one, and that is the whole design. + * The Driver's words were "we don't want to break the CSS that's already working": the grid is + * repeat(auto-fill, minmax(240px, 1fr)), which already gives 1 column on a phone and 3-5 on a + * desktop by measuring the container. So Auto writes NO inline style and the stylesheet is + * untouched; only an explicit override sets grid-template-columns. A "smart" Auto that computed a + * number and wrote it back would be the same picture until the container changed size, and then + * silently wrong. + * + * ⚠ AN OVERRIDE IS HONOURED EVEN WHEN IT IS A BAD IDEA — 5 columns on a 390px phone is 62px cards. + * That is deliberate: the reader asked. The escape is that Auto is the default and is one click + * away, and the control says which one is active. Clamping someone's explicit choice to what we + * think is sensible is how a control stops being trusted. + */ +const PER_ROW_OPTIONS = ['auto', '1', '2', '3', '4', '5'] +const perRowKey = computed(() => `pgg-cards-per-row:${cfg.value.profile || 'default'}`) +const perRow = ref('auto') +onMounted(() => { + // Per PROFILE, not per bundle: two grids on one page are two different lists, and useDashboardLayout + // already paid for that lesson — a shared key made one caller inherit another's saved layout. + try { + const saved = localStorage.getItem(perRowKey.value) + if (saved && PER_ROW_OPTIONS.includes(saved)) perRow.value = saved + } catch (_) { /* private mode / blocked storage: the default is correct, not an error */ } +}) +watch(perRow, (v) => { try { localStorage.setItem(perRowKey.value, v) } catch (_) {} }) +/** Auto contributes NOTHING, so the stylesheet keeps its own rule. */ +const gridStyle = computed(() => (perRow.value === 'auto' + ? null + : { gridTemplateColumns: `repeat(${perRow.value}, minmax(0, 1fr))` })) + const t = computed(() => { const host = { ...(cfg.value.t || {}), ...(props.t || {}) } const locale = cfg.value.locale || cfg.value.lang || undefined @@ -1011,7 +1042,18 @@ onMounted(() => { -
+ +
+ +
+