fix(session): canonical end + idle purge orphan code sweep#159
Conversation
|
Warning Review limit reached
Next review available in: 18 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
WalkthroughThe PR centralizes session termination in a transactional helper, preserves terminal outcomes, expands result finalization for abandoned sessions, adds cursor-based orphan session-code cleanup, and integrates that cleanup and higher idle-session batch limits into the scheduled purge. ChangesSession lifecycle and cleanup
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ScheduledPurge
participant OrphanSweep
participant SessionCodes
participant Sessions
participant SystemConfig
ScheduledPurge->>OrphanSweep: invoke cleanup
OrphanSweep->>SystemConfig: read cursor
OrphanSweep->>SessionCodes: query ordered page
OrphanSweep->>Sessions: load linked sessions
Sessions-->>OrphanSweep: return session state
OrphanSweep->>SessionCodes: delete orphan codes
OrphanSweep->>SystemConfig: write next cursor
Possibly related PRs
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
f0b4fa4 to
ae5fc91
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@functions/session/endSessionCanonical.mjs`:
- Around line 22-26: Clear the session’s stale code field in the already-ended
branch of endSessionCanonical by transactionally updating the session document
before returning, while retaining deletion of sessionCodes/{code}. Update
functions/test/endSessionCanonical.test.mjs lines 68-85 to expect this cleanup
write and assert that its payload clears code.
In `@functions/session/orphanSessionCodes.mjs`:
- Around line 88-105: Update the orphan-session processing around withSessions
and the final deletion to batch Firestore operations: collect valid session
document references and load them with db.getAll, then associate the returned
snapshots with each code document, and replace the per-entry delete Promise.all
with a single WriteBatch or BulkWriter operation. Preserve
selectOrphanCodeDocs(withSessions, limit) behavior and delete only the selected
orphan code documents.
In `@functions/session/purgeStaleSessions.mjs`:
- Around line 4-5: Ensure purgeStaleSessions completes within its scheduled
execution window by either configuring an explicit timeoutSeconds on its
onSchedule definition or reducing IDLE_PURGE_BATCH_LIMIT from 200 to a safe
sequential-processing size; preserve the existing orphan-code sweep and
delete-purge behavior.
In `@functions/test/orphanSessionCodes.test.mjs`:
- Around line 111-173: Add a test for sweepOrphanSessionCodes where
cursorState.lastCodeId points beyond the final document and the startAfterId
query returns an empty page, verifying the sweep wraps to the first page and
processes orphan codes there. Use the existing buildSweepDb and cursorState
fixtures, and assert the deleted IDs and returned orphan count.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: db2aeadd-8e94-4d1b-af0e-3fa47bc18582
📒 Files selected for processing (10)
functions/handlers/triggers.mjsfunctions/session/autoEndIdleSessions.mjsfunctions/session/endSessionCanonical.mjsfunctions/session/finalizeGameResult.mjsfunctions/session/orphanSessionCodes.mjsfunctions/session/purgeStaleSessions.mjsfunctions/test/autoEndIdleSessions.test.mjsfunctions/test/endSessionCanonical.test.mjsfunctions/test/finalizeGameResult.test.mjsfunctions/test/orphanSessionCodes.test.mjs
| if (data.status === "ended" || typeof data.endedAt === "string") { | ||
| if (code) { | ||
| tx.delete(db.collection("sessionCodes").doc(code)); | ||
| } | ||
| return; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Clear the session’s stale code in the already-ended branch.
This branch deletes sessionCodes/{code} but returns without deleting sessions.code, breaking the canonical end-state invariant.
functions/session/endSessionCanonical.mjs#L22-L26: transactionally delete the sessioncodefield before returning.functions/test/endSessionCanonical.test.mjs#L68-L85: expect that cleanup update and assert its payload clearscode.
Proposed fix
if (data.status === "ended" || typeof data.endedAt === "string") {
+ tx.update(sessionRef, { code: FieldValue.delete() });
if (code) {
tx.delete(db.collection("sessionCodes").doc(code));
}
return;
}As per path instructions, review Firestore write safety and error handling.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (data.status === "ended" || typeof data.endedAt === "string") { | |
| if (code) { | |
| tx.delete(db.collection("sessionCodes").doc(code)); | |
| } | |
| return; | |
| if (data.status === "ended" || typeof data.endedAt === "string") { | |
| tx.update(sessionRef, { code: FieldValue.delete() }); | |
| if (code) { | |
| tx.delete(db.collection("sessionCodes").doc(code)); | |
| } | |
| return; |
📍 Affects 2 files
functions/session/endSessionCanonical.mjs#L22-L26(this comment)functions/test/endSessionCanonical.test.mjs#L68-L85
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@functions/session/endSessionCanonical.mjs` around lines 22 - 26, Clear the
session’s stale code field in the already-ended branch of endSessionCanonical by
transactionally updating the session document before returning, while retaining
deletion of sessionCodes/{code}. Update
functions/test/endSessionCanonical.test.mjs lines 68-85 to expect this cleanup
write and assert that its payload clears code.
Source: Path instructions
| const withSessions = await Promise.all( | ||
| codesSnap.docs.map(async (codeDoc) => { | ||
| const codeData = codeDoc.data() ?? {}; | ||
| const sessionId = | ||
| typeof codeData.sessionId === "string" ? codeData.sessionId : null; | ||
|
|
||
| let sessionData = null; | ||
| if (sessionId) { | ||
| const sessionSnap = await db.collection("sessions").doc(sessionId).get(); | ||
| sessionData = sessionSnap.exists ? sessionSnap.data() : null; | ||
| } | ||
|
|
||
| return { codeDoc, codeData, sessionData }; | ||
| }), | ||
| ); | ||
|
|
||
| const orphans = selectOrphanCodeDocs(withSessions, limit); | ||
| await Promise.all(orphans.map((entry) => entry.codeDoc.ref.delete())); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
Batch the per-doc session reads and code deletes.
Each page issues up to limit individual sessions.doc(id).get() calls and up to limit individual codeDoc.ref.delete() calls via Promise.all. Firestore Admin SDK supports batched reads (db.getAll(...)) and batched writes (WriteBatch/BulkWriter), which reduce round trips from O(limit) to O(1) and lower cost/latency, especially now that this runs on every scheduled purge with limit up to 100.
♻️ Example using batched reads
- const withSessions = await Promise.all(
- codesSnap.docs.map(async (codeDoc) => {
- const codeData = codeDoc.data() ?? {};
- const sessionId =
- typeof codeData.sessionId === "string" ? codeData.sessionId : null;
-
- let sessionData = null;
- if (sessionId) {
- const sessionSnap = await db.collection("sessions").doc(sessionId).get();
- sessionData = sessionSnap.exists ? sessionSnap.data() : null;
- }
-
- return { codeDoc, codeData, sessionData };
- }),
- );
+ const codeEntries = codesSnap.docs.map((codeDoc) => {
+ const codeData = codeDoc.data() ?? {};
+ const sessionId =
+ typeof codeData.sessionId === "string" ? codeData.sessionId : null;
+ return { codeDoc, codeData, sessionId };
+ });
+
+ const sessionRefs = codeEntries
+ .filter((e) => e.sessionId)
+ .map((e) => db.collection("sessions").doc(e.sessionId));
+ const sessionSnaps = sessionRefs.length ? await db.getAll(...sessionRefs) : [];
+ const sessionDataById = new Map(
+ sessionSnaps.filter((s) => s.exists).map((s) => [s.id, s.data()]),
+ );
+
+ const withSessions = codeEntries.map((e) => ({
+ codeDoc: e.codeDoc,
+ codeData: e.codeData,
+ sessionData: e.sessionId ? sessionDataById.get(e.sessionId) ?? null : null,
+ }));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@functions/session/orphanSessionCodes.mjs` around lines 88 - 105, Update the
orphan-session processing around withSessions and the final deletion to batch
Firestore operations: collect valid session document references and load them
with db.getAll, then associate the returned snapshots with each code document,
and replace the per-entry delete Promise.all with a single WriteBatch or
BulkWriter operation. Preserve selectOrphanCodeDocs(withSessions, limit)
behavior and delete only the selected orphan code documents.
| /** Idle auto-end throughput (indexed + legacy fill) — higher than delete purge. */ | ||
| export const IDLE_PURGE_BATCH_LIMIT = 200; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files 'functions/**' | sed -n '1,120p'
echo
echo "== outline purgeStaleSessions.mjs =="
ast-grep outline functions/session/purgeStaleSessions.mjs --view expanded || true
echo
echo "== outline triggers.mjs =="
ast-grep outline functions/handlers/triggers.mjs --view expanded || true
echo
echo "== relevant lines purgeStaleSessions.mjs =="
cat -n functions/session/purgeStaleSessions.mjs | sed -n '1,120p'
echo
echo "== relevant lines triggers.mjs =="
cat -n functions/handlers/triggers.mjs | sed -n '1,260p'
echo
echo "== firebase-functions version =="
cat package.json | jq -r '.dependencies["firebase-functions"], .devDependencies["firebase-functions"]' 2>/dev/null || trueRepository: gelbh/jetlag
Length of output: 14015
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== search for timeoutSeconds and schedules =="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' 'timeoutSeconds|onSchedule|schedule:' functions package.json
echo
echo "== search for scheduled function definitions =="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' 'pubsub\.schedule|onSchedule\(' functionsRepository: gelbh/jetlag
Length of output: 595
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== firebase-functions package files =="
fd -a 'firebase-functions' . | sed -n '1,50p'Repository: gelbh/jetlag
Length of output: 189
🌐 Web query:
firebase functions v2 scheduler onSchedule default timeoutSeconds when not set documentation
💡 Result:
For Firebase Functions (2nd generation) using the onSchedule handler, the default timeout is 60 seconds when timeoutSeconds is not explicitly set [1][2][3]. While the underlying Google Cloud Functions v2 infrastructure may sometimes have different defaults in other contexts (such as Cloud Run services), the Cloud Functions for Firebase documentation consistently identifies 60 seconds as the default timeout for these deployments [1]. You can override this default by providing a timeoutSeconds value within the options object when calling onSchedule [4][5]: javascript import { onSchedule } from "firebase-functions/v2/scheduler"; export const myScheduledFunction = onSchedule( { schedule: "every day 00:00", timeoutSeconds: 300, // Explicitly set to 5 minutes }, async (event) => { // Function logic here } ); For 2nd-gen scheduled functions, the maximum allowable timeout is 1800 seconds (30 minutes) [5][6][1].
Citations:
- 1: https://deepwiki.com/firebase/firebase-functions/4-function-configuration
- 2: https://docs.cloud.google.com/functions/docs/reference/rpc/google.cloud.functions.v2
- 3: https://cloud.google.com/php/docs/reference/cloud-functions/latest/V2.ServiceConfig
- 4: https://stackoverflow.com/questions/76503936/how-to-increase-memory-for-scheduled-2nd-generation-google-cloud-function-withou
- 5: https://www.rapidevelopers.com/firebase-tutorial/how-to-schedule-background-functions-in-firebase
- 6: https://firebase.google.com/docs/reference/functions/2nd-gen/node/firebase-functions.globaloptions
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== autoEndIdleSessions.mjs =="
cat -n functions/session/autoEndIdleSessions.mjs | sed -n '1,260p'
echo
echo "== orphanSessionCodes.mjs =="
cat -n functions/session/orphanSessionCodes.mjs | sed -n '1,260p'Repository: gelbh/jetlag
Length of output: 6068
Idle purge batch is too large for the scheduled function’s default 60s timeout.
IDLE_PURGE_BATCH_LIMIT jumps to 200, but purgeStaleSessions still processes each idle session sequentially and now also runs the orphan-code sweep and delete purge in the same invocation. Since onSchedule has no explicit timeoutSeconds, this function inherits the 60s default, so the run can be cut off mid-batch. Set an explicit timeout or reduce the batch size.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@functions/session/purgeStaleSessions.mjs` around lines 4 - 5, Ensure
purgeStaleSessions completes within its scheduled execution window by either
configuring an explicit timeoutSeconds on its onSchedule definition or reducing
IDLE_PURGE_BATCH_LIMIT from 200 to a safe sequential-processing size; preserve
the existing orphan-code sweep and delete-purge behavior.
| test("sweepOrphanSessionCodes deletes orphans only", async () => { | ||
| const deleted = []; | ||
| const cursorState = { lastCodeId: null, writes: [] }; | ||
| const sessions = { | ||
| live: { status: "active" }, | ||
| dead: { status: "ended" }, | ||
| }; | ||
| const codes = [ | ||
| makeCodeDoc("DEAD", "dead"), | ||
| makeCodeDoc("LIVE", "live"), | ||
| makeCodeDoc("ORPH", "missing"), | ||
| ]; | ||
| for (const code of codes) { | ||
| code.ref.delete = async () => deleted.push(code.id); | ||
| } | ||
|
|
||
| const db = buildSweepDb({ | ||
| codesByPage: { first: codes }, | ||
| sessions, | ||
| deleted, | ||
| cursorState, | ||
| }); | ||
|
|
||
| const orphansDeleted = await sweepOrphanSessionCodes(db, { limit: 100 }); | ||
| assert.equal(orphansDeleted, 2); | ||
| assert.deepEqual(deleted.sort(), ["DEAD", "ORPH"]); | ||
| assert.equal(cursorState.lastCodeId, null); | ||
| }); | ||
|
|
||
| test("sweepOrphanSessionCodes advances cursor past a full live first page", async () => { | ||
| const deleted = []; | ||
| const cursorState = { lastCodeId: null, writes: [] }; | ||
| const sessions = { | ||
| live1: { status: "active" }, | ||
| live2: { status: "active" }, | ||
| dead: { status: "ended" }, | ||
| }; | ||
|
|
||
| const firstPage = [makeCodeDoc("AAA", "live1"), makeCodeDoc("BBB", "live2")]; | ||
| const secondPage = [makeCodeDoc("CCC", "dead")]; | ||
| for (const code of [...firstPage, ...secondPage]) { | ||
| code.ref.delete = async () => deleted.push(code.id); | ||
| } | ||
|
|
||
| const db = buildSweepDb({ | ||
| codesByPage: { | ||
| first: firstPage, | ||
| after: { BBB: secondPage }, | ||
| }, | ||
| sessions, | ||
| deleted, | ||
| cursorState, | ||
| }); | ||
|
|
||
| const firstRun = await sweepOrphanSessionCodes(db, { limit: 2 }); | ||
| assert.equal(firstRun, 0); | ||
| assert.equal(deleted.length, 0); | ||
| assert.equal(cursorState.lastCodeId, "BBB"); | ||
|
|
||
| const secondRun = await sweepOrphanSessionCodes(db, { limit: 2 }); | ||
| assert.equal(secondRun, 1); | ||
| assert.deepEqual(deleted, ["CCC"]); | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Add coverage for the empty-page wraparound branch.
Tests cover the full-page and partial-page-at-end cases, but not the scenario where a page fetched with startAfterId returns empty (cursor past the real end), which should wrap back to the first page (orphanSessionCodes.mjs lines 84-86). Consider adding a test where the stored cursor points past the last document.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@functions/test/orphanSessionCodes.test.mjs` around lines 111 - 173, Add a
test for sweepOrphanSessionCodes where cursorState.lastCodeId points beyond the
final document and the startAfterId query returns an empty page, verifying the
sweep wraps to the first page and processes orphan codes there. Use the existing
buildSweepDb and cursorState fixtures, and assert the deleted IDs and returned
orphan count.
ae5fc91 to
a1c6a8b
Compare
* fix(session): end idle sessions with outcome and delete codes * fix(session): raise idle purge batch and sweep orphan codes * fix(session): finalize game results on abandoned outcome * fix(session): ship thermos remediation for canonical end race * fix(session): address coderabbit cli review for end/purge * fix(session): delete session codes inside end transaction
* fix(session): end idle sessions with outcome and delete codes * fix(session): raise idle purge batch and sweep orphan codes * fix(session): finalize game results on abandoned outcome * fix(session): ship thermos remediation for canonical end race * fix(session): address coderabbit cli review for end/purge * fix(session): delete session codes inside end transaction
…h 2 updates (#150) * fix(ci): update settings map tab visual baseline (#74) * test(e2e): update settings map tab snapshot for tilt control * chore(ship): add npm run ship:poll wrapper script * fix(sentry): ignore IDB deleted errors (#75) * fix(sentry): ignore IDB deleted errors Fixes JETLAG-1S * fix(sentry): bind IDB reset to failed connection * fix(sentry): guard recaptcha and view transitions (#76) * fix(sentry): guard recaptcha and transitions Fixes JETLAG-1P JETLAG-1T * test(services): cover app check and IDB error helpers * fix: address coderabbit review for sentry error helpers * revert(map): remove tilted view preference (#77) * revert(map): remove tilted view preference * fix: address coderabbit review for map tilt revert * fix(admin): resolve CSP violations and redirect-first sign-in (#78) * fix(worker): route all requests through worker for nonce CSP * fix(csp): allow CF challenge scripts and drop insights beacon * fix(auth): use redirect-first Google sign-in under strict CSP * chore(changeset): admin CSP and sign-in fixes * fix(auth): remove unused test import * fix(auth): ship thermos remediation for redirect-first OAuth * fix(auth): address coderabbit review for OAuth tests and changelog * feat(photo): add external send fallback when upload is down (#79) * feat(photo): add sent_externally answer kind * feat(chat): replace photo upload with external send fallback * test(photo): cover mark-sent flow in unit, rules, and e2e * chore(changeset): photo external send fallback * fix(photo): disable mark-sent buttons while answer saves * fix(photo): address coderabbit review for errors and legacy preview * fix(admin): live-refresh map annotations in monitor (#80) * fix(admin): gate session sync on auth and clear annotations on switch * test(session): unmount hooks between useSessionSync emulator cases * fix(session): address CodeRabbit on admin annotation sync * chore(release): 0.6.3 (#81) * feat(game): game over, stats, friends & leaderboards (#82) * docs(game): add game-over stats leaderboard design spec * feat(game): game over stats, friends, and leaderboards * test(session): cover resetSessionForRematch callable client * test(firestore): cover game result and profile serialization * test(firestore): cover game result subscription client * test(firestore): cover trail and location writes for coverage gate * test(e2e): update smoke specs for Play hub home IA * fix: address coderabbit review for game over stats PR * fix: harden game-over subscription and trail sync edge cases * fix: simplify auth-loading splash on stats routes * chore(release): version 0.7.0 (#83) * chore(release): version 0.7.0 * test(emulator): retry storage rules emulator cleanup * test(e2e): fix home safe-area spec for Play hub IA (#84) * fix(map): harden elimination worker and radar submit errors (#85) * fix(map): harden elimination worker and radar submit errors * fix: address coderabbit review for radar hardening * feat(map): policy-driven question-setting placement camera (#86) * feat(map): add policy-driven question-setting placement camera * fix: address coderabbit review for placement camera * fix: address remaining coderabbit review for placement camera * chore(release): version 0.7.1 (#87) * fix(auth): restore popup-first Google sign-in on premium (#88) * fix(auth): restore popup-first Google sign-in on premium * fix(auth): address coderabbit review for premium sign-in * chore(release): version 0.7.2 (#89) * feat(auth): unique username claim and social gates (#90) * feat(auth): claim unique username after sign-in * test(auth): align claimUsername tests with handler errors * fix(auth): ship thermos remediation for username identity * fix(auth): allow profile hook reset without lint error * fix(auth): tidy profile hook lint disables * fix(auth): address coderabbit review for username claim * fix(auth): require claimed username for profile identity * feat(friends): username search and friend requests (#91) * feat(friends): search usernames and manage friend requests * fix(friends): resolve mutual requests without orphan pending docs * fix(friends): satisfy lint for list load and error cause * fix(friends): rate-limit actions, cap pending lists, expand tests * test(friends): cover profileFriends client callable wrapper * fix(friends): loosen profileFriends test mock typing * feat(leaderboard): ranked list with board subscribe (#92) * feat(leaderboard): ranked list UI with board subscribe * fix(leaderboard): lint subscribe effect and harden entry parse * fix(leaderboard): tidy set-state-in-effect lint disable * test(leaderboard): cover entry parse and board subscribe * fix(leaderboard): drop unused onSnapshot error param in test * fix(leaderboard): contiguous ranks after invalid entry filter * fix(leaderboard): respect reduced motion on rank skeleton * fix(navigation): wrap route navigate in startViewTransition for WebKit (#93) * fix(navigation): wrap route navigate in startViewTransition for WebKit * fix(navigation): address coderabbit route transition findings * feat(map): smoother hybrid camera motion for question placement (#94) * feat(map): smoother hybrid camera motion for question placement * test(map): add computeFramedCenterZoom regression test * test(map): add computeFramedCenterZoom padding and clamp cases * refactor(map): extract isLargeCameraJump into domain layer * fix(map): reset focusPreferFly after one-shot reframe is consumed * chore(release): version 0.8.0 (#95) * fix(sentry): filter IDB closing and Safari Load failed (#96) * fix(sentry): filter IDB closing and Safari Load failed Fixes JETLAG-1X Fixes JETLAG-1V * fix(sentry): ship thermos remediation for client noise filters * test(sentry): cover Load failed trim in client noise predicate * fix(map): harden map export against html2canvas oklch (#97) * fix(map): harden map export against oklch html2canvas parse Fixes JETLAG-8 * fix(map): ship thermos remediation for map export oklch handling * fix(map): rewrite clone colors to rgb before html2canvas capture * fix(map): address coderabbit review for html2canvas color rewrite * chore(release): version 0.8.1 (#98) * chore(release): version 0.8.1 * docs(release): polish 0.8.1 notes for player-facing wording * fix(sentry): match Safari Load failed host suffixes (#99) * fix(sentry): match Safari Load failed host suffixes Fixes JETLAG-1Y * fix(sentry): tighten Load failed host-suffix matcher * test(e2e): add mobile layout and axe smoke checks (#101) * test(e2e): add mobile layout and axe smoke checks * fix(e2e): ship thermos remediation for layout axe smoke * fix(e2e): assert map more-tools control stays in viewport * ci: add stylelint for hand-written css (#100) * ci: add stylelint for hand-written css * fix(ci): ship thermos remediation for stylelint base * fix(ci): make pre-commit stylelint paths filename-safe * fix(ci): harden pre-commit path handling for bash * fix(ci): reject partial staging in pre-commit lint hooks * fix(ci): fail closed when inspecting unstaged pre-commit paths * test(e2e): harden visual smoke coverage for entry screens (#103) * ci: add lighthouse mobile budgets on pull requests (#102) * ci: add lighthouse mobile budgets on pull requests * fix(ci): ship thermos remediation for lighthouse config * fix(ci): use lighthouse target-size assertion id * fix(ci): disable checkout credentials for lighthouse job * fix(sync): ignore already-exists on trail point append (#104) * fix(sync): ignore already-exists on trail point append * fix(ci): re-exec pre-commit under bash for husky sh * feat(leaderboard): layout viewport quality gates (#105) * feat(leaderboard): scroll metric filters as chip strip * test(e2e): gate social layout overflow and visuals * ci: add nightly layout-deep playwright workflow * fix(e2e): ship thermos remediation for layout viewport gates * fix: address coderabbit review for layout viewport gates * fix(e2e): assert metric chip tap height not width * feat(changelog): nest closed versions under minor and major groups (#106) * feat(changelog): nest closed versions under minor and major groups * fix(changelog): avoid render-time reassignment for latest highlight * fix(changelog): sort entries and cover empty/invalid grouping edges * chore(release): version 0.8.2 (#107) * fix(admin): frame real play area on first monitor join (#108) * fix(admin): re-read session after join so map frames play area * fix(admin): extract join-preview helpers under 1k-line limit * fix(admin): use getDocFromServer after join membership write * test(admin): mock getDocFromServer on join re-read path * fix(admin): keep join-preview helpers leaflet-free for emulator * docs(changelog): clarify admin observe framing changeset copy * feat(thermometer): cancel orphan and stuck GPS walks (#109) * feat(thermometer): cancel orphan and stuck GPS walks * fix(thermometer): move identity-heal walk cancel out of annotations * fix(thermometer): avoid Date.now during MapTimerCluster render * fix(thermometer): satisfy react-compiler and unused walk id lint * fix(thermometer): address coderabbit cancel review * test(thermometer): type getDocs mock for identity-heal coverage * feat(leaderboard): board filters, lead pack, and sticky self footer (#110) * feat(leaderboard): board filters, lead pack, and sticky self footer * fix(leaderboard): fix board ready locator and stale self footer * fix(leaderboard): fix self-entry metric type and effect lint * fix(leaderboard): drop unused eslint-disable in self entry * fix(leaderboard): address coderabbit board chrome review * test(leaderboard): update filters visual snapshot for board chrome * test(leaderboard): update filters visual snapshot for board chrome * test(leaderboard): use 390x115 filters screenshot as baseline * feat(ui): desktop content column for entry screens (#111) * feat(ui): add desktop content column for entry screens * fix(ui): scope desktop entry CTA max-width to entry hosts * fix(ui): address coderabbit desktop entry review * fix(ui): restore Feedback desktop flex column parent * fix(ui): address coderabbit desktop entry spacing and tests * test(e2e): refresh tutorial sandbox visual baselines on main (#116) * feat(ui): desktop social column and friends master-detail (#112) * feat(ui): desktop social column and friends master-detail * fix(friends): allow selection clear effect for desktop master-detail * fix(leaderboard): constrain self footer to social column on desktop * feat(map): desktop ops shell with left tool rail (#113) * feat(map): desktop ops shell with left tool rail * fix(map): win rail dock CSS over fixed bottom dock * fix(map): restore walking-cancel props on desktop status rail * feat(map): desktop contextual rail + sheet host (#114) * feat(map): add contextual rail and sheet host for desktop ops * fix(map): gate rail Escape and tool shortcuts * fix(map): split rail panel hook for fast refresh and harden shortcuts * fix(map): drop unused ContextualRailTab import * docs(desktop): add desktop-ops-adapt changeset for wide-screen layout (#115) * chore(release): version 0.9.0 (#117) * chore(release): version 0.9.0 * chore(release): bump package-lock to 0.9.0 * perf(caching): edge Cache-Control + elevation/entitlements/join soft TTL (#118) * perf(worker): set cache-control for assets and geo * perf(geo): persist elevation samples in indexeddb * perf(billing): persist premium entitlements snapshot * perf(session): debounce and cache join code preview * fix(caching): harden entitlements uid check and elevation hydrate order * test(session): assert join preview cache expires at ttl boundary * perf(functions): shared Overpass L2 cache (KV + R2) (#119) * perf(functions): add shared overpass l2 cache via r2 and kv * fix(functions): wire overpass l2 secrets and sigv4 header order * fix(session): Background Sync honesty + Transitland 5m L1 (#120) * fix(session): honor background sync for offline annotations * fix(pwa): restore skip_waiting and sync retry without clients * chore(release): version 0.9.1 (#121) * fix(functions): make overpass l2 secrets optional for deploy (#122) * ci: modernize PR-gated suite and thin main CD (#123) * ci: modernize PR-gated suite and thin main CD * fix(ci): ship thermos remediation for paths-filter and deploy outputs * fix(ci): address coderabbit review for checkout and shell env * chore(ci): soften CodeRabbit App for CLI-primary gate (#143) * chore: purge .cursor/ path mentions from tracked files * chore: ignore agent-local IDE dir and harden pre-push * chore: drop agent-local dir from tracked gitignore * ci(dependabot): group updates and auto-merge safe npm bumps (#144) * ci(dependabot): group npm patch and minor by dependency type * ci(dependabot): auto-merge npm patch and minor when checks pass * docs: note Dependabot grouping and auto-merge policy * fix(ci): ship thermos remediation for Dependabot auto-merge deploy * fix: address coderabbit cli review for Dependabot config * fix: address coderabbit cli review for automerge head bind * chore(deps): bump the production-dependencies group Bumps the production-dependencies group in /functions with 2 updates: [@sentry/node](https://github.com/getsentry/sentry-javascript) and [firebase-functions](https://github.com/firebase/firebase-functions). Updates `@sentry/node` from 10.65.0 to 10.67.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](getsentry/sentry-javascript@10.65.0...10.67.0) Updates `firebase-functions` from 7.2.5 to 7.3.0 - [Release notes](https://github.com/firebase/firebase-functions/releases) - [Commits](firebase/firebase-functions@v7.2.5...v7.3.0) --- updated-dependencies: - dependency-name: "@sentry/node" dependency-version: 10.67.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: firebase-functions dependency-version: 7.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> * chore: purge IDE config path mentions from tracked files * chore: ignore local IDE dir and harden pre-push * chore: drop local IDE dir from tracked gitignore * ci(dependabot): group updates and auto-merge safe npm bumps (#144) * ci(dependabot): group npm patch and minor by dependency type * ci(dependabot): auto-merge npm patch and minor when checks pass * docs: note Dependabot grouping and auto-merge policy * fix(ci): ship thermos remediation for Dependabot auto-merge deploy * fix: address coderabbit cli review for Dependabot config * fix: address coderabbit cli review for automerge head bind * fix(overpass): normalize AbortError to timed-out 504 without Sentry (#155) * fix(overpass): normalize AbortError and retry HTTP 500 with failover logs * test(overpass): cover failover AbortError and retryable statuses * fix(overpass): ship thermos remediation for failover status classify * fix: address coderabbit cli review for overpass failover tests * fix: address coderabbit cli review for overpass body cancel * fix: address coderabbit cli review for async body cancel * fix(overpass): align QL timeout with 25s fetch abort (#157) * feat(admin): annotation filters and sorts on session list (#158) * feat(admin): add annotation count and last-annotation session filters * test(admin): add annotation fields to AdminPanel fixtures * fix(admin): coalesce session list refresh and fix initial loading (#160) * fix(session): canonical end + idle purge orphan code sweep (#159) * fix(session): end idle sessions with outcome and delete codes * fix(session): raise idle purge batch and sweep orphan codes * fix(session): finalize game results on abandoned outcome * fix(session): ship thermos remediation for canonical end race * fix(session): address coderabbit cli review for end/purge * fix(session): delete session codes inside end transaction * feat(session): host leave promote and endSession callables (#161) * feat(session): add leaveHostSession and endSession callables * feat(session): wire host leave promote and endSession callable * fix(session): end alone host leave inside promote transaction * fix(session): fall back to client end when callables unavailable * fix(session): reclaim orphan session codes on create (#162) * fix(session): reclaim orphan session codes on create * fix(session): allow orphan sessionCodes delete in rules * test(session): expect missing code after endRemoteSession delete * chore(release): 0.9.2 host leave transfer notes (#163) * feat(analytics): replace GA with PostHog EU (#166) * chore(deps): add posthog-js * feat(analytics): replace GA with PostHog facade * feat(analytics): PostHog env CSP and privacy copy * feat(analytics): wire session and premium events * chore: add changeset for PostHog analytics * fix(analytics): ship thermos remediation for event layering * fix(analytics): address coderabbit cli review for scrub and host * fix(analytics): disable PostHog GeoIP and external features * fix(test): use vi.stubEnv for PostHog prod init coverage * fix(map): restore iPhone PWA status bar safe-area inset (#169) * perf(a11y): public-shell floors and create weight (#167) * perf(firebase): lazy-init App Check on first token use * fix(a11y): allow pinch zoom in viewport meta * fix(a11y): raise contrast on action and dim tokens * fix(create): request geolocation only on user gesture * perf(create): defer map mount and preconnect basemaps * chore: add changeset for perf a11y shells * ci(lhci): raise public-shell score floors * fix(test): avoid parameter properties in App Check mock * fix(create): ship thermos remediation for map defer ownership * fix(firebase): arm App Check before Functions callables * fix(ci): address coderabbit cli review for lhci and App Check * fix(ci): mock App Check in functions lazy tests and ease LHCI floor * fix(ci): ease mobile LHCI floors after PostHog on main --------- * feat(analytics): PostHog consent banner (#168) * feat(analytics): add consent preference storage * feat(analytics): gate PostHog behind consent * feat(analytics): add production consent banner * chore: add changeset for analytics consent * fix(analytics): ship thermos remediation for consent capture gate * fix(analytics): seed e2e consent and capture pageview on accept * fix(analytics): seed consent denial in first-run e2e --------- * chore(release): 0.9.3 analytics, consent, and a11y notes (#170) * chore(ci): reject Cursor attribution trailers in commit-msg (#171) * chore(ci): harden commit-msg hook before commitlint --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Tomer Gelbhart <gelbharttomer@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ectory with 13 updates (#164) * fix(ci): update settings map tab visual baseline (#74) * test(e2e): update settings map tab snapshot for tilt control * chore(ship): add npm run ship:poll wrapper script * fix(sentry): ignore IDB deleted errors (#75) * fix(sentry): ignore IDB deleted errors Fixes JETLAG-1S * fix(sentry): bind IDB reset to failed connection * fix(sentry): guard recaptcha and view transitions (#76) * fix(sentry): guard recaptcha and transitions Fixes JETLAG-1P JETLAG-1T * test(services): cover app check and IDB error helpers * fix: address coderabbit review for sentry error helpers * revert(map): remove tilted view preference (#77) * revert(map): remove tilted view preference * fix: address coderabbit review for map tilt revert * fix(admin): resolve CSP violations and redirect-first sign-in (#78) * fix(worker): route all requests through worker for nonce CSP * fix(csp): allow CF challenge scripts and drop insights beacon * fix(auth): use redirect-first Google sign-in under strict CSP * chore(changeset): admin CSP and sign-in fixes * fix(auth): remove unused test import * fix(auth): ship thermos remediation for redirect-first OAuth * fix(auth): address coderabbit review for OAuth tests and changelog * feat(photo): add external send fallback when upload is down (#79) * feat(photo): add sent_externally answer kind * feat(chat): replace photo upload with external send fallback * test(photo): cover mark-sent flow in unit, rules, and e2e * chore(changeset): photo external send fallback * fix(photo): disable mark-sent buttons while answer saves * fix(photo): address coderabbit review for errors and legacy preview * fix(admin): live-refresh map annotations in monitor (#80) * fix(admin): gate session sync on auth and clear annotations on switch * test(session): unmount hooks between useSessionSync emulator cases * fix(session): address CodeRabbit on admin annotation sync * chore(release): 0.6.3 (#81) * feat(game): game over, stats, friends & leaderboards (#82) * docs(game): add game-over stats leaderboard design spec * feat(game): game over stats, friends, and leaderboards * test(session): cover resetSessionForRematch callable client * test(firestore): cover game result and profile serialization * test(firestore): cover game result subscription client * test(firestore): cover trail and location writes for coverage gate * test(e2e): update smoke specs for Play hub home IA * fix: address coderabbit review for game over stats PR * fix: harden game-over subscription and trail sync edge cases * fix: simplify auth-loading splash on stats routes * chore(release): version 0.7.0 (#83) * chore(release): version 0.7.0 * test(emulator): retry storage rules emulator cleanup * test(e2e): fix home safe-area spec for Play hub IA (#84) * fix(map): harden elimination worker and radar submit errors (#85) * fix(map): harden elimination worker and radar submit errors * fix: address coderabbit review for radar hardening * feat(map): policy-driven question-setting placement camera (#86) * feat(map): add policy-driven question-setting placement camera * fix: address coderabbit review for placement camera * fix: address remaining coderabbit review for placement camera * chore(release): version 0.7.1 (#87) * fix(auth): restore popup-first Google sign-in on premium (#88) * fix(auth): restore popup-first Google sign-in on premium * fix(auth): address coderabbit review for premium sign-in * chore(release): version 0.7.2 (#89) * feat(auth): unique username claim and social gates (#90) * feat(auth): claim unique username after sign-in * test(auth): align claimUsername tests with handler errors * fix(auth): ship thermos remediation for username identity * fix(auth): allow profile hook reset without lint error * fix(auth): tidy profile hook lint disables * fix(auth): address coderabbit review for username claim * fix(auth): require claimed username for profile identity * feat(friends): username search and friend requests (#91) * feat(friends): search usernames and manage friend requests * fix(friends): resolve mutual requests without orphan pending docs * fix(friends): satisfy lint for list load and error cause * fix(friends): rate-limit actions, cap pending lists, expand tests * test(friends): cover profileFriends client callable wrapper * fix(friends): loosen profileFriends test mock typing * feat(leaderboard): ranked list with board subscribe (#92) * feat(leaderboard): ranked list UI with board subscribe * fix(leaderboard): lint subscribe effect and harden entry parse * fix(leaderboard): tidy set-state-in-effect lint disable * test(leaderboard): cover entry parse and board subscribe * fix(leaderboard): drop unused onSnapshot error param in test * fix(leaderboard): contiguous ranks after invalid entry filter * fix(leaderboard): respect reduced motion on rank skeleton * fix(navigation): wrap route navigate in startViewTransition for WebKit (#93) * fix(navigation): wrap route navigate in startViewTransition for WebKit * fix(navigation): address coderabbit route transition findings * feat(map): smoother hybrid camera motion for question placement (#94) * feat(map): smoother hybrid camera motion for question placement * test(map): add computeFramedCenterZoom regression test * test(map): add computeFramedCenterZoom padding and clamp cases * refactor(map): extract isLargeCameraJump into domain layer * fix(map): reset focusPreferFly after one-shot reframe is consumed * chore(release): version 0.8.0 (#95) * fix(sentry): filter IDB closing and Safari Load failed (#96) * fix(sentry): filter IDB closing and Safari Load failed Fixes JETLAG-1X Fixes JETLAG-1V * fix(sentry): ship thermos remediation for client noise filters * test(sentry): cover Load failed trim in client noise predicate * fix(map): harden map export against html2canvas oklch (#97) * fix(map): harden map export against oklch html2canvas parse Fixes JETLAG-8 * fix(map): ship thermos remediation for map export oklch handling * fix(map): rewrite clone colors to rgb before html2canvas capture * fix(map): address coderabbit review for html2canvas color rewrite * chore(release): version 0.8.1 (#98) * chore(release): version 0.8.1 * docs(release): polish 0.8.1 notes for player-facing wording * fix(sentry): match Safari Load failed host suffixes (#99) * fix(sentry): match Safari Load failed host suffixes Fixes JETLAG-1Y * fix(sentry): tighten Load failed host-suffix matcher * test(e2e): add mobile layout and axe smoke checks (#101) * test(e2e): add mobile layout and axe smoke checks * fix(e2e): ship thermos remediation for layout axe smoke * fix(e2e): assert map more-tools control stays in viewport * ci: add stylelint for hand-written css (#100) * ci: add stylelint for hand-written css * fix(ci): ship thermos remediation for stylelint base * fix(ci): make pre-commit stylelint paths filename-safe * fix(ci): harden pre-commit path handling for bash * fix(ci): reject partial staging in pre-commit lint hooks * fix(ci): fail closed when inspecting unstaged pre-commit paths * test(e2e): harden visual smoke coverage for entry screens (#103) * ci: add lighthouse mobile budgets on pull requests (#102) * ci: add lighthouse mobile budgets on pull requests * fix(ci): ship thermos remediation for lighthouse config * fix(ci): use lighthouse target-size assertion id * fix(ci): disable checkout credentials for lighthouse job * fix(sync): ignore already-exists on trail point append (#104) * fix(sync): ignore already-exists on trail point append * fix(ci): re-exec pre-commit under bash for husky sh * feat(leaderboard): layout viewport quality gates (#105) * feat(leaderboard): scroll metric filters as chip strip * test(e2e): gate social layout overflow and visuals * ci: add nightly layout-deep playwright workflow * fix(e2e): ship thermos remediation for layout viewport gates * fix: address coderabbit review for layout viewport gates * fix(e2e): assert metric chip tap height not width * feat(changelog): nest closed versions under minor and major groups (#106) * feat(changelog): nest closed versions under minor and major groups * fix(changelog): avoid render-time reassignment for latest highlight * fix(changelog): sort entries and cover empty/invalid grouping edges * chore(release): version 0.8.2 (#107) * fix(admin): frame real play area on first monitor join (#108) * fix(admin): re-read session after join so map frames play area * fix(admin): extract join-preview helpers under 1k-line limit * fix(admin): use getDocFromServer after join membership write * test(admin): mock getDocFromServer on join re-read path * fix(admin): keep join-preview helpers leaflet-free for emulator * docs(changelog): clarify admin observe framing changeset copy * feat(thermometer): cancel orphan and stuck GPS walks (#109) * feat(thermometer): cancel orphan and stuck GPS walks * fix(thermometer): move identity-heal walk cancel out of annotations * fix(thermometer): avoid Date.now during MapTimerCluster render * fix(thermometer): satisfy react-compiler and unused walk id lint * fix(thermometer): address coderabbit cancel review * test(thermometer): type getDocs mock for identity-heal coverage * feat(leaderboard): board filters, lead pack, and sticky self footer (#110) * feat(leaderboard): board filters, lead pack, and sticky self footer * fix(leaderboard): fix board ready locator and stale self footer * fix(leaderboard): fix self-entry metric type and effect lint * fix(leaderboard): drop unused eslint-disable in self entry * fix(leaderboard): address coderabbit board chrome review * test(leaderboard): update filters visual snapshot for board chrome * test(leaderboard): update filters visual snapshot for board chrome * test(leaderboard): use 390x115 filters screenshot as baseline * feat(ui): desktop content column for entry screens (#111) * feat(ui): add desktop content column for entry screens * fix(ui): scope desktop entry CTA max-width to entry hosts * fix(ui): address coderabbit desktop entry review * fix(ui): restore Feedback desktop flex column parent * fix(ui): address coderabbit desktop entry spacing and tests * test(e2e): refresh tutorial sandbox visual baselines on main (#116) * feat(ui): desktop social column and friends master-detail (#112) * feat(ui): desktop social column and friends master-detail * fix(friends): allow selection clear effect for desktop master-detail * fix(leaderboard): constrain self footer to social column on desktop * feat(map): desktop ops shell with left tool rail (#113) * feat(map): desktop ops shell with left tool rail * fix(map): win rail dock CSS over fixed bottom dock * fix(map): restore walking-cancel props on desktop status rail * feat(map): desktop contextual rail + sheet host (#114) * feat(map): add contextual rail and sheet host for desktop ops * fix(map): gate rail Escape and tool shortcuts * fix(map): split rail panel hook for fast refresh and harden shortcuts * fix(map): drop unused ContextualRailTab import * docs(desktop): add desktop-ops-adapt changeset for wide-screen layout (#115) * chore(release): version 0.9.0 (#117) * chore(release): version 0.9.0 * chore(release): bump package-lock to 0.9.0 * perf(caching): edge Cache-Control + elevation/entitlements/join soft TTL (#118) * perf(worker): set cache-control for assets and geo * perf(geo): persist elevation samples in indexeddb * perf(billing): persist premium entitlements snapshot * perf(session): debounce and cache join code preview * fix(caching): harden entitlements uid check and elevation hydrate order * test(session): assert join preview cache expires at ttl boundary * perf(functions): shared Overpass L2 cache (KV + R2) (#119) * perf(functions): add shared overpass l2 cache via r2 and kv * fix(functions): wire overpass l2 secrets and sigv4 header order * fix(session): Background Sync honesty + Transitland 5m L1 (#120) * fix(session): honor background sync for offline annotations * fix(pwa): restore skip_waiting and sync retry without clients * chore(release): version 0.9.1 (#121) * fix(functions): make overpass l2 secrets optional for deploy (#122) * ci: modernize PR-gated suite and thin main CD (#123) * ci: modernize PR-gated suite and thin main CD * fix(ci): ship thermos remediation for paths-filter and deploy outputs * fix(ci): address coderabbit review for checkout and shell env * chore(ci): soften CodeRabbit App for CLI-primary gate (#143) * chore: purge IDE config path mentions from tracked files * chore: ignore local IDE dir and harden pre-push * chore: drop local IDE dir from tracked gitignore * ci(dependabot): group updates and auto-merge safe npm bumps (#144) * ci(dependabot): group npm patch and minor by dependency type * ci(dependabot): auto-merge npm patch and minor when checks pass * docs: note Dependabot grouping and auto-merge policy * fix(ci): ship thermos remediation for Dependabot auto-merge deploy * fix: address coderabbit cli review for Dependabot config * fix: address coderabbit cli review for automerge head bind * fix(overpass): normalize AbortError to timed-out 504 without Sentry (#155) * fix(overpass): normalize AbortError and retry HTTP 500 with failover logs * test(overpass): cover failover AbortError and retryable statuses * fix(overpass): ship thermos remediation for failover status classify * fix: address coderabbit cli review for overpass failover tests * fix: address coderabbit cli review for overpass body cancel * fix: address coderabbit cli review for async body cancel * fix(overpass): align QL timeout with 25s fetch abort (#157) * feat(admin): annotation filters and sorts on session list (#158) * feat(admin): add annotation count and last-annotation session filters * test(admin): add annotation fields to AdminPanel fixtures * fix(admin): coalesce session list refresh and fix initial loading (#160) * fix(session): canonical end + idle purge orphan code sweep (#159) * fix(session): end idle sessions with outcome and delete codes * fix(session): raise idle purge batch and sweep orphan codes * fix(session): finalize game results on abandoned outcome * fix(session): ship thermos remediation for canonical end race * fix(session): address coderabbit cli review for end/purge * fix(session): delete session codes inside end transaction * feat(session): host leave promote and endSession callables (#161) * feat(session): add leaveHostSession and endSession callables * feat(session): wire host leave promote and endSession callable * fix(session): end alone host leave inside promote transaction * fix(session): fall back to client end when callables unavailable * fix(session): reclaim orphan session codes on create (#162) * fix(session): reclaim orphan session codes on create * fix(session): allow orphan sessionCodes delete in rules * test(session): expect missing code after endRemoteSession delete * chore(release): 0.9.2 host leave transfer notes (#163) * feat(analytics): replace GA with PostHog EU (#166) * chore(deps): add posthog-js * feat(analytics): replace GA with PostHog facade * feat(analytics): PostHog env CSP and privacy copy * feat(analytics): wire session and premium events * chore: add changeset for PostHog analytics * fix(analytics): ship thermos remediation for event layering * fix(analytics): address coderabbit cli review for scrub and host * fix(analytics): disable PostHog GeoIP and external features * fix(test): use vi.stubEnv for PostHog prod init coverage * fix(map): restore iPhone PWA status bar safe-area inset (#169) * perf(a11y): public-shell floors and create weight (#167) * perf(firebase): lazy-init App Check on first token use * fix(a11y): allow pinch zoom in viewport meta * fix(a11y): raise contrast on action and dim tokens * fix(create): request geolocation only on user gesture * perf(create): defer map mount and preconnect basemaps * chore: add changeset for perf a11y shells * ci(lhci): raise public-shell score floors * fix(test): avoid parameter properties in App Check mock * fix(create): ship thermos remediation for map defer ownership * fix(firebase): arm App Check before Functions callables * fix(ci): address coderabbit cli review for lhci and App Check * fix(ci): mock App Check in functions lazy tests and ease LHCI floor * fix(ci): ease mobile LHCI floors after PostHog on main --------- * feat(analytics): PostHog consent banner (#168) * feat(analytics): add consent preference storage * feat(analytics): gate PostHog behind consent * feat(analytics): add production consent banner * chore: add changeset for analytics consent * fix(analytics): ship thermos remediation for consent capture gate * fix(analytics): seed e2e consent and capture pageview on accept * fix(analytics): seed consent denial in first-run e2e --------- * chore(release): 0.9.3 analytics, consent, and a11y notes (#170) * chore(deps-dev): bump the development-dependencies group across 1 directory with 13 updates Bumps the development-dependencies group with 12 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@changesets/cli](https://github.com/changesets/changesets) | `2.31.0` | `2.31.1` | | [@cloudflare/workers-types](https://github.com/cloudflare/workerd) | `5.20260712.1` | `5.20260724.1` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.61.1` | `1.62.0` | | [@sentry/vite-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) | `5.3.0` | `5.4.0` | | [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.2` | `4.3.3` | | [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.3` | `6.0.4` | | [eslint](https://github.com/eslint/eslint) | `10.3.0` | `10.8.0` | | [firebase-tools](https://github.com/firebase/firebase-tools) | `15.23.0` | `15.24.0` | | [stylelint](https://github.com/stylelint/stylelint) | `17.14.0` | `17.14.1` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.59.4` | `8.65.0` | | [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.0.16` | `8.1.5` | | [wrangler](https://github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler) | `4.110.0` | `4.114.0` | Updates `@changesets/cli` from 2.31.0 to 2.31.1 - [Release notes](https://github.com/changesets/changesets/releases) - [Commits](https://github.com/changesets/changesets/compare/@changesets/cli@2.31.0...@changesets/cli@2.31.1) Updates `@cloudflare/workers-types` from 5.20260712.1 to 5.20260724.1 - [Release notes](https://github.com/cloudflare/workerd/releases) - [Changelog](https://github.com/cloudflare/workerd/blob/main/RELEASE.md) - [Commits](https://github.com/cloudflare/workerd/commits) Updates `@playwright/test` from 1.61.1 to 1.62.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](microsoft/playwright@v1.61.1...v1.62.0) Updates `@sentry/vite-plugin` from 5.3.0 to 5.4.0 - [Release notes](https://github.com/getsentry/sentry-javascript-bundler-plugins/releases) - [Changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md) - [Commits](getsentry/sentry-javascript-bundler-plugins@5.3.0...5.4.0) Updates `@tailwindcss/vite` from 4.3.2 to 4.3.3 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-vite) Updates `@vitejs/plugin-react` from 6.0.3 to 6.0.4 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.4/packages/plugin-react) Updates `eslint` from 10.3.0 to 10.8.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v10.3.0...v10.8.0) Updates `firebase-tools` from 15.23.0 to 15.24.0 - [Release notes](https://github.com/firebase/firebase-tools/releases) - [Changelog](https://github.com/firebase/firebase-tools/blob/main/CHANGELOG.md) - [Commits](firebase/firebase-tools@v15.23.0...v15.24.0) Updates `stylelint` from 17.14.0 to 17.14.1 - [Release notes](https://github.com/stylelint/stylelint/releases) - [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md) - [Commits](stylelint/stylelint@17.14.0...17.14.1) Updates `tailwindcss` from 4.3.2 to 4.3.3 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/tailwindcss) Updates `typescript-eslint` from 8.59.4 to 8.65.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/typescript-eslint) Updates `vite` from 8.0.16 to 8.1.5 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v8.1.5/packages/vite) Updates `wrangler` from 4.110.0 to 4.114.0 - [Release notes](https://github.com/cloudflare/workers-sdk/releases) - [Commits](https://github.com/cloudflare/workers-sdk/commits/wrangler@4.114.0/packages/wrangler) --- updated-dependencies: - dependency-name: "@changesets/cli" dependency-version: 2.31.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: "@cloudflare/workers-types" dependency-version: 5.20260724.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: "@playwright/test" dependency-version: 1.62.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: "@sentry/vite-plugin" dependency-version: 5.4.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: "@tailwindcss/vite" dependency-version: 4.3.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: "@vitejs/plugin-react" dependency-version: 6.0.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: eslint dependency-version: 10.8.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: firebase-tools dependency-version: 15.24.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: stylelint dependency-version: 17.14.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: tailwindcss dependency-version: 4.3.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: typescript-eslint dependency-version: 8.65.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: vite dependency-version: 8.1.5 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: wrangler dependency-version: 4.114.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> * chore(ci): reject Cursor attribution trailers in commit-msg (#171) * chore(ci): harden commit-msg hook before commitlint --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Tomer Gelbhart <gelbharttomer@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
endSessionCanonicalwritesstatus/endedAt/gameOutcome, clears the code field, and deletessessionCodes/{code}in one transaction (preserves terminal outcomes likefound).abandoned; orphan sweep failures are isolated so stale-session purge still proceeds.Test plan
cd functions && node --test test/endSessionCanonical.test.mjs test/autoEndIdleSessions.test.mjs test/orphanSessionCodes.test.mjsSummary by CodeRabbit
Bug Fixes
Tests