Skip to content

algorithm-catalog: live preview of the event page as it will look on the Disasters Portal - #253

Merged
kyle-lesinger merged 12 commits into
mainfrom
algorithm-catalog/live-portal-preview
Oct 21, 2026
Merged

kyle-lesinger merged 12 commits into
mainfrom
algorithm-catalog/live-portal-preview

Conversation

@kyle-lesinger

Copy link
Copy Markdown
Member

What

The Page content fields now drive a live preview of the page as it will look on science.data.nasa.gov/disasters, docked beside the form and updated on every keystroke.

  • A faithful replica, not a reflow. The preview has the portal's header, hero masthead ("PUBLISHED:"), sidebar (On this page · TYPE / NEED / HAZARD tags · Region · Start Date), "What the U.S. government is doing", Overview and Event Data Gallery. Fonts, --color-* tokens and proportions were measured off the live California page. It renders at the portal's 1280px desktop width, and <PortalFrame> scales it to fit.
  • Real gallery cards. Each product's portalData card is shown with its real title, description, tags (including the portal's "+ N more ...") and thumbnail. scripts/sync_portal_cards.py snapshots them into data/portal-cards.json and public/portal/ (1.4 MB, thumbnails downsized), because the app never fetches at run time. Layers with no portal card are listed under the preview.
  • What you see is what ships. The text goes through the same typographic() transform as the generated portal file, curly quotes included.
  • Toggle. "👁 Live preview" sits in the Page content header. It's sticky, remembered per browser, open by default at ≥1200px, and stacks below the form under 1100px. Events → event → Page preview shows the same replica.
  • The old in-app EventPage.tsx and its .page-* CSS are removed.

Verified

  • validate_data.py and rules_parity_test.py pass, as do npm run typecheck and npm run build.
  • scripts/e2e.mjs passes 35/35 in Chromium. The new checks: the preview is docked; it updates live (sub-title with curly quotes, Need tag, intro); it shows the real "Landsat True Color Imagery" card with its thumbnail decoded.
  • portal_pr.mjs --dry-run against disasters-portal@develop still passes the portal's own lint:ci and typecheck after the typographic() refactor.

…the Disasters Portal

The page preview used the catalog's own styling and only appeared in Review.
It is now a replica of the portal page itself — header, hero masthead, sidebar
tags, Overview and Event Data Gallery, with the portal's fonts, colors and
proportions measured off the live California page — rendered at its 1280px
desktop width and scaled to fit.

- Docked beside the Submit form (sticky, toggleable, remembered per browser)
  and updated on every keystroke; Events -> Page preview shows the same.
- The gallery shows the portal's REAL data cards for each product, snapshotted
  by scripts/sync_portal_cards.py into data/portal-cards.json + public/portal/.
- Text goes through the same typographic() transform as the generated portal
  file, so the preview shows exactly what the portal will (curly quotes too).
… inferred

Images: Page content gains an Images block — a Google Drive file link per
image, placed as the hero (masthead + card thumbnail) or inline in the
Overview, with required alt text and an optional caption. The live preview
loads them from Drive and says plainly when a file isn't shared. When the
portal PR opens, portal_pr.mjs downloads each into public/img/event/, sniffs
the bytes (an unshared Drive file returns an HTML page), reads the real
width/height the portal's image block needs, and writes image blocks; a failed
image is listed in the PR instead of breaking it. Rules mirrored in
rules.ts/validate_data.py with parity fixtures (folder links, placement, alt,
one hero).

Page address: the portal page id is no longer an input. A new page's id is
derived from the event name, so the name must follow the portal convention
"<Event> <Month> <YYYY>" (error), with a warning when it disagrees with the
start date. Existing portal pages keep their recorded id. "U.S." now slugs to
"us", matching us-winter-storm-jan-2026.
…ad; no address field

- The masthead is always the portal's default event image, so the hero option
  is gone. Instead there is ONE optional image after the intro, matching the
  Venezuela / Typhoon Sinlaku pages: a Google Drive file link + required alt
  text + an optional caption (with credit). It becomes the portal's image block
  between the Overview text and the Event Data Gallery; the preview's caption
  is styled as measured on the live portal.
- The page address field is removed from the form entirely. It is derived from
  the event name (whose "<Month> <YYYY>" ending is enforced) and shown only in
  the live preview's header.
…andard

The portal's image standard (disasters-portal #556, refined by #654) is: real
WebP via `cwebp -q 80 -m 6 -metadata none`, at most 1920px wide, under 500 KB.
The Drive image the portal PR adds now always meets it:

- toPortalWebp(): a WebP that already complies is kept byte for byte
  (re-encoding lossy WebP only loses quality); anything else is encoded at q80,
  capped at 1920px, and stepped down in width only if still over 500 KB. Body
  images are not capped at 1280px (#654 dropped that rule: it blurred
  high-DPI screenshots). GIFs are refused with a clear message.
- The PR body records each conversion, e.g. "11,594 KB PNG -> 292 KB WebP
  (q80, 3943 -> 1920px)". The workflow installs cwebp.
- The preview's card snapshots use the same encoding (480px WebP): 1.38 MB of
  JPEG -> 878 KB, and the sync script prunes files it no longer references.
…ucts right)

The picker was one long scrolling list of every sensor and its products.
It is now a vertical tablist of sensors (catalog order, modality icon and
accent, selected/total count, status badge) beside the selected sensor's
products. Search filters both columns, and an All matches entry lists
every hit grouped by sensor.

- Status badge is generic: any non-production status, labelled through a
  STATUS_LABEL map (prototype, external = no algorithm yet), else raw.
- Keyboard: Down from search enters the list; Up/Down/Home/End move
  sensors; Right/Enter go into products; Up/Down/Left inside; Esc closes
  from anywhere (document listener). After an add, focus moves to the next
  addable row instead of dropping to body.
- Sizing scoped to .req-prodpick so PlacePicker's .req-picker is
  unchanged: up to 760px wide (capped by viewport minus the anchor's x),
  fixed height, columns scroll independently; under 560px the sensors
  become a horizontal strip and hazards wrap under each product.
- e2e: sensor count equals algorithm count, Sentinel-2 vs Landsat, swir
  search + add, keyboard add, Esc closes.
…rithm yet

A product made outside this catalog (OPERA, UAVSAR, Planet, ...) can now be
listed so a request can ask for it and the event page links its Disasters
Portal card, without pretending a DPS job exists.

- types: AlgorithmStatus gains "external"; Modality gains "model" (GAIA,
  GPM IMERG: neither one optical nor one SAR sensor), with its colour/icon.
- drafts: autoProducts skips external, so a hazard never auto-selects one.
- picker, chips, Algorithms cards and detail: badged "no algorithm yet";
  detail hides DPS name/inputs/resources and links the portal data page.
- What this will run: says the layer is produced outside the catalog and no
  DPS job is created.
- validate_data.py: status vocabulary; external must declare no inputs.
- e2e: lazy thumbnails are scrolled into view before measuring; new checks
  that OPERA DSWx is never auto-selected, can be added by hand, is labelled,
  and shows its real portal card in the live preview.
…ed (59/59)

Adds the 40 data-gallery products that had no algorithm here, as 16
status:"external" entries grouped by source: OPERA, UAVSAR, AVIRIS-3,
PlanetScope, Vantor, SkySat, ECOSTRESS, GPM IMERG, HydroSAR, NISAR, GAIA,
WB-57 DyNAMITE, Black Marble composites, Sentinel-1 damage assessment,
Sentinel-1+2 burn severity and Sentinel-2 change products.

Each product's label is its portal card title, portalData its card constant,
hazards its portal categories mapped through hazards.json (tsunami dropped),
primaryHazards empty. Dates not stated on the cards are approximate mission
or record starts. Thumbnails are the portal card images, 600x400 <=150 KB,
credited in thumbs/CREDITS.md. portal-cards.json re-synced: 59 cards.
…g/live-portal-preview

# Conflicts:
#	algorithm-catalog/data/portal-cards.json
#	algorithm-catalog/scripts/e2e.mjs
#	algorithm-catalog/src/request/ProductPicker.tsx
Each product's stacAsset (the product-specific asset key on the live items,
e.g. "truecolor", "colorir", "backscatter", "blackmarble_hd") and
stacCollections are now GENERATED by scripts/sync_stac_assets.py from the
live Disasters STAC. They are never hand-edited and the app offers no way to
edit them. cog_default and the rendered_preview/dashboard assets are not
asset names and are never used.

Collections are matched to products by rule, with ranked claims; a tie is
assigned to nobody and reported, never guessed. 52 of 71 products are linked.
The picker shows the STAC asset name as the product token, and a product no
live collection carries shows its own token, dashed. validate_data.py rejects
a hand-edited shape; `sync_stac_assets.py --check` detects drift from live
STAC.

Also: the e2e picker test anchors on the exact "Sentinel-2" sensor title,
since the merged catalog now has three Sentinel-2-named sensors.
…pped from the preview

- "+ Add products": an added product's row stays clickable. Clicking its ✓
  (hover shows a red −) removes it, through the same removeProduct as the
  chip's ×. Previously added rows were disabled, so there was no way to
  de-select from the picker.
- Live preview: a picked product with no Disasters Portal Data Gallery card
  (e.g. Umbra gamma-naught, Black Marble NOAA-20) now shows as a dashed draft
  card in the Event Data Gallery, saying the published page won't show it
  until the portal adds an entry. This replaces the easy-to-miss footnote.
  Real cards render unchanged.
- Products step: each sensor group's heading carries its modality accent
  (icon + coloured rule) and is larger and heavier than the product chips
  under it, so the sensor names no longer blend into the chips.
…added

The selected products are listed above the "Add products" button, so every
product added from the open picker grew the page above it and pushed the
picker down: 26-66 px per add, off screen after a few. Now the picker's
position is recorded just before each add/remove made from it, and the page is
scrolled by exactly the difference after the list re-renders, so the picker
stays under the cursor. A scroll the user makes themselves is never undone.

e2e: adds four products from different sensors and removes one, asserting 0 px
picker drift per change. Without the fix it reports 26, 66, 66, 66 px.
@kyle-lesinger
kyle-lesinger merged commit d0b813a into main Oct 21, 2026
34 checks passed
@kyle-lesinger
kyle-lesinger deleted the algorithm-catalog/live-portal-preview branch October 21, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant