Kit 4c-2/4c-3 follow-up: both one-cycle barrel shims retired, and the gate holds the window shut
The chip bar (4c-2) and the facet rail (4c-3) each moved into library/components/dashboards/ and were
re-exported from the plugin barrel for ONE cycle, so that moving a file and changing its importers
were separate, reviewable landings. That cycle is over.
BLAST RADIUS, measured on main rather than assumed: exactly ONE real importer. research-dashboard.vue
imported DashboardFacetRail from './src/components/dashboards'; it now reads
'@map/components/dashboards/DashboardFacetRail.vue'. DashboardFilterChips had ZERO importers, so its
shim served nobody and is simply gone. Everything else that named either component was a gate already
pointing at the library path, or prose.
check:legend-mirrors arm 5 INVERTS, and the inversion is the point rather than an edit. While the
shim existed, the risk was an importer failing to resolve, so the arm REQUIRED the re-export. With the
shim retired the risk is the opposite and larger: a re-export creeping back makes the plugin barrel
look like the owner of a library component, which is exactly how check:filter-multivalue once ended up
reading a shim instead of the panel it was written to guard, and passing vacuously. A one-cycle shim
that nobody retires is permanent, so the gate now holds the window closed — it fails on a returning
re-export AND on any file still importing either name through the barrel or its old path.
★ THE THREE FILES MUST LAND TOGETHER. Measured: the new gate produces 3 arm-5 failures against
unmodified main, because main still has both shims and the barrel import. check:legend-mirrors is in
gates:post, so landing the gate without the source changes turns every build red. All three failures
are in the two files this commit fixes, so nothing outside it is affected and no other owner is
blocked.
Two false positives of my own, both caught by running the gate rather than reading it:
- The importer test used `import … <Name> … [\s\S]{0,200}? from '…'`, and that span CROSSED INTO THE
NEXT STATEMENT: after the fix, research-dashboard.vue has the new single-name import immediately
followed by a braced import ending `from './src/components/dashboards'`, so the regex started on the
corrected line and found the barrel path two statements later. It reported the file it had just been
fixed in. Now bounded to one statement with a negative lookahead for the next `import`.
- That is the third unbounded-span false positive I have written today — the others walked a brace
match past a callback-less watch() and read a chip's internal name as prose. The shape is the
lesson, so it is written down beside the fix: never let a span cross a statement boundary.
Verified on main + these changes: check:legend-mirrors, check:sfc-parse, check:no-free-identifiers,
check:filter-multivalue, check:no-cross-plugin-imports, check:composites-mount, check:library-index and
check:setup-order all pass. Arm 5 negative-tested both ways: a returning barrel re-export and a
reverted importer are each caught, and the clean state passes.
Source only. research-dashboard and dashboard-plus-grid need a rebuild.
This commit is contained in:
parent
667c6331c8
commit
9c86d91c7f
3 changed files with 67 additions and 13 deletions
|
|
@ -33,10 +33,13 @@ const T = (key, en) => tStatic(key) || en
|
|||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { useDashboardShell, fmt } from './src/composables/useDashboardShell.js'
|
||||
import { useDashboardLayout } from '@map/composables/useDashboardLayout.js'
|
||||
// The facet rail is a KIT component now (landing 4c-3) and is imported from the library directly.
|
||||
// The plugin barrel re-exported it for one cycle so the move and the import change were separate
|
||||
// landings; that shim is gone, so this is the only line that names its location.
|
||||
import DashboardFacetRail from '@map/components/dashboards/DashboardFacetRail.vue'
|
||||
import {
|
||||
DashboardTopBar,
|
||||
DashboardResearchFilters,
|
||||
DashboardFacetRail,
|
||||
DashboardMapPanel,
|
||||
DashboardDataTable,
|
||||
DashboardChartsRail,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
* or dropping the projection from the visible text, fails 2; translating a RELIGION_FAMILIES value
|
||||
* fails 2 naming the link it would break.
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { readFileSync, existsSync, readdirSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
|
|
@ -40,6 +40,23 @@ const TAG = 'LEGEND-MIRRORS'
|
|||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
|
||||
const fails = []
|
||||
const notes = []
|
||||
/* Every .vue/.js under the two source roots — the importer sweep needs a file list, and building it
|
||||
* here keeps arm 5 from trusting a hardcoded list of "the files that could import this". */
|
||||
function sources(dir = null, acc = []) {
|
||||
const base = dir || ROOT
|
||||
let entries = []
|
||||
try { entries = readdirSync(base, { withFileTypes: true }) } catch (_) { return acc }
|
||||
for (const e of entries) {
|
||||
const full = join(base, e.name)
|
||||
const rel = full.slice(ROOT.length + 1)
|
||||
if (/(^|\/)(node_modules|dist|public|\.git)(\/|$)/.test(rel)) continue
|
||||
if (dir === null && !/^(plugin-designs|library)$/.test(e.name)) continue
|
||||
if (e.isDirectory()) sources(full, acc)
|
||||
else if (/\.(vue|js|mjs)$/.test(e.name)) acc.push(rel)
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
const read = (rel) => { const p = join(ROOT, rel); return existsSync(p) ? readFileSync(p, 'utf8') : null }
|
||||
|
||||
// ── 1. logical insets on the two surfaces the survey named ───────────────────────────────────
|
||||
|
|
@ -259,9 +276,45 @@ if (!fails.some((f) => f.startsWith('1'))) notes.push(`insets: ${SURFACES.length
|
|||
fails.push(`5 ${NEW} is missing — the kit component the barrel now points at does not exist.`)
|
||||
} else {
|
||||
if (read(OLD)) fails.push(`5 ${OLD} still exists. A move that leaves the original behind is a COPY, and two chip bars over one filterSet drift apart the moment one is edited.`)
|
||||
// ★ THIS ASSERTION INVERTED WHEN THE SHIM WINDOW CLOSED, and the inversion is the point rather
|
||||
// than an edit. While the one-cycle shim existed, the thing that could break was an importer of
|
||||
// the barrel failing to resolve, so the arm REQUIRED the re-export. Now that the shim is retired
|
||||
// the risk is the opposite and larger: a re-export creeping back makes the plugin barrel look
|
||||
// like the owner of a library component, which is exactly how check:filter-multivalue ended up
|
||||
// reading a shim instead of the panel it was written to guard, and passing vacuously.
|
||||
// A one-cycle shim that nobody retires is permanent, so the gate now holds the window closed.
|
||||
const barrel = read(BARREL) || ''
|
||||
if (!/DashboardFilterChips.*@map\/components\/dashboards/.test(barrel)) {
|
||||
fails.push(`5 ${BARREL} does not re-export DashboardFilterChips from the library — an importer of the barrel would resolve the path that just moved.`)
|
||||
const barrelCode = barrel.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/(^|[^:])\/\/[^\n]*/g, '$1')
|
||||
for (const moved of ['DashboardFilterChips', 'DashboardFacetRail']) {
|
||||
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.`)
|
||||
}
|
||||
// And nobody may still be reading it through the barrel or from the path it left.
|
||||
for (const rel of sources()) {
|
||||
const src = read(rel); if (!src) continue
|
||||
const code = src.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/(^|[^:])\/\/[^\n]*/g, '$1')
|
||||
// ★ ONE IMPORT STATEMENT AT A TIME. The first version used `import … ${moved} …
|
||||
// [\s\S]{0,200}? from '…'`, and that span CROSSED INTO THE NEXT STATEMENT: after the fix,
|
||||
// research-dashboard.vue reads `import DashboardFacetRail from '@map/…'` immediately followed
|
||||
// by a braced import ending `from './src/components/dashboards'`, so the regex started at the
|
||||
// CORRECT line and found the barrel path two statements later. It reported the file it had
|
||||
// just been fixed in. Third unbounded-span false positive I have written today (the others
|
||||
// walked a brace match past a callback-less watch() and read a chip label's own name as
|
||||
// prose), so the shape is the lesson: never let a span cross a statement boundary — bound it
|
||||
// with a negative lookahead for the next statement keyword.
|
||||
const IMPORTS = /\bimport\b(?:(?!\bimport\b)[\s\S])*?\bfrom\s*'([^']*)'/g
|
||||
let stmt
|
||||
while ((stmt = IMPORTS.exec(code))) {
|
||||
const spec = stmt[0]
|
||||
const path = stmt[1]
|
||||
if (!new RegExp(`\\b${moved}\\b`).test(spec)) continue
|
||||
const viaBarrel = /components\/dashboards$/.test(path)
|
||||
const viaOldPath = new RegExp(`plugin-designs[^']*${moved}\\.vue$`).test(path)
|
||||
if (viaBarrel || viaOldPath) {
|
||||
fails.push(`5 ${rel} still imports ${moved} from '${path}'. It moved to library/components/dashboards/ — import it from '@map/components/dashboards/${moved}.vue'.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Comments first: this file's own notes name cfg.legend and useTreeLegendLabels.
|
||||
const code = src.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/(^|[^:])\/\/[^\n]*/g, '$1')
|
||||
|
|
|
|||
|
|
@ -12,19 +12,17 @@
|
|||
* standalone GeocoderComponent per config.search entry, each consuming the shared
|
||||
* 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
|
||||
// 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
|
||||
// check:filter-multivalue reading a shim instead of the panel it was written to guard.
|
||||
export { default as DashboardTopBar } from './DashboardTopBar.vue'
|
||||
// MOVED to library/components/dashboards/ (kit landing 4c-3), re-exported for one cycle like the
|
||||
// chip bar before it, so research-dashboard.vue's import list does not change in the landing that
|
||||
// moves the file.
|
||||
export { default as DashboardFacetRail } from '@map/components/dashboards/DashboardFacetRail.vue'
|
||||
export { default as DashboardDataTable } from './DashboardDataTable.vue'
|
||||
export { default as DashboardChartsRail } from './DashboardChartsRail.vue'
|
||||
export { default as DashboardDetailDrawer } from './DashboardDetailDrawer.vue'
|
||||
// MOVED to library/components/dashboards/ (kit landing 4c-2) and re-exported here for one cycle,
|
||||
// so no consumer's import line has to change in the same landing that moves the file. Anything
|
||||
// importing DashboardFilterChips from this barrel keeps working; the re-export goes when the rail
|
||||
// follows it in 4c-3 and this barrel becomes the library's own.
|
||||
export { default as DashboardFilterChips } from '@map/components/dashboards/DashboardFilterChips.vue'
|
||||
export { default as DashboardBreadcrumb } from './DashboardBreadcrumb.vue'
|
||||
export { default as DashboardMapPanel } from './DashboardMapPanel.vue'
|
||||
export { default as DashboardSearchHeader } from './DashboardSearchHeader.vue'
|
||||
|
|
|
|||
Loading…
Reference in a new issue