Make the charts rail a tab strip, with Gospel Resources disabled
Some checks are pending
build-gate / build (push) Waiting to run

The Driver asked "a long time ago" that the chart become a tab so Gospel
Resources could sit beside it, with Charts selected by default. The rail had a
plain "Charts" heading.

Builds the half that is buildable. The container needs no gospel data and no
ruling; what the second tab ENCODES does -- he described it as the same facet
breakdown re-measured on a different axis, "people groups by their gospel
resources availability", and three decisions are still open: the official
lostness number, key-less versus a registered Joshua Project key, and the UI
shape. Guessing any of them ships a wrong picture that looks right, which is
this codebase's most expensive failure mode.

So the second tab renders DISABLED rather than hidden, carrying its reason in
the title attribute. A hidden tab is indistinguishable from an unbuilt one; a
disabled tab that names what it waits on is a status report the Driver sees
every time he looks at the rail.

The strip inherits .rd-h's exact typography -- weight 700, --fs-md, uppercase,
.5px tracking -- so the rail's visual weight is unchanged and this reads as the
same header gaining an option, not as new chrome. The selected tab uses the
accent underline the facet rail already uses for an active row, so one visual
language covers both rails. .rd-h is kept but was verified unused in this file
and everywhere else under research-dashboard/.

Verified the backtick rule that has broken this build three times: zero
backticks inside the useShadowStyles literal (chars 5459-18249); the one in a
JSDoc comment sits outside it. check:template-literal and check:sfc-parse pass.
This commit is contained in:
doxa-maps-88655c70 2026-09-22 20:19:32 +00:00
parent 9cae7fe16a
commit 5d12841fe7

View file

@ -6,11 +6,41 @@
* click via delegation on the stable container (per-element @click is unreliable in a virtualized/
* patched defineCustomElement tree). Tweens via CSS (bar width + slice opacity).
*/
import { inject, computed } from 'vue'
import { inject, computed, ref } from 'vue'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { aggregate, buildDonut, buildBars, lbl, buildCountryRegionColors } from '../../composables/useDashboardAggregations.js'
import { buildSearchAccessors, makeSearchMatcher } from '../../composables/useDashboardShell.js'
/**
* ── THE RAIL'S TAB STRIP ──────────────────────────────────────────────────────────────────────
* Charts is selected by default, which is the Driver's wording: "Charts will be a SELECTED TAB
* that will show the charts."
*
* ★ GOSPEL RESOURCES SHIPS DISABLED, NOT HIDDEN, and that is a deliberate choice rather than a
* placeholder. Its content is not a separate dataset panel — the Driver described it as the SAME
* facet breakdown re-measured on a different axis ("people groups by their gospel resources
* availability"), and three decisions about that data are still open: the official lostness number,
* whether to stay key-less or register a Joshua Project key, and the UI shape. Guessing any of them
* would ship a wrong picture that looks right, which is this codebase's most expensive failure mode.
*
* A HIDDEN tab is indistinguishable from an unbuilt one. A disabled tab carrying its reason in the
* title attribute tells the Driver exactly what it is waiting on, every time he looks at the rail —
* which is worth more than the tab being invisible until it is perfect.
*
* `tabs` is a plain array rather than config-driven on purpose: there are two of them and one is a
* stub. Making it configurable now would be an abstraction over a single real case.
*/
const tabs = [
{ id: 'charts', label: 'Charts' },
{
id: 'gospel',
label: 'Gospel Resources',
disabled: true,
why: 'Waiting on three decisions: the official lostness number, key-less vs a registered Joshua Project key, and the UI shape. See IDEA-GOSPEL-RESOURCES-INTEGRATION-STRATEGY.',
},
]
const activeTab = ref('charts')
const dash = inject('dashboardData', null)
const bus = inject('selectionBus', null)
const cfg = inject('dashboardConfig', {})
@ -105,7 +135,26 @@ useShadowStyles(`
.rd-charts::-webkit-scrollbar-track{ background:transparent; }
.rd-charts::-webkit-scrollbar-thumb{ background:var(--dx-scroll-thumb, #c4c8c0); border-radius:8px; }
.rd-charts::-webkit-scrollbar-thumb:hover{ background:#9aa197; }
/* ★ THE TAB STRIP REPLACES .rd-h, and inherits its exact typography on purpose: weight 700, the
--fs-md token, uppercase, .5px tracking. The rail's visual weight is unchanged, so this reads
as the same header having gained a second option rather than as new chrome appearing. .rd-h is
kept below only because a future rail section may want a plain heading again; it is currently
unused in this file and in every other file under research-dashboard/ (checked). */
.rd-charts .rd-h{ font-weight:700; font-size:var(--fs-md,12px); text-transform:uppercase; letter-spacing:.5px; color:var(--rd-muted, #6d766e); }
.rd-charts .rd-tabs{ display:flex; gap:2px; align-items:stretch; border-bottom:1px solid var(--rd-border, #d1d4cd); margin-bottom:6px; }
.rd-charts .rd-tab{ appearance:none; background:none; border:none; cursor:pointer;
padding:4px 8px 5px; font-family:inherit; font-weight:700; font-size:var(--fs-md,12px);
text-transform:uppercase; letter-spacing:.5px; color:var(--rd-muted, #6d766e);
border-bottom:2px solid transparent; margin-bottom:-1px; white-space:nowrap; }
.rd-charts .rd-tab:hover:not(:disabled){ color:var(--rd-text, #3b463d); }
/* The selected tab is the ACCENT underline plus full-strength text — the same signal the facet
rail uses for an active row, so one visual language covers both rails. */
.rd-charts .rd-tab.on{ color:var(--rd-text, #3b463d); border-bottom-color:var(--rd-accent, #92b195); }
/* A disabled tab stays LEGIBLE at .45 rather than being hidden: it is a status report, and its
title attribute names the three decisions it waits on. Hiding it would make "not built yet"
and "does not exist" look identical. */
.rd-charts .rd-tab.off{ opacity:.45; cursor:not-allowed; }
.rd-charts .rd-tab:focus-visible{ outline:2px solid var(--rd-accent, #92b195); outline-offset:-2px; }
/* 10px -> 7px 6px: reclaims ~8px of width per card for the plot area itself. */
.rd-chart{ border:1px solid var(--rd-line, #d1d4cd); border-radius:var(--r-lg,10px); background:var(--rd-bg, #f3f3f1); padding:7px 6px; }
.rd-chart .rd-ct{ font-weight:700; font-size:var(--fs-sm,11px); color:var(--rd-ink, #3b463d); margin-bottom:6px; }
@ -202,7 +251,31 @@ useShadowStyles(`
<template>
<aside class="rd-charts" data-region="charts-rail" @click="onChartClick">
<div class="rd-h">Charts</div>
<!--
★ THE RAIL IS A TAB STRIP, NOT A HEADING (Driver: "I asked a long time ago that the chart
becomes a tab, so that there are tabs, so that you can have the Gospel Resources tab next to
Charts. Charts will be a selected tab that will show the charts.")
THIS IS HALF THE CARD, DELIBERATELY. The container is buildable now; what the Gospel Resources
tab ENCODES is not, because it waits on three open Driver decisions (the official lostness
number, key-less vs a registered Joshua Project key, and the UI shape). Building the strip now
means the tab exists the moment those land, instead of the whole feature waiting on them.
So the second tab renders DISABLED with an explicit reason rather than being hidden. A hidden
tab is indistinguishable from an unbuilt one; a disabled tab that says why is a status report.
-->
<div class="rd-tabs" role="tablist" aria-label="Rail views">
<button
v-for="t in tabs" :key="t.id"
class="rd-tab" :class="{ on: activeTab === t.id, off: t.disabled }"
role="tab" type="button"
:aria-selected="activeTab === t.id ? 'true' : 'false'"
:disabled="t.disabled"
:title="t.disabled ? t.why : t.label"
:data-tab="t.id"
@click.stop="!t.disabled && (activeTab = t.id)"
>{{ t.label }}</button>
</div>
<template v-for="chart in charts" :key="chart.id">
<div v-if="chart.type === 'donut'" class="rd-chart" :data-chart="chart.facet">