Cleanup & Follow-ups โ 2026-05-14 โ
Status: In progress Scope: Operational housekeeping and small follow-ups accumulated during the secret-manager / onboarding-docs work (#505, #518, #521).
This is a working agenda for today's session. Items get pruned as they ship, not just appended. Real-scope work (#342, #343, etc.) remains in GitHub Issues; this doc is for the in-between.
1. Recently shipped (context) โ
| PR | Title | Merged |
|---|---|---|
| #505 | feat(env): GCP Secret Manager bootstrap + Cloud Functions secret-injection fix | 2026-05-11 |
| #518 | feat(env): auto-fetch VITE_FIREBASE_* via firebase apps:sdkconfig | 2026-05-13 |
| #521 | docs(onboarding): focus ENVIRONMENT_SETUP on new-dev day-one, archive provisioning | 2026-05-14 |
End state: new-dev onboarding is a 5-line procedure. Bootstrap pulls 25 values automatically; only 3 per-dev values are manual (GH_PAT, GITHUB_OWNER, optional ADC path).
2. In-flight on your side โ
Main checkout is on claude/billing-admin-bq-wiring with apps/admin/src/admin/billing/Billing.jsx open โ not my work, staying out of the way unless asked.
3. Small follow-ups (no GH issue worth creating) โ
Bundle these into one PR when picked up:
[x] OpenAI dead-code cleanup โ done in this PR
- Deleted
apps/web/src/__tests__/workflows/ai-issue-triage-mock.test.js - Deleted
apps/web/src/__tests__/workflows/ai-issue-triage-real.test.js - Removed
'api.openai.com'fromtooling/scripts/validate-headers.jsEXCLUDED_DOMAINS - Deleted dead duplicate
scripts/validate-headers.js - Updated
validate-triage-consistency.jstestFiles list - Removed
test:workflows:realnpm script (+ description) - Updated 2 docs (
HEADERS_VALIDATION_IMPLEMENTATION.md,HEADERS_VALIDATION.md) to referencetooling/scripts/validate-headers.js
- Deleted
[x] Pre-existing CSP gap โ done in this PR
- Added
'cloud.google.com'totooling/scripts/validate-headers.jsEXCLUDED_DOMAINS (matches thedash.cloudflare.com/mail.google.compattern for doc-only references) npm run validate:headersnow passes locally
- Added
[x] Worktree disk cleanup โ partial (done in this session)
- Removed
.claude/worktrees/304-audit+ branchcopilot/add-secret-manager-layer(PR #505 merged) - Removed
.claude/worktrees/340-version-sdk-to-ci+ branchchore/refactor-prefix-labels(PR #495 merged) - Left
.claude/worktrees/env-bootstrap-firebasein place โ this is the current working tree for #522 - Left
.claude/worktrees/privacy-audit-2026-05-11alone โ not mine, looks like a separate session's WIP
- Removed
4. Watch / decisions deferred โ
- Prod
DISCORD_WEBHOOK_URLโlantern-app-prodSecret Manager doesn't have this yet. Same fix pattern as dev:gcloud secrets create+ remove the orphan plain env var from thecreatefeaturerequestCloud Run service. Per operator preference, not asking about prod until full migration is initiated. - 8 newly-provisioned dev secrets mentioned rotation as a planned follow-up. No urgency.
- Pre-existing service-API CI gap โ
services/api/*vitest suites only run via localnpm run validate, not in CI. Surfaced during the validate audit; not on critical path yet.
5. Bundled into this PR โ
- [x] #342 admin stale-bundle bug โ done in this PR
- Diagnosis: admin isn't a PWA (no service worker). The "stale bundle" is a generic SPA freshness issue โ open tabs hold the loaded JS in memory and never re-fetch HTML on client-side navigation.
- Fix: poll
/version.jsonevery 60s + on visibility change. Surface a "new version available" banner when buildId changes. User clicks Reload (no auto-reload, persistent banner โ matches operator-tool ergonomics). - Files:
tooling/scripts/generate-version.mjs(added--out=<path>flag so admin can share the existing generator),apps/admin/package.jsonpredev/prebuild chains the generator,apps/admin/public/_headersaddsno-cachefor/version.json,apps/admin/src/lib/versionCheck.js(new),apps/admin/src/shared/components/NewVersionBanner.jsx(new), wired intoapps/admin/src/App.jsx,.gitignoreexcludesapps/admin/public/version.json. - Verified: admin build runs prebuild, generates the version stamp, vite bundle succeeds.
6. #343 surgical fix (de-scoped) โ
- [x] #343 surgical subset โ done in this PR
- Re-audited #343 against current dev: bootstrap reliably populates
.env.localso the "missing env var" risk is mostly gone in dev. Hardcoded Cloud Run dev-URL fallbacks (analyticsApi/docsApi/merchantsApi/assistantApi) still work.venueApiClient.jsalready throws loudly when env var missing. - One real silent landmine:
flash.jswas logging viadevLog.warn(dev-only), so a missingVITE_ANALYTICS_API_URLin prod = events silently discarded with zero console output. Fixed: introduced awarnAnalyticsDisabled()helper that logs once viaconsole.errorunconditionally. Touches the 2 call sites (flushBuffer,init). The 3rd site (_loadDynamicEvents) intentionally stays silent โ it's a one-time conditional load gate, not an event-dropping path. - Deferred: the full registry-based
getApiBaseUrl()helper. Its value was mostly prod-misconfig defense, and the operator is staying on dev. Pick up when prod migration starts.
- Re-audited #343 against current dev: bootstrap reliably populates
7. #351 openapi drift audit โ partial fix โ
[x] Service inventory check โ confirmed clean.
- 7 HTTP API services tracked everywhere:
packages/shared/services/index.js(API_SERVICESregistry),services/api/*dirs,openapi.jsonper service,deploy-dev.ymljobs. All match. No orphans, no missing entries. - Other services in
services/: 1 Discord bot, 1 Dataform project, 1 Firebase Functions package, 3 Cloud Run Jobs (cloudflare/github/invoices ingest). Correctly NOT inAPI_SERVICES(none are HTTP APIs).
- 7 HTTP API services tracked everywhere:
[x] Linter enhancements โ done in this PR
- Paren-aware
app.useparsing: the prior regex[^)]*couldn't span middleware function calls (e.g.requireRole('admin')โ the inner)broke the match), so mount prefixes were never captured when middleware was chained. New paren-balanced scan (findAppUseMounts) handles arbitrary middleware including nested parens, strings, brackets. normalizePathextended: Express splat (*name) now normalizes to{param}same as Express:nameand OpenAPI{name}. Resolves docs-api drift end-to-end.
- Paren-aware
[x] 3 services moved to enforced: docs, assistant, venues. Previously reported false-positive drift; now correctly report
OK (enforced).[ ] Out of scope (separate effort):
- analytics-api: 7 real undocumented endpoints in the impl (
bq-export-status,bq-schema,trigger/*,taxonomy,events/custom,scheduled/pseudonymize-user-ids). Needs actual spec authoring. - auth-api: uses inline
express.Router()dispatch in index.js (adminDispatch.post('/signin', ..., adminAuthRoutes)) which the linter doesn't follow. Either teach the linter that pattern or refactor auth's mount style. Documented in theENFORCED_SERVICEScomment.
- analytics-api: 7 real undocumented endpoints in the impl (
8. Ratchet + pre-push hook โ drift can't grow โ
[x] Ratchet baseline โ done in this PR
- New
tooling/scripts/openapi-sync-baseline.jsonfreezes the current drift onanalytics(7 entries) andauth(53 entries). Anything beyond this list fails the linter. - Linter behavior: enforced services fail on any drift; non-enforced services pass if drift โ baseline, fail if drift grows beyond baseline. Also nudges to tighten the baseline when drift shrinks.
npm run openapi-sync:update-baselineโ regenerates the file from current state. Use after intentional cleanup or after adding a route + spec entry in tandem.
- New
[x] Husky pre-push hook โ done in this PR
.husky/pre-pushrunsnpm run validate -- --scope openapi,format(~8s) before anygit push. Catches openapi drift + format issues locally before they hit the remote, no CI minutes burned.- Bypass mechanisms (documented in the hook itself):
git push --no-verifyโ one-shot, built-inLANTERN_SKIP_PREPUSH=1 git pushโ one-shot via env var (more visible)mv .husky/pre-push .husky/pre-push.disabledโ permanent disable
- Setup is automatic:
npm installrunspreparescript which installs husky's hook wiring. New devs and new worktrees both work out of the box.
9. #361 CSS tokens consolidation + web dedupe โ done in this PR โ
[x] Token drift detected + handled
- Removed from admin's
:root(values matched shared theme exactly):--success,--danger,--info,--warning,--accent-500,--accent-600,--accent-700,--accent-foreground,--radius,--radius-sm,--radius-md,--radius-lg,--radius-xl,--glass-gradient. - Kept as intentional dark-theme overrides with inline comments:
--accent-50: #ffedd5(theme.css ships#fff7edโ too light on admin's pure-black bg) and--accent-100: #fee2b0(theme.css ships#ffedd5). Same rationale: light accents need saturation to stay visible against pure black. - Re-pointed legacy aliases:
--radius-1..4now resolve tovar(--radius-sm/md/lg/xl)so they can't drift from the canonical scale. - Verified admin + web both build clean.
- Removed from admin's
[x] Token policy comment added at the top of
apps/admin/src/shared/styles/styles.cssper the issue's follow-up, documenting which tokens belong in shared theme vs. local.[x] Web dedupe (extension of #361): post-merge audit surfaced that the issue's claim "web was already deduped" was outdated โ web's styles.css still had 10 duplicate token declarations (
--success,--danger,--info,--radius,--glass-gradient,--accent-50,--accent-100,--accent-500,--accent-700,--accent-foreground) matchingpackages/ui/theme.cssexactly. All removed. Web now inherits them from shared theme.[x] Theme palette blocks preserved: web's
html[data-theme='warm'/'cool']andhtml[data-scheme='light'/'dark']blocks legitimately override shared tokens when a theme/scheme attr is set on<html>. Those stay โ they're intentional, not duplicates.[x] Canonical policy documented in
packages/ui/theme.css: the file header now spells out the full workflow โ what belongs shared vs. local, how to add new tokens, how to declare intentional overrides, etc. Both apps' styles.css have shorter pointer-comments referencing back to it.
10. Shortlist now empty โ no outstanding "easy picks" โ
(Bigger items deferred: tiered auth recovery, Node 24 migration, OfferCards extraction, analytics-api spec authoring, auth-api spec refactor. None are pull-it-off-the-shelf small.)
11. Conventions learned this cycle (now in memory) โ
These live in user-level Claude Code auto-memory at ~/.claude/projects/-home-mechelle-repos-lantern-app/memory/ (not checked into this repo). Names listed here for grepability:
feedback_open_prs_as_draftsโgh pr create --draftalways; user flips ready when she wants CI to run.feedback_bundle_related_work_into_fewer_prsโ drift-repair, label fixes, follow-up cleanups all fold into the same branch unless there's a real reviewability/risk reason to split.