From b5d62598aa16eb1bd9b9e9fe0fb8e43ccdcd9378 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:05:28 -0700 Subject: [PATCH 01/27] Add central provider layer category registry --- occu-med-map/src/providerLayerRegistry.ts | 88 +++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 occu-med-map/src/providerLayerRegistry.ts diff --git a/occu-med-map/src/providerLayerRegistry.ts b/occu-med-map/src/providerLayerRegistry.ts new file mode 100644 index 00000000..d7fc7d67 --- /dev/null +++ b/occu-med-map/src/providerLayerRegistry.ts @@ -0,0 +1,88 @@ +export type ProviderLayerCategoryId = + | 'urgent-cares' + | 'occupational-health-clinics' + | 'dentists' + | 'blue-hive' + | 'faa-examiners' + | 'dot-examiners' + | 'labs' + | 'imaging' + | 'audiology' + | 'general-practitioners' + | 'pharmacy' + | 'international-providers' + | 'usa-embassy-recommended' + | 'uploaded-clinics'; + +export type ProviderLayerCategory = { + id: ProviderLayerCategoryId; + label: string; + channel: string; + color: string; + endpoint: string; + explorerSource?: string; + explorerClinicType?: string; +}; + +function category( + id: ProviderLayerCategoryId, + label: string, + color: string, + explorer: { source?: string; clinicType?: string } = {}, +): ProviderLayerCategory { + return { + id, + label, + channel: `category-${id}`, + color, + endpoint: `/api/provider-category-layers/${id}`, + explorerSource: explorer.source, + explorerClinicType: explorer.clinicType, + }; +} + +/** + * Single UI registry for ordinary provider-map categories. + * + * Adding a provider category should require one entry here plus one matching + * server-side category definition. Sidebar toggles, Mapbox channels, and the + * Provider Explorer category selector all derive from this registry. + */ +export const PROVIDER_LAYER_CATEGORIES: readonly ProviderLayerCategory[] = [ + category('urgent-cares', 'Urgent Cares', '#38bdf8', { clinicType: 'urgent_care' }), + category('occupational-health-clinics', 'Occupational Health Clinics', '#22d3ee', { clinicType: 'occupational_health_clinic' }), + category('dentists', 'Dentists', '#a78bfa', { clinicType: 'dental' }), + category('blue-hive', 'Blue Hive', '#60a5fa', { source: 'bluehive' }), + category('faa-examiners', 'FAA Examiners', '#f59e0b', { clinicType: 'faa_provider' }), + category('dot-examiners', 'DOT Examiners', '#fb923c', { clinicType: 'dot_provider' }), + category('labs', 'Labs', '#34d399', { clinicType: 'lab' }), + category('imaging', 'Imaging', '#f472b6', { clinicType: 'imaging' }), + category('audiology', 'Audiology', '#2dd4bf', { clinicType: 'audiology' }), + category('general-practitioners', 'General Practitioners', '#818cf8', { clinicType: 'general_practitioner' }), + category('pharmacy', 'Pharmacy', '#4ade80', { clinicType: 'pharmacy_vaccination' }), + category('international-providers', 'International Providers', '#06b6d4', { source: 'healthsites_osm' }), + category('usa-embassy-recommended', 'U.S. Embassy Recommended', '#facc15', { source: 'embassy_clinic_docs' }), + category('uploaded-clinics', 'Uploaded Clinics', '#c084fc', { source: 'my-clinics' }), +] as const; + +export const PUBLIC_HEALTH_LAYER = { + id: 'naccho-local-health-departments', + label: 'NACCHO Local Health Departments', + channel: 'naccho', + color: '#34d399', + endpoint: '/api/naccho-lhd', +} as const; + +export function getProviderLayerCategory(id: string): ProviderLayerCategory | undefined { + return PROVIDER_LAYER_CATEGORIES.find((entry) => entry.id === id); +} + +export const PROVIDER_EXPLORER_SOURCE_OPTIONS = [ + ['all', 'All sources'], + ...PROVIDER_LAYER_CATEGORIES + .filter((entry) => Boolean(entry.explorerSource)) + .map((entry) => [entry.explorerSource as string, entry.label] as [string, string]), + ['live', 'Live'], + ['saved', 'Saved'], + ['candidates', 'Candidates'], +] as const; From e1b252368d788b7e939027293ca2c5ddbd900147 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:05:56 -0700 Subject: [PATCH 02/27] Add categorized provider layer API --- .../src/routes/providerCategoryLayers.ts | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 api-server/src/routes/providerCategoryLayers.ts diff --git a/api-server/src/routes/providerCategoryLayers.ts b/api-server/src/routes/providerCategoryLayers.ts new file mode 100644 index 00000000..415f6659 --- /dev/null +++ b/api-server/src/routes/providerCategoryLayers.ts @@ -0,0 +1,317 @@ +import { Router, type Request, type Response } from "express"; +import { getProviderDatabaseProjects, type ProviderDatabaseProject } from "@workspace/db"; +import { isPersistenceConfigured } from "../lib/networkMapPersistence"; +import { queryWithStatementTimeout } from "../lib/queryWithStatementTimeout"; +import { parseOptionalNumber } from "../lib/providerCoordinates"; + +const router = Router(); +const MAX_PAGE_SIZE = 5000; + +type ProviderProjectFamily = ProviderDatabaseProject["family"] | "all"; +type Bounds = { north: number; south: number; east: number; west: number }; +type CategoryDefinition = { + family: ProviderProjectFamily; + typeKeys?: string[]; + capabilityPatterns?: string[]; + sourceKeys?: string[]; +}; + +const CATEGORY_DEFINITIONS: Record = { + "urgent-cares": { + family: "all", + typeKeys: ["urgent_care"], + capabilityPatterns: ["urgent care", "walk-in", "walk in"], + }, + "occupational-health-clinics": { + family: "all", + typeKeys: ["occupational_health_clinic"], + capabilityPatterns: ["occupational", "occ med", "employee health", "workers comp", "fit-for-duty", "fit for duty"], + }, + dentists: { + family: "all", + typeKeys: ["dental"], + capabilityPatterns: ["dental", "dentist", "dd 2813"], + }, + "blue-hive": { + family: "primary", + sourceKeys: ["bluehive"], + }, + "faa-examiners": { + family: "all", + typeKeys: ["faa_provider"], + capabilityPatterns: ["faa", "aviation medical", "aerospace medicine"], + }, + "dot-examiners": { + family: "all", + typeKeys: ["dot_provider"], + capabilityPatterns: ["dot exam", "dot medical", "fmcsa", "cdl medical"], + }, + labs: { + family: "all", + typeKeys: ["lab"], + capabilityPatterns: ["laboratory", "lab", "toxicology", "specimen collection", "drug screen", "phlebotomy"], + }, + imaging: { + family: "all", + typeKeys: ["imaging"], + capabilityPatterns: ["imaging", "radiology", "x-ray", "xray", "mri", "ct scan", "ultrasound"], + }, + audiology: { + family: "all", + capabilityPatterns: ["audiology", "audiogram", "audiometry", "hearing"], + }, + "general-practitioners": { + family: "all", + typeKeys: ["general_practitioner"], + capabilityPatterns: ["general practitioner", "general practice", "primary care", "family medicine", "internal medicine"], + }, + pharmacy: { + family: "all", + typeKeys: ["pharmacy_vaccination"], + capabilityPatterns: ["pharmacy", "vaccination", "immunization", "travel medicine"], + }, + "international-providers": { + family: "healthsites", + }, + "usa-embassy-recommended": { + family: "usa-embassy", + }, + "uploaded-clinics": { + family: "primary", + sourceKeys: ["my_clinics_upload"], + }, +}; + +function addParam(params: unknown[], value: unknown): string { + params.push(value); + return `$${params.length}`; +} + +function asBounds(req: Request): Bounds | null { + const useBounds = req.query.useBounds === "true" || req.query.bounds === "true"; + if (!useBounds) return null; + const north = parseOptionalNumber(req.query.north); + const south = parseOptionalNumber(req.query.south); + const east = parseOptionalNumber(req.query.east); + const west = parseOptionalNumber(req.query.west); + if (north === null || south === null || east === null || west === null) return null; + return { north, south, east, west }; +} + +function matchingProjects(definition: CategoryDefinition): ProviderDatabaseProject[] { + const projects = getProviderDatabaseProjects(); + if (definition.family === "all") return projects; + return projects.filter((project) => project.family === definition.family); +} + +async function canonicalViewAvailable(project: ProviderDatabaseProject): Promise { + const { rows } = await queryWithStatementTimeout( + project.pool, + "SELECT to_regclass('public.provider_master_map_view') IS NOT NULL AS ok", + [], + ); + return rows[0]?.ok === true; +} + +function categoryWhere(definition: CategoryDefinition, bounds: Bounds | null, params: unknown[]): string { + const conditions = [ + "pmv.lat IS NOT NULL", + "pmv.lng IS NOT NULL", + "pmv.lat BETWEEN -90 AND 90", + "pmv.lng BETWEEN -180 AND 180", + "(pmv.lat <> 0 OR pmv.lng <> 0)", + ]; + + if (definition.sourceKeys?.length) { + const placeholder = addParam(params, definition.sourceKeys.map((value) => value.toLowerCase())); + conditions.push(`lower(COALESCE(pmv.source_key, '')) = ANY(${placeholder}::text[])`); + } + + const typePredicates: string[] = []; + if (definition.typeKeys?.length) { + const placeholder = addParam(params, definition.typeKeys.map((value) => value.toLowerCase())); + typePredicates.push(`lower(COALESCE(pmv.primary_provider_type, '')) = ANY(${placeholder}::text[])`); + } + if (definition.capabilityPatterns?.length) { + const placeholder = addParam(params, definition.capabilityPatterns.map((value) => `%${value.toLowerCase()}%`)); + typePredicates.push(`lower(array_to_string(COALESCE(pmv.capability_tags, ARRAY[]::text[]), ' ')) LIKE ANY(${placeholder}::text[])`); + } + if (typePredicates.length) conditions.push(`(${typePredicates.join(" OR ")})`); + + if (bounds) { + conditions.push(`pmv.lat BETWEEN ${addParam(params, bounds.south)} AND ${addParam(params, bounds.north)}`); + conditions.push( + bounds.west <= bounds.east + ? `pmv.lng BETWEEN ${addParam(params, bounds.west)} AND ${addParam(params, bounds.east)}` + : `(pmv.lng >= ${addParam(params, bounds.west)} OR pmv.lng <= ${addParam(params, bounds.east)})`, + ); + } + + return conditions.join(" AND "); +} + +async function countProject(project: ProviderDatabaseProject, definition: CategoryDefinition, bounds: Bounds | null): Promise { + const params: unknown[] = []; + const where = categoryWhere(definition, bounds, params); + const { rows } = await queryWithStatementTimeout( + project.pool, + `SELECT count(*)::int AS total FROM public.provider_master_map_view pmv WHERE ${where}`, + params, + ); + return Number(rows[0]?.total || 0); +} + +async function loadProjectPage( + project: ProviderDatabaseProject, + definition: CategoryDefinition, + bounds: Bounds | null, + limit: number, + offset: number, +): Promise[]> { + const params: unknown[] = []; + const where = categoryWhere(definition, bounds, params); + const limitParam = addParam(params, limit); + const offsetParam = addParam(params, offset); + const { rows } = await queryWithStatementTimeout(project.pool, ` + SELECT + pmv.id, + pmv.master_key, + pmv.name, + pmv.address, + pmv.city, + pmv.admin_area, + pmv.postal_code, + pmv.lat, + pmv.lng, + pmv.phone, + pmv.website, + pmv.primary_provider_type, + pmv.capability_tags, + pmv.source_key, + pmv.source_kind, + pmv.quality_score + FROM public.provider_master_map_view pmv + WHERE ${where} + ORDER BY pmv.name ASC, pmv.id ASC + LIMIT ${limitParam} OFFSET ${offsetParam} + `, params); + return rows; +} + +function toProvider(row: Record, category: string): Record { + const type = String(row.primary_provider_type || "unknown"); + const tags = Array.isArray(row.capability_tags) ? row.capability_tags.map(String) : [type]; + return { + id: String(row.master_key || row.id || ""), + source_id: String(row.master_key || row.id || ""), + name: String(row.name || "Unnamed provider"), + address: row.address ?? null, + address_1: row.address ?? null, + city: row.city ?? null, + admin_area: row.admin_area ?? null, + state: row.admin_area ?? null, + postal_code: row.postal_code ?? null, + zip: row.postal_code ?? null, + lat: Number(row.lat), + lng: Number(row.lng), + phone: row.phone ?? null, + website: row.website ?? null, + clinic_type: type, + providerType: type, + category: type, + services: tags, + categories: tags, + types: tags, + source: String(row.source_key || "indexed"), + data_source: String(row.source_key || "indexed"), + source_kind: String(row.source_kind || "stored"), + trust_tier: Number(row.quality_score || 0) >= 0.85 ? "verified" : Number(row.quality_score || 0) >= 0.7 ? "registry" : "directory", + confidence_score: row.quality_score == null ? null : Number(row.quality_score), + provider_layer_category: category, + }; +} + +router.get("/provider-category-layers/:category", async (req: Request, res: Response) => { + const category = req.params.category; + const definition = CATEGORY_DEFINITIONS[category]; + if (!definition) { + res.status(400).json({ error: `Unknown provider category: ${category}`, categories: Object.keys(CATEGORY_DEFINITIONS) }); + return; + } + + try { + if (!isPersistenceConfigured()) { + res.json({ providers: [], count: 0, loaded: 0, total: 0, page: 1, limit: 0, hasMore: false, category, visibleCapped: false }); + return; + } + + const limit = Math.min(Math.max(Number(req.query.limit) || 2000, 1), MAX_PAGE_SIZE); + const page = Math.max(Number(req.query.page) || 1, 1); + const bounds = asBounds(req); + const warnings: string[] = []; + + const probes = ( + await Promise.all(matchingProjects(definition).map(async (project) => { + try { + if (!(await canonicalViewAvailable(project))) throw new Error("canonical provider view is unavailable"); + return { project, total: await countProject(project, definition, bounds) }; + } catch (error) { + warnings.push(`${project.id}: ${error instanceof Error ? error.message : String(error)}`); + return null; + } + })) + ).filter((probe): probe is { project: ProviderDatabaseProject; total: number } => Boolean(probe)); + + const total = probes.reduce((sum, probe) => sum + probe.total, 0); + let offset = (page - 1) * limit; + let remaining = limit; + const rows: Record[] = []; + + for (const probe of probes) { + if (remaining <= 0) break; + if (offset >= probe.total) { + offset -= probe.total; + continue; + } + const requested = Math.min(remaining, probe.total - offset); + try { + rows.push(...await loadProjectPage(probe.project, definition, bounds, requested, offset)); + } catch (error) { + warnings.push(`${probe.project.id}: ${error instanceof Error ? error.message : String(error)}`); + } + remaining -= requested; + offset = 0; + } + + const providers = rows.map((row) => toProvider(row, category)); + res.json({ + providers, + count: providers.length, + loaded: providers.length, + total, + page, + limit, + hasMore: page * limit < total, + category, + databaseProjects: probes.map((probe) => probe.project.id), + partial: warnings.length > 0, + ...(warnings.length ? { warnings, warning: warnings.join(" ") } : {}), + visibleCapped: false, + }); + } catch (error) { + const warning = error instanceof Error ? error.message : "Provider category layer query failed"; + console.error(`[ProviderCategoryLayers] ${category} query failed:`, error); + res.status(503).json({ + providers: [], + count: 0, + loaded: 0, + total: 0, + category, + warning, + transientFailure: true, + visibleCapped: false, + }); + } +}); + +export default router; From 28600fd6e8bed6a79fec9b2025a523b67307bc8e Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:06:05 -0700 Subject: [PATCH 03/27] Register categorized provider layer API --- api-server/src/routes/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api-server/src/routes/index.ts b/api-server/src/routes/index.ts index 049c23ec..7f529c1a 100644 --- a/api-server/src/routes/index.ts +++ b/api-server/src/routes/index.ts @@ -18,6 +18,7 @@ import vectorIndexRouter from "./vectorIndex"; import providerUploadLifecycleRouter from "./providerUploadLifecycle"; import providerDatasetUploadsRouter from "./providerDatasetUploads"; import providerLayersRouter from "./providerLayers"; +import providerCategoryLayersRouter from "./providerCategoryLayers"; import googlePlacesRouter from "./googlePlaces"; import enhancedSearchRouter from "./enhancedSearch"; import providerExplorerRouter from "./providerExplorer"; @@ -54,6 +55,7 @@ router.use(stabilizeProviderLayerRequests); router.use(providerUploadLifecycleRouter); router.use(providerDatasetUploadsRouter); router.use(providerLayersRouter); +router.use(providerCategoryLayersRouter); router.use(myClinicsUploadRouter); router.use(googlePlacesRouter); router.use(enhancedSearchRouter); From a58242b7497b20b9e3fb83c8c9d0e30f5315ada7 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:06:34 -0700 Subject: [PATCH 04/27] Make provider dataset map channels dynamic --- .../src/providerDatasetNativeMapRuntime.ts | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/occu-med-map/src/providerDatasetNativeMapRuntime.ts b/occu-med-map/src/providerDatasetNativeMapRuntime.ts index bdf3fd11..513d69df 100644 --- a/occu-med-map/src/providerDatasetNativeMapRuntime.ts +++ b/occu-med-map/src/providerDatasetNativeMapRuntime.ts @@ -1,7 +1,8 @@ import mapboxgl from "mapbox-gl"; import { getTrackedMapboxMaps, registerMapboxMapInitializer } from "./mapboxMapLifecycleRuntime"; -export type ProviderDatasetChannel = "bluehive" | "dentists" | "inventory" | "indexed" | "my-clinics" | "naccho" | "uploaded"; +/** Provider dataset channels are registry-driven; new categories do not require a runtime union edit. */ +export type ProviderDatasetChannel = string; type DatasetRenderOptions = { baseColor: string; @@ -40,16 +41,30 @@ type ProviderDatasetDiagnosticsGlobal = typeof globalThis & { }; }; -const CHANNELS: ProviderDatasetChannel[] = ["bluehive", "dentists", "inventory", "indexed", "my-clinics", "naccho", "uploaded"]; +const DEFAULT_CHANNELS: ProviderDatasetChannel[] = ["bluehive", "dentists", "inventory", "indexed", "my-clinics", "naccho", "uploaded"]; +const channels = new Set(DEFAULT_CHANNELS); const states = new Map(); -for (const channel of CHANNELS) { - states.set(channel, { + +function emptyState(): ChannelState { + return { collection: { type: "FeatureCollection", features: [] }, baseColor: "#0891b2", glow: false, - }); + }; } +function stateFor(channel: ProviderDatasetChannel): ChannelState { + channels.add(channel); + let state = states.get(channel); + if (!state) { + state = emptyState(); + states.set(channel, state); + } + return state; +} + +for (const channel of DEFAULT_CHANNELS) stateFor(channel); + function safeId(channel: ProviderDatasetChannel): string { return channel.replace(/[^a-z0-9-]/gi, "-"); } @@ -102,7 +117,7 @@ function heatmapColor(baseColor: string): mapboxgl.Expression { } function ensureChannel(map: mapboxgl.Map, channel: ProviderDatasetChannel): void { - const state = states.get(channel)!; + const state = stateFor(channel); const channelIds = ids(channel); const existing = map.getSource(channelIds.source) as mapboxgl.GeoJSONSource | undefined; if (existing) existing.setData(state.collection); @@ -145,7 +160,7 @@ function ensureChannel(map: mapboxgl.Map, channel: ProviderDatasetChannel): void } function updateChannel(channel: ProviderDatasetChannel): void { - const state = states.get(channel)!; + const state = stateFor(channel); const channelIds = ids(channel); for (const map of getTrackedMapboxMaps()) { try { @@ -159,8 +174,7 @@ function updateChannel(channel: ProviderDatasetChannel): void { } function getProviderDatasetSnapshot(channel: ProviderDatasetChannel): ProviderDatasetSnapshot { - const state = states.get(channel); - const features = state?.collection.features ?? []; + const features = states.get(channel)?.collection.features ?? []; return { channel, featureCount: features.length, @@ -199,6 +213,7 @@ export function renderProviderDataset( }, }); } + channels.add(channel); states.set(channel, { collection: { type: "FeatureCollection", features }, baseColor: options.baseColor, @@ -209,7 +224,7 @@ export function renderProviderDataset( } export function clearProviderDataset(channel: ProviderDatasetChannel): void { - const previous = states.get(channel)!; + const previous = stateFor(channel); states.set(channel, { ...previous, collection: { type: "FeatureCollection", features: [] }, @@ -224,7 +239,7 @@ function markHandled(originalEvent: unknown): void { } function renderedHit(map: mapboxgl.Map, point: mapboxgl.Point): DatasetHit | null { - const layers = CHANNELS.map((channel) => ids(channel).points).filter((layer) => Boolean(map.getLayer(layer))); + const layers = [...channels].map((channel) => ids(channel).points).filter((layer) => Boolean(map.getLayer(layer))); if (!layers.length) return null; const box: [[number, number], [number, number]] = [[point.x - 12, point.y - 12], [point.x + 12, point.y + 12]]; try { @@ -249,7 +264,7 @@ function renderedHit(map: mapboxgl.Map, point: mapboxgl.Point): DatasetHit | nul function stateHit(map: mapboxgl.Map, point: mapboxgl.Point, maxDistance = 16): DatasetHit | null { let nearest: DatasetHit | null = null; - for (const channel of CHANNELS) { + for (const channel of channels) { const channelIds = ids(channel); if (!map.getLayer(channelIds.points) || !map.getSource(channelIds.source)) continue; const state = states.get(channel); @@ -281,7 +296,7 @@ registerMapboxMapInitializer({ window.clearTimeout(styleRetryTimer); styleRetryTimer = 0; try { - CHANNELS.forEach((channel) => ensureChannel(map, channel)); + [...channels].forEach((channel) => ensureChannel(map, channel)); } catch (error) { console.debug("Provider dataset native map waiting for Mapbox style", error); styleRetryTimer = window.setTimeout(apply, 50); From 2448dda2ad3322c8ca376d71c695bcff7cdd9c56 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:07:11 -0700 Subject: [PATCH 05/27] Remove provider explorer map render caps --- occu-med-map/src/providerExplorerNativeMapRuntime.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/occu-med-map/src/providerExplorerNativeMapRuntime.ts b/occu-med-map/src/providerExplorerNativeMapRuntime.ts index 58ec418b..25e7bee2 100644 --- a/occu-med-map/src/providerExplorerNativeMapRuntime.ts +++ b/occu-med-map/src/providerExplorerNativeMapRuntime.ts @@ -223,7 +223,7 @@ function fitProviders(providers: ProviderFeature[]): void { } export function renderProviderExplorerPins(providers: ProviderFeature[], options: ProviderRenderOptions): number { - const drawable = providers.slice(0, 1000).map((provider) => providerFeature(provider, options, "pins")).filter(Boolean) as GeoJSON.Feature[]; + const drawable = providers.map((provider) => providerFeature(provider, options, "pins")).filter(Boolean) as GeoJSON.Feature[]; collections.pins = { type: "FeatureCollection", features: drawable }; updateMaps("pins"); if (options.fit) fitProviders(providers); @@ -298,14 +298,14 @@ export function renderProviderExplorerLive(providers: ProviderFeature[], options liveProviders.clear(); providers.forEach((provider) => liveProviders.set(String(provider.id || ""), provider)); liveAction = options.onAction || null; - const features = providers.slice(0, 1000).map((provider) => providerFeature(provider, options, "live")).filter(Boolean) as GeoJSON.Feature[]; + const features = providers.map((provider) => providerFeature(provider, options, "live")).filter(Boolean) as GeoJSON.Feature[]; collections.live = { type: "FeatureCollection", features }; updateMaps("live"); return features.length; } export function renderProviderExplorerGaps(providers: ProviderFeature[], options: ProviderRenderOptions): number { - const features = providers.slice(0, 500).map((provider) => providerFeature(provider, options, "gaps")).filter(Boolean) as GeoJSON.Feature[]; + const features = providers.map((provider) => providerFeature(provider, options, "gaps")).filter(Boolean) as GeoJSON.Feature[]; collections.gaps = { type: "FeatureCollection", features }; updateMaps("gaps"); return features.length; From 293210f4363d0b56fec20e65277598702df35b61 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:07:46 -0700 Subject: [PATCH 06/27] Render provider layer sidebar from central registry --- .../src/ProviderLayerRegistryPanel.tsx | 282 ++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 occu-med-map/src/ProviderLayerRegistryPanel.tsx diff --git a/occu-med-map/src/ProviderLayerRegistryPanel.tsx b/occu-med-map/src/ProviderLayerRegistryPanel.tsx new file mode 100644 index 00000000..a20cf09a --- /dev/null +++ b/occu-med-map/src/ProviderLayerRegistryPanel.tsx @@ -0,0 +1,282 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { getActiveMapboxMap } from './dualMapEngineRuntime'; +import { fetchProviderLayer } from './providerLayerRequestRuntime'; +import { clearProviderDataset, renderProviderDataset } from './providerDatasetNativeMapRuntime'; +import { + PROVIDER_LAYER_CATEGORIES, + PUBLIC_HEALTH_LAYER, + type ProviderLayerCategory, +} from './providerLayerRegistry'; + +type LayerDefinition = ProviderLayerCategory | typeof PUBLIC_HEALTH_LAYER; +type LayerState = { + enabled: boolean; + loading: boolean; + count: number; + total: number; + error: string; + warning: string; +}; + +type LayerStateMap = Record; + +const EMPTY_LAYER_STATE: LayerState = { + enabled: false, + loading: false, + count: 0, + total: 0, + error: '', + warning: '', +}; + +function initialState(): LayerStateMap { + return Object.fromEntries( + [...PROVIDER_LAYER_CATEGORIES, PUBLIC_HEALTH_LAYER].map((entry) => [entry.id, { ...EMPTY_LAYER_STATE }]), + ); +} + +function escapeHtml(value: unknown): string { + return String(value ?? '').replace(/[&<>"']/g, (char) => ({ + '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', + }[char] || char)); +} + +function safeHttpUrl(value: unknown): string | null { + if (typeof value !== 'string' || !value.trim()) return null; + try { + const url = new URL(value); + return url.protocol === 'http:' || url.protocol === 'https:' ? url.toString() : null; + } catch { + return null; + } +} + +function providerPopup(provider: any, layer: LayerDefinition): string { + const name = escapeHtml(provider?.name || provider?.clinic_name || layer.label); + const address = escapeHtml([ + provider?.address || provider?.address_1, + provider?.city, + provider?.admin_area || provider?.state, + provider?.postal_code || provider?.zip, + ].filter(Boolean).join(', ') || 'Address unavailable'); + const phone = typeof provider?.phone === 'string' && provider.phone.trim() ? provider.phone.trim() : ''; + const website = safeHttpUrl(provider?.website); + const source = escapeHtml(provider?.source || provider?.data_source || layer.label); + const type = escapeHtml(provider?.clinic_type || provider?.providerType || provider?.category || ''); + return `
+
${name}
+
${escapeHtml(layer.label)}
+
${address}
+ ${type ? `
${type}
` : ''} + ${phone ? `` : ''} + ${website ? `` : ''} +
${source}
+
`; +} + +function layerStatus(state: LayerState): string { + if (state.loading) return 'Loading all records in view…'; + if (state.error) return state.error; + if (!state.enabled) return 'Off'; + const loaded = `${state.count.toLocaleString()} mapped`; + const total = state.total > state.count ? ` · ${state.total.toLocaleString()} matching` : ''; + return `${loaded}${total}${state.warning ? ' · partial result' : ''}`; +} + +function Toggle({ definition, state, onChange }: { + definition: LayerDefinition; + state: LayerState; + onChange: (enabled: boolean) => void; +}) { + return
+
+ {definition.label} + {layerStatus(state)} +
+ +
; +} + +function findLegacyProviderLayerList(): HTMLElement | null { + for (const section of Array.from(document.querySelectorAll('section.command-section'))) { + const title = section.querySelector('.command-section-title span')?.textContent?.trim(); + if (title !== 'Provider Layers') continue; + return section.querySelector('.workflow-layer-list'); + } + return null; +} + +export default function ProviderLayerRegistryPanel() { + const [host, setHost] = useState(null); + const [layers, setLayers] = useState(initialState); + const controllers = useRef(new Map()); + const reloadTimer = useRef(0); + const layerStateRef = useRef(layers); + layerStateRef.current = layers; + + const definitions = useMemo( + () => [...PROVIDER_LAYER_CATEGORIES, PUBLIC_HEALTH_LAYER], + [], + ); + + useEffect(() => { + let disposed = false; + const suppressLegacyControls = () => { + const list = findLegacyProviderLayerList(); + if (!list) return; + for (const child of Array.from(list.children)) { + if (!(child instanceof HTMLElement)) continue; + if (child.dataset.providerRegistryOwned === 'true') continue; + child.dataset.providerRegistrySuppressed = 'true'; + child.style.display = 'none'; + } + if (!disposed) setHost((current) => current === list ? current : list); + }; + suppressLegacyControls(); + const observer = new MutationObserver(suppressLegacyControls); + observer.observe(document.body, { childList: true, subtree: true }); + return () => { + disposed = true; + observer.disconnect(); + document.querySelectorAll('[data-provider-registry-suppressed="true"]').forEach((element) => { + element.style.removeProperty('display'); + delete element.dataset.providerRegistrySuppressed; + }); + }; + }, []); + + async function loadLayer(definition: LayerDefinition): Promise { + controllers.current.get(definition.id)?.abort(); + const controller = new AbortController(); + controllers.current.set(definition.id, controller); + setLayers((current) => ({ + ...current, + [definition.id]: { ...current[definition.id], loading: true, error: '', warning: '' }, + })); + + try { + const map = getActiveMapboxMap(); + if (!map) throw new Error('Map is not ready'); + const bounds = map.getBounds(); + const params = new URLSearchParams({ + useBounds: 'true', + north: String(bounds.getNorth()), + south: String(bounds.getSouth()), + east: String(bounds.getEast()), + west: String(bounds.getWest()), + limit: '5000', + }); + const response = await fetchProviderLayer(`${definition.endpoint}?${params.toString()}`, { signal: controller.signal }); + if (controller.signal.aborted) return; + const data = await response.json().catch(() => ({})); + if (!response.ok || data?.error || data?.transientFailure) { + throw new Error(data?.warning || data?.error || `HTTP ${response.status}`); + } + const providers = Array.isArray(data?.providers) ? data.providers : []; + const mapped = renderProviderDataset(definition.channel, providers, { + baseColor: definition.color, + glow: false, + buildPopup: (provider: any) => providerPopup(provider, definition), + }); + setLayers((current) => ({ + ...current, + [definition.id]: { + ...current[definition.id], + loading: false, + count: mapped, + total: Number(data?.total ?? providers.length) || providers.length, + error: '', + warning: String(data?.warning || ''), + }, + })); + } catch (error) { + if (controller.signal.aborted) return; + clearProviderDataset(definition.channel); + setLayers((current) => ({ + ...current, + [definition.id]: { + ...current[definition.id], + loading: false, + count: 0, + total: 0, + error: error instanceof Error ? error.message : 'Layer load failed', + warning: '', + }, + })); + } finally { + if (controllers.current.get(definition.id) === controller) controllers.current.delete(definition.id); + } + } + + function setEnabled(definition: LayerDefinition, enabled: boolean): void { + setLayers((current) => ({ + ...current, + [definition.id]: { ...current[definition.id], enabled, error: enabled ? '' : current[definition.id].error }, + })); + if (!enabled) { + controllers.current.get(definition.id)?.abort(); + controllers.current.delete(definition.id); + clearProviderDataset(definition.channel); + setLayers((current) => ({ + ...current, + [definition.id]: { ...current[definition.id], loading: false, count: 0, total: 0, warning: '' }, + })); + return; + } + void loadLayer(definition); + } + + useEffect(() => { + const reloadVisible = () => { + window.clearTimeout(reloadTimer.current); + reloadTimer.current = window.setTimeout(() => { + for (const definition of definitions) { + if (layerStateRef.current[definition.id]?.enabled) void loadLayer(definition); + } + }, 350); + }; + window.addEventListener('network-map:native-camera', reloadVisible); + return () => { + window.removeEventListener('network-map:native-camera', reloadVisible); + window.clearTimeout(reloadTimer.current); + controllers.current.forEach((controller) => controller.abort()); + controllers.current.clear(); + definitions.forEach((definition) => clearProviderDataset(definition.channel)); + }; + // Definitions are registry constants; loadLayer intentionally reads current map state per call. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [definitions]); + + if (!host) return null; + return createPortal( +
+ {PROVIDER_LAYER_CATEGORIES.map((definition) => ( + setEnabled(definition, enabled)} + /> + ))} +
+ PUBLIC HEALTH DATA +
+ setEnabled(PUBLIC_HEALTH_LAYER, enabled)} + /> +
, + host, + ); +} From 922069558efc13415050e7e3396e078a5df1cd48 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:08:04 -0700 Subject: [PATCH 07/27] Mount registry-driven provider layer panel --- occu-med-map/src/main.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/occu-med-map/src/main.tsx b/occu-med-map/src/main.tsx index 01a4c681..1c4d7083 100644 --- a/occu-med-map/src/main.tsx +++ b/occu-med-map/src/main.tsx @@ -17,6 +17,7 @@ import "./providerExplorerRequestStabilityRuntime"; // overwritten later by a lazily loaded default-selection restore. import "./providerSourceSelectionPersistenceRuntime"; import App from "./App"; +import ProviderLayerRegistryPanel from "./ProviderLayerRegistryPanel"; import AppErrorBoundary, { ApplicationFailureScreen } from "./AppErrorBoundary"; import { installGlobalBootDiagnostics, @@ -124,7 +125,10 @@ const phaseTwoPreview = new URLSearchParams(window.location.search).get("p2-prev function renderStandardApplication(): void { root.render( - + <> + + + , ); } @@ -153,7 +157,10 @@ async function boot(): Promise { const { default: PhaseTwoShell } = await import("./PhaseTwoShell"); root.render( - + + + + , ); } catch (error) { From c6a63b1caf53c205ebc615b3d7e06b91e3d601df Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:08:43 -0700 Subject: [PATCH 08/27] Drive Provider Explorer categories from layer registry --- occu-med-map/src/DatasetBrowser.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/occu-med-map/src/DatasetBrowser.tsx b/occu-med-map/src/DatasetBrowser.tsx index 8766eb80..4b3a2069 100644 --- a/occu-med-map/src/DatasetBrowser.tsx +++ b/occu-med-map/src/DatasetBrowser.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { getProviderLayerCategory, PROVIDER_EXPLORER_SOURCE_OPTIONS, PROVIDER_LAYER_CATEGORIES } from './providerLayerRegistry'; export type ProviderFeature = { id:string; source:string; source_kind:'stored'|'live'|'saved'|'candidate'; name:string; normalized_name?:string|null; clinic_type:string; services:string[]; categories:string[]; @@ -16,7 +17,8 @@ type ProviderExplorerResponse = { providers?:ProviderFeature[]; records?:Provide type ProviderExplorerStatus = { persistenceConfigured?:boolean; spatialEngine?:string; candidatePersistence?:boolean; savedPersistence?:boolean; liveAdapters?:string[]; schema?:string }; type Props = { open:boolean; onClose:()=>void; getMapBounds?:()=>{north:number;south:number;east:number;west:number}|null; getCurrentRadius?:()=>{lat:number;lng:number;radiusMiles:number}|null; onViewOnMap?:(providers:ProviderFeature[], filters:ProviderExplorerFilters)=>void; onViewDensity?:(filters:ProviderExplorerFilters)=>void; onCompare?:(filters:ProviderExplorerFilters)=>void; onLoad?:(key:DatasetKey)=>void; sharedFilters?:ProviderExplorerFilters; onFiltersChange?:(filters:ProviderExplorerFilters)=>void; onOpenMatchingInDatabase?:(filters:ProviderExplorerFilters)=>void } & Record; -const SOURCE_OPTIONS = [ ['all','All'], ['bluehive','BlueHive'], ['dentists','Dentists'], ['indexed','Indexed'], ['my-clinics','My Clinics'], ['live','Live'], ['saved','Saved'], ['candidates','Candidates'] ]; +const SOURCE_OPTIONS = PROVIDER_EXPLORER_SOURCE_OPTIONS; +const CATEGORY_OPTIONS = [['all','All provider categories'], ...PROVIDER_LAYER_CATEGORIES.map((entry)=>[entry.id,entry.label] as [string,string])] as const; const KIND_OPTIONS = [ ['all','All kinds'], ['stored','Stored Neon'], ['live','Live discovery'], ['saved','Saved clinics'], ['candidate','Candidates'] ]; const SOURCE_MODE_OPTIONS = [ ['database','Database only'], ['live','Live only'], ['blended','Database + Live'], ['saved','Saved only'], ['candidate','Candidate only'] ]; const LIMIT = 25; @@ -25,6 +27,8 @@ const EMPTY_FILTERS: ProviderExplorerFilters = { source:'all', source_kind:'all' function mapsLink(provider:ProviderFeature) { const query = provider.lat != null && provider.lng != null ? `${provider.lat},${provider.lng}` : `${provider.name} ${[provider.address,provider.city,provider.admin_area,provider.country].filter(Boolean).join(', ')}`; return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(query)}`; } function sourceMode(filters:ProviderExplorerFilters) { if(filters.source_kind === 'live' || filters.source === 'live') return 'live'; if(filters.source_kind === 'saved' || filters.source === 'saved') return 'saved'; if(filters.source_kind === 'candidate' || filters.source === 'candidates') return 'candidate'; if(filters.includeLive) return 'blended'; return 'database'; } function applySourceMode(filters:ProviderExplorerFilters, mode:string):ProviderExplorerFilters { if(mode === 'live') return {...filters,source:'live',source_kind:'live',includeLive:true,includeStored:false,includeSaved:false,includeCandidates:false}; if(mode === 'blended') return {...filters,source:'all',source_kind:'all',includeLive:true,includeStored:true,includeSaved:true,includeCandidates:true}; if(mode === 'saved') return {...filters,source:'saved',source_kind:'saved',includeLive:false,includeStored:false,includeSaved:true,includeCandidates:false}; if(mode === 'candidate') return {...filters,source:'candidates',source_kind:'candidate',includeLive:false,includeStored:false,includeSaved:false,includeCandidates:true}; return {...filters,source:'all',source_kind:'all',includeLive:false,includeStored:true,includeSaved:true,includeCandidates:true}; } +function selectedCategory(filters:ProviderExplorerFilters):string { const match=PROVIDER_LAYER_CATEGORIES.find((entry)=>entry.explorerSource ? filters.source===entry.explorerSource : entry.explorerClinicType ? filters.source==='all'&&filters.clinicType===entry.explorerClinicType : false); return match?.id || 'all'; } +function applyCategory(filters:ProviderExplorerFilters,id:string):ProviderExplorerFilters { if(id==='all') return {...filters,source:'all',clinicType:''}; const entry=getProviderLayerCategory(id); if(!entry) return filters; return {...filters,source:entry.explorerSource||'all',clinicType:entry.explorerClinicType||'',source_kind:'all',includeStored:true,includeLive:false}; } const FACET_KEYS: Array<{key:keyof ProviderExplorerFilters; label:string}> = [ {key:'source', label:'Source'}, {key:'source_kind', label:'Kind'}, {key:'country', label:'Country'}, {key:'admin_area', label:'Admin area'}, {key:'clinicType', label:'Clinic type'}, {key:'service', label:'Service/category'}, @@ -84,6 +88,7 @@ export default function DatasetBrowser({ open,onClose,getMapBounds,getCurrentRad

Provider Intelligence Explorer

Neon-backed global database + live/candidate workflow. Source mode: {sourceMode(filters)}.

+ setFilter('q',e.target.value)} placeholder="Search name, service, city" /> From 13b91c6a34bb186e17283d9793d53d93b58270ff Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:11:03 -0700 Subject: [PATCH 09/27] Fix category layer bounds guard and pagination batch size --- occu-med-map/src/ProviderLayerRegistryPanel.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/occu-med-map/src/ProviderLayerRegistryPanel.tsx b/occu-med-map/src/ProviderLayerRegistryPanel.tsx index a20cf09a..c8660183 100644 --- a/occu-med-map/src/ProviderLayerRegistryPanel.tsx +++ b/occu-med-map/src/ProviderLayerRegistryPanel.tsx @@ -168,13 +168,16 @@ export default function ProviderLayerRegistryPanel() { const map = getActiveMapboxMap(); if (!map) throw new Error('Map is not ready'); const bounds = map.getBounds(); + if (!bounds) throw new Error('Map bounds are not ready'); const params = new URLSearchParams({ useBounds: 'true', north: String(bounds.getNorth()), south: String(bounds.getSouth()), east: String(bounds.getEast()), west: String(bounds.getWest()), - limit: '5000', + // Use the common transport page size. The request runtime auto-paginates + // until every matching viewport record has been assembled. + limit: '2000', }); const response = await fetchProviderLayer(`${definition.endpoint}?${params.toString()}`, { signal: controller.signal }); if (controller.signal.aborted) return; From b86c0a09ee43f6c907819e2dda6f58a3b24a4a49 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:12:21 -0700 Subject: [PATCH 10/27] Normalize provider category route parameter --- api-server/src/routes/providerCategoryLayers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api-server/src/routes/providerCategoryLayers.ts b/api-server/src/routes/providerCategoryLayers.ts index 415f6659..023bf938 100644 --- a/api-server/src/routes/providerCategoryLayers.ts +++ b/api-server/src/routes/providerCategoryLayers.ts @@ -232,7 +232,8 @@ function toProvider(row: Record, category: string): Record { - const category = req.params.category; + const rawCategory = req.params.category; + const category = Array.isArray(rawCategory) ? rawCategory[0] || "" : rawCategory || ""; const definition = CATEGORY_DEFINITIONS[category]; if (!definition) { res.status(400).json({ error: `Unknown provider category: ${category}`, categories: Object.keys(CATEGORY_DEFINITIONS) }); From 6114f0469ab5f08092f49ef53ff4322f30032252 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:13:50 -0700 Subject: [PATCH 11/27] Use shared DOM observer for provider layer registry --- occu-med-map/src/ProviderLayerRegistryPanel.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/occu-med-map/src/ProviderLayerRegistryPanel.tsx b/occu-med-map/src/ProviderLayerRegistryPanel.tsx index c8660183..68c0e89b 100644 --- a/occu-med-map/src/ProviderLayerRegistryPanel.tsx +++ b/occu-med-map/src/ProviderLayerRegistryPanel.tsx @@ -3,6 +3,7 @@ import { createPortal } from 'react-dom'; import { getActiveMapboxMap } from './dualMapEngineRuntime'; import { fetchProviderLayer } from './providerLayerRequestRuntime'; import { clearProviderDataset, renderProviderDataset } from './providerDatasetNativeMapRuntime'; +import { subscribeToSharedDomObserver } from './runtimeControllerRegistry'; import { PROVIDER_LAYER_CATEGORIES, PUBLIC_HEALTH_LAYER, @@ -143,11 +144,10 @@ export default function ProviderLayerRegistryPanel() { if (!disposed) setHost((current) => current === list ? current : list); }; suppressLegacyControls(); - const observer = new MutationObserver(suppressLegacyControls); - observer.observe(document.body, { childList: true, subtree: true }); + const unsubscribe = subscribeToSharedDomObserver('provider-layer-registry-panel', suppressLegacyControls); return () => { disposed = true; - observer.disconnect(); + unsubscribe(); document.querySelectorAll('[data-provider-registry-suppressed="true"]').forEach((element) => { element.style.removeProperty('display'); delete element.dataset.providerRegistrySuppressed; From 90a457280af5fe895dcd061665618db646cc4c7b Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:15:33 -0700 Subject: [PATCH 12/27] Align P2 shell contract with registry layer panel --- occu-med-map/scripts/phase-two-map-smoke.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/occu-med-map/scripts/phase-two-map-smoke.ts b/occu-med-map/scripts/phase-two-map-smoke.ts index bf05ff3f..6a2af526 100644 --- a/occu-med-map/scripts/phase-two-map-smoke.ts +++ b/occu-med-map/scripts/phase-two-map-smoke.ts @@ -126,10 +126,11 @@ assert.doesNotMatch(main, /phaseTwoPreviewIsolation/); assert.match(main, /import\("\.\/phaseTwoMapBridge"\)/); assert.match(main, /import\("\.\/phase-two-controls\.css"\)/); assert.match(main, /import\("\.\/PhaseTwoShell"\)/); +assert.match(main, /import ProviderLayerRegistryPanel from ["']\.\/ProviderLayerRegistryPanel["']/); assert.doesNotMatch(main, /phaseTwoLegacyLayerBridge/); assert.match(main, /function renderStandardApplication\(\): void/); -assert.match(main, /\s*\s*<\/AppErrorBoundary>/); -assert.match(main, /\s*<\/PhaseTwoShell>\s*<\/AppErrorBoundary>/); +assert.match(main, /\s*<>\s*\s*\s*<\/>\s*<\/AppErrorBoundary>/); +assert.match(main, /\s*\s*\s*\s*<\/PhaseTwoShell>\s*<\/AppErrorBoundary>/); assert.match(main, /catch \(error\) \{[\s\S]*recordBootFailure\("phase-two-preview"[\s\S]*renderStandardApplication\(\);/); const app = readFileSync(resolve(here, '../src/App.tsx'), 'utf8'); From 57d8ee6d9a69e1bbed27821361d62820c069f417 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:17:19 -0700 Subject: [PATCH 13/27] Preserve luminous density control with categorized layers --- occu-med-map/src/ProviderLayerRegistryPanel.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/occu-med-map/src/ProviderLayerRegistryPanel.tsx b/occu-med-map/src/ProviderLayerRegistryPanel.tsx index 68c0e89b..9d3292ab 100644 --- a/occu-med-map/src/ProviderLayerRegistryPanel.tsx +++ b/occu-med-map/src/ProviderLayerRegistryPanel.tsx @@ -138,6 +138,14 @@ export default function ProviderLayerRegistryPanel() { for (const child of Array.from(list.children)) { if (!(child instanceof HTMLElement)) continue; if (child.dataset.providerRegistryOwned === 'true') continue; + const label = child.querySelector('.workflow-layer-name')?.textContent?.trim(); + // Luminous Density is a visualization control, not a provider dataset. + // Keep it available while replacing only the old provider-source toggles. + if (label === 'Luminous Density') { + child.style.removeProperty('display'); + delete child.dataset.providerRegistrySuppressed; + continue; + } child.dataset.providerRegistrySuppressed = 'true'; child.style.display = 'none'; } From 1608ee95ece2ac5d3f0e08f903656977ba9bb846 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:20:43 -0700 Subject: [PATCH 14/27] Exercise categorized provider layer in browser acceptance --- .../ci-mapbox-native-tools-acceptance.mjs | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/occu-med-map/scripts/ci-mapbox-native-tools-acceptance.mjs b/occu-med-map/scripts/ci-mapbox-native-tools-acceptance.mjs index 426a69cf..e58c3da9 100644 --- a/occu-med-map/scripts/ci-mapbox-native-tools-acceptance.mjs +++ b/occu-med-map/scripts/ci-mapbox-native-tools-acceptance.mjs @@ -108,24 +108,23 @@ async function mockApi(page) { }); } if (pathname.includes("provider-explorer")) return json(route, { providers: [], total: 0, page: 1, hasMore: false, stored_count: 0, live_count: 0, live_only: [] }); - if (pathname === "/api/provider-layers/indexed") { + if (pathname === "/api/provider-category-layers/general-practitioners") { return json(route, { providers: [ - { clinic_name: "CI Indexed Clinic One", name: "CI Indexed Clinic One", lat: 20.4, lng: 0.4, address_1: "10 Indexed Way", city: "CI City", state: "CI", zip: "00001", phone: "+1 555 0301", website: "https://example.invalid/indexed-one", source_id: "indexed-ci-1", data_source: "indexed", category: "clinic", clinic_type: "general_practitioner", providerType: "general_practitioner", services: "primary care", types: ["primary care"] }, - { clinic_name: "CI Indexed Clinic Two", name: "CI Indexed Clinic Two", lat: 20.43, lng: 0.43, address_1: "20 Indexed Way", city: "CI City", state: "CI", zip: "00002", phone: "+1 555 0302", website: "https://example.invalid/indexed-two", source_id: "indexed-ci-2", data_source: "indexed", category: "clinic", clinic_type: "general_practitioner", providerType: "general_practitioner", services: "primary care", types: ["primary care"] }, + { clinic_name: "CI Indexed Clinic One", name: "CI Indexed Clinic One", lat: 20.4, lng: 0.4, address_1: "10 Indexed Way", city: "CI City", state: "CI", zip: "00001", phone: "+1 555 0301", website: "https://example.invalid/indexed-one", source_id: "indexed-ci-1", data_source: "indexed", category: "clinic", clinic_type: "general_practitioner", providerType: "general_practitioner", services: ["primary care"], types: ["primary care"] }, + { clinic_name: "CI Indexed Clinic Two", name: "CI Indexed Clinic Two", lat: 20.43, lng: 0.43, address_1: "20 Indexed Way", city: "CI City", state: "CI", zip: "00002", phone: "+1 555 0302", website: "https://example.invalid/indexed-two", source_id: "indexed-ci-2", data_source: "indexed", category: "clinic", clinic_type: "general_practitioner", providerType: "general_practitioner", services: ["primary care"], types: ["primary care"] }, ], count: 2, loaded: 2, total: 2, - source: "indexed", + category: "general-practitioners", page: 1, limit: 2000, hasMore: false, - all: false, - storage: "provider_master", visibleCapped: false, }); } + if (pathname.includes("provider-category-layers")) return json(route, { providers: [], count: 0, loaded: 0, total: 0, page: 1, hasMore: false, visibleCapped: false }); if (pathname.includes("provider-layers")) return json(route, { providers: [], count: 0, loaded: 0, total: 0, page: 1, hasMore: false, visibleCapped: false }); if (pathname.includes("health") || pathname.includes("ready")) return json(route, { ok: true, status: "ok" }); if (pathname.includes("inventory") || pathname.includes("coverage")) return json(route, { providers: [], total: 0, cells: [] }); @@ -219,31 +218,31 @@ async function waitForActiveMapIdle(page, mode = "2d") { }, mode); } -async function indexedProviderDiagnostics(page) { +async function categoryProviderDiagnostics(page) { return page.evaluate(() => { const lifecycle = window.__NETWORK_MAP_MAPBOX_LIFECYCLE__?.getDiagnostics?.() || null; const maps = window.__NETWORK_MAP_MAPBOX_LIFECYCLE__?.getMaps?.() || []; const map = maps.find((candidate) => candidate.getContainer().closest(".mapbox-2d-host")); - const toggle = document.querySelector('input[aria-label="Indexed Providers"]'); + const toggle = document.querySelector('input[aria-label="General Practitioners"]'); const row = toggle?.closest(".workflow-layer"); - const snapshot = window.__NETWORK_MAP_PROVIDER_DATASET_NATIVE__?.getSnapshot?.("indexed") || null; - if (!map) return { lifecycle, snapshot, noMap: true, indexedRow: row?.textContent || "" }; + const snapshot = window.__NETWORK_MAP_PROVIDER_DATASET_NATIVE__?.getSnapshot?.("category-general-practitioners") || null; + if (!map) return { lifecycle, snapshot, noMap: true, categoryRow: row?.textContent || "" }; const style = map.getStyle(); const nativeLayers = (style?.layers || []) - .filter((layer) => String(layer.id).startsWith("provider-dataset-native-indexed")) + .filter((layer) => String(layer.id).startsWith("provider-dataset-native-category-general-practitioners")) .map((layer) => ({ id: layer.id, type: layer.type, source: layer.source || null })); const features = snapshot?.features || []; return { lifecycle, - indexedChecked: Boolean(toggle?.checked), - indexedRow: String(row?.textContent || "").replace(/\s+/g, " ").trim(), + categoryChecked: Boolean(toggle?.checked), + categoryRow: String(row?.textContent || "").replace(/\s+/g, " ").trim(), styleLoaded: map.isStyleLoaded(), loaded: map.loaded(), moving: map.isMoving(), center: { lng: map.getCenter().lng, lat: map.getCenter().lat }, zoom: map.getZoom(), nativeLayers, - sourceId: "provider-dataset-native-indexed", + sourceId: "provider-dataset-native-category-general-practitioners", sourceFeatureCount: snapshot?.featureCount || 0, clinicOneCount: features.filter((feature) => String(feature.popupHtml || "").includes("CI Indexed Clinic One")).length, popupPreviews: features.slice(0, 20).map((feature) => String(feature.popupHtml || "").slice(0, 180)), @@ -267,64 +266,64 @@ try { await page.waitForFunction(() => (window.__NETWORK_MAP_MAPBOX_LIFECYCLE__?.getMaps?.() || []).some((map) => map.getContainer().closest(".mapbox-2d-host")), null, { timeout: 20_000 }); await page.locator(".mapbox-2d-host .mapboxgl-canvas").waitFor({ state: "visible", timeout: 15_000 }); - const indexedToggle = page.getByRole("checkbox", { name: "Indexed Providers" }); - const indexedResponsePredicate = (response) => { + const categoryToggle = page.getByRole("checkbox", { name: "General Practitioners" }); + const categoryResponsePredicate = (response) => { try { const url = new URL(response.url()); - return url.pathname === "/api/provider-layers/indexed" && response.request().method() === "GET"; + return url.pathname === "/api/provider-category-layers/general-practitioners" && response.request().method() === "GET"; } catch { return false; } }; - const indexedResponsePromise = page.waitForResponse(indexedResponsePredicate, { timeout: 12_000 }); - await indexedToggle.check(); - const indexedResponse = await indexedResponsePromise; - assert.equal(indexedResponse.ok(), true, `Indexed Providers request failed with HTTP ${indexedResponse.status()}`); - const indexedPayload = await indexedResponse.json(); - assert.equal(indexedPayload.providers?.length, 2, "Indexed Providers API must return both CI clinics"); + const categoryResponsePromise = page.waitForResponse(categoryResponsePredicate, { timeout: 12_000 }); + await categoryToggle.check(); + const categoryResponse = await categoryResponsePromise; + assert.equal(categoryResponse.ok(), true, `General Practitioners request failed with HTTP ${categoryResponse.status()}`); + const categoryPayload = await categoryResponse.json(); + assert.equal(categoryPayload.providers?.length, 2, "General Practitioners API must return both CI clinics"); - const viewportRefreshPromise = page.waitForResponse(indexedResponsePredicate, { timeout: 6000 }).catch(() => null); + const viewportRefreshPromise = page.waitForResponse(categoryResponsePredicate, { timeout: 6000 }).catch(() => null); await page.evaluate(() => { const maps = window.__NETWORK_MAP_MAPBOX_LIFECYCLE__?.getMaps?.() || []; const map = maps.find((candidate) => candidate.getContainer().closest(".mapbox-2d-host")); - if (!map) throw new Error("2D Mapbox map unavailable for indexed-provider test"); + if (!map) throw new Error("2D Mapbox map unavailable for categorized-provider test"); map.jumpTo({ center: [0.415, 20.415], zoom: 9 }); }); const viewportRefresh = await viewportRefreshPromise; if (viewportRefresh) { - assert.equal(viewportRefresh.ok(), true, `Indexed viewport refresh failed with HTTP ${viewportRefresh.status()}`); + assert.equal(viewportRefresh.ok(), true, `Categorized provider viewport refresh failed with HTTP ${viewportRefresh.status()}`); const refreshPayload = await viewportRefresh.json(); - assert.equal(refreshPayload.providers?.length, 2, "Indexed viewport refresh must keep both CI clinics"); + assert.equal(refreshPayload.providers?.length, 2, "Categorized provider viewport refresh must keep both CI clinics"); } await page.waitForFunction(() => Boolean( - document.querySelector('input[aria-label="Indexed Providers"]')?.checked + document.querySelector('input[aria-label="General Practitioners"]')?.checked ), null, { timeout: 15_000 }); await waitForActiveMapIdle(page, "2d"); await page.waitForFunction(() => { const maps = window.__NETWORK_MAP_MAPBOX_LIFECYCLE__?.getMaps?.() || []; const map = maps.find((candidate) => candidate.getContainer().closest(".mapbox-2d-host")); - const snapshot = window.__NETWORK_MAP_PROVIDER_DATASET_NATIVE__?.getSnapshot?.("indexed"); + const snapshot = window.__NETWORK_MAP_PROVIDER_DATASET_NATIVE__?.getSnapshot?.("category-general-practitioners"); return Boolean( - map?.getLayer("provider-dataset-native-indexed-points") - && map.getSource("provider-dataset-native-indexed") + map?.getLayer("provider-dataset-native-category-general-practitioners-points") + && map.getSource("provider-dataset-native-category-general-practitioners") && snapshot?.featureCount >= 2 && snapshot.features.some((feature) => String(feature.popupHtml || "").includes("CI Indexed Clinic One")) ); }, null, { timeout: 10_000 }); - const beforeIndexedClick = await indexedProviderDiagnostics(page); - console.log("INDEXED_PROVIDER_DIAGNOSTICS_BEFORE_CLICK", JSON.stringify(beforeIndexedClick)); + const beforeCategoryClick = await categoryProviderDiagnostics(page); + console.log("CATEGORY_PROVIDER_DIAGNOSTICS_BEFORE_CLICK", JSON.stringify(beforeCategoryClick)); assert.ok( - beforeIndexedClick.lifecycle?.initializers?.some((initializer) => initializer.id === "provider-dataset-native-map"), + beforeCategoryClick.lifecycle?.initializers?.some((initializer) => initializer.id === "provider-dataset-native-map"), "Native provider dataset interaction owner must be registered before provider clicks", ); - assert.equal(beforeIndexedClick.sourceFeatureCount, 2, "First-party indexed provider state must contain both CI clinics"); + assert.equal(beforeCategoryClick.sourceFeatureCount, 2, "First-party categorized provider state must contain both CI clinics"); - const indexedPoint = await active2dMapPoint(page, 0.4, 20.4); - await page.mouse.click(indexedPoint.x, indexedPoint.y); + const categoryPoint = await active2dMapPoint(page, 0.4, 20.4); + await page.mouse.click(categoryPoint.x, categoryPoint.y); try { await page.getByText("CI Indexed Clinic One").first().waitFor({ state: "visible", timeout: 8_000 }); } catch (error) { - const afterIndexedClick = await indexedProviderDiagnostics(page); - console.error("INDEXED_PROVIDER_DIAGNOSTICS_AFTER_CLICK", JSON.stringify(afterIndexedClick)); + const afterCategoryClick = await categoryProviderDiagnostics(page); + console.error("CATEGORY_PROVIDER_DIAGNOSTICS_AFTER_CLICK", JSON.stringify(afterCategoryClick)); throw error; } @@ -435,7 +434,7 @@ try { await page.waitForFunction(() => /1 facilities from OSM/i.test(document.querySelector(".live-panel.open")?.textContent || ""), null, { timeout: 15_000 }); assert.deepEqual(pageErrors, [], `Mapbox native tool acceptance saw page errors: ${pageErrors.join("; ")}`); - console.log(`Mapbox native tool acceptance passed in ${browserName}: indexed providers, radius, 2D/3D, density, hex, provider click ownership, and OSM Live Finder.`); + console.log(`Mapbox native tool acceptance passed in ${browserName}: categorized providers, radius, 2D/3D, density, hex, provider click ownership, and OSM Live Finder.`); } catch (error) { await page.screenshot({ path: path.join(artifactDir, "failure.png"), fullPage: true }).catch(() => undefined); fs.writeFileSync(path.join(artifactDir, "error.txt"), `${error instanceof Error ? error.stack || error.message : String(error)}\n\nPage errors:\n${pageErrors.join("\n")}`); @@ -443,4 +442,4 @@ try { } finally { await context.close(); await browser.close(); -} +} \ No newline at end of file From fa2c6b5973db67a1bc2f513fd0e17708e16661da Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Sun, 16 Aug 2026 21:22:57 -0700 Subject: [PATCH 15/27] Prove category controls and uncapped NACCHO pagination in browsers --- .../ci-mapbox-native-tools-acceptance.mjs | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/occu-med-map/scripts/ci-mapbox-native-tools-acceptance.mjs b/occu-med-map/scripts/ci-mapbox-native-tools-acceptance.mjs index e58c3da9..d16f180b 100644 --- a/occu-med-map/scripts/ci-mapbox-native-tools-acceptance.mjs +++ b/occu-med-map/scripts/ci-mapbox-native-tools-acceptance.mjs @@ -10,6 +10,24 @@ if (!browserType) throw new Error(`Unsupported browser ${browserName}`); const artifactDir = path.resolve(process.cwd(), "test-results", "mapbox-native-tools", browserName); fs.mkdirSync(artifactDir, { recursive: true }); +const expectedProviderLayerLabels = [ + "Urgent Cares", + "Occupational Health Clinics", + "Dentists", + "Blue Hive", + "FAA Examiners", + "DOT Examiners", + "Labs", + "Imaging", + "Audiology", + "General Practitioners", + "Pharmacy", + "International Providers", + "U.S. Embassy Recommended", + "Uploaded Clinics", + "NACCHO Local Health Departments", +]; + function json(route, payload, status = 200) { return route.fulfill({ status, contentType: "application/json", body: JSON.stringify(payload) }); } @@ -124,6 +142,48 @@ async function mockApi(page) { visibleCapped: false, }); } + if (pathname === "/api/naccho-lhd") { + const requestedPage = Math.max(Number(url.searchParams.get("page") || 1), 1); + const requestedLimit = Math.max(Number(url.searchParams.get("limit") || 2000), 1); + const total = 2501; + const offset = (requestedPage - 1) * requestedLimit; + const pageCount = Math.max(0, Math.min(requestedLimit, total - offset)); + const providers = Array.from({ length: pageCount }, (_, localIndex) => { + const index = offset + localIndex; + return { + id: `naccho-ci-${index}`, + source_id: `naccho-ci-${index}`, + name: `CI Health Department ${index + 1}`, + lat: 20.6 + (index % 50) * 0.0002, + lng: 0.6 + (Math.floor(index / 50) % 50) * 0.0002, + address: `${index + 1} Public Health Way`, + city: "CI City", + admin_area: "CI", + country: "US", + postal_code: "00000", + phone: null, + website: null, + clinic_type: "local_health_department", + public_health_services: ["public health"], + services: ["public health"], + categories: ["public_health", "local_health_department"], + source: "NACCHO Local Health Department Directory", + source_kind: "stored", + trust_tier: "directory", + }; + }); + return json(route, { + providers, + count: providers.length, + loaded: providers.length, + total, + page: requestedPage, + limit: requestedLimit, + hasMore: offset + providers.length < total, + source: "NACCHO Local Health Department Directory", + visibleCapped: false, + }); + } if (pathname.includes("provider-category-layers")) return json(route, { providers: [], count: 0, loaded: 0, total: 0, page: 1, hasMore: false, visibleCapped: false }); if (pathname.includes("provider-layers")) return json(route, { providers: [], count: 0, loaded: 0, total: 0, page: 1, hasMore: false, visibleCapped: false }); if (pathname.includes("health") || pathname.includes("ready")) return json(route, { ok: true, status: "ok" }); @@ -266,6 +326,12 @@ try { await page.waitForFunction(() => (window.__NETWORK_MAP_MAPBOX_LIFECYCLE__?.getMaps?.() || []).some((map) => map.getContainer().closest(".mapbox-2d-host")), null, { timeout: 20_000 }); await page.locator(".mapbox-2d-host .mapboxgl-canvas").waitFor({ state: "visible", timeout: 15_000 }); + for (const label of expectedProviderLayerLabels) { + await page.getByRole("checkbox", { name: label }).waitFor({ state: "visible", timeout: 10_000 }); + } + assert.equal(await page.locator('input[aria-label="Indexed Providers"]:visible').count(), 0, "Generic Indexed Providers toggle must no longer be visible"); + assert.equal(await page.getByRole("checkbox", { name: "Luminous Density" }).isVisible(), true, "Luminous Density visualization control must remain available"); + const categoryToggle = page.getByRole("checkbox", { name: "General Practitioners" }); const categoryResponsePredicate = (response) => { try { @@ -327,6 +393,20 @@ try { throw error; } + const nacchoToggle = page.getByRole("checkbox", { name: "NACCHO Local Health Departments" }); + await nacchoToggle.check(); + await page.waitForFunction(() => ( + window.__NETWORK_MAP_PROVIDER_DATASET_NATIVE__?.getSnapshot?.("naccho")?.featureCount === 2501 + ), null, { timeout: 15_000 }); + const nacchoFeatureCount = await page.evaluate(() => ( + window.__NETWORK_MAP_PROVIDER_DATASET_NATIVE__?.getSnapshot?.("naccho")?.featureCount || 0 + )); + assert.equal(nacchoFeatureCount, 2501, "NACCHO layer must auto-paginate and render more than the old 1,000-record ceiling"); + await nacchoToggle.uncheck(); + await page.waitForFunction(() => ( + window.__NETWORK_MAP_PROVIDER_DATASET_NATIVE__?.getSnapshot?.("naccho")?.featureCount === 0 + ), null, { timeout: 10_000 }); + const beforeRadiusFeatures = await liveFinderSnapshotFeatureCount(page, "drop"); const radiusButton = await clickByText(page, /Radius Tool/i); await page.waitForFunction((button) => button.classList.contains("active"), await radiusButton.elementHandle(), { timeout: 5_000 }); @@ -434,7 +514,7 @@ try { await page.waitForFunction(() => /1 facilities from OSM/i.test(document.querySelector(".live-panel.open")?.textContent || ""), null, { timeout: 15_000 }); assert.deepEqual(pageErrors, [], `Mapbox native tool acceptance saw page errors: ${pageErrors.join("; ")}`); - console.log(`Mapbox native tool acceptance passed in ${browserName}: categorized providers, radius, 2D/3D, density, hex, provider click ownership, and OSM Live Finder.`); + console.log(`Mapbox native tool acceptance passed in ${browserName}: categorized providers, uncapped NACCHO pagination, radius, 2D/3D, density, hex, provider click ownership, and OSM Live Finder.`); } catch (error) { await page.screenshot({ path: path.join(artifactDir, "failure.png"), fullPage: true }).catch(() => undefined); fs.writeFileSync(path.join(artifactDir, "error.txt"), `${error instanceof Error ? error.stack || error.message : String(error)}\n\nPage errors:\n${pageErrors.join("\n")}`); From 4f59ae393192d9c53d9b9c7de68cd9a8fdcd5984 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 11:59:16 -0700 Subject: [PATCH 16/27] Require explicit Provider Explorer visualization intent --- ...derExplorerExplicitVisualizationRuntime.ts | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts diff --git a/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts new file mode 100644 index 00000000..bf5d0ac1 --- /dev/null +++ b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts @@ -0,0 +1,129 @@ +import { clearProviderExplorerNative } from "./providerExplorerNativeMapRuntime"; +import { registerRuntimeOwner, subscribeToSharedDomObserver } from "./runtimeControllerRegistry"; + +type ProviderExplorerIntentGlobal = typeof window & { + __NETWORK_MAP_PROVIDER_EXPLORER_INTENT__?: { + isExplicitlyActive: () => boolean; + reset: () => void; + }; +}; + +const VISUALIZATION_LABELS = new Set([ + "density", + "hex field", + "8px points", + "density + points", + "dot density", +]); + +let explicitlyActive = false; +let unsubscribeDom: (() => void) | null = null; + +function setIntent(active: boolean): void { + explicitlyActive = active; + document.documentElement.dataset.providerExplorerVisualizationActive = active ? "true" : "false"; +} + +function clearExplorerVisualization(): void { + clearProviderExplorerNative(); +} + +function normalizeButtonLabel(button: HTMLButtonElement): string { + return `${button.textContent || ""} ${button.getAttribute("aria-label") || ""}` + .replace(/\s+/g, " ") + .trim() + .toLowerCase(); +} + +function restoreInactivePresentation(): void { + if (explicitlyActive) return; + + const aggregateCount = window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("aggregate")?.featureCount || 0; + const dotCount = window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("dots")?.featureCount || 0; + if (aggregateCount > 0 || dotCount > 0) clearProviderExplorerNative(["aggregate", "dots"]); + + const drawer = document.querySelector(".provider-explorer-drawer"); + if (!drawer) return; + + drawer.querySelectorAll(".provider-visualization-grid button.active").forEach((button) => { + button.classList.remove("active"); + }); + + const status = drawer.querySelector(".provider-map-status"); + const readyText = "Choose a visualization to render providers on the map."; + if (status && status.textContent?.trim() !== readyText) status.textContent = readyText; +} + +function reset(): void { + setIntent(false); + clearExplorerVisualization(); + restoreInactivePresentation(); +} + +function handleClick(event: MouseEvent): void { + const target = event.target instanceof Element ? event.target : null; + const button = target?.closest("button"); + if (!button || button.disabled) return; + + const drawer = button.closest(".provider-explorer-drawer"); + if (!drawer) return; + + const label = normalizeButtonLabel(button); + if (VISUALIZATION_LABELS.has(label)) { + setIntent(true); + return; + } + + if (button.getAttribute("aria-label") === "Close Provider Explorer" || label === "clear filters") { + reset(); + } +} + +function handleWorkspaceChange(): void { + window.requestAnimationFrame(() => { + if (document.documentElement.dataset.occumedworkspace !== "explorer") { + reset(); + return; + } + restoreInactivePresentation(); + }); +} + +function cleanup(): void { + document.removeEventListener("click", handleClick, true); + window.removeEventListener("network-map:sidebar-workspace", handleWorkspaceChange); + unsubscribeDom?.(); + unsubscribeDom = null; +} + +function install(): void { + if (!registerRuntimeOwner( + "provider-explorer-explicit-visualization", + "Require explicit user intent before Provider Explorer visualization layers can remain on the map", + )) return; + + setIntent(false); + document.addEventListener("click", handleClick, true); + window.addEventListener("network-map:sidebar-workspace", handleWorkspaceChange); + unsubscribeDom = subscribeToSharedDomObserver( + "provider-explorer-explicit-visualization", + restoreInactivePresentation, + ); + window.addEventListener("beforeunload", cleanup, { once: true }); + + (window as ProviderExplorerIntentGlobal).__NETWORK_MAP_PROVIDER_EXPLORER_INTENT__ = { + isExplicitlyActive: () => explicitlyActive, + reset, + }; + + window.setTimeout(restoreInactivePresentation, 0); + window.setTimeout(restoreInactivePresentation, 500); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", install, { once: true }); +} else { + install(); +} + +export {}; From 31261c615426458d289dc9eda5318fb8d4863326 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 11:59:34 -0700 Subject: [PATCH 17/27] Harden sidebar close controls and inactive Explorer state --- .../sidebar-workspace-regression-fixes.css | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 occu-med-map/src/sidebar-workspace-regression-fixes.css diff --git a/occu-med-map/src/sidebar-workspace-regression-fixes.css b/occu-med-map/src/sidebar-workspace-regression-fixes.css new file mode 100644 index 00000000..51eb013c --- /dev/null +++ b/occu-med-map/src/sidebar-workspace-regression-fixes.css @@ -0,0 +1,67 @@ +/* Focused regression rules layered after sidebar-workspace-final-fixes.css. + Geometry remains owned by the consolidated workspace stylesheet; these rules + only normalize text close controls and the explicit-off Explorer state. */ + +html[data-occumed-workspace-ready="true"] body + .provider-explorer-drawer .provider-drawer-header .rp-close, +html[data-occumed-workspace-ready="true"] body + .live-panel .rp-header .rp-close { + box-sizing: border-box !important; + display: inline-flex !important; + flex: 0 0 auto !important; + align-items: center !important; + justify-content: center !important; + width: auto !important; + min-width: 62px !important; + max-width: none !important; + height: 30px !important; + min-height: 30px !important; + max-height: 30px !important; + margin: 0 !important; + padding: 0 10px !important; + overflow: visible !important; + color: var(--workspace-text) !important; + background: var(--workspace-control-bg) !important; + border: 1px solid rgba(72, 139, 174, 0.5) !important; + border-radius: 8px !important; + box-shadow: inset 0 1px 0 rgba(255,255,255,.04) !important; + font-size: 10px !important; + font-weight: 800 !important; + line-height: 1 !important; + letter-spacing: .02em !important; + text-align: center !important; + text-transform: none !important; + white-space: nowrap !important; + writing-mode: horizontal-tb !important; + text-orientation: mixed !important; + cursor: pointer !important; + pointer-events: auto !important; +} + +html[data-occumed-workspace-ready="true"] body + .provider-explorer-drawer .provider-drawer-header .rp-close:hover:not(:disabled), +html[data-occumed-workspace-ready="true"] body + .live-panel .rp-header .rp-close:hover:not(:disabled) { + color: #fff !important; + background: var(--workspace-control-hover) !important; + border-color: var(--workspace-border-strong) !important; +} + +/* React keeps its last visualization mode selected internally so filters can + refresh an explicitly chosen view. Until the user actually chooses a mode, + do not visually present Density as enabled. */ +html[data-provider-explorer-visualization-active="false"] body + .provider-explorer-drawer .provider-visualization-grid button.active { + color: var(--workspace-text) !important; + background: var(--workspace-control-bg) !important; + border-color: rgba(72, 139, 174, 0.46) !important; + box-shadow: inset 0 1px 0 rgba(255,255,255,.035) !important; +} + +/* Keep action targets usable at the desktop widths shown in production. + Legacy button rules are allowed to style them, but not collapse them. */ +html[data-occumed-workspace-ready="true"] body + :is(.provider-explorer-drawer, .occumed-map-tools-panel, .live-panel) + button:not(.mapboxgl-ctrl button):not(:disabled) { + pointer-events: auto !important; +} From df778ecc4c79fd2c0176a471530cb1f2ffe3b605 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 11:59:58 -0700 Subject: [PATCH 18/27] Install sidebar regression hardening runtimes --- occu-med-map/src/main.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/occu-med-map/src/main.tsx b/occu-med-map/src/main.tsx index 1c4d7083..1aaaa9f6 100644 --- a/occu-med-map/src/main.tsx +++ b/occu-med-map/src/main.tsx @@ -12,6 +12,7 @@ import "./providerLocationFinderRuntime"; import "./providerTypeNormalizationRuntime"; import { switchMapModeWithTransition } from "./dualMapTransitionRuntime"; import "./providerExplorerRequestStabilityRuntime"; +import "./providerExplorerExplicitVisualizationRuntime"; // Source selection is user-facing state, not optional telemetry. Install its // change listener before React mounts so a fast user toggle can never be // overwritten later by a lazily loaded default-selection restore. @@ -50,9 +51,11 @@ import "./sidebarWorkspacePanelGuardRuntime"; import "./ui-system.css"; import "./startup-hardening.css"; // The consolidated sidebar layer intentionally loads after every synchronous -// shell/theme stylesheet. It is the sole final owner of sidebar geometry, -// workspace visibility, hit testing, and scrolling. +// shell/theme stylesheet. It remains the owner of sidebar geometry, workspace +// visibility, hit testing, and scrolling. The following regression sheet only +// normalizes text close controls and the explicit-off Explorer presentation. import "./sidebar-workspace-final-fixes.css"; +import "./sidebar-workspace-regression-fixes.css"; import "./dialogControllerRuntime"; import "./generalUiIntegrityRuntime"; From b32de053bafadc55360349a80fc6ceafd4c83ce5 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 12:00:23 -0700 Subject: [PATCH 19/27] Type Provider Explorer intent diagnostics safely --- .../providerExplorerExplicitVisualizationRuntime.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts index bf5d0ac1..89faa388 100644 --- a/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts +++ b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts @@ -8,6 +8,12 @@ type ProviderExplorerIntentGlobal = typeof window & { }; }; +type ProviderExplorerNativeGlobal = typeof window & { + __NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?: { + getSnapshot?: (channel: "pins" | "aggregate" | "dots" | "live" | "gaps") => { featureCount?: number } | undefined; + }; +}; + const VISUALIZATION_LABELS = new Set([ "density", "hex field", @@ -38,8 +44,9 @@ function normalizeButtonLabel(button: HTMLButtonElement): string { function restoreInactivePresentation(): void { if (explicitlyActive) return; - const aggregateCount = window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("aggregate")?.featureCount || 0; - const dotCount = window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("dots")?.featureCount || 0; + const native = (window as ProviderExplorerNativeGlobal).__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__; + const aggregateCount = native?.getSnapshot?.("aggregate")?.featureCount || 0; + const dotCount = native?.getSnapshot?.("dots")?.featureCount || 0; if (aggregateCount > 0 || dotCount > 0) clearProviderExplorerNative(["aggregate", "dots"]); const drawer = document.querySelector(".provider-explorer-drawer"); From 014a7a08832830c046672586e2de27b4637cabf6 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 12:01:45 -0700 Subject: [PATCH 20/27] Add sidebar regression browser acceptance --- .../ci-sidebar-regression-acceptance.mjs | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs diff --git a/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs b/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs new file mode 100644 index 00000000..b80cddf2 --- /dev/null +++ b/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs @@ -0,0 +1,200 @@ +import assert from "node:assert/strict"; +import { chromium, webkit } from "playwright"; + +const baseUrl = process.env.NETWORK_MAP_CI_UI_URL || "http://127.0.0.1:4173"; +const browserName = process.env.NETWORK_MAP_BROWSER || "chromium"; +const browserType = { chromium, webkit }[browserName]; +if (!browserType) throw new Error(`Unsupported browser ${browserName}`); + +function json(route, payload, status = 200) { + return route.fulfill({ status, contentType: "application/json", body: JSON.stringify(payload) }); +} + +async function installMocks(page) { + const emptyStyle = { + version: 8, + name: "Sidebar regression CI", + sources: {}, + layers: [{ id: "ci-background", type: "background", paint: { "background-color": "#e7edf3" } }], + }; + + await page.route("https://api.mapbox.com/**", async (route) => { + const url = new URL(route.request().url()); + if (url.pathname.includes("/styles/v1/")) return json(route, emptyStyle); + return route.fulfill({ status: 204, body: "" }); + }); + await page.route("https://events.mapbox.com/**", (route) => route.fulfill({ status: 204, body: "" })); + await page.route("https://nominatim.openstreetmap.org/**", (route) => json(route, [])); + await page.route("https://maps.googleapis.com/**", (route) => json(route, { results: [], status: "ZERO_RESULTS" })); + + await page.route("**/api/**", async (route) => { + const request = route.request(); + if (request.method() !== "GET") return json(route, { ok: true, success: true, id: "sidebar-ci" }); + const url = new URL(request.url()); + const pathname = url.pathname; + + if (pathname.endsWith("/revision")) return json(route, { revision: "sidebar-regression-ci" }); + if (pathname.includes("provider-explorer/density") || pathname.includes("provider-explorer/hex")) { + return json(route, { + total: 28, + cells: [ + { lat: 34.05, lng: -118.24, count: 18 }, + { lat: 36.17, lng: -115.14, count: 10 }, + ], + }); + } + if (pathname.includes("provider-explorer/map")) { + return json(route, { providers: [], total: 0, page: 1, hasMore: false }); + } + if (pathname.includes("provider-explorer")) { + return json(route, { providers: [], total: 0, page: 1, hasMore: false, stored_count: 0, live_count: 0, live_only: [] }); + } + if (pathname.includes("provider-category-layers")) { + return json(route, { providers: [], total: 0, count: 0, loaded: 0, page: 1, limit: 2000, hasMore: false, visibleCapped: false }); + } + if (pathname.includes("naccho-lhd") || pathname.includes("provider-layers")) { + return json(route, { providers: [], total: 0, count: 0, loaded: 0, page: 1, hasMore: false, visibleCapped: false }); + } + if (pathname.includes("health") || pathname.includes("ready")) return json(route, { ok: true, status: "ok" }); + if (pathname.includes("search") || pathname.includes("finder") || pathname.includes("npi")) { + return json(route, { providers: [], results: [], items: [], total: 0 }); + } + if (pathname.includes("inventory") || pathname.includes("coverage")) return json(route, { providers: [], total: 0, cells: [] }); + return json(route, {}); + }); +} + +async function activateWorkspace(page, label) { + const tab = page.locator(".occumed-sidebar-workspace-tab").filter({ hasText: label }).first(); + await tab.waitFor({ state: "visible", timeout: 15_000 }); + await tab.click(); + await page.waitForFunction((expected) => Array.from(document.querySelectorAll(".occumed-sidebar-workspace-tab")) + .some((candidate) => candidate.textContent?.includes(expected) && candidate.getAttribute("aria-selected") === "true"), label, { timeout: 10_000 }); +} + +async function assertButtonsHittable(page, selector, label) { + const failures = await page.locator(selector).evaluate((root) => { + const visible = (element) => { + const style = getComputedStyle(element); + const rect = element.getBoundingClientRect(); + return !element.hidden + && style.display !== "none" + && style.visibility !== "hidden" + && Number(style.opacity || "1") > 0 + && rect.width > 2 + && rect.height > 2 + && rect.right > 0 + && rect.bottom > 0 + && rect.left < innerWidth + && rect.top < innerHeight; + }; + + return Array.from(root.querySelectorAll("button:not(:disabled)")) + .filter((button) => visible(button)) + .map((button) => { + const rect = button.getBoundingClientRect(); + const x = Math.min(innerWidth - 1, Math.max(0, rect.left + rect.width / 2)); + const y = Math.min(innerHeight - 1, Math.max(0, rect.top + rect.height / 2)); + const hit = document.elementFromPoint(x, y); + const ok = hit === button || Boolean(hit && button.contains(hit)); + return ok ? null : { + label: `${button.textContent || button.getAttribute("aria-label") || "button"}`.replace(/\s+/g, " ").trim(), + rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height }, + hit: hit instanceof HTMLElement ? `${hit.tagName}.${hit.className}` : String(hit), + }; + }) + .filter(Boolean); + }); + assert.deepEqual(failures, [], `${label}: enabled buttons must be real pointer hit targets`); +} + +async function explorerCounts(page) { + return page.evaluate(() => ({ + pins: window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("pins")?.featureCount || 0, + aggregate: window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("aggregate")?.featureCount || 0, + dots: window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("dots")?.featureCount || 0, + live: window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("live")?.featureCount || 0, + gaps: window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("gaps")?.featureCount || 0, + })); +} + +async function assertWorkspaceButtons(page, label) { + await activateWorkspace(page, label); + const selector = label === "Providers" + ? ".sidebar.occumed-sidebar-workspace-scope" + : label === "Map Tools" + ? ".occumed-map-tools-panel" + : label === "Finder" + ? ".live-panel.open" + : ".provider-explorer-drawer.open"; + await page.locator(selector).waitFor({ state: "visible", timeout: 10_000 }); + await assertButtonsHittable(page, selector, `${label} workspace`); +} + +async function switchMode(page, mode) { + const button = page.locator(`.map-dimension-toggle button[data-map-mode="${mode}"]`).first(); + await button.waitFor({ state: "visible", timeout: 10_000 }); + if (await page.evaluate((expected) => window.__NETWORK_MAP_GLOBE__?.getMode?.() === expected, mode)) return; + await button.click(); + await page.waitForFunction((expected) => window.__NETWORK_MAP_GLOBE__?.getMode?.() === expected, mode, { timeout: 35_000 }); +} + +const browser = await browserType.launch({ headless: true }); +const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, reducedMotion: "reduce" }); +const page = await context.newPage(); +page.setDefaultTimeout(10_000); + +try { + await installMocks(page); + await page.goto(baseUrl, { waitUntil: "domcontentloaded", timeout: 30_000 }); + await page.waitForFunction(() => document.documentElement.dataset.occumedWorkspaceReady === "true", null, { timeout: 20_000 }); + await page.waitForFunction(() => Boolean(window.__NETWORK_MAP_PROVIDER_EXPLORER_INTENT__), null, { timeout: 20_000 }); + await page.waitForTimeout(900); + + await assertWorkspaceButtons(page, "Providers"); + const expectedLabels = [ + "Urgent Cares", "Occupational Health Clinics", "Dentists", "Blue Hive", "FAA Examiners", "DOT Examiners", + "Labs", "Imaging", "Audiology", "General Practitioners", "Pharmacy", "International Providers", + "U.S. Embassy Recommended", "Uploaded Clinics", "NACCHO Local Health Departments", + ]; + for (const label of expectedLabels) { + await page.locator(`input[aria-label="${label}"]`).waitFor({ state: "visible", timeout: 10_000 }); + } + assert.equal(await page.locator('input[aria-label="Indexed Providers"]:visible').count(), 0, "Legacy Indexed Providers toggle must not remain visible"); + + await assertWorkspaceButtons(page, "Map Tools"); + await assertWorkspaceButtons(page, "Finder"); + await assertWorkspaceButtons(page, "Explorer"); + + await page.waitForTimeout(750); + assert.equal(await page.evaluate(() => document.documentElement.dataset.providerExplorerVisualizationActive), "false", "Explorer visualization must begin explicitly off"); + assert.deepEqual(await explorerCounts(page), { pins: 0, aggregate: 0, dots: 0, live: 0, gaps: 0 }, "Provider Explorer must not render data before a visualization is selected"); + + const close = page.getByRole("button", { name: "Close Provider Explorer" }); + const closeGeometry = await close.evaluate((button) => { + const rect = button.getBoundingClientRect(); + return { width: rect.width, height: rect.height, scrollWidth: button.scrollWidth, clientWidth: button.clientWidth }; + }); + assert.ok(closeGeometry.width >= 60, `Explorer Close width is too small: ${closeGeometry.width}`); + assert.ok(closeGeometry.height >= 28, `Explorer Close height is too small: ${closeGeometry.height}`); + assert.ok(closeGeometry.scrollWidth <= closeGeometry.clientWidth + 2, "Explorer Close text must not wrap or overflow"); + + const explorer = page.locator(".provider-explorer-drawer.open"); + await explorer.getByRole("button", { name: /^Density$/ }).click(); + await page.waitForFunction(() => document.documentElement.dataset.providerExplorerVisualizationActive === "true"); + await page.waitForFunction(() => (window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("aggregate")?.featureCount || 0) === 2, null, { timeout: 10_000 }); + + await close.click(); + await page.waitForFunction(() => document.documentElement.dataset.occumedworkspace === "providers", null, { timeout: 10_000 }); + assert.deepEqual(await explorerCounts(page), { pins: 0, aggregate: 0, dots: 0, live: 0, gaps: 0 }, "Closing Explorer must clear Explorer-owned map overlays"); + + await switchMode(page, "3d"); + for (const workspace of ["Providers", "Map Tools", "Finder", "Explorer"]) await assertWorkspaceButtons(page, workspace); + await switchMode(page, "2d"); + for (const workspace of ["Providers", "Map Tools", "Finder", "Explorer"]) await assertWorkspaceButtons(page, workspace); + + console.log(`Sidebar regression acceptance passed for ${browserName}.`); +} finally { + await context.close(); + await browser.close(); +} From 01d2fb28ad32fb0d0e10f87027f1c1830b00f556 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 12:03:17 -0700 Subject: [PATCH 21/27] Gate sidebar regressions in Chromium and WebKit --- .github/workflows/hardening-acceptance.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/hardening-acceptance.yml b/.github/workflows/hardening-acceptance.yml index 864436f2..25f0468a 100644 --- a/.github/workflows/hardening-acceptance.yml +++ b/.github/workflows/hardening-acceptance.yml @@ -101,6 +101,13 @@ jobs: NETWORK_MAP_CI_UI_URL: http://127.0.0.1:4173 shell: bash run: timeout --signal=TERM --kill-after=10s 150s pnpm --filter @workspace/occu-med-map exec node scripts/ci-mapbox-native-tools-acceptance.mjs + - name: Run sidebar regression acceptance + if: matrix.route == 'standard' + env: + NETWORK_MAP_BROWSER: ${{ matrix.browser }} + NETWORK_MAP_CI_UI_URL: http://127.0.0.1:4173 + shell: bash + run: timeout --signal=TERM --kill-after=10s 150s pnpm --filter @workspace/occu-med-map exec node scripts/ci-sidebar-regression-acceptance.mjs - name: Upload browser failure evidence if: failure() uses: actions/upload-artifact@v4 From c933be0297aafcb782ba8f8299facd4fdd0ab0be Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 12:07:35 -0700 Subject: [PATCH 22/27] Recognize Explorer visualization clicks by visible label --- .../src/providerExplorerExplicitVisualizationRuntime.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts index 89faa388..b5cee4e1 100644 --- a/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts +++ b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts @@ -34,11 +34,8 @@ function clearExplorerVisualization(): void { clearProviderExplorerNative(); } -function normalizeButtonLabel(button: HTMLButtonElement): string { - return `${button.textContent || ""} ${button.getAttribute("aria-label") || ""}` - .replace(/\s+/g, " ") - .trim() - .toLowerCase(); +function visibleButtonLabel(button: HTMLButtonElement): string { + return (button.textContent || "").replace(/\s+/g, " ").trim().toLowerCase(); } function restoreInactivePresentation(): void { @@ -75,7 +72,7 @@ function handleClick(event: MouseEvent): void { const drawer = button.closest(".provider-explorer-drawer"); if (!drawer) return; - const label = normalizeButtonLabel(button); + const label = visibleButtonLabel(button); if (VISUALIZATION_LABELS.has(label)) { setIntent(true); return; From 9eb2e318c493a71d324b7f558e0cd3974d300257 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 12:08:07 -0700 Subject: [PATCH 23/27] Use visible Explorer labels in sidebar regression acceptance --- occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs b/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs index b80cddf2..ce21824c 100644 --- a/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs +++ b/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs @@ -180,7 +180,7 @@ try { assert.ok(closeGeometry.scrollWidth <= closeGeometry.clientWidth + 2, "Explorer Close text must not wrap or overflow"); const explorer = page.locator(".provider-explorer-drawer.open"); - await explorer.getByRole("button", { name: /^Density$/ }).click(); + await explorer.locator(".provider-visualization-grid button").filter({ hasText: /^Density$/ }).click(); await page.waitForFunction(() => document.documentElement.dataset.providerExplorerVisualizationActive === "true"); await page.waitForFunction(() => (window.__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__?.getSnapshot?.("aggregate")?.featureCount || 0) === 2, null, { timeout: 10_000 }); From 0349f691ce346b5a4b7d29f45a4de698fca6ec9d Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 12:12:16 -0700 Subject: [PATCH 24/27] Bind Explorer intent to visualization control group --- ...oviderExplorerExplicitVisualizationRuntime.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts index b5cee4e1..328f1d55 100644 --- a/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts +++ b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts @@ -14,14 +14,6 @@ type ProviderExplorerNativeGlobal = typeof window & { }; }; -const VISUALIZATION_LABELS = new Set([ - "density", - "hex field", - "8px points", - "density + points", - "dot density", -]); - let explicitlyActive = false; let unsubscribeDom: (() => void) | null = null; @@ -72,12 +64,16 @@ function handleClick(event: MouseEvent): void { const drawer = button.closest(".provider-explorer-drawer"); if (!drawer) return; - const label = visibleButtonLabel(button); - if (VISUALIZATION_LABELS.has(label)) { + // The visualization grid is the ownership boundary. Do not depend on button + // text or accessible-name composition here: Safari/WebKit and browser tests + // can produce different text/aria combinations, but a click inside this grid + // always means the user explicitly asked to render one visualization mode. + if (button.closest(".provider-visualization-grid")) { setIntent(true); return; } + const label = visibleButtonLabel(button); if (button.getAttribute("aria-label") === "Close Provider Explorer" || label === "clear filters") { reset(); } From 4b7bfb6546f3e0eb7b7a83a59ddae5d4e6212812 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 12:16:15 -0700 Subject: [PATCH 25/27] Bind Explorer visualization intent directly to controls --- ...iderExplorerExplicitVisualizationRuntime.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts index 328f1d55..ab87119d 100644 --- a/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts +++ b/occu-med-map/src/providerExplorerExplicitVisualizationRuntime.ts @@ -16,6 +16,7 @@ type ProviderExplorerNativeGlobal = typeof window & { let explicitlyActive = false; let unsubscribeDom: (() => void) | null = null; +const boundVisualizationButtons = new WeakSet(); function setIntent(active: boolean): void { explicitlyActive = active; @@ -30,7 +31,20 @@ function visibleButtonLabel(button: HTMLButtonElement): string { return (button.textContent || "").replace(/\s+/g, " ").trim().toLowerCase(); } +function bindVisualizationControls(): void { + document.querySelectorAll(".provider-explorer-drawer .provider-visualization-grid button").forEach((button) => { + if (boundVisualizationButtons.has(button)) return; + boundVisualizationButtons.add(button); + // Bind on the control itself as well as the document owner below. This is + // deliberate: Safari/WebKit and programmatic HTMLElement.click() both have + // to pass through the exact same explicit-intent gate before React renders + // a Provider Explorer visualization. + button.addEventListener("click", () => setIntent(true), { capture: true }); + }); +} + function restoreInactivePresentation(): void { + bindVisualizationControls(); if (explicitlyActive) return; const native = (window as ProviderExplorerNativeGlobal).__NETWORK_MAP_PROVIDER_EXPLORER_NATIVE__; @@ -64,10 +78,6 @@ function handleClick(event: MouseEvent): void { const drawer = button.closest(".provider-explorer-drawer"); if (!drawer) return; - // The visualization grid is the ownership boundary. Do not depend on button - // text or accessible-name composition here: Safari/WebKit and browser tests - // can produce different text/aria combinations, but a click inside this grid - // always means the user explicitly asked to render one visualization mode. if (button.closest(".provider-visualization-grid")) { setIntent(true); return; From a5d948a3f983d54145ebe670e497326896f843b2 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 12:17:10 -0700 Subject: [PATCH 26/27] Gate Provider Explorer visualization requests on explicit intent --- ...providerExplorerRequestStabilityRuntime.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/occu-med-map/src/providerExplorerRequestStabilityRuntime.ts b/occu-med-map/src/providerExplorerRequestStabilityRuntime.ts index 5c84eab7..05dc15df 100644 --- a/occu-med-map/src/providerExplorerRequestStabilityRuntime.ts +++ b/occu-med-map/src/providerExplorerRequestStabilityRuntime.ts @@ -17,6 +17,12 @@ type RuntimeSnapshot = { lastCompletedRequestId: number; }; +type ProviderExplorerIntentGlobal = typeof window & { + __NETWORK_MAP_PROVIDER_EXPLORER_INTENT__?: { + isExplicitlyActive?: () => boolean; + }; +}; + declare global { interface Window { __OCCUMED_PROVIDER_EXPLORER_STABILITY__?: RuntimeSnapshot; @@ -64,10 +70,38 @@ function finish(record: ActiveRequest): void { } } +function visualizationIsExplicitlyActive(): boolean { + return Boolean( + (window as ProviderExplorerIntentGlobal) + .__NETWORK_MAP_PROVIDER_EXPLORER_INTENT__ + ?.isExplicitlyActive?.(), + ); +} + +function inactiveVisualizationResponse(channel: "aggregate" | "pins"): Response { + const payload = channel === "aggregate" + ? { cells: [], total: 0, explicitVisualizationRequired: true } + : { providers: [], total: 0, page: 1, hasMore: false, explicitVisualizationRequired: true }; + return new Response(JSON.stringify(payload), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); +} + registerNetworkRequestMiddleware("provider-explorer-request-stability", async (context, next) => { const channel = channelFor(context.input); if (!channel) return next(); + // App.tsx historically refreshes the last Explorer visualization whenever + // map readiness or filters change. Treat that refresh as inert until a user + // explicitly selects a visualization. This prevents density/pins from + // fetching or reappearing on startup, after Close, or after filter changes. + // A real visualization-button click sets intent during capture before React + // performs the request, so explicitly requested rendering is unaffected. + if ((channel === "aggregate" || channel === "pins") && !visualizationIsExplicitlyActive()) { + return inactiveVisualizationResponse(channel); + } + const previous = active.get(channel); if (previous && !previous.completed) { previous.controller.abort(abortError(`Superseded by a newer Provider Explorer ${channel} request.`)); From 1a331967d7bbaefb09922b59c3116d4f21355e08 Mon Sep 17 00:00:00 2001 From: Occumed79 Date: Mon, 17 Aug 2026 12:20:42 -0700 Subject: [PATCH 27/27] Scroll sidebar controls into view before pointer audit --- .../ci-sidebar-regression-acceptance.mjs | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs b/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs index ce21824c..e304d59b 100644 --- a/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs +++ b/occu-med-map/scripts/ci-sidebar-regression-acceptance.mjs @@ -73,39 +73,47 @@ async function activateWorkspace(page, label) { } async function assertButtonsHittable(page, selector, label) { - const failures = await page.locator(selector).evaluate((root) => { - const visible = (element) => { + const root = page.locator(selector); + const buttons = root.locator("button:not(:disabled)"); + const failures = []; + const count = await buttons.count(); + + for (let index = 0; index < count; index += 1) { + const button = buttons.nth(index); + if (!(await button.isVisible())) continue; + + // Sidebar workspaces intentionally scroll. A control that is below the + // fold is not a dead control, so first bring the entire target into the + // actual clipped viewport and only then test the pointer hit target. + await button.scrollIntoViewIfNeeded(); + await page.waitForTimeout(20); + + const result = await button.evaluate((element) => { const style = getComputedStyle(element); const rect = element.getBoundingClientRect(); - return !element.hidden - && style.display !== "none" + const x = Math.min(innerWidth - 1, Math.max(0, rect.left + rect.width / 2)); + const y = Math.min(innerHeight - 1, Math.max(0, rect.top + rect.height / 2)); + const hit = document.elementFromPoint(x, y); + const ok = style.display !== "none" && style.visibility !== "hidden" && Number(style.opacity || "1") > 0 && rect.width > 2 && rect.height > 2 - && rect.right > 0 - && rect.bottom > 0 - && rect.left < innerWidth - && rect.top < innerHeight; - }; - - return Array.from(root.querySelectorAll("button:not(:disabled)")) - .filter((button) => visible(button)) - .map((button) => { - const rect = button.getBoundingClientRect(); - const x = Math.min(innerWidth - 1, Math.max(0, rect.left + rect.width / 2)); - const y = Math.min(innerHeight - 1, Math.max(0, rect.top + rect.height / 2)); - const hit = document.elementFromPoint(x, y); - const ok = hit === button || Boolean(hit && button.contains(hit)); - return ok ? null : { - label: `${button.textContent || button.getAttribute("aria-label") || "button"}`.replace(/\s+/g, " ").trim(), - rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height }, - hit: hit instanceof HTMLElement ? `${hit.tagName}.${hit.className}` : String(hit), - }; - }) - .filter(Boolean); - }); - assert.deepEqual(failures, [], `${label}: enabled buttons must be real pointer hit targets`); + && rect.left >= 0 + && rect.top >= 0 + && rect.right <= innerWidth + && rect.bottom <= innerHeight + && (hit === element || Boolean(hit && element.contains(hit))); + return ok ? null : { + label: `${element.textContent || element.getAttribute("aria-label") || "button"}`.replace(/\s+/g, " ").trim(), + rect: { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom, width: rect.width, height: rect.height }, + hit: hit instanceof HTMLElement ? `${hit.tagName}.${hit.className}` : String(hit), + }; + }); + if (result) failures.push(result); + } + + assert.deepEqual(failures, [], `${label}: enabled buttons must be reachable and real pointer hit targets`); } async function explorerCounts(page) {