Kit landing 4c-4: the breadcrumb completes the facet-cascade group, and three dead shims go

THE GROUP WAS SCHEDULED AS ONE LANDING AND DELIVERED AS TWO. Section 8 of the spec puts the facet
rail, the chip bar and the BREADCRUMB in a single landing. The rail moved in 4c-3 and the chips in
4c-2; the breadcrumb was left behind, and nothing failed, because it is mounted by nobody. A leftover
like that is invisible unless someone re-reads the spec against the tree, which is what this landing
came out of.

The breadcrumb gets exactly the inversion the chip bar got: no useTreeLegendLabels import, the
resolver read from dashboardConfig.legend, and the SAME unconfigured default — identity, joining an
array. Identical on purpose rather than by copy-paste: the breadcrumb and the chips render the same
filterSet in the same band, so if their defaults differed a half-configured dashboard would show one
value resolved and the other raw, side by side. Its dead `dataStore` / `searchSourceId` injects go
too; they existed only to build the composable that is no longer imported.

★ AND THREE ONE-CYCLE SHIMS FROM LANDINGS 2 AND 3 WERE NEVER RETIRED. plugin-designs/research-
dashboard/src/composables/ still held useDashboardLayout.js (11 lines), useSelectionCamera.js (11) and
useCsvExport.js (14), each a pure re-export of the library module. MEASURED before deleting: ZERO
importers. Every consumer — including DashboardMapPanel, DashboardDataTable, DashboardDetailDrawer,
useDashboardShell, gospel-internet's board and research-dashboard.vue — already reads
'@map/composables/…' directly, and a tree-wide grep for a relative import of any of the three returns
nothing. So they were dead code that made the plugin look like it still owned three library modules.

I wrote "a one-cycle shim that nobody retires is permanent" into the previous landing's gate comment
while three older ones were sitting in the tree unnoticed. They were not found by a gate; they were
found by listing what is still in plugin-designs and comparing it against the spec.

check:legend-mirrors arm 5 now guards three moved names instead of two, so a re-export of the
breadcrumb creeping back into the plugin barrel is caught the same way.

Source only. research-dashboard and dashboard-plus-grid need a rebuild.
This commit is contained in:
Doxa Maps 2026-09-27 05:01:27 +00:00
parent 65faa2d971
commit 79e373d7cb
7 changed files with 24 additions and 42 deletions

View file

@ -28,6 +28,8 @@ and a change here can break other maps.
`import … from '@map/components/charts/BarChart.vue'`
- **ProgressBar** — ProgressBar.vue — the DX-life progress bar: a single-value track, or a segmented one with a legend.
`import … from '@map/components/charts/ProgressBar.vue'`
- **DashboardBreadcrumb** — ── KIT LANDING 4c-4: MOVED FROM plugin-designs/research-dashboard/ ───────────────────────────
`import … from '@map/components/dashboards/DashboardBreadcrumb.vue'`
- **DashboardFacetRail** — DashboardFacetRail (was Sidebar.vue) — RDASH redesign phase 2: the floating @map SemanticTreeLegend is DROPPED
`import … from '@map/components/dashboards/DashboardFacetRail.vue'`
- **DashboardFilterChips** — DashboardFilterChips — the live filterSet rendered as removable AND-stacked chips

View file

@ -1,4 +1,14 @@
<script setup>
/**
* ── KIT LANDING 4c-4: MOVED FROM plugin-designs/research-dashboard/ ───────────────────────────
* The breadcrumb completes the facet-cascade group the spec scheduled together — rail (4c-3), chips
* (4c-2), breadcrumb. It was left behind when the other two moved, which is the whole reason it is
* called out here: a group scheduled as one landing was delivered as two, and the leftover is
* invisible unless someone re-reads the spec against the tree.
*
* Like the chip bar, it is mounted by NOBODY today (it was exported from the plugin barrel and
* rendered nowhere), so its gates mean "it mounts and degrades", not "it looks right".
*/
import { tStatic } from '@map/i18n/index.js'
const L = (key, en) => tStatic('dashboard.' + key) || en
/**
@ -8,7 +18,7 @@ const L = (key, en) => tStatic('dashboard.' + key) || en
*/
import { inject, computed } from 'vue'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { useTreeLegendLabels } from '../../composables/useTreeLegendLabels.js'
// No research label import: the shell supplies the resolver on dashboardConfig.legend (landing 4b).
const bus = inject('selectionBus', null)
const cfg = inject('dashboardConfig', {})
@ -18,7 +28,14 @@ const filterSet = bus?.filterSet || {}
// filter chips do (dashboard.config.js:54-57 — codes must never reach a user-facing menu).
// DISPLAY ONLY: `k` is what popTo() acts on and the stored value is never rewritten, so the bus's
// byte-match contract is untouched.
const { labelFor } = useTreeLegendLabels(cfg, inject('dataStore', null), inject('searchSourceId', null))
// ★ THE SAME RESOLVER, AND THE SAME UNCONFIGURED DEFAULT, AS THE CHIP BAR (4c-2). Identical on
// purpose: the breadcrumb and the chips render the same filterSet, so if their defaults differed a
// half-configured dashboard would show one value resolved and the other raw, in the same band.
// The list join matters for the same reason it did there — a filterSet value is an ARRAY once two
// values are selected (spec D1), and `String(['A012','A013'])` prints comma-jammed.
// `dataStore` and `searchSourceId` are NOT injected any more: they existed only to construct
// useTreeLegendLabels, and an unused inject is a claim about the host.
const labelFor = (facet, value) => (cfg?.legend?.labelFor ? cfg.legend.labelFor(facet, value) : (Array.isArray(value) ? value.join(', ') : value))
const crumbs = computed(() => Object.entries(filterSet).map(([k, v]) => ({ k, v: String(labelFor(k, v)) })))
function popTo(k) {

View file

@ -285,7 +285,7 @@ if (!fails.some((f) => f.startsWith('1'))) notes.push(`insets: ${SURFACES.length
// A one-cycle shim that nobody retires is permanent, so the gate now holds the window closed.
const barrel = read(BARREL) || ''
const barrelCode = barrel.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/(^|[^:])\/\/[^\n]*/g, '$1')
for (const moved of ['DashboardFilterChips', 'DashboardFacetRail']) {
for (const moved of ['DashboardFilterChips', 'DashboardFacetRail', 'DashboardBreadcrumb']) {
if (new RegExp(`export[^\\n]*${moved}`).test(barrelCode)) {
fails.push(`5 ${BARREL} re-exports ${moved}, which lives in library/components/dashboards/. The one-cycle shim is retired: importers read '@map/components/dashboards/${moved}.vue' directly, and a barrel re-export makes the plugin look like the owner of a kit component.`)
}

View file

@ -13,8 +13,8 @@
* useGeocoderSearch composable via a busAdapter sink (kind-switch stays in the composable).
*/
// ── THE TWO MOVED PANELS ARE NOT HERE ────────────────────────────────────────────────────────
// DashboardFacetRail and DashboardFilterChips live in library/components/dashboards/ (kit landings
// 4c-3 and 4c-2). Each was re-exported from here for ONE cycle so that moving a file and changing
// DashboardFacetRail, DashboardFilterChips and DashboardBreadcrumb live in library/components/
// dashboards/ (kit landings 4c-3, 4c-2 and 4c-4). Each was re-exported from here for ONE cycle so that moving a file and changing
// its importers were separate, reviewable landings; both shims are now retired. Import them from
// '@map/components/dashboards/<Name>.vue' — deliberately NOT re-added here, because a barrel that
// re-exports a library component makes the plugin look like its owner and is the thing that kept
@ -23,7 +23,6 @@ export { default as DashboardTopBar } from './DashboardTopBar.vue'
export { default as DashboardDataTable } from './DashboardDataTable.vue'
export { default as DashboardChartsRail } from './DashboardChartsRail.vue'
export { default as DashboardDetailDrawer } from './DashboardDetailDrawer.vue'
export { default as DashboardBreadcrumb } from './DashboardBreadcrumb.vue'
export { default as DashboardMapPanel } from './DashboardMapPanel.vue'
export { default as DashboardSearchHeader } from './DashboardSearchHeader.vue'
// DashboardResearchFilters — the DX-life research-page search bar + expand-filters dropdown SET

View file

@ -1,14 +0,0 @@
/**
* useCsvExport.js — RE-EXPORT SHIM (dashboard-kit landing 3).
*
* The real thing now lives at library/composables/useCsvExport.js. Same one-cycle contract as
* landings 1 and 2: every importer keeps working unchanged while the move is verified on its own,
* and this file is deleted in the next landing after a measured check that nothing still imports it.
*
* EXPORT_FIELDS travels with it. Re-exporting only the composable would have been a silent break:
* the field list is imported separately by the gate that checks the CSV's shape.
*
* It re-exports; it does not duplicate. A second copy would drift silently while both halves kept
* passing their gates.
*/
export { useCsvExport, EXPORT_FIELDS } from '@map/composables/useCsvExport.js'

View file

@ -1,11 +0,0 @@
/**
* useDashboardLayout.js — RE-EXPORT SHIM (dashboard-kit landing 2).
*
* The engine now lives at library/composables/useDashboardLayout.js. Same one-cycle contract as
* landing 1's bus shim: every importer keeps working unchanged while the move is verified on its
* own, and this file is deleted in the next landing.
*
* It re-exports; it does not duplicate. A second copy would drift silently while both halves kept
* passing their gates.
*/
export { useDashboardLayout } from '@map/composables/useDashboardLayout.js'

View file

@ -1,11 +0,0 @@
/**
* useSelectionCamera.js — RE-EXPORT SHIM (dashboard-kit landing 3).
*
* The real thing now lives at library/composables/useSelectionCamera.js. Same one-cycle contract as landings 1
* and 2: every importer keeps working unchanged while the move is verified on its own, and this
* file is deleted in the next landing after a measured check that nothing still imports it.
*
* It re-exports; it does not duplicate. A second copy would drift silently while both halves kept
* passing their gates.
*/
export { useSelectionCamera, selectionBounds, CAMERA_POLICY } from '@map/composables/useSelectionCamera.js'