Mark the two dashboard-only facets, so they stop looking like broken buttons
language_family and religion_family are clickable, they highlight, and they narrow every chart above them -- and they cannot move the grid below. Measured 2026-09-23: dashboard 2107 -> 677/306/222/151 and 2107 -> 693/649/610/83, grid unchanged every time. To a user that is indistinguishable from a dead control; the only way to learn it was to click and squint. Wiring them is not the fix and would be worse. Both values are DERIVED -- a language family resolved via langFamilyByIso for ~96% of rows, a religion family computed from the first letter of a religion code -- so they exist on no row field. Mapping religion_family onto `religion` would compare "Hinduism" against a CODE and filter the grid to zero. So the honest fix is a label, not a wire. A facet now declares `dashboardOnly: true` in dashboard.config.js; DashboardFacetRail reads that from config rather than hardcoding facet names, and renders a muted half-circle plus a title and an extended aria-label saying the row updates the charts only. These rows stay fully clickable, deliberately unlike `zero` and `other` rows, which are inert because they would do nothing at all. Dimming them the same way would understate what they really do. Verified in a browser on the built tree: 41 of 66 rows marked, 41 glyphs rendered, marked facets are exactly language_family and religion_family and nothing else, 40 still clickable (the one that is not is a zero row that correctly keeps its mark). Behaviour unchanged -- a marked row still takes the dashboard 2,108 -> 677, an unmarked row still filters at 548. Gates: facet-cascade, facet-one-field, facet-changes-map, one-palette, synced-css, composite-srcs, template-literal, sfc-parse all pass; a11y-button-names passes at 769/769 buttons named.
This commit is contained in:
parent
121275e0f2
commit
9076ec1d61
10 changed files with 121 additions and 66 deletions
|
|
@ -90,6 +90,18 @@ export default {
|
|||
// family totals sum exactly to the leaf-code totals.
|
||||
religion_family: {
|
||||
label: 'Religion Family',
|
||||
// ★ DASHBOARD-ONLY, AND SAYING SO IS THE FIX. This facet narrows the dashboard correctly but
|
||||
// can never move the grid beneath it, because the value it emits ("Hinduism") is a DERIVED
|
||||
// family name that exists on no row field — the rows carry religion CODES. Mapping it in
|
||||
// dashboard-plus-grid's FACET_TO_PROP would compare "Hinduism" against a code and filter the
|
||||
// grid to ZERO, which is worse than leaving it unmapped.
|
||||
//
|
||||
// Measured 2026-09-23 (interop sweep, 4 rows tested): dashboard 2107 -> 693/649/610/83, grid
|
||||
// unchanged every time. That combination is indistinguishable from a broken button: the row
|
||||
// highlights, the numbers above move, and the list below ignores you. The honest fix is not to
|
||||
// wire it — it is to TELL the user this row drives the dashboard only, which is what this flag
|
||||
// makes the rail render.
|
||||
dashboardOnly: true,
|
||||
get: (r) => {
|
||||
const code = r.religion && typeof r.religion === 'object' ? r.religion.value : r.religion
|
||||
const c = String(code || '').charAt(0).toUpperCase()
|
||||
|
|
@ -128,6 +140,11 @@ export default {
|
|||
// byte-match contract intact.
|
||||
language_family: {
|
||||
label: 'Language Family',
|
||||
// ★ DASHBOARD-ONLY — same class as religion_family, different cause. This value is DERIVED
|
||||
// via langFamilyByIso for ~96% of rows, so it exists on no row field the grid can filter on.
|
||||
// Measured 2026-09-23 (4 rows tested): dashboard 2107 -> 677/306/222/151, grid unchanged.
|
||||
// See the religion_family note above for why declaring it beats wiring it.
|
||||
dashboardOnly: true,
|
||||
get: (r) => {
|
||||
const api = lbl(r.imb_language_family)
|
||||
if (api) return canonicalFamilyName(api)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,26 @@ const filterSet = bus?.filterSet || {}
|
|||
|
||||
// facet-key -> row accessor (sourced from config) — drives both the count tally and setFilter.
|
||||
const FACET_ACC = Object.fromEntries(Object.entries(cfg.facets || {}).map(([k, f]) => [k, f.get]))
|
||||
|
||||
// ── FACETS THAT DRIVE THE DASHBOARD BUT CANNOT MOVE A GRID BELOW IT ──────────────────────────
|
||||
// Read from config (`facets.<key>.dashboardOnly`), never a hardcoded list here: the rail is generic
|
||||
// and the dashboard that hosts it owns which of its facets are derived.
|
||||
//
|
||||
// THE DEFECT THIS MARKS (measured 2026-09-23, interop sweep). language_family and religion_family
|
||||
// are DERIVED — a language family resolved via langFamilyByIso for ~96% of rows, a religion family
|
||||
// computed from the first letter of a religion code. Both narrow this dashboard correctly (2107 ->
|
||||
// 677/306/222/151 and 2107 -> 693/649/610/83) and neither can filter a row grid, because the value
|
||||
// they emit exists on no row field. Wiring them would be WORSE than leaving them: mapping
|
||||
// religion_family onto `religion` compares "Hinduism" against a CODE and empties the grid.
|
||||
//
|
||||
// ★ SO THE HONEST FIX IS A LABEL, NOT A WIRE. Unmarked, these rows are indistinguishable from
|
||||
// broken buttons — the row highlights, the numbers above move, the list below ignores you, and the
|
||||
// only way to learn that is to click and squint. They stay fully CLICKABLE (unlike `zero`/`other`
|
||||
// rows, which are inert because they would do nothing at all); what changes is that the row now
|
||||
// says what it does.
|
||||
const DASHBOARD_ONLY = new Set(
|
||||
Object.entries(cfg.facets || {}).filter(([, f]) => f && f.dashboardOnly).map(([k]) => k)
|
||||
)
|
||||
// rows under the current filterSet, MINUS the facet(s) being aggregated (so each list stays navigable).
|
||||
const matchSearch = makeSearchMatcher(bus, buildSearchAccessors(cfg))
|
||||
function rowsExcluding(exclude) {
|
||||
|
|
@ -74,6 +94,9 @@ function toRows(agg, facet, level) {
|
|||
return agg.map((d) => ({
|
||||
facet, value: d.key, label: d.key, count: d.value, frac: d.value / max, level,
|
||||
other: !!d.other, active: !d.other && filterSet[facet] === d.key,
|
||||
// Carried per-ROW rather than looked up in the template, so the zero-row baseline snapshot
|
||||
// (which spreads a row object) keeps the mark when a row is re-admitted.
|
||||
dashboardOnly: DASHBOARD_ONLY.has(facet),
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -599,6 +622,12 @@ useShadowStyles(`
|
|||
.fr-bar i{ display:block; height:100%; background:var(--dx-accent, #92b195); border-radius:2px; transition:width .3s; }
|
||||
.fr-row.on .fr-bar i{ background:var(--rd-accent,#e67e22); }
|
||||
.fr-cnt{ flex:0 0 auto; font-variant-numeric:tabular-nums; color:var(--dx-muted, #6d766e); min-width:30px; text-align:right; font-size:11px; margin-left:8px; }
|
||||
/* DASHBOARD-ONLY MARK. A half-filled circle reads as "half of this works" without needing a
|
||||
legend, and it is muted so it never competes with the count beside it. Deliberately NOT the
|
||||
.zero treatment (opacity:.42 on the whole row): a zero row does nothing at all, while these
|
||||
rows genuinely drive every chart above — dimming them would understate what they do. */
|
||||
.fr-row.dash-only .fr-donly{ flex:0 0 auto; margin-left:6px; font-size:10px; line-height:1;
|
||||
color:var(--dx-muted, #6d766e); opacity:.75; }
|
||||
`, 'rd-facetrail')
|
||||
</script>
|
||||
|
||||
|
|
@ -621,12 +650,13 @@ useShadowStyles(`
|
|||
a row the current filter drove to zero. Those two render as plain, muted,
|
||||
non-focusable text so the rail never offers a target that does nothing. -->
|
||||
<div v-for="r in sec.rows" :key="r.facet + ':' + r.value" class="fr-row"
|
||||
:class="['l' + r.level, { on: r.active, other: r.other, zero: r.zero, inert: r.other || r.zero }]"
|
||||
:class="['l' + r.level, { on: r.active, other: r.other, zero: r.zero, inert: r.other || r.zero, 'dash-only': r.dashboardOnly }]"
|
||||
:data-facet="(r.other || r.zero) ? null : r.facet" :data-value="(r.other || r.zero) ? null : r.value"
|
||||
:tabindex="(r.other || r.zero) ? null : 0"
|
||||
:role="(r.other || r.zero) ? null : 'button'"
|
||||
:aria-pressed="(r.other || r.zero) ? null : (r.active ? 'true' : 'false')"
|
||||
:aria-label="(r.other || r.zero) ? null : ((r.active ? 'Remove filter ' : 'Filter by ') + r.label + ', ' + r.count.toLocaleString('en-US') + ' groups')">
|
||||
:aria-label="(r.other || r.zero) ? null : ((r.active ? 'Remove filter ' : 'Filter by ') + r.label + ', ' + r.count.toLocaleString('en-US') + ' groups' + (r.dashboardOnly ? '. Updates the charts on this dashboard only; it does not filter the list below.' : ''))"
|
||||
:title="r.dashboardOnly ? 'Updates this dashboard only — the list below does not filter on ' + r.label : null">
|
||||
<!-- tree trunks: one span per ancestor level, same glyph vocabulary as the
|
||||
research-map's SemanticTreeLegend so the hierarchy is DRAWN, not just padded. -->
|
||||
<span v-for="(seg, i) in r.trunks" :key="'tr' + i" class="fr-trunk" :class="'fr-trunk-' + seg"></span>
|
||||
|
|
@ -645,6 +675,10 @@ useShadowStyles(`
|
|||
<span class="fr-lbl">{{ r.label }}</span>
|
||||
<span class="fr-bar"><i :style="{ width: Math.max(4, r.frac * 100) + '%' }"></i></span>
|
||||
<span class="fr-cnt">{{ r.count.toLocaleString('en-US') }}</span>
|
||||
<!-- A VISIBLE mark, not only a title/aria one. A tooltip answers a question the user
|
||||
does not know to ask: the whole defect is that this row LOOKS identical to a row
|
||||
that filters the list. The glyph is what prompts the hover. -->
|
||||
<span v-if="r.dashboardOnly" class="fr-donly" aria-hidden="true">◐</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
|
||||
<script src="../../dashboard-plus-grid.js" integrity="sha384-/qhSKrIAbbUgEn7TNhtn6VuqpE3ApxLg8otkL8MD4KexFyTVrnmvztqD81Ourj1W" crossorigin="anonymous"></script>
|
||||
<script src="../../dashboard-plus-grid.js" integrity="sha384-ou8O6omzXBxwc6kTFOpeL5BDJT3ZHUNWLuSVl7MoHpQDBjniuzGLf48sOVwC0L8+" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
|
||||
<script src="../dashboard-plus-grid.js" integrity="sha384-/qhSKrIAbbUgEn7TNhtn6VuqpE3ApxLg8otkL8MD4KexFyTVrnmvztqD81Ourj1W" crossorigin="anonymous"></script>
|
||||
<script src="../dashboard-plus-grid.js" integrity="sha384-ou8O6omzXBxwc6kTFOpeL5BDJT3ZHUNWLuSVl7MoHpQDBjniuzGLf48sOVwC0L8+" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
8
public/doxa-maps/manifest.json
generated
8
public/doxa-maps/manifest.json
generated
|
|
@ -189,7 +189,7 @@
|
|||
"bundle": "dashboard-plus-grid",
|
||||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"output": "doxa-maps/dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"integrity": "sha384-/qhSKrIAbbUgEn7TNhtn6VuqpE3ApxLg8otkL8MD4KexFyTVrnmvztqD81Ourj1W",
|
||||
"integrity": "sha384-ou8O6omzXBxwc6kTFOpeL5BDJT3ZHUNWLuSVl7MoHpQDBjniuzGLf48sOVwC0L8+",
|
||||
"elements": [
|
||||
"dashboard-plus-grid"
|
||||
],
|
||||
|
|
@ -205,7 +205,7 @@
|
|||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./dashboard-plus-grid/dashboard-plus-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-/qhSKrIAbbUgEn7TNhtn6VuqpE3ApxLg8otkL8MD4KexFyTVrnmvztqD81Ourj1W\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-ou8O6omzXBxwc6kTFOpeL5BDJT3ZHUNWLuSVl7MoHpQDBjniuzGLf48sOVwC0L8+\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -764,7 +764,7 @@
|
|||
"bundle": "research-dashboard",
|
||||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"output": "doxa-maps/research-dashboard/research-dashboard.js",
|
||||
"integrity": "sha384-7deBWTaAE1237o9NDmPofHmH+1/3OXG+9lOkAlrPqTbCgSHe6TqRFvBlZX7JZ7iv",
|
||||
"integrity": "sha384-4HNE72UOF2zrBYURaocKyph8t3C+COB4DAROu4CfbSE/GqvwsEe4pKRGbN1rNlk/",
|
||||
"elements": [
|
||||
"research-dashboard-map"
|
||||
],
|
||||
|
|
@ -780,7 +780,7 @@
|
|||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./research-dashboard/research-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-7deBWTaAE1237o9NDmPofHmH+1/3OXG+9lOkAlrPqTbCgSHe6TqRFvBlZX7JZ7iv\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-4HNE72UOF2zrBYURaocKyph8t3C+COB4DAROu4CfbSE/GqvwsEe4pKRGbN1rNlk/\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
|
||||
},
|
||||
"bundle": "research-dashboard"
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <research-dashboard-map> custom element. -->
|
||||
<script src="../research-dashboard.js" integrity="sha384-7deBWTaAE1237o9NDmPofHmH+1/3OXG+9lOkAlrPqTbCgSHe6TqRFvBlZX7JZ7iv" crossorigin="anonymous"></script>
|
||||
<script src="../research-dashboard.js" integrity="sha384-4HNE72UOF2zrBYURaocKyph8t3C+COB4DAROu4CfbSE/GqvwsEe4pKRGbN1rNlk/" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue