Skip to content

chore: lint clean (0 errors) + make CI lint blocking - #175

Merged
snackman merged 10 commits into
masterfrom
chore/lint-clean
Oct 1, 2026
Merged

snackman merged 10 commits into
masterfrom
chore/lint-clean

Conversation

@snackman

@snackman snackman commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Summary

Gets npm run lint to 0 errors (was 50 errors / 63 warnings → now 0 errors / 3 warnings) and makes lint blocking in CI.

Commits are grouped by category so you can review them one at a time:

  1. eslint config: turned @next/next/no-img-element off, with a comment explaining why: flyers and avatars are hot-linked from arbitrary hosts. Also ignored the gitignored scripts/migrate-itinerary-ids.ts.
  2. Mechanical fixes: no-explicit-any (typed the RPC/event rows in api/ads/report), prefer-const, an empty interface, and an unescaped '.
  3. next/link: internal <a href="/"> / /api links now use <Link>.
  4. useSyncExternalStore for browser state: new hooks that read browser state without a hydration mismatch:
    • useLocalStorageState, used for view-mode persistence
    • useMediaQuery
    • useIsClient
    • useNow
    • the admin page's sessionStorage session restore also uses this approach
  5. Derive state during render instead of effect+setState, or use React's "adjust state when a prop changes" pattern.
  6. Refs, immutability and memoization errors, plus the targeted disables listed below.
  7. exhaustive-deps fixes for real bugs (see below).
  8. Unused vars removed.
  9. CI: removed continue-on-error from the Lint step.

Real bugs fixed along the way

  • ItineraryPanel: when you clicked a conference tab, the sync effect immediately reset it to the main view's active conference.
  • ShareCardModal: the download filename used a stale card title (missing cardTitle dep). The default title also kept the previous conference name (missing displayName/conferenceName deps).
  • useFriendRequests.sendRequest: a stale searchResults closure meant the optimistic outgoing request was skipped.

Targeted eslint-disables (all have a -- reason)

  • src/app/page.tsx: no-html-link-for-pages on /data. /data redirects to an external Google Sheet, so it needs a full-page navigation.
  • src/lib/sponsor-extraction.ts: no-explicit-any on the untyped Luma API JSON. This disable already existed but was on the wrong line; it has been moved.
  • src/hooks/useItinerary.ts: set-state-in-effect. The load is tied to the auth and Supabase sync state machine, so refactoring it is high-risk.
  • src/hooks/useAuthGatedActions.ts: set-state-in-effect. Finishes a pending star once auth completes.
  • src/app/itinerary/s/[code]/page.tsx: set-state-in-effect. Finishes a pending copy once sign-in completes.
  • src/hooks/useFriendCode.ts and src/hooks/useFriendRequests.ts: set-state-in-effect. These are false positives: the functions are async and only call setState after an await.

Warnings left as-is

  • ListView useVirtualizer incompatible-library notice.
  • Legacy description-mining fallback in sponsor-extraction.ts, which is explicitly kept.

Verification

  • npm run lint: 0 errors
  • npx tsc --noEmit: passes
  • npm test: 279 passed
  • npx next build: passes
  • Playwright smoke test on next dev for /, /kbw2026 (switched list/table/gallery/map views, then reloaded to check the saved view), /itinerary and /admin. Console errors matched the origin/master baseline exactly; both runs show the same two pre-existing errors:
    • the colgroup whitespace hydration warning
    • headless WebGL errors
  • Admin session restore from sessionStorage checked separately.

Manual QA suggestions

  • View mode: the saved view is restored on reload, and a first visit defaults to table on desktop and list on mobile.
  • Itinerary panel: switching conference tabs works now (behavior change, see the bug above).
  • Theme: the admin per-conference theme still applies, and a manual override wins.
  • Admin: a remembered session restores, and the Theme/Ad Inventory tabs load their per-conference values.
  • Share card: title edits show up in the downloaded filename.
  • Comments, POIs and RSVPs clear on sign-out.

🤖 Generated with Claude Code

snackman and others added 9 commits October 1, 2026 07:29
…local migration script

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…caped entity

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/data stays a plain <a> (targeted disable): it is a redirect to an external
Google Sheet and needs a full-page navigation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace mount-time effect+setState hydration of browser state with
hydration-safe useSyncExternalStore hooks (server snapshot = previous SSR
default, client value applied right after hydration):
- useLocalStorageState (new): view mode persistence (useViewMode)
- useMediaQuery (new): CommentSection mobile detection
- useIsClient (new): OnboardingWizard portal gate
- useNow (new): FriendMarker staleness (no Date.now() in render)
- admin page: sessionStorage session restore; loading flags derived

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…etState

- Compute derived values in render (itinerary conference default, theme,
  friend status, OG image cache hit, check-in toast visibility, loading flags)
- Use the 'adjust state when a prop changes' pattern for resets keyed on
  props/user (TableView date label, admin Theme/AdInventory tabs,
  SubmitEventModal conference, comments/POIs/RSVP resets)
- ItineraryPanel: fixes a bug where picking a conference tab was immediately
  reset to the main view's active conference by the sync effect
- AddressAutocomplete: drop unused isLoaded state

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bles

- GalleryView: precompute group start indices instead of mutating a render var
- POISearchBar: declare handleClose before the Escape effect and depend on it
- useDragReorder: update latest-callback ref in an effect, not during render
- useABTest: useCallback on primitive ids (compiler can preserve memoization)
- useFriendRequests: clear on sign-out during render; add missing
  searchResults dep to sendRequest (stale closure meant the optimistic
  outgoing request was skipped)
- Targeted set-state-in-effect disables (with reasons) for auth-driven
  deferred actions and async false positives

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…values

- ShareCardModal handleDownload: missing cardTitle dep made the downloaded
  filename use a stale (initial) title after the user edited it
- ShareCardModal reset-on-close: missing displayName/conferenceName deps left
  the default card title pointing at the previous conference
- EventCard impression observers: include compact

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also drops a stale eslint-disable directive in EventApp. Left as-is:
the legacy description-mining fallback in sponsor-extraction.ts
(explicitly kept) and ListView's useVirtualizer incompatible-library notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
sheeets Ready Ready Preview Oct 1, 2026 11:56am UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — cdba60c9 Deployed Oct 1, 2026 by vercel[bot]
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