Skip to content

Refactor: replace prop drilling with composables for shared display state #348

Description

@irees

Background

The Census Demographics PR (#338) added several new pieces of URL-backed state and display preferences to app/pages/tne.vue (choroplethElement, shadeByDensity, onlyWithStops, unitSystem, etc.). Each one is plumbed as an individual v-model or prop:

  • ~10 v-models on cal-filter
  • ~15 props on cal-map
  • Further plumbing inside cal-mapcal-legend and cal-map-viewer-ts

Adding one new knob (like onlyWithStops) currently means editing 4–5 files. The problem is structural: state that belongs together (choropleth display, display preferences) is being plumbed piece-by-piece instead of shared.

Plan: two composables

Vue's idiomatic answer here is composables that own state, not a giant state object passed as a prop. The URL is already the source of truth — composables that read/write route.query give every consumer the same view without prop drilling.

1. useScenarioUrlState()

Owns the URL-backed display state currently scattered across tne.vue:

  • showAggAreas, aggregateLayer, choroplethElement, shadeByDensity, onlyWithStops
  • Possibly: dataDisplayMode, hideUnmarked, flexServicesEnabled, flexColorBy

tne.vue, cal-filter, and cal-map each call the composable directly. Removes ~10 v-models on cal-filter and several props on cal-map.

2. useDisplayPreferences()

Cross-cutting bits that lots of components want:

  • unitSystem (currently threaded tnemaplegend / map-viewer-ts / census-panel)
  • isAllDayMode
  • Any other display-only flags that descendants of cal-map need

Removes the chain plumbing through cal-map.

Out of scope

  • No Pinia / app-wide store. URL is already canonical; composables are sufficient.
  • No mega ScenarioState object passed as a prop. Breaks fine-grained reactivity, conflicts with v-model, hides what each component depends on.
  • Scenario inputs (bbox, dates, geographyIds) and scenario filters (agencies, route types, weekdays) are already mostly bundled into scenarioConfig / scenarioFilter computeds. Leave them.
  • Selection state (selectedAggregationGeoid, selectedPanelData) is localized to tne.vuecal-census-panel. Already small after recent collapse. Leave.
  • Leaf-component props. For low-fan-out per-component data, explicit props are still nicer to read. Composables are for the high-fan-out shared state.

Acceptance criteria

  • cal-filter no longer receives the choropleth/aggregation v-models from tne.vue; calls the composable directly.
  • cal-legend, cal-map-viewer-ts, and cal-census-panel no longer receive unitSystem as a prop.
  • Adding a new URL-backed display flag is a one-file change.
  • No regression in URL state persistence or query-string format.
  • Lint, typecheck, tests pass.

Suggested order

  1. useScenarioUrlState first — biggest immediate impact.
  2. useDisplayPreferences second — smaller win, more cross-cutting.
  3. Stop. Re-evaluate after these land before reaching for more.

Why now

Punt until #338 merges. That PR introduced the bulk of the new plumbing and is already complex enough; doing both at once risks blurring the diff and making review harder.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions