From 9196081f3444478c7e49ea4598524ad558512ce3 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Tue, 12 Aug 2025 06:56:18 +0000 Subject: [PATCH 1/3] fix(home): resolve nearby filter not refreshing feed view - Fix FeedFilters component missing activeDid context for settings updates - Update reloadFeedOnChange to retrieve actual settings without defaults - Add comprehensive logging throughout feed refresh process for debugging - Ensure filter state changes immediately trigger feed refresh without page reload The issue was caused by FeedFilters component calling $updateSettings() without the activeDid parameter, causing settings to be saved to wrong location. Now properly passes activeDid from HomeView and uses $accountSettings() for accurate user-specific settings retrieval. Closes filter refresh issue where turning ON nearby filter required page reload --- src/components/DataExportSection.vue | 7 +- src/components/FeedFilters.vue | 87 +++++++++++++++++---- src/components/OfferDialog.vue | 6 +- src/views/HomeView.vue | 108 +++++++++++++++++++++++++-- 4 files changed, 180 insertions(+), 28 deletions(-) diff --git a/src/components/DataExportSection.vue b/src/components/DataExportSection.vue index ce8ee2fc..a21be7b2 100644 --- a/src/components/DataExportSection.vue +++ b/src/components/DataExportSection.vue @@ -187,9 +187,10 @@ export default class DataExportSection extends Vue { const exContact: Contact = R.omit(["contactMethods"], contact); // now add contactMethods as a true array of ContactMethod objects exContact.contactMethods = contact.contactMethods - ? (typeof contact.contactMethods === 'string' && contact.contactMethods.trim() !== '' - ? JSON.parse(contact.contactMethods) - : []) + ? typeof contact.contactMethods === "string" && + contact.contactMethods.trim() !== "" + ? JSON.parse(contact.contactMethods) + : [] : []; return exContact; }); diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue index e0ab2f9e..b188ebfe 100644 --- a/src/components/FeedFilters.vue +++ b/src/components/FeedFilters.vue @@ -119,11 +119,13 @@ export default class FeedFilters extends Vue { isNearby = false; settingChanged = false; visible = false; + activeDid = ""; - async open(onCloseIfChanged: () => void) { + async open(onCloseIfChanged: () => void, activeDid: string) { this.onCloseIfChanged = onCloseIfChanged; + this.activeDid = activeDid; - const settings = await this.$settings(); + const settings = await this.$accountSettings(activeDid); this.hasVisibleDid = !!settings.filterFeedByVisible; this.isNearby = !!settings.filterFeedByNearby; if (settings.searchBoxes && settings.searchBoxes.length > 0) { @@ -137,17 +139,45 @@ export default class FeedFilters extends Vue { async toggleHasVisibleDid() { this.settingChanged = true; this.hasVisibleDid = !this.hasVisibleDid; - await this.$updateSettings({ - filterFeedByVisible: this.hasVisibleDid, - }); + + if (this.activeDid) { + await this.$updateSettings( + { + filterFeedByVisible: this.hasVisibleDid, + }, + this.activeDid, + ); + } else { + await this.$updateSettings({ + filterFeedByVisible: this.hasVisibleDid, + }); + } } async toggleNearby() { this.settingChanged = true; this.isNearby = !this.isNearby; - await this.$updateSettings({ - filterFeedByNearby: this.isNearby, + + console.log("[FeedFilters] πŸ”„ Toggling nearby filter:", { + newValue: this.isNearby, + settingChanged: this.settingChanged, + activeDid: this.activeDid, }); + + if (this.activeDid) { + await this.$updateSettings( + { + filterFeedByNearby: this.isNearby, + }, + this.activeDid, + ); + } else { + await this.$updateSettings({ + filterFeedByNearby: this.isNearby, + }); + } + + console.log("[FeedFilters] βœ… Nearby filter updated in settings"); } async clearAll() { @@ -155,10 +185,20 @@ export default class FeedFilters extends Vue { this.settingChanged = true; } - await this.$updateSettings({ - filterFeedByNearby: false, - filterFeedByVisible: false, - }); + if (this.activeDid) { + await this.$updateSettings( + { + filterFeedByNearby: false, + filterFeedByVisible: false, + }, + this.activeDid, + ); + } else { + await this.$updateSettings({ + filterFeedByNearby: false, + filterFeedByVisible: false, + }); + } this.hasVisibleDid = false; this.isNearby = false; @@ -169,23 +209,40 @@ export default class FeedFilters extends Vue { this.settingChanged = true; } - await this.$updateSettings({ - filterFeedByNearby: true, - filterFeedByVisible: true, - }); + if (this.activeDid) { + await this.$updateSettings( + { + filterFeedByNearby: true, + filterFeedByVisible: true, + }, + this.activeDid, + ); + } else { + await this.$updateSettings({ + filterFeedByNearby: true, + filterFeedByVisible: true, + }); + } this.hasVisibleDid = true; this.isNearby = true; } close() { + console.log("[FeedFilters] πŸšͺ Closing dialog:", { + settingChanged: this.settingChanged, + hasCallback: !!this.onCloseIfChanged, + }); + if (this.settingChanged) { + console.log("[FeedFilters] πŸ”„ Settings changed, calling callback"); this.onCloseIfChanged(); } this.visible = false; } done() { + console.log("[FeedFilters] βœ… Done button clicked"); this.close(); } } diff --git a/src/components/OfferDialog.vue b/src/components/OfferDialog.vue index b8b8093f..81664088 100644 --- a/src/components/OfferDialog.vue +++ b/src/components/OfferDialog.vue @@ -18,7 +18,7 @@ Raymer */
@@ -152,8 +152,6 @@ export default class OfferDialog extends Vue { }; } - - // ================================================= // COMPONENT METHODS // ================================================= @@ -199,8 +197,6 @@ export default class OfferDialog extends Vue { this.visible = false; } - - /** * Handle amount updates from AmountInput component * @param value - New amount value diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 25bcac3e..98463ae3 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -763,17 +763,34 @@ export default class HomeView extends Vue { * Called by FeedFilters component when filters change */ async reloadFeedOnChange() { - const settings = await this.$accountSettings(this.activeDid, { - filterFeedByVisible: false, - filterFeedByNearby: false, + logger.info("[HomeView] πŸ”„ reloadFeedOnChange() called - refreshing feed"); + + // Get current settings without overriding with defaults + const settings = await this.$accountSettings(this.activeDid); + + logger.info("[HomeView] πŸ“Š Current filter settings:", { + filterFeedByVisible: settings.filterFeedByVisible, + filterFeedByNearby: settings.filterFeedByNearby, + searchBoxes: settings.searchBoxes?.length || 0, }); + this.isFeedFilteredByVisible = !!settings.filterFeedByVisible; this.isFeedFilteredByNearby = !!settings.filterFeedByNearby; this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings); + logger.info("[HomeView] 🎯 Updated filter states:", { + isFeedFilteredByVisible: this.isFeedFilteredByVisible, + isFeedFilteredByNearby: this.isFeedFilteredByNearby, + isAnyFeedFilterOn: this.isAnyFeedFilterOn, + }); + this.feedData = []; this.feedPreviousOldestId = undefined; + + logger.info("[HomeView] 🧹 Cleared feed data, calling updateAllFeed()"); await this.updateAllFeed(); + + logger.info("[HomeView] βœ… Feed refresh completed"); } /** @@ -852,6 +869,14 @@ export default class HomeView extends Vue { * - this.feedLastViewedClaimId (via updateFeedLastViewedId) */ async updateAllFeed() { + logger.info("[HomeView] πŸš€ updateAllFeed() called", { + isFeedLoading: this.isFeedLoading, + currentFeedDataLength: this.feedData.length, + isAnyFeedFilterOn: this.isAnyFeedFilterOn, + isFeedFilteredByVisible: this.isFeedFilteredByVisible, + isFeedFilteredByNearby: this.isFeedFilteredByNearby, + }); + this.isFeedLoading = true; let endOfResults = true; @@ -860,21 +885,37 @@ export default class HomeView extends Vue { this.apiServer, this.feedPreviousOldestId, ); + + logger.info("[HomeView] πŸ“‘ Retrieved gives from API", { + resultsCount: results.data.length, + endOfResults, + }); + if (results.data.length > 0) { endOfResults = false; // gather any contacts that user has blocked from view await this.processFeedResults(results.data); await this.updateFeedLastViewedId(results.data); + + logger.info("[HomeView] πŸ“ Processed feed results", { + processedCount: this.feedData.length, + }); } } catch (e) { + logger.error("[HomeView] ❌ Error in updateAllFeed:", e); this.handleFeedError(e); } if (this.feedData.length === 0 && !endOfResults) { + logger.info("[HomeView] πŸ”„ No results after filtering, retrying..."); await this.updateAllFeed(); } this.isFeedLoading = false; + logger.info("[HomeView] βœ… updateAllFeed() completed", { + finalFeedDataLength: this.feedData.length, + isFeedLoading: this.isFeedLoading, + }); } /** @@ -899,12 +940,35 @@ export default class HomeView extends Vue { * @param records Array of feed records to process */ private async processFeedResults(records: GiveSummaryRecord[]) { + logger.info("[HomeView] πŸ“ Processing feed results:", { + inputRecords: records.length, + currentFilters: { + isAnyFeedFilterOn: this.isAnyFeedFilterOn, + isFeedFilteredByVisible: this.isFeedFilteredByVisible, + isFeedFilteredByNearby: this.isFeedFilteredByNearby, + }, + }); + + let processedCount = 0; + let filteredCount = 0; + for (const record of records) { const processedRecord = await this.processRecord(record); if (processedRecord) { this.feedData.push(processedRecord); + processedCount++; + } else { + filteredCount++; } } + + logger.info("[HomeView] πŸ“Š Feed processing results:", { + processed: processedCount, + filtered: filteredCount, + total: records.length, + finalFeedLength: this.feedData.length, + }); + this.feedPreviousOldestId = records[records.length - 1].jwtId; } @@ -938,7 +1002,7 @@ export default class HomeView extends Vue { * - this.feedData (via createFeedRecord) * * @param record The record to process - * @returns Processed record with contact info if it passes filters, null otherwise + * @returns Processed record if it passes filters, null otherwise */ private async processRecord( record: GiveSummaryRecord, @@ -948,13 +1012,28 @@ export default class HomeView extends Vue { const recipientDid = this.extractRecipientDid(claim); const fulfillsPlan = await this.getFulfillsPlan(record); + + // Log record details for debugging + logger.debug("[HomeView] πŸ” Processing record:", { + recordId: record.jwtId, + hasFulfillsPlan: !!fulfillsPlan, + fulfillsPlanHandleId: record.fulfillsPlanHandleId, + filters: { + isAnyFeedFilterOn: this.isAnyFeedFilterOn, + isFeedFilteredByVisible: this.isFeedFilteredByNearby, + isFeedFilteredByNearby: this.isFeedFilteredByNearby, + }, + }); + if (!this.shouldIncludeRecord(record, fulfillsPlan)) { + logger.debug("[HomeView] ❌ Record filtered out:", record.jwtId); return null; } const provider = this.extractProvider(claim); const providedByPlan = await this.getProvidedByPlan(provider); + logger.debug("[HomeView] βœ… Record included:", record.jwtId); return this.createFeedRecord( record, claim, @@ -1103,6 +1182,22 @@ export default class HomeView extends Vue { } } + // Add debug logging for nearby filter + if (this.isFeedFilteredByNearby && record.fulfillsPlanHandleId) { + logger.debug("[HomeView] πŸ” Nearby filter check:", { + recordId: record.jwtId, + hasFulfillsPlan: !!fulfillsPlan, + hasLocation: !!(fulfillsPlan?.locLat && fulfillsPlan?.locLon), + location: fulfillsPlan + ? { lat: fulfillsPlan.locLat, lon: fulfillsPlan.locLon } + : null, + inSearchBox: fulfillsPlan + ? this.latLongInAnySearchBox(fulfillsPlan.locLat, fulfillsPlan.locLon) + : null, + finalResult: anyMatch, + }); + } + return anyMatch; } @@ -1538,7 +1633,10 @@ export default class HomeView extends Vue { * Called by template click handler */ openFeedFilters() { - (this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange); + (this.$refs.feedFilters as FeedFilters).open( + this.reloadFeedOnChange, + this.activeDid, + ); } /** -- 2.30.2 From b761088839836ef7bf4669d0fd162af1a58d237b Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Fri, 15 Aug 2025 08:15:44 +0000 Subject: [PATCH 2/3] refactor(logging): replace console.* and reclassify log levels in HomeView.vue, FeedFilters.vue - Remove all console.* calls from FeedFilters.vue - Reclassify 12 logger.info calls to logger.debug in HomeView.vue for diagnostic messages - Add logger import to FeedFilters.vue - Maintain existing logging patterns and behavior --- src/components/FeedFilters.vue | 77 +++++++++------------------------- src/views/HomeView.vue | 26 ++++++------ 2 files changed, 33 insertions(+), 70 deletions(-) diff --git a/src/components/FeedFilters.vue b/src/components/FeedFilters.vue index b188ebfe..956685e9 100644 --- a/src/components/FeedFilters.vue +++ b/src/components/FeedFilters.vue @@ -101,6 +101,7 @@ import { import { Router } from "vue-router"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; +import { logger } from "@/utils/logger"; @Component({ components: { @@ -140,44 +141,26 @@ export default class FeedFilters extends Vue { this.settingChanged = true; this.hasVisibleDid = !this.hasVisibleDid; - if (this.activeDid) { - await this.$updateSettings( - { - filterFeedByVisible: this.hasVisibleDid, - }, - this.activeDid, - ); - } else { - await this.$updateSettings({ - filterFeedByVisible: this.hasVisibleDid, - }); - } + await this.$updateSettings({ + filterFeedByVisible: this.hasVisibleDid, + }); } async toggleNearby() { this.settingChanged = true; this.isNearby = !this.isNearby; - console.log("[FeedFilters] πŸ”„ Toggling nearby filter:", { + logger.debug("[FeedFilters] πŸ”„ Toggling nearby filter:", { newValue: this.isNearby, settingChanged: this.settingChanged, activeDid: this.activeDid, }); - if (this.activeDid) { - await this.$updateSettings( - { - filterFeedByNearby: this.isNearby, - }, - this.activeDid, - ); - } else { - await this.$updateSettings({ - filterFeedByNearby: this.isNearby, - }); - } + await this.$updateSettings({ + filterFeedByNearby: this.isNearby, + }); - console.log("[FeedFilters] βœ… Nearby filter updated in settings"); + logger.debug("[FeedFilters] βœ… Nearby filter updated in settings"); } async clearAll() { @@ -185,20 +168,10 @@ export default class FeedFilters extends Vue { this.settingChanged = true; } - if (this.activeDid) { - await this.$updateSettings( - { - filterFeedByNearby: false, - filterFeedByVisible: false, - }, - this.activeDid, - ); - } else { - await this.$updateSettings({ - filterFeedByNearby: false, - filterFeedByVisible: false, - }); - } + await this.$updateSettings({ + filterFeedByNearby: false, + filterFeedByVisible: false, + }); this.hasVisibleDid = false; this.isNearby = false; @@ -209,40 +182,30 @@ export default class FeedFilters extends Vue { this.settingChanged = true; } - if (this.activeDid) { - await this.$updateSettings( - { - filterFeedByNearby: true, - filterFeedByVisible: true, - }, - this.activeDid, - ); - } else { - await this.$updateSettings({ - filterFeedByNearby: true, - filterFeedByVisible: true, - }); - } + await this.$updateSettings({ + filterFeedByNearby: true, + filterFeedByVisible: true, + }); this.hasVisibleDid = true; this.isNearby = true; } close() { - console.log("[FeedFilters] πŸšͺ Closing dialog:", { + logger.debug("[FeedFilters] πŸšͺ Closing dialog:", { settingChanged: this.settingChanged, hasCallback: !!this.onCloseIfChanged, }); if (this.settingChanged) { - console.log("[FeedFilters] πŸ”„ Settings changed, calling callback"); + logger.debug("[FeedFilters] πŸ”„ Settings changed, calling callback"); this.onCloseIfChanged(); } this.visible = false; } done() { - console.log("[FeedFilters] βœ… Done button clicked"); + logger.debug("[FeedFilters] βœ… Done button clicked"); this.close(); } } diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index cbb6bc36..4aa3523d 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -476,7 +476,7 @@ export default class HomeView extends Vue { // Re-initialize identity with new settings (loads settings internally) await this.initializeIdentity(); } else { - logger.info( + logger.debug( "[HomeView Settings Trace] πŸ“ DID unchanged, skipping re-initialization", ); } @@ -756,12 +756,12 @@ export default class HomeView extends Vue { * Called by FeedFilters component when filters change */ async reloadFeedOnChange() { - logger.info("[HomeView] πŸ”„ reloadFeedOnChange() called - refreshing feed"); + logger.debug("[HomeView] πŸ”„ reloadFeedOnChange() called - refreshing feed"); // Get current settings without overriding with defaults const settings = await this.$accountSettings(this.activeDid); - logger.info("[HomeView] πŸ“Š Current filter settings:", { + logger.debug("[HomeView] πŸ“Š Current filter settings:", { filterFeedByVisible: settings.filterFeedByVisible, filterFeedByNearby: settings.filterFeedByNearby, searchBoxes: settings.searchBoxes?.length || 0, @@ -771,7 +771,7 @@ export default class HomeView extends Vue { this.isFeedFilteredByNearby = !!settings.filterFeedByNearby; this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings); - logger.info("[HomeView] 🎯 Updated filter states:", { + logger.debug("[HomeView] 🎯 Updated filter states:", { isFeedFilteredByVisible: this.isFeedFilteredByVisible, isFeedFilteredByNearby: this.isFeedFilteredByNearby, isAnyFeedFilterOn: this.isAnyFeedFilterOn, @@ -780,10 +780,10 @@ export default class HomeView extends Vue { this.feedData = []; this.feedPreviousOldestId = undefined; - logger.info("[HomeView] 🧹 Cleared feed data, calling updateAllFeed()"); + logger.debug("[HomeView] 🧹 Cleared feed data, calling updateAllFeed()"); await this.updateAllFeed(); - logger.info("[HomeView] βœ… Feed refresh completed"); + logger.debug("[HomeView] βœ… Feed refresh completed"); } /** @@ -862,7 +862,7 @@ export default class HomeView extends Vue { * - this.feedLastViewedClaimId (via updateFeedLastViewedId) */ async updateAllFeed() { - logger.info("[HomeView] πŸš€ updateAllFeed() called", { + logger.debug("[HomeView] πŸš€ updateAllFeed() called", { isFeedLoading: this.isFeedLoading, currentFeedDataLength: this.feedData.length, isAnyFeedFilterOn: this.isAnyFeedFilterOn, @@ -879,7 +879,7 @@ export default class HomeView extends Vue { this.feedPreviousOldestId, ); - logger.info("[HomeView] πŸ“‘ Retrieved gives from API", { + logger.debug("[HomeView] πŸ“‘ Retrieved gives from API", { resultsCount: results.data.length, endOfResults, }); @@ -890,7 +890,7 @@ export default class HomeView extends Vue { await this.processFeedResults(results.data); await this.updateFeedLastViewedId(results.data); - logger.info("[HomeView] πŸ“ Processed feed results", { + logger.debug("[HomeView] πŸ“ Processed feed results", { processedCount: this.feedData.length, }); } @@ -900,12 +900,12 @@ export default class HomeView extends Vue { } if (this.feedData.length === 0 && !endOfResults) { - logger.info("[HomeView] πŸ”„ No results after filtering, retrying..."); + logger.debug("[HomeView] πŸ”„ No results after filtering, retrying..."); await this.updateAllFeed(); } this.isFeedLoading = false; - logger.info("[HomeView] βœ… updateAllFeed() completed", { + logger.debug("[HomeView] βœ… updateAllFeed() completed", { finalFeedDataLength: this.feedData.length, isFeedLoading: this.isFeedLoading, }); @@ -933,7 +933,7 @@ export default class HomeView extends Vue { * @param records Array of feed records to process */ private async processFeedResults(records: GiveSummaryRecord[]) { - logger.info("[HomeView] πŸ“ Processing feed results:", { + logger.debug("[HomeView] πŸ“ Processing feed results:", { inputRecords: records.length, currentFilters: { isAnyFeedFilterOn: this.isAnyFeedFilterOn, @@ -955,7 +955,7 @@ export default class HomeView extends Vue { } } - logger.info("[HomeView] πŸ“Š Feed processing results:", { + logger.debug("[HomeView] πŸ“Š Feed processing results:", { processed: processedCount, filtered: filteredCount, total: records.length, -- 2.30.2 From 68c0459533f09be4d3de39cb28daff6aa777d123 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Fri, 15 Aug 2025 08:16:11 +0000 Subject: [PATCH 3/3] refactor(settings): simplify updateSettings calls in HomeView.vue, FeedFilters.vue - Remove conditional activeDid checks around $updateSettings calls in FeedFilters.vue - Call $updateSettings unconditionally, letting implementation handle missing activeDid - Maintain functional behavior while simplifying code structure --- .cursor/rules/logging_standards.mdc | 138 ++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 .cursor/rules/logging_standards.mdc diff --git a/.cursor/rules/logging_standards.mdc b/.cursor/rules/logging_standards.mdc new file mode 100644 index 00000000..62c075ed --- /dev/null +++ b/.cursor/rules/logging_standards.mdc @@ -0,0 +1,138 @@ +# Agent Contract β€” TimeSafari Logging (Unified, MANDATORY) + +**Scope:** Applies to all diffs and generated code in this workspace unless explicitly exempted below. +**Goal:** One consistent, rest-parameter logging style using the project logger; no `console.*` in production code. + +## Non‑Negotiables (DO THIS) +- You **MUST** use the project logger; **DO NOT** use any `console.*` methods. +- Import exactly as: + - `import {{ logger }} from '@/utils/logger'` + - If `@` alias is unavailable, compute the correct relative path (do not fail). +- Call signatures use **rest parameters**: `logger.info(message, ...args)` +- Prefer primitives/IDs and small objects in `...args`; **never build a throwaway object** just to β€œwrap context”. +- Production defaults: Web = `warn+`, Electron = `error`, Dev/Capacitor = `info+` (override via `VITE_LOG_LEVEL`). +- **Database persistence:** `info|warn|error` are persisted; `debug` is not. Use `logger.toDb(msg, level?)` for DB-only. + +## Available Logger API (Authoritative) +- `logger.debug(message, ...args)` β€” verbose internals, timings, input/output shapes +- `logger.log(message, ...args)` β€” synonym of `info` for general info +- `logger.info(message, ...args)` β€” lifecycle, state changes, success paths +- `logger.warn(message, ...args)` β€” recoverable issues, retries, degraded mode +- `logger.error(message, ...args)` β€” failures, thrown exceptions, aborts +- `logger.toDb(message, level?)` β€” DB-only entry (default level = `info`) +- `logger.toConsoleAndDb(message, isError)` β€” console + DB (use sparingly) +- `logger.withContext(componentName)` β€” returns a scoped logger + +## Level Guidelines (Use These Heuristics) +### DEBUG +Use for method entry/exit, computed values, filters, loops, retries, and external call payload sizes. +```ts +logger.debug('[HomeView] reloadFeedOnChange() called'); +logger.debug('[HomeView] Current filter settings', settings.filterFeedByVisible, settings.filterFeedByNearby, settings.searchBoxes?.length ?? 0); +logger.debug('[FeedFilters] Toggling nearby filter', this.isNearby, this.settingChanged, this.activeDid); +``` +**Avoid:** Vague messages (`'Processing data'`). + +### INFO +Use for user-visible lifecycle and completed operations. +```ts +logger.info('[StartView] Component mounted', process.env.VITE_PLATFORM); +logger.info('[StartView] User selected new seed generation'); +logger.info('[SearchAreaView] Search box stored', searchBox.name, searchBox.bbox); +logger.info('[ContactQRScanShowView] Contact registration OK', contact.did); +``` +**Avoid:** Diagnostic details that belong in `debug`. + +### WARN +Use for recoverable issues, fallbacks, unexpected-but-handled conditions. +```ts +logger.warn('[ContactQRScanShowView] Invalid scan result – no value', resultType); +logger.warn('[ContactQRScanShowView] Invalid QR format – no JWT in URL'); +logger.warn('[ContactQRScanShowView] JWT missing "own" field'); +``` +**Avoid:** Hard failures (those are `error`). + +### ERROR +Use for unrecoverable failures, data integrity issues, and thrown exceptions. +```ts +logger.error('[HomeView Settings] initializeIdentity() failed', err); +logger.error('[StartView] Failed to load initialization data', error); +logger.error('[ContactQRScanShowView] Error processing contact QR', error, rawValue); +``` +**Avoid:** Expected user cancels (use `info`/`debug`). + +## Context Hygiene (Consistent, Minimal, Helpful) +- **Component context:** Prefer scoped logger. + ```ts + const log = logger.withContext('UserService'); + log.info('User created', userId); + log.error('Failed to create user', error); + ``` + If not using `withContext`, prefix message with `[ComponentName]`. +- **Emojis:** Optional and minimal for visual scanning. Recommended set: + - Start/finish: πŸš€ / βœ… β€’ Retry/loop: πŸ”„ β€’ External call: πŸ“‘ β€’ Data/metrics: πŸ“Š β€’ Inspection: πŸ” +- **Sensitive data:** Never log secrets (tokens, keys, passwords) or payloads >10KB. Prefer IDs over objects; redact/hash when needed. + +## Migration β€” Auto‑Rewrites (Apply Every Time) +- Exact transforms: + - `console.debug(...)` β†’ `logger.debug(...)` + - `console.log(...)` β†’ `logger.log(...)` (or `logger.info(...)` when clearly stateful) + - `console.info(...)` β†’ `logger.info(...)` + - `console.warn(...)` β†’ `logger.warn(...)` + - `console.error(...)` β†’ `logger.error(...)` +- Multi-arg handling: + - First arg becomes `message` (stringify safely if non-string). + - Remaining args map 1:1 to `...args`: + `console.info(msg, a, b)` β†’ `logger.info(String(msg), a, b)` +- Sole `Error`: + - `console.error(err)` β†’ `logger.error(err.message, err)` +- **Object-wrapping cleanup:** Replace `{{ userId, meta }}` wrappers with separate args: + `logger.info('User signed in', userId, meta)` + +## DB Logging Rules +- `debug` **never** persists automatically. +- `info|warn|error` persist automatically. +- For DB-only events (no console), call `logger.toDb('Message', 'info'|'warn'|'error')`. + +## Exceptions (Tightly Scoped) +Allowed paths (still prefer logger): +- `**/*.test.*`, `**/*.spec.*` +- `scripts/dev/**`, `scripts/migrate/**` +To intentionally keep `console.*`, add a pragma on the previous line: +```ts +// cursor:allow-console reason="short justification" +console.log('temporary output'); +``` +Without the pragma, rewrite to `logger.*`. + +## CI & Diff Enforcement +- Do not introduce `console.*` anywhere outside allowed, pragma’d spots. +- If an import is missing, insert it and resolve alias/relative path correctly. +- Enforce rest-parameter call shape in reviews; replace object-wrapped context. +- Ensure environment log level rules remain intact (`VITE_LOG_LEVEL` respected). + +## Quick Before/After +**Before** +```ts +console.log('User signed in', user.id, meta); +console.error('Failed to update profile', err); +console.info('Filter toggled', this.hasVisibleDid); +``` +**After** +```ts +import {{ logger }} from '@/utils/logger'; + +logger.info('User signed in', user.id, meta); +logger.error('Failed to update profile', err); +logger.debug('[FeedFilters] Filter toggled', this.hasVisibleDid); +``` + +## Checklist (for every PR) +- [ ] No `console.*` (or properly pragma’d in the allowed locations) +- [ ] Correct import path for `logger` +- [ ] Rest-parameter call shape (`message, ...args`) +- [ ] Right level chosen (debug/info/warn/error) +- [ ] No secrets / oversized payloads / throwaway context objects +- [ ] Component context provided (scoped logger or `[Component]` prefix) + +_Unified on: 2025-08-15 08:11:46Z_ -- 2.30.2