Skip to content

refactor(explore): surface policy bills as a top-level data source tab #183

@William-Hill

Description

@William-Hill

Problem

Policy bills are a first-class dataset in the D4BL explore experience, but the UI
currently buries them so thoroughly that the D4BL founder didn't know the feature
existed when shown the tool.

Today, in ui-nextjs/app/explore/page.tsx:

  • Bills are not in DATA_SOURCES / DataSourceTabs, so there is no entry
    point from the tab row at the top of the page.
  • /api/explore/policies?state=<abbrev> is only called as a side-effect of the
    main fetch when a user has already clicked a state on the map
    (page.tsx:157-162).
  • When bills do render, they appear as a small <PolicyBadge> next to the
    selected state name (page.tsx:389), below the map, filter panel, data
    table, state annotation, explain panel, and state-vs-national chart. The
    badge is effectively the last thing on the page and only shows conditionally.

Result: users have to (1) be on some unrelated dataset, (2) know to click a
state, (3) scroll past five other components, and (4) notice a small badge — all
to discover that we have state-level legislative tracking at all.

Proposed change

Promote Policy Bills to a top-level entry in DataSourceTabs, on the same
footing as Census ACS, CDC Health, etc.

High-level scope:

  • Add a policy (or bills) entry to DATA_SOURCES in
    ui-nextjs/lib/explore-config.ts with hasData: true, its own accent color,
    description, and source URL.
  • When this tab is active, the page should render a bills-first view instead of
    the metric-map layout — see Design direction below for the committed
    approach.
  • Keep the existing "bills badge next to a selected state" affordance on the
    other data source tabs so cross-references from Census/CDC/etc. still work.
  • Decouple the bills fetch from the main fetchData callback in page.tsx
    so it can drive its own lifecycle when the policy tab is active.

Design direction

Default view: state view, not national aggregate. Bills are event-shaped
data (status + timestamps), not a continuous metric — the natural user question
is "what's happening in my state" or "what's happening anywhere on topic X",
both of which resolve to a filtered list of bills, not a count. A national
choropleth of bill counts would technically surface the feature but would
still require a drill-down to see any actual bill.

Aesthetic: "Legislative Signal" — the tab reads as a monitoring feed for a
selected state's bill activity, not a dashboard. Cohesive with the existing
dark terminal-adjacent aesthetic of the explore page (#292929 / #1a1a1a /
#00ff32 accent); no new font family, no new color language.

Layout:

  • Top: data source tabs (unchanged).
  • Map + filter rail, same 2-column grid as the existing metric layout.
    • Map: reuse StateMap, recolor states by recency of last bill action
      (log-scaled days since last action — green hot = recent, dark = dormant).
      This answers "where is something happening" at a glance, vs. raw counts
      which just reward populous states. Hover tooltip reframes to legislative
      language: "State · N bills · last action Xd ago".
    • Filter rail: drop the metric/race/year triad. Replace with State selector
      (searchable dropdown), Status multi-select (chip group), Topic multi-select
      (reuse the topic chips already in PolicyTable.tsx:49-60, hoisted into
      the rail).
  • Main content below: an activity feed sorted by last_action_date DESC,
    not a table. Generous vertical rhythm (~72px per row), not dense rows.

The memorable detail — phase glyphs: every bill gets a 5-segment
monospace phase indicator showing where it sits in the legislative lifecycle:

  • ▓░░░░ introduced
  • ▓▓░░░ in committee
  • ▓▓▓░░ passed one chamber
  • ▓▓▓▓░ passed both chambers
  • ▓▓▓▓▓ signed into law
  • ▓░░░░ red-tinted = failed / dead

This turns a row of 20 bills into a visual histogram of where policy is
stuck in this state. Cheap to build (~30 lines, one component), visually
consistent with the terminal aesthetic (blocks + monospace), and gives every
bill legibility that a status pill can't.

Row composition (per bill in the feed):

  • font-mono bill number, phase glyph, relative timestamp ("2d ago", "last
    week", "march 14").
  • Body-font title.
  • Topic tags as small chips (existing PolicyTable chip styling).
  • A View → link to the source bill URL.

Motion (restrained):

  • When a state is selected and the feed swaps contents, stagger-fade the
    first 5 rows in with a ~40ms delay between them. This makes "scanner
    snapping to a new frequency" physical.
  • Optional: the last filled segment of the phase glyph on the
    most-recently-acted bill can pulse subtly (1s opacity oscillation).
  • Do not animate the map recolor on initial load. Map is the reference frame;
    feed is the signal.

Empty states: when no state is selected, the feed area shows
"// select a state to begin monitoring" in font-mono, in the dim green
used for dormant map states. Keeps the aesthetic coherent when there's
nothing to show.

Explicit anti-patterns (what not to build):

  • No KPI card row at the top ("247 bills · 12 passed · 3 topics"). Dashboard
    slop. Put total and last-action-date inline in the state header.
  • No five-color status pills. The existing PolicyTable uses
    bg-blue-900 / bg-green-900 / etc. — the weakest part of its visual.
    Replace with monochrome phase glyphs (one green, one red-for-failed).
  • No new font family just for this tab. Coherence > novelty.
  • No "Export CSV" button on a discovery surface. If someone needs CSV, it
    belongs on a separate data-access surface.

Acceptance criteria

  • "Policy Bills" (or similar) appears as a top-level tab in the explore
    page alongside the other datasets.
  • Selecting that tab shows a bills-focused layout (state view — map +
    filter rail + activity feed) without requiring the user to first click
    a state.
  • Every bill in the feed renders with a 5-segment phase glyph reflecting
    its status in the legislative lifecycle.
  • Map is recolored by recency of last bill action (not raw counts).
  • Existing per-state bill badge on other tabs still works.
  • A first-time visitor can discover the bill tracking feature without
    being told it exists.

Out of scope

  • New ingestion or backend policy endpoints — /api/explore/policies and the
    PolicyBill SQLAlchemy model are already in place.
  • Bill detail pages / deep-linking to a specific bill.
  • Server-side aggregation endpoints (counts by state, facet counts). Client
    can aggregate the bounded /api/explore/policies response.
  • URL deep-linking for tab + filters (?tab=policy&state=CA&topic=housing).
  • A new font family or broader visual refresh of the explore page.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions