Skip to content

Interactive Explorer: integrate Hana mockup feedback (Light path on search semantics) #178

@rdhyee

Description

@rdhyee

Captures the Interactive Explorer feedback from the 2026-05-08 tech call discussion of Hana's wireframe mockup (Figma node 213:394). Hana noted "corrections coming"; this issue tracks the Light-path subset that integrates with our shipped FTS work without re-opening the locked contracts.

Background

The mockup proposes a search UX that introduces a third semantic alongside our recent option C (locked in #164 / PR #166): two explicit buttons in the side panel — "Search Selected Areas" (viewport-scoped) and "Search Entire World" (full-corpus). EXPLORER_STATE.md §6 currently commits to option C with side-panel + result-pin overlay; the mockup overlays a viewport-scope toggle on top.

Two implementation paths:

Decision: Light first. Heavy deferred until we've shipped the substrate work in #169-#172 and have data on whether viewport-scoped search materially shifts user behavior.

Light-path spec — search-semantics

UI

Two buttons in the side panel below the search input + facet checkboxes (matching the Figma mockup):

The current single "Search" button → replaced by these two. Pressing Enter in the search input runs whichever was last clicked, defaulting to Search Entire World (preserves current behavior for keyboard users).

Backend

In doSearch(scope):

async function doSearch(scope) {
    // ... existing instrumentation + token tokenization ...

    let viewportPredicate = '';
    if (scope === 'area') {
        const rect = viewer.camera.computeViewRectangle(viewer.scene.globe.ellipsoid);
        if (rect) {
            const south = Cesium.Math.toDegrees(rect.south);
            const north = Cesium.Math.toDegrees(rect.north);
            const west  = Cesium.Math.toDegrees(rect.west);
            const east  = Cesium.Math.toDegrees(rect.east);
            // Dateline-crossing case (west > east): split into two ranges.
            // The lat/lng columns live in the lite parquet, joined as `l` in the CTE.
            if (west > east) {
                viewportPredicate = `
                    AND l.latitude BETWEEN ${south} AND ${north}
                    AND (l.longitude BETWEEN ${west} AND 180
                      OR l.longitude BETWEEN -180 AND ${east})
                `;
            } else {
                viewportPredicate = `
                    AND l.latitude BETWEEN ${south} AND ${north}
                    AND l.longitude BETWEEN ${west} AND ${east}
                `;
            }
        }
    }

    const results = await db.query(`
        WITH matches AS (
            SELECT pid, label, source, place_name, (${score}) AS relevance_score
            FROM read_parquet('${facets_url}')
            WHERE ${searchWhere}
            ${sourceFilterSQL('source')}
            ${facetFilterSQL()}
            ORDER BY relevance_score DESC
            LIMIT 50
        )
        SELECT m.pid, m.label, m.source, l.latitude, l.longitude,
               m.place_name, m.relevance_score
        FROM matches m
        LEFT JOIN read_parquet('${lite_url}') l USING (pid)
        WHERE 1=1 ${viewportPredicate}
        ORDER BY m.relevance_score DESC, m.label
    `);
}

The viewport predicate goes on the outer query (post-join), not the CTE — because the inner CTE searches sample_facets_v2 which doesn't have lat/lng. This means the CTE may return up to 50 matches that fail the viewport predicate; the outer LIMIT can drop us below 50 results in viewport-scoped mode. For v1 this is fine (users can pan to widen the area). A future tuning could increase the inner LIMIT to e.g. 200 when in area mode.

URL state

  • New query param ?search_scope=area|world. Hydrated by applyQueryToSearch(); written by writeQueryState().
  • Default: world (omitted from URL).
  • Camera position is the source of truth for what "area" means; no need to encode the rect in the URL — ?v=1&lat=…&lng=…&alt=… already captures it.

Result-pin overlay

The option C result-pin overlay (per EXPLORER_STATE.md §6) still applies for both scopes. Pin coordinates already reflect what was found; viewport-scope just narrows the candidate set.

Open question

Does Hana's design intent want "Search Selected Areas" as the primary button (left position) or the secondary? Mockup shows orange (selected areas) on the left and blue (entire world) on the right, suggesting selected-areas is primary. If so, the keyboard default may want to flip to area-scope. Defer until confirmed with Hana/Andrea.

Other mockup-derived items (separate from search semantics)

These are net-new UX work, no conflict with shipped contracts. Not in scope for the Light search-semantics PR; tracked here for future follow-ups:

  • Educational tooltips ("What is a Cluster?", "What is a Sample?") — onboarding boxes on the left edge of the explorer.
  • Tree-selection vocabularies for material / sample / sample-feature hierarchies — Hana to make a mockup diagram.
  • Top-level sample-type icons (5 per category) for missing-image fallback in sample cards.
  • Visual accessibility: halos around dots for color-blind / low-vision users.
  • Native Cesium control panel on the left edge — currently hidden; mockup shows it visible.
  • Quick Stats panel restructure — Selected Cluster moved into the same table as Quick Stats.
  • Visible "Copy Link to Selected View" button — currently we have a shareBtn; mockup styles it more prominently with a labeled button.
  • Permanent sample-rows table at the bottom — mockup shows the table view always-visible under the globe rather than as a Globe/Table toggle. May conflict with current binary view (Phase 5: rename Interactive Explorer to /explorer.html with redirects #162); needs a UX call.

Each of these gets its own issue once Hana's "corrections coming" iteration lands.

Acceptance for this issue (Light path only)

  • Two buttons in the side panel: "Search Selected Areas" + "Search Entire World"
  • ?search_scope=area|world URL param, hydrated + written
  • doSearch(scope) SQL conditionally adds the viewport predicate, with dateline-crossing handled
  • Result-pin overlay continues to work in both modes
  • Perf-smoke (explorer: search perf-smoke baseline (#167) #173) extended with one viewport-scoped query case in the canonical set
  • EXPLORER_STATE.md §6 amended: option C remains the default; the area-scoped variant is documented as a Light extension that does not require revisiting A/B/C

Out of scope

Refs

#163 (UX rework umbrella), #165 (FTS umbrella), PR #166 (state contract option C), PR #177 (interim recall fix), Hana mockup Figma 213:394.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestexplorerInteractive Explorer features

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions