Skip to content

2026-06-10 Bulk Session Tracker โ€‹

Live working log for the 2026-06-10 multi-topic session. Single roll-up PR off claude/bulk-2026-06-10 โ†’ dev; sub-branches merge in locally. Updated as work lands.

Session scope โ€‹

#TopicIssueSub-branchStatus
1Map background โ†’ black + VenuesMap Storybook playground (theming props + controls)#574feat/574-map-dark-basemapmerged (6 commits)
2CTA audit & consolidation against Foundations/CTAs catalog (step 9 auth/landing/merchant primaries deferred)#564refactor/564-cta-consolidationmerged (7 commits)
3User-facing public website (apps/site), decoupled from VitePress dev docs โ€” "What Lantern Is" deck port#575feat/575-user-sitemerged (3 commits)
4GitHub billing: receipt-day costs + realtime current period; Copilot tier + status-card fixes#576fix/576-github-billing-receipt-daymerged (5 commits)
5Dynamic config: maintenance mode + editable venue config (#263; #237 re-scoped, stays open)#263, #237feat/263-dynamic-configmerged (7 commits)
6(mid-session) Hero ad missing on cold loads โ€” venues-anchored gate + one-way late-fill + parallel selection#577fix/577-hero-cold-load-gatemerged (1 commit)

Operating rules for this session โ€‹

  • Main checkout stays on dev untouched; all work in worktrees under .claude/worktrees/.
  • Integration branch claude/bulk-2026-06-10 (off origin/dev) is the remote backup surface โ€” sub-branches merge into it locally, it gets pushed periodically (standing approval granted for this session's branches, pre-PR).
  • One draft PR at the end (Closes/Refs lines map every issue). npm run validate runs once before the PR.
  • Issues filed for any topic that lacks one, before implementation.
  • Operator review gate (added 2026-06-10 mid-session): after sub-branches merge into claude/bulk-2026-06-10 and validation passes, the branch is pushed and work PAUSES for Mechelle's review. The draft PR is opened only after her explicit go-ahead.

Inputs โ€‹

  • Design deck: Claude Design handoff bundle (What Lantern Is.html + design system + chat transcripts), extracted to /tmp/lantern-deck/what-lantern-is-slide-deck/.
  • #237 vs #263: overlap confirmed โ€” both move venue config to Firestore + admin editing. #263 (Phase C of closed #262) is the newer, detailed spec; #237's uncovered remainder (category-map/search-expansion editability) stays out of scope unless trivial.
  • #325 (Docs CMS, audience-scoped builds) is related to topic 3 but distinct: topic 3 is a public marketing/about site from the deck, not the docs CMS.

Decisions โ€‹

  • Map (#574): swap to OpenFreeMap dark style (same keyless host, already in CSP) + post-load paint overrides (pure-black background, brightened labels) + marker/badge contrast bumps + dark popup CSS (fixes pre-existing white-on-white popup bug). Rejected: CSS invert filters (mangles labels), vendored style JSON (50-layer maintenance).
  • CTA (#564): DashboardButton is the canonical Button (superset, only real consumers); the flat #f5333a red becomes the danger variant's look (it's the documented deliberate design in ActiveLanternView) and all extinguish surfaces converge on it via a single performExtinguish(reason); filter-chip active = white; new ToggleSwitch primitive with role="switch"; UI_CATEGORY_FILTERS exported from venueConfig. Auth amber-400/landing/merchant primary reconciliation deferred (biggest visual blast radius โ€” follow-up).
  • Site (#575): apps/site Vite vanilla workspace (no React); faithful port of the final 18-section deck as a responsive scroll page; tweaks panel dropped (defaults are the design: amber accent, standard glow, numbered dividers, flicker on behind prefers-reduced-motion); the two surviving em dashes rephrased per the operator's no-em-dash rule; beacon swatch derives from @lantern/ui palette; dev-only deploy wiring (prod intentionally deferred).
  • Billing (#576): receipt-day subscription rows from invoice_actuals (keyed raw_email_id, not gap-gated, subscription charge only); metered branch unchanged; reconciliation estimate includes receipt rows; GITHUB_COPILOT_PLAN=pro_plus; Copilot card picks full-month subscription charge; Billing.jsx stops discarding the CF's live GitHub number for the current-month headline. BQ cleanup of old prorated rows is a documented post-merge step, not run in this session.
  • Config (#263): maintenance doc gets allow read: if true (logged-out visitors must see the gate too); other systemConfig docs read = authenticated; write = admin. VENUE_REFRESH_THRESHOLDS gets actually wired into venueRefreshService (currently dead display-only config). #237 stays open, re-scoped to its uncovered remainder (OSM tag lists / category-map editing).
  • Docs exposure finding: standalone VitePress deploys (docs.ourlantern.app, docs.dev.ourlantern.app) serve the full internal corpus with no auth gate โ€” reported to operator privately; deliberately NOT filed as a public issue and NOT changed this session.

Round 2 โ€” operator QA follow-ups (all merged) โ€‹

  • Hero (#577) moved into Places Nearby: renders as the section's first card slot with a reserved sponsored-skeleton while the offer call is unsettled (1.5s cap post-venues, suppressed for areas recorded ad-less; stale hero sightings count as a prior). In-place swap = zero layout shift; late-fill fallback now inserts within the section.
  • VenuesMap story realism: default strip layout mirrors real venue density (tight clusters along a street corridor + stragglers, deterministic), layout/clusterSpread controls; story regrouped to Components/Dashboard/VenuesMap beside LivingFlame.
  • Venue Config tabbed layout: users-page idiom (PageHeader breadcrumb Config โ†’ Venue Config + routed PageTabs: Tiers | Import | Refresh | Overpass), single header Save (primary) + Reset (secondary), state survives tab switches, unsaved-changes badge. All editing semantics unchanged. Recommendation noted: later collapse the whole Config section into one tabbed page (separate PR).
  • Site v2 (#575) โ€” deck port โ†’ real website: sticky nav (scrollspy, mobile disclosure menu) + footer chrome as shared partials; motion layer (scroll reveals, living flame + embers, parallax ghost numbers, connector draw-in, beacon shimmer) fully behind prefers-reduced-motion; Vite MPA architecture + README add-a-page recipe; default-deny SEO lockout (every hostname noindexed via worker header + meta tag unless in an allowlist that ships empty). Build โ†’ two independent critics (design + quality) โ†’ polish pass applied all MUSTs (tablet/phone nav layout, beacon palette visibility, <main> landmark, footer contrast, no-JS disclaimer) + self-hosted Inter, strict CSP without unsafe-inline. Decisions: this content = the marketing site's home page (/); domain layout (operator, 2026-06-10): app keeps the apex (ourlantern.app / dev.ourlantern.app), website = light.ourlantern.app / light.dev.ourlantern.app โ€” deliberate inversion of marketing-at-apex since the app URL is what users share. Recorded in apps/site/README.md Domains section.

Landed โ€‹

  • All five topic branches merged into claude/bulk-2026-06-10 with zero conflicts; full npm run validate = 21/21 PASS (including the session's own new checks: CTA Classes, Site Validation).
  • Hero-ad investigation (mid-session addition): selection data verified healthy in Firestore (1 live hero offer), server selection timed at ~2.0s cold / ~0.9s warm vs the client's 250ms first-paint gate (OFFERS_FIRST_PAINT_TIMEOUT_MS, HomeView.jsx). Fix pending operator decision.

Post-merge manual steps (deliberately NOT done in this PR) โ€‹

  1. BigQuery cleanup (billing #576): delete old prorated GitHub rows + rebuild โ€” exact DELETEs, invocation, and validation queries in docs/worklog/2026-06-10_github-billing-receipt-day_NOTES.md. Also force-dispatch the ingest-github deploy (path-gated, so the GITHUB_COPILOT_PLAN=pro_plus env fix won't roll out on its own); May 28 invoice needs an ingest-invoices re-capture (NULL subscription breakdown).
  2. Cloudflare Pages project (site #575): one-time npm run site:pages:create:dev, then attach light.dev.ourlantern.app in the CF dashboard (domain layout decided โ€” see README Domains section; the worker is default-deny so no host constant needs editing for dev).
  3. Firestore rules deploy (config #263): rules ship with the PR and deploy via the normal GitHub Actions flow on merge; maintenance mode + venue config editing are inert until then. No seeding needed โ€” all readers tolerate missing systemConfig docs.
  4. Docs-exposure decision (reported privately, not filed): standalone VitePress deploys at docs.ourlantern.app / docs.dev.ourlantern.app serve the internal corpus ungated.

Pre-PR code review (2026-06-11) โ€‹

xhigh 9-angle review over origin/dev...HEAD: 51 raw candidates โ†’ 33 verified non-refuted โ†’ top 15 CONFIRMED fixed pre-PR:

  • services/api/venues/src/routes/import.js โ€” .default(true) on the include-tier flags made the config-driven importDefaults unreachable; flags are now .optional() so omitted flags fall through to systemConfig overrides.
  • apps/admin/src/admin/config/ConfigVenue.jsx โ€” reset-confirm copy claimed live clients pick defaults up in seconds; reworded to claim only what the venue API actually consumes.
  • apps/admin/src/admin/config/ConfigVenue.jsx โ€” Refresh Thresholds tab now carries a "Not yet wired" caveat (values save, nothing reads them yet).
  • apps/admin/src/admin/config/ConfigVenue.jsx โ€” Import Defaults lat/lng/radius/limit fields carry the same "Not yet wired" caveat (include-tier checkboxes ARE consumed).
  • apps/admin/src/admin/config/ConfigVenue.jsx โ€” Overpass timeout validation now requires >= 1000 ms (sub-second values rounded to [timeout:0] server-side).
  • services/api/venues/src/services/osm.service.js โ€” converted Overpass timeout clamped to >= 1s so a bad stored override can never emit [timeout:0].
  • services/api/venues/src/lib/dynamicConfig.js โ€” tier-filter tags from Firestore are sanitized (/^[a-z0-9_:-]+$/) before being joined into the Overpass QL regex; non-conforming tags dropped with a warning, emptied arrays fall back to compiled defaults.
  • apps/admin/src/admin/config/ConfigFeatureFlags.jsx โ€” maintenance toggle no longer publishes the unsaved textarea draft; it writes the persisted message, so only "Save message" publishes drafts.
  • apps/admin/src/admin/config/ConfigFeatureFlags.jsx โ€” stopped writing updatedBy/updatedByEmail into world-readable systemConfig/maintenance (leaked admin UID + email to logged-out visitors); display removed.
  • apps/admin/src/admin/config/ConfigVenue.jsx โ€” same leak one tier down: updatedBy/updatedByEmail removed from systemConfig/venues writes and from the header display.
  • firestore.rules โ€” maintenance-doc comment updated to match the new write shape (enabled/message/updatedAt only; never write admin identity).
  • services/dataform/definitions/norm/fact_cost_line_items.sqlx โ€” GitHub subscription double-count self-heal: incremental-only pre_operations DELETE of both regimes' subscription rows (receipt-day + synthetic) before the MERGE re-inserts the currently-owned set; a github_usage_raw backfill moves the ownership floor and the MERGE never deletes.
  • tooling/scripts/lint.cta-classes.js โ€” --update-baseline now prunes ignore entries pointing at deleted files (the warning recommended a command that could never prune them).
  • tooling/scripts/lint.story-coverage.js โ€” --update-baseline no longer silently deletes the titleExceptions key.
  • tooling/scripts/lint.story-coverage.js โ€” titleExceptions now excuse only the minSegments depth rule; an excepted story still fails on a wrong group prefix or missing title.

Standing follow-up (issue to be filed): refreshThresholds and importDefaults lat/lng/radius/limit overrides save to systemConfig/venues but are not yet consumed anywhere (only tierFilters, overpass.*, and importDefaults.include* reach the venue API). The admin UI now carries an honest caveat until they're wired.

Built with VitePress