[pull] main from cwjoshuak:main#32
Merged
Merged
Conversation
…DaisyUI 5 Comprehensive dependency upgrade and migration. All deps to latest, with explicit handling for breaking changes: - Pin next@16.2.5; React 19, TypeScript 5, Tailwind 4 (CSS-first config via @tailwindcss/postcss + @plugin daisyui in globals.css), DaisyUI 5, Luxon 3. - Node pinned to 22 via .nvmrc (Next 16 requires >=20.9). - Drop core-js (modern browser targets handle Array.prototype.at). - Migrate i18n: next-i18next was rewritten in v16 and dropped Pages-Router helpers. Replace with plain react-i18next + bundled translations in util/i18n.ts. No more serverSideTranslations / appWithTranslation. - Replace @olerichter00/use-localstorage with in-house hook (util/useLocalStorage.ts); the upstream lib was unmaintained since 2021 and SSR-unsafe. - Switch @tabler/icons (v1, dead) to @tabler/icons-react. - Replace next/image with plain <img loading="lazy" decoding="async"> for the three icon usages. Eliminates Vercel image-transformation quota burn — the icons are 20-38px so the loss of Next's optimization pipeline is negligible. - Move .mp3 alert sounds to /public/sounds and reference as URL strings; drop the bespoke webpack asset rule (Turbopack-friendly). - Add Luxon 3 nullability guards (Interval.start/end are now DateTime | null). - React 19: useRef<T>(undefined), JSX namespace -> React.JSX. - Extract isWithinNotifyWindow predicate from pages/alarms.tsx into util/alarmTrigger.ts so the alarm-trigger logic is testable in isolation. - Restore DaisyUI 4 .label flex layout via globals.css compat block (v5 removed the default flex/justify-between/align-items styling). - Hydration fixes for clock cells (gate on mounted state). - Skip events whose IDs aren't in events.json instead of crashing (data.json drift symptom — will resolve with the data pipeline).
…logic Test infrastructure for the modernized stack so future changes have a regression net. - Vitest 4 + jsdom + RTL + jest-dom matchers for component/unit tests. - Playwright config (chromium) for e2e — specs to be filled in next. - GitHub Actions: typecheck + Vitest on PRs/pushes; separate e2e job that builds and runs Playwright with artifact upload. Initial coverage focuses on load-bearing pure logic — not UI chrome: - GameEvent.latest(): next-interval semantics across boundary cases. - createTableData: structural row-pairing invariants (1/2/3/4 events, invisible-cell padding for odd counts). Cell components mocked so the test is about the math, not the GameEventTableCell render tree. - generateTimestampStrings: diff-based color-state classes (past/imminent/ in-progress/upcoming). - isWithinNotifyWindow: window math, disabled-event short-circuit, no upcoming-event short-circuit, exact-boundary inclusion.
Phase A of the planned shadcn rewrite. Sets up the foundation; no JSX has been migrated yet (DaisyUI classes still litter the pages). - Drop daisyui; add shadcn deps (Radix primitives, cva, clsx, tailwind- merge, lucide-react, tw-animate-css). - styles/globals.css: replace daisyui plugin block with shadcn CSS vars for light + dark, @theme inline mapping, .dark class variant. - Add components.json + lib/utils.ts (cn helper). - Add @/* path alias in tsconfig + Vitest alias. - Add primitive components/ui/{button,dialog,checkbox,label,select, slider,dropdown-menu,card,separator,scroll-area}.tsx. - Drop components/index.js barrel — Turbopack didn't like .js re-exporting .tsx and threw an opaque "Element type is invalid" during render. Switch to direct default-exports throughout. - Update one Vitest mock path that pointed at the barrel. Build, typecheck, and 23/23 unit tests stay green.
Phase B. The four modals previously used a CSS-only checkbox-toggle pattern that DaisyUI provided (an <input type=\"checkbox\"> + <label htmlFor=...> trigger). Switch to controlled <Dialog> primitives so we can drop daisyui entirely. - AlarmConfigModal: rebuild with Dialog/Checkbox/Select/Slider/Label, two-column grid layout, sound preview on select-change and on slider commit. Volume slider now also updates Howler.volume in real time. - MerchantConfigModal: same treatment, simpler layout. - ChangeLogModal: Dialog + ScrollArea, structured Entry helper to dedupe the per-date list markup. - GitHubModal: Dialog with semantic links. - _app.tsx: hoist gh + changelog open state, pass setters to SideBar. - SideBar: ghost <Button> triggers in place of <label htmlFor>; PayPal link uses asChild + <a>. - alarms.tsx + merchants.tsx: own configOpen state; settings cog calls setConfigOpen(true). Modals still use some legacy color/btn classes inside (cleanup happens in the page-chrome and table-cell phases). Build/typecheck clean, 23/23 unit tests still pass. Also drop deprecated tsconfig baseUrl (paths-only resolution is the modern form in TS 5).
Phase C. Strips the DaisyUI navbar / select / btn / table-of-tables scaffolding from the page headers and rebuilds with shadcn Button, Select, plus plain Tailwind for layout. NavBar: - Single sticky-feel header with brand on the left, tabs (Alarms / Merchants) using bg-secondary for the active route, link cluster + lucide Languages icon + Select on the right. - Drop the `select select-bordered` locale switcher; use react-i18next changeLanguage instead of Next router locale (Next 16 dropped the i18n routing config). - Discord banner pulled out into its own simple bar above the header. Alarms page: - Replace the giant nested <table> chrome with grid grid-cols- [220px_1fr]: filter sidebar on the left, current/upcoming events on the right. - Date controls become outline icon buttons + a card-style "current selection" button that shows month/day and the relative-time annotation underneath. - Region select + dual-line current/server time block on the right; the active line is bold, the other is muted-foreground. - Section title row uses a real <h2> and a Select for notify-mins. - Filter sidebar entries are now real <button>s with active state via bg-secondary, count on the right in tabular-nums. - Inner currentEventsTable / fullEventsTable still wrap GameEvent cells in <table>; those are Phase D's job. Merchants page (header only): - Same treatment for region/server selects, settings cog, and the current/server time pair. Schedule reference + merchant table body still use the old layout (Phase D). Drop the `classnames` package import that was being used twice. Build / typecheck / 23 unit tests still green.
Phase D. The dense table-cell layout with a permanently-visible DaisyUI dropdown menu is gone; cells are now real cards with a real controlled menu. GameEventTableCell: - Card layout: icon, title with item-level prefix, countdown on the right, time chips below. - Disable menu moves to a per-card MoreVertical button via shadcn DropdownMenu. Items: Disable Once / 12h / Daily reset / Weekly reset, plus a destructive "Disable all future". Disabled events show a single Enable item instead. - Repeated grand-prix events keep their group/show toggle but as the first dropdown item with a separator, not a parallel control flow. - Extract disable handlers (disableOnce / disable12Hours / disableDailyReset / disableWeeklyReset / disableThreeWeeks / enable) so the JSX no longer has six near-identical 8-line inline closures. - Drop the ~120 lines of dead bottom-of-file commented JSX. MerchantTableCell: - Card layout with bg-card / border / spacing. Spawned merchants get an amber accent (border + tinted bg in light/dark). Out-of-progress merchants fade to opacity-70. - Replace classnames with cn; drop unused imports (Interval, merchantSchedules, the inactive mSchedules side-effect). - Pull RARITY_TEXT, rarityFromId, goodItemRarityClass, iconURL, openLocationImage out as module-scope helpers. - Item lists use a real ul with rarity-color text and 20x20 icons. - Potential-spawns sidebar becomes a right-side column; hidden when hide-potential-spawns or merchant.spawned. createTableData: - generateTimestampStrings color tokens move off DaisyUI semantics (text-success / text-slate-400/25) onto Tailwind/shadcn-friendly colors (text-emerald-600 / text-muted-foreground/40). - Padding cell loses the now-unneeded flex/space-x-4 chrome. Updated 2 unit tests for the new color-class names. 23/23 still pass; build + typecheck clean.
Phase E. Wraps up the shadcn rewrite.
Theme:
- Switch dark-mode plumbing from DaisyUI's data-theme="dark" + .dark
class double-toggle to a single classList.toggle('dark', ...). The
custom-variant in globals.css already keys off .dark, and shadcn
CSS vars propagate through the component tree.
- One line each in pages/alarms.tsx and pages/merchants.tsx.
Polish:
- Drop dead buttonClick DOM manipulation in alarms.tsx — it added
/removed btn-active classes that no longer exist; the active filter
state is already driven by selectedEventType React state.
- Rebuild merchants page schedules section: replace the giant
table-of-tables with a top action row (data-by SaintBot, last-updated
timestamp, vote button) above a 3-column grid of bg-card schedule
reference cards. Schedule 1/2/3 each show its rotation times in
one column and merchant→location in another, with proper tabular-nums.
- Suppress hydration warning on the last-updated timestamp.
- Replace last legacy bg-stone-400 / dark:bg-base-200 / btn-outline
classes with shadcn-friendly equivalents.
Addresses several issues from a fresh-data review: - Dark-mode flash on reload: add an inline pre-hydration script in _document.tsx that reads localStorage.darkMode (or prefers-color- scheme as a fallback) and applies the .dark class before React paints. - Dark-mode toggle from the settings modal didn't take effect until reload because each useLocalStorage instance was an independent state. Add a custom 'lostark:localStorage' window event that the setter dispatches and other instances on the same key listen to. Also subscribe to the native 'storage' event for cross-tab sync. - Light-mode page background was pure white, leaving the cards/buttons flat. Drop --background to oklch(0.975) with a subtle blue tint; cards stay pure white so they layer visibly. Equivalent tweaks for --secondary, --muted, --accent, --border with the same tint. - Chrome too constrained: bump max-w-7xl (1280px) → max-w-[1800px] on NavBar / alarms / merchants containers and trim the lg padding. - Date arrows + settings cog feel small at 36x36; bump to 44x44 (size-11) with size-5 icons inside. - Filter sidebar was 220px with no narrow-screen story. On <lg the filter list now lays out as wrapping flex-row pills; on lg it goes back to a 260px column. - Cells stacked side-by-side on every width. Now stack vertically on <md (single column for half/quarter-screen side-panel use) and pair side-by-side on >=md. - Modal rows weren't fully clickable: wrap each Row's content in the <Label> directly with hover background, so the entire pill is the hit target. - GameEventTableCell countdown showed -00:00:00 (yellow) for events with no upcoming occurrence today. Hide the countdown chip when latest() returns nothing. - Missing i18n key (e.g. id 3025 -> "Sevek Atun") was rendering as the bare id. Fall back to the metadata name from data/events.json when t() returns the key unchanged.
Three follow-ups from the latest review: - Light mode was visually flat — page bg too close to white made cards/dropdowns/modals indistinguishable from the page. Push the page bg darker (oklch 0.94 with a faint blue tint) and pull --card / --popover up to oklch 0.99 so they read as elevated surfaces. Tighten --secondary / --muted / --accent / --border values to give a clean three-level hierarchy: page → card → button surface. Dark mode unchanged. - DialogContent was using bg-background; that's the page color, so modals matched the page in light mode. Switch to bg-card so the modal sits visibly on top of the page. - Outline Button variant also used bg-background; switch to bg-card so outline buttons (date arrows, region/notify selects, schedule cards) stand out from the page. - Sidebar icon column: bump button size from 36 to 48 (size-12) and icons from default to size-6. Now matches the visual weight of the date/settings buttons in the page header. - The [?] hint next to "Group repeat events" was a bare span with a native title — easy to miss and not styled. Add the shadcn Tooltip primitive (components/ui/tooltip.tsx + @radix-ui/react-tooltip dep) and wrap the hint in a real tooltip trigger so hover/focus shows the explanation.
…ters - Reinstall without --legacy-peer-deps. The flag was a holdover from the React-17 era when @testing-library/react v12 / next-i18next 16 / Next 12 had peer-dep collisions. Everything's modern now and resolves cleanly. Drop it from the CI workflow too. - Filter sidebar buttons (Fever Time, Siege Content, ...) had a border but no fill, so they merged into the page bg. Add bg-card to the inactive state — keep bg-secondary for the selected filter so the active state still reads. - SelectTrigger primitive shipped with bg-transparent which had the same problem on the notify-mins / region / language / server selects. Switch to bg-card with bg-accent on hover; transition the background-color too. - Drop the leading "-" from event/merchant countdowns — the original intent was T-minus but it just reads as a negative number out of context.
- Event labels were rendering the i18n translation as primary, which picked up stale hand-curated strings from public/locales/en/events.json (e.g. "[Assail]Preigelli-[Siege]-[Raid]Liebertane" for id 9001 when the freshly scraped data/events.json says "[Siege] Preigelli VS. Liebertane"). Flip the precedence: prefer the metadata name from data/events.json. Fall back to i18n only when (a) metadata is missing, or (b) the user is on a non-English locale and a real translation exists. ZH overrides keep working via zh/events.json. - Filter buttons looked like flat list rows — easy to miss as interactive. Now they read as cards: shadow-sm at rest, slight hover lift (translate-y, hover:shadow), active:scale-[0.99] press feedback, and the selected filter fills with primary color (bg-primary text-primary-foreground border-primary shadow-md) so the active state is unambiguous. Count chips invert their muted color for the active fill so they stay legible.
- The Disable Once / 12h / All-future menu items were no-ops for events whose latest() returned undefined — i.e. anything whose last occurrence already happened today. The handlers anchored their durations to next.end and short-circuited on null. Switch the anchor to (next?.end ?? DateTime.now()) so the action always shifts the user-visible window forward. - Drop the DropdownMenuLabel I'd added with a non-existent i18n key (alarms:disable.until) — it was rendering as the raw key. The five Disable items are self-explanatory without a category header. - Filter sidebar buttons sat 8px above the first event card because the cards have p-2 padding and the aside had none. Add lg:pt-2 to the aside to align top edges. Mobile (single column) keeps the tighter top. - Merchants page now leads with a "Work in Progress" banner — amber badge + sentence explaining the upcoming crowdsourced-vote model. Pins the design intent for the next pass when we wire submissions.
- Event cards now show a "Disabled" pill next to the title when the
alarm is suppressed, plus a relative-time hint ("· in 12 hours")
derived from gameEvent.disabled. The opacity-50 dim was the only
prior signal and was easy to miss.
- Settings cog on /alarms and /merchants moves from variant=ghost
(transparent) to variant=secondary so it reads as a real button
surface — distinct from the page bg but visibly less prominent
than the date arrows / region select which stay on outline (bg-card).
- Drop the orphaned "Vote" link on /merchants. The WIP banner already
pitches the upcoming crowdsourced flow; pointing users at a Discord
vote link is a stale CTA from the old data flow.
pages/api/{hello,items,server-maintenance,regions/all,regions/[region]}.ts
were demo skeletons returning hardcoded fixtures. Nothing in the app
called them. Delete the whole pages/api/ directory.
common/{Item,Region,Server}.ts were only referenced by those routes;
remove them too. Drop the unused Item import in WanderingMerchant.ts.
Build / typecheck / 23 tests still green.
Radix's Dialog warns when DialogContent has neither a Description nor
aria-describedby={undefined}. Add an sr-only DialogDescription to each
of the four modals (AlarmConfigModal, MerchantConfigModal,
ChangeLogModal, GitHubModal) so the warning clears and screen readers
get a one-line summary of what each modal does.
The wss://ws.lostarktimer.app socket was opened on every /merchants mount, triggered repeated reconnect attempts in dev (where NEXT_PUBLIC_VERCEL_ENV is unset), and pushed empty data into the table. With the page parked behind the WIP banner there's no reason to keep the connection live. - Drop the io / Socket imports and the two effects that handled socket lifecycle and per-server join. - Drop merchantAPIData / dataLastRefreshed setters since nothing else writes them; merchantAPIData stays as an empty object so the filter/sort math still runs and the static merchant data continues to render. - Drop the socket.io-client dep entirely. When crowdsourcing lands we'll bring back a different transport.
Phase 1.3. With the UI close to deployment, lock the golden paths and the cross-component localStorage broadcast in tests so we don't regress on the way to prod. E2E (Playwright, 8 specs): - / redirects to /alarms - header chrome (date arrows, settings, region) renders - next/prev day buttons advance the date label - region select switches and persists across reload - filter buttons highlight (bg-primary) when selected - settings modal opens, dark mode toggles immediately (without reload), modal closes - /merchants WIP banner is visible - /merchants settings modal opens aria-label="Selected date" added to the date pill so the role-based selector is stable. Unit (Vitest, 9 specs for useLocalStorage): - hydrates from existing localStorage - returns initial value when empty - lazy-evaluates initialValue function once - persists on set - removes the key when set undefined - broadcasts to other consumers via the lostark:localStorage event (the regression that caused the dark-mode toggle to require a reload) - ignores broadcasts on unrelated keys - falls back gracefully on corrupt JSON - reflects cross-tab native storage events Total now 32 unit + 8 e2e passing.
The hardcoded region/server lists in data/regions.json and util/static.ts predated the 2024 server-merge consolidation: they listed five regions (US West / US East / EU Central / EU West / South America) with dozens of long-dead servers and used fixed UTC offsets that produced wrong times for half the year. Replace with the current three-region topology, scraped from Xeio/WanderLost (https://github.com/Xeio/WanderLost), the canonical source for lostmerchants.com: - NA East (America/New_York): Balthorr, Inanna, Luterra, Nineveh, Vairgrys - NA West (America/Los_Angeles): Brelshaza, Thaemine - EU Central (Europe/Warsaw): Arcturus, Elpon, Gienah, Ortuus, Ratik IANA timezone names mean Luxon now handles DST automatically. This fixes a long-standing bug where region times were 1 hour off during DST transitions. util/types/types.ts now derives RegionKey and ServerKey directly from data/regions.json so the type system tracks the data, not a parallel hand-curated union. Add resolveRegion() in util/static.ts as a localStorage-migration shim: existing users with stored regionTZName === 'US East' get mapped to 'NA East', 'US West' to 'NA West', 'EU West' and 'South America' fall back to 'NA East' (closest live region). Without this, returning users would see an empty region select. Update the e2e spec to assert on the new labels.
- Move data/{data,events,msgs}.json -> public/data/ and switch the
alarms page from module-level require() to a runtime fetch in a
useEffect. The three files together are ~80 KB raw and were
previously bundled into the alarms page chunk on every load. Now
they're served as static assets, cached independently of the JS
deploy, and don't bloat the initial JS download. Brief "Loading
events…" state covers the fetch window.
- buildEventMapping / buildGroupedEvents / buildEventTypeMapping
helpers replace the module-scope IIFEs so the same logic runs once
the fetch resolves.
- Add an empty-state card when fullEventsTable is empty and data has
loaded ("No events for May 12. The schedule is empty for this day…").
Previously the page rendered a blank gap; users couldn't tell
whether the data was missing or the day was just quiet.
- The category-filter variant of the empty-state mentions trying the
"All" filter so users don't get stuck thinking the page is broken.
…mpty days\"
Putting data/{data,events,msgs}.json in /public exposes them at
https://lostarktimer.app/data/data.json — defeats the goal of keeping
the cleaned schedule from being trivially scraped. Move them back to
/data and restore the module-level require() in pages/alarms.tsx.
The JSON ends up bundled into the alarms page chunk again, but isn't
served at a stable URL.
Keep the empty-state card from that commit — it doesn't depend on the
data location and is a real UX win (was "blank page" for days with
no rotations, now a "No events for May 12…" card).
The full data/data.json (~76 KB raw, ~15 KB gzipped) was bundled into
the alarms page chunk and shipped on every visit even though only one
day's worth of events is ever rendered at a time.
Pre-build pass:
- scripts/build-day-data.mjs splits data/data.json into
data/days/<month>-<day>.json files, each ~5–8 KB. Hooked into npm
predev / prebuild so the chunks regenerate any time data.json
changes.
- /data/days/ is gitignored — the files are always derivable.
Page change:
- pages/alarms.tsx loadDayData() does a templated dynamic import
(import(\`../data/days/\${month}-\${day}.json\`)) so webpack emits one
fingerprinted chunk per file at build time. The chunks aren't at a
stable URL, so they're not trivially scrapable.
- The events-processing useEffect now operates on a per-day slice
({ type: <ilvl-or-list> }) instead of walking the full month/day
tree. Source still uses two shapes at the bottom (dict for most
types, list of single-key dicts for type 2) — handled with a small
recursive visitor.
- Skip events whose source produced an invalid DateTime (NaN hour,
malformed time string), and roll end forward a day when the source
emits a window that crosses midnight (e.g. "23:00-01:00") so the
resulting Interval is valid.
E2E + unit suites still pass.
The legacy +10-minute adjustment for sailing-style and certain island
event types (TYPE_IDS_INCREASE_BY_10_MIN / EVENT_IDS_INCREASE_BY_10_MIN)
was bumping start forward but reconstructing end from the source's
end-hour string. That worked when the source emitted single times
("2:00") because the end-hour fallback used start.hour after the bump.
normalize.py now canonicalises single times to degenerate ranges
("2:00" -> "2:00-2:00"), so endHr is non-empty and we no longer fall
back. start became 02:10, end stayed 02:00 — end before start. The
just-added spanning-midnight roll then bumped end forward a day,
producing 23h50m "windows" for every event, which is why the alarms
page was suddenly listing dozens of all-day events.
Match the original lostarktimer-utils/scraper.js behavior: apply the
+10 to both start and end so degenerate single-time ranges stay as
points and real ranges shift consistently. The spanning-midnight
roll stays as a defensive guard for legitimate cross-midnight windows.
Match the alarms page's existing convention — the imminent-events table is wrapped in a ring-2 ring-amber-500/40 to make the active group visually pop. Apply the same ring per-card on merchants while inProgress(serverTime) is true (spawn window open). The pre-existing amber bg/border for spawned merchants is preserved underneath; the ring is the cross-cutting 'happening now' signal that works whether or not a sighting has been reported.
Capture the load-bearing context that's currently only in the recent session: stack, conventions, runtime data flow, why-we-chose-X, and how to deploy / activate the scraper pipeline. Keeps future work oriented without having to re-derive everything from git history. - CLAUDE.md: agent-oriented project guide. Stack, layout, critical conventions (no public/data, IANA tz, +10 applied to both endpoints, useLocalStorage broadcast, class="dark" theme, controlled modals, required DialogDescription). Commands. Pointers to the scraper repo and WanderLost. - docs/ARCHITECTURE.md: runtime data flow for /alarms with build-time per-day chunking, theme system, i18n, modal pattern, region/server topology table. - docs/DECISIONS.md: log of non-obvious calls — modernize over rewrite, bundled vs public data, per-day chunks, react-i18next vs next-i18next, prefer metadata names over stale i18n, +10 applied to both endpoints, region migration shim, controlled modals, class="dark" only, merchants parked, dropped --legacy-peer-deps, no <Image> for tiny icons, scraper atomic-write resiliency, spanning-midnight roll. - docs/DEPLOY.md: pre-flight, Vercel preview, scraper-pipeline activation (push + LOSTARK_ALARMS_TOKEN secret + manual run), watching prod, rollback. - docs/DATA_PIPELINE.md: scrape→normalize→PR flow diagram, source description, why-private-scraper-repo, what normalize does and doesn't do, failure modes, how to add a new normalization rule.
What still has to happen, what's deliberately not done, and what's parked. Surface the operational steps (push scraper, set LOSTARK_ALARMS_TOKEN, deploy, optional Mongo/Supabase teardown) in one file so the next session doesn't re-derive them from chat history. Reference it as the first thing to read from CLAUDE.md.
should_deploy.sh was a 2023 Vercel "Ignored Build Step" override that's no longer in use. sound.d.ts declared *.mp3 modules but no code does import-from-mp3 — alarm sounds load via URL strings from public/sounds/. Add /test-results/ + /playwright-report/ + .env.sentry-build-plugin to .gitignore.
@sentry/nextjs for the new observability layer. jsdom was on "latest" which had drifted to 29.x; pinning to ^26 prevents future jsdom releases from re-breaking the Vitest suite.
Node 22+ ships an experimental localStorage on globalThis that shadows jsdom's. Without --localstorage-file <path>, it's a degraded plain-Object Storage with no clear/setItem methods. Vitest's populateGlobal doesn't include localStorage in its hardcoded keys list, so when Node has a builtin, jsdom's never reaches the test. Force jsdom's localStorage and sessionStorage onto globalThis after env init. Works on any Node version regardless of the builtin. See docs/decisions/0018-vitest-jsdom-storage-shadow.md.
All three init configs use enabled: VERCEL_ENV === 'production' so the SDK is loaded but inert outside Vercel prod. DSN comes from NEXT_PUBLIC_SENTRY_DSN; org/project come from SENTRY_ORG/SENTRY_PROJECT env vars (build plugin reads them by default). Trims vs wizard defaults: tracesSampleRate 1 -> 0.1, removed enableLogs (would forward every console.* to Sentry), removed automaticVercelMonitors (no Vercel crons), enabled tunnelRoute '/monitoring' so ad-blockers don't drop client error reports. pages/_error.tsx is the Pages-Router boundary that forwards exceptions via Sentry.captureUnderscoreErrorException. See docs/decisions/0017-analytics-and-observability.md.
…nents Replaces the inline <Script> block in _app.tsx with two extracted components in components/analytics/. UmamiScript reads the script URL + website ID from NEXT_PUBLIC_UMAMI_SRC / NEXT_PUBLIC_UMAMI_WEBSITE_ID and no-ops if either is unset. CloudflareAnalytics is gated on NEXT_PUBLIC_VERCEL_ENV === 'production'. GA was duplicative — Cloudflare Insights already covers traffic, Umami covers product events. One fewer privacy footprint.
Skips Firebase + Mongo from the historical .env.local — both unused.
Last entry was 4/7/2022. Add a one-shot summary of the modernization (region migration, redesign, dark-mode polish, faster alarm-page loads, merchants page parked). Replace the stale "Planned" list with the only thing actually planned: the Wandering Merchants rebuild.
Split the monolithic DECISIONS.md into one file per choice under docs/decisions/, with a README index. Adds two new ADRs (0017 analytics and observability stack, 0018 vitest jsdom Storage shadow workaround). Sanitize for the public repo: drop the upstream source domain, the private scraper repo path, the personal Umami host, and the "data-as-value-add" / "not trivially scrapable" framing that effectively mapped attack surface. Reframe ADRs 0003/0004 as architecture, not security. Delete docs/DATA_PIPELINE.md (moved out of this repo), docs/DECISIONS.md (replaced by docs/decisions/), and docs/NEXT_SESSION.md (status snapshot that's now expired). Update CLAUDE.md "See also" pointers and DEPLOY.md to focus on the env-var setup that matters here.
components/analytics/track.ts defines a typed EventMap + track() that no-ops when window.umami isn't loaded. Properties are chosen for low-cardinality grouping; volume is bucketed at the call site so raw floats don't pollute Umami. Wired: - alarms.tsx: region_change, date_change (prev/next/today), notify_minutes_change, alarm_triggered (per event in the notify window when the trigger condition fires) - AlarmConfigModal.tsx: dark_mode, view_24hr, localized_time, hide_disabled, move_disabled_bottom, group_repeats (source='settings'), desktop_notifications (with permission state), alert_sound, volume_change (on commit), reset_disabled_alarms - GameEventTableCell.tsx: event_disable (5 duration variants), event_enable, group_repeats (source='cell') - SideBar.tsx: changelog_open, github_modal_open Updated ADR 0017 with the full event taxonomy table.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )