Skip to content

Promote dev - #41

Merged
zackrylangford merged 27 commits into
mainfrom
dev
Sep 4, 2026
Merged

zackrylangford merged 27 commits into
mainfrom
dev

Conversation

@zackrylangford

Copy link
Copy Markdown
Collaborator

No description provided.

aronnax-scout and others added 26 commits September 2, 2026 14:34
…claims

Addresses the four review points on PR #76, each re-verified at its
primary source rather than taken from the review text.

Blocking:
- Next.js 16.3.4 is four items, not three; the omitted one re-enables AVIF
  Image Optimization. Record that the pinned 16.3.3 is itself a critical
  security release (GHSA-p293-qw3h-jr36, GHSA-2xp9-vwfh-vxw4) and answer
  the exposure question: no next/image import anywhere and no images
  config in next.config.ts, so the AVIF surface is nil.
- configure-aws-credentials@v6.0.0's only breaking change is the node24
  runner bump requiring runner >= v2.327.1, not session-tagging defaults.
  transitive-tag-keys ships in the same release as a feature.

Non-blocking:
- Name all three lagging workflows in the action item; backend-ci.yml
  also runs setup-python@v5.
- Add GHSA-p498-v437-472g (@humanfs/node < 0.16.8), published seven
  minutes before the audit was committed, and reconcile the counts:
  25 dev-only, 28 total.

Also records that the Urgent lockfile refresh landed on dev as #78, whose
lockfile resolves @humanfs/node to 0.16.8.
docs(scout): dependency & framework report
The sweep scans full items instead of names only (projection does not
reduce consumed capacity), writes {pl_name, removed_at,
last_known_snapshot} to a new exoplanet-tombstones-<env> table for every
stale record, and submits the deletes only once that batch has landed.
A tombstone failure propagates to the existing top-level catch, so the
run reports sweep_aborted with nothing deleted. removed_at is the run's
last_updated timestamp so both share one clock.

The sync function gets a write-only policy on the tombstone table.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
A resync rebuilds ExoplanetsTable but cannot rebuild tombstones, so the
default DeletionPolicy of Delete would make this the one resource whose
loss is unrecoverable. Retain over DeletionProtectionEnabled because it
also survives a resource replacement (e.g. a future key-schema change),
and no teardown workflow exists for either stack.

Also: stored.keys() - archive_names instead of set(stored) - ..., and a
README note that a retried sweep advances removed_at to the last attempt.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The border token measured ~1.5:1 against surface, background and
surfaceAlt in nautilus, cosmicDawn and starlight, so inputs, range
rails and outline buttons whose only affordance is their border were
near-invisible. WCAG 1.4.11 asks for 3:1.

Each theme keeps its border hue but drops saturation to 20% and moves
lightness until the value clears 3:1 on all three surfaces borders are
actually drawn on (nav on background, ESI modal and table header rows on
surfaceAlt). Keeping the original 40% saturation would have produced a
vivid periwinkle outline that reads as colour rather than structure.

  nautilus   #2B3566 -> #5E668D  (surface 3.10, bg 3.39, alt 3.22)
  cosmicDawn #2A3063 -> #5C618A  (surface 3.16, bg 3.31, alt 3.22)
  starlight  #D7DEEF -> #7E8BA9  (surface 3.41, bg 3.26, alt 3.13)

A token-level test now guards the ratio so a future surface or border
tweak cannot silently regress it.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…r #83

The records table's since/previous history is the one thing the next
sync cannot recompute, so it carries the same DeletionPolicy /
UpdateReplacePolicy: Retain that PR #83 settled on for tombstones.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…sides

--color-border is painted as a hover fill under --color-text in four places
(modal close buttons, quick-view, range track). Raising border toward the
surface also moves it toward text, so the guard now checks both directions.
deploy-aws-backend.yml holds the only id-token: write in the repo and
ran the oldest actions. Move it, codeql.yml and backend-ci.yml to the
majors ci.yml already uses: checkout v7, setup-python v7, setup-sam v3,
configure-aws-credentials v6. Every crossed major is a Node 24 runner
bump; none of the removed inputs or blocked triggers are used here.
- 386 → 418 for the radius+mass-no-temperature bucket (386 was the
  no-insolation-either count); buckets reconcile to 1,035
- Decision 5: float/Decimal boundary owned by a new values.py
  (measured + to_decimal); refresh compares on the Decimal side;
  new AC + float-rejecting FakeTable
- Baseline cards read 'tracked since', not 'held since' (AC + decision 3)
- DynamoDBReadPolicy + DynamoDBWritePolicy instead of CrudPolicy
- Vercel IAM grant promoted to a named task (3); tasks renumbered
- ROADMAP: fix Shipped #1 inputs in task 1, add Records entry in task 6

Co-Authored-By: Claude Code <noreply@anthropic.com>
* esi: require radius, mass and temperature; export unrounded esi_similarity

Drop the two-of-three MINIMUM_COMPONENTS floor. esi_similarity(record)
returns the unrounded geometric mean over exactly pl_rade, pl_bmasse and
pl_eqt, or None if any is missing or out of domain; compute_esi is its
rounded wrapper. Tests re-pinned: the two-input vectors now expect None,
including an Earth-radius, Earth-mass planet with no temperature.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* ESIModal: state the all-three rule, fixed 1/3 exponent, adaptation note

Co-Authored-By: Claude Code <noreply@anthropic.com>

* ROADMAP: ESI is derived from radius, mass and temperature, not density

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: aronnaxp <aronnaxp@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
… table (#93)

* Move measured() and to_decimal() into values.py

values.py owns the float/Decimal boundary that esi.py, app.py and the
upcoming records.py all share. No behaviour change: esi._is_measured
becomes values.measured, app.to_decimal becomes values.to_decimal.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Add records.py: registry, holder computation and reconcile

Seven v1 records ranked over the raw archive (finite, > 0 via measured;
Most Earth-like on the unrounded esi_similarity so its candidates are
exactly the scored planets). One state item per record with a bounded
previous list (20); baseline writes previous: []; same-holder value
refresh compares on the Decimal side so an unchanged value never
writes. update_records never raises; update_records_after_sweep skips
when the sweep aborted and logs why.

Tests use a fake table whose put_item refuses Python floats, so the
Decimal boundary is pinned rather than trusted.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Wire records into the sync: RecordsTable, env, policies, counters

app.py reconciles records after the sweep and reports records_changed
and records_aborted. template.yaml adds exoplanet-records-${Environment}
(PK record_id, DeletionPolicy and UpdateReplacePolicy Retain), the
RECORDS_TABLE_NAME env var, DynamoDBReadPolicy + DynamoDBWritePolicy on
it (no DeleteItem) and a stack output. README documents the table, the
item shape and the records step.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Share the paginated Scan and narrow the no-delete claim

scan_all(table, key) in dynamo.py replaces the two verbatim _scan_items
copies in sweep.py and records.py; test_dynamo.py pins the pagination
protocol once. The records policy comment and README no longer claim
history is undeletable: DynamoDBWritePolicy grants BatchWriteItem, which
deletes, so the honest reason is that the sync never needs DeleteItem.
RecordSpec holds max/min directly instead of a string looked up in a
dict, so an invalid direction is unrepresentable, and measure is typed
as what it returns.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: aronnaxp <aronnaxp@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
… and a shared date formatter (#95)

* lib/dynamo: generic scanAll and the records table name

Lifts the pagination loop out of scanAllPlanets so a second Scan caller
does not duplicate it; scanAllPlanets keeps only the projection aliasing.
recordsTableName mirrors planetsTableName (EXOPLANETS_RECORDS_TABLE,
default exoplanet-records-dev).

Co-Authored-By: Claude Code <noreply@anthropic.com>

* lib/syncDate: extract the planet page's sync date formatter

formatSyncDate() is the page's SYNC_DATE/describeSync formatting, moved
so the records surfaces and the planet page cannot disagree on a date.
The page keeps its ', synced <date>.' sentence; no behaviour change.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* lib/records: types, TS registry and fetchRecords()

The presentation half of the records registry (label, blurb, format)
under the seven ids records.py tracks, plus fetchRecords(): one paginated
Scan of the records table, intersected on id and returned in registry
order, or { status: 'unavailable' } — never throws.

Formatters reuse measurement()/lightYearsAway() so a record reads as the
planet page does; Most Earth-like rounds the stored similarity to the
badge's 0-100 score.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* lib/syncDate: read offset-less sync stamps as UTC

The Lambda writes datetime.utcnow().isoformat() with no offset, which
Date parses in the host's zone; off-UTC the formatter then printed the
wrong calendar day. Append Z when no zone is present so the date is the
same wherever the page renders.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* lib/records: esiScore reads a non-finite value as unknown

Matches the guard every other formatter inherits from measurement().

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: aronnaxp <aronnaxp@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
…r and sitemap (#97)

* records: add the Most Earth-like caveat to the registry and share SITE_NAME from lib/site

The honesty line is presentation, so it lives beside the label and blurb rather than as an id check in the card. SITE_NAME moves out of planetMetadata so the records page can title itself the same way.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* records: /records page with per-record cards, tenure wording and previous holders

Server-rendered behind Suspense + connection() so a failed Scan costs one response, not a cached page. A baseline reads "tracked since"; "held since" only once a record has changed hands. Previous holders sit collapsed in a native disclosure. unavailable and an empty table each get a calm notice.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* records: link /records from the NavBar and list it in the sitemap

Co-Authored-By: Claude Code <noreply@anthropic.com>

* records: give the previous-holders list an explicit role and size the skeleton from the registry

Safari/VoiceOver drops list semantics from a marker-less <ol>, so the
history disclosure now carries role="list" like every other styled list
in the repo; the test pins the attribute itself, since jsdom's implicit
role would otherwise let it regress silently.

RECORD_COUNT is exported from lib/records so the loading skeleton can't
drift from the registry, and a displaced holder with an unformattable
value and a corrupt window is covered to prove it degrades to a bare link.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: aronnaxp <aronnaxp@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
… (#99)

* Records frontend: homepage RecordsStrip and the ROADMAP Shipped entry

Add components/home/RecordsStrip between Hero and Latest Discoveries: the
three most recently changed records (since desc, registry-order ties),
each with holder link, registry-formatted value and, only when a holder
was displaced, "Took the record from X on <date>". Suspense +
connection() and the unavailable/empty notices match Latest Discoveries,
so the strip never renders empty. Shell always links to /records.

ROADMAP: add Shipped entry 5, Records Broken, and move it out of the
Direction paragraph's "follow from the same diff" list.

Closes #98

Co-Authored-By: Claude Code <noreply@anthropic.com>

* RecordsStrip: date the change by the displaced holder's until and pin newest-first previous

RecordChange now reads the date from displaced.until instead of record.since, so
the name and date in "Took the record from X on D" come from the same holder and
cannot disagree even if the previous[0] pick were wrong. Tests add a two-entry
previous fixture (fails if .at(0) becomes .at(-1)), pin role="list" with
toHaveAttribute as app/records/page.test.tsx does, and move the corrupt-date
case onto until.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: aronnaxp <aronnaxp@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: aronnax-builder[bot] <aronnax-builder[bot]@users.noreply.github.com>
…acked' (#102)

The unavailable branch of RecordList presented a failed read as normal
operation and disagreed with the homepage RecordsStrip on the same
condition. Align it with the LatestDiscoveries contract the strip
already follows; the genuinely-empty branch keeps its wording.

Also list EXOPLANETS_RECORDS_TABLE in the README .env.local block.

Closes #101

Co-authored-by: aronnaxp <aronnaxp@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
feat: Records Broken — unified ESI, persisted superlatives, /records and a homepage strip
@vercel

vercel Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
exoplanethub-com Ignored Ignored Preview Sep 4, 2026 8:58pm UTC

Request Review

@zackrylangford
zackrylangford merged commit cd46891 into main Sep 4, 2026
10 checks passed
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.

2 participants