fix(home): resolve nearby filter not refreshing feed view #162
Open
anomalist
wants to merge 4 commits from nearby-filter
into master
3 changed files with 264 additions and 8 deletions
@ -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_ |
@ -476,7 +476,7 @@ export default class HomeView extends Vue { |
|||||
// Re-initialize identity with new settings (loads settings internally) |
// Re-initialize identity with new settings (loads settings internally) |
||||
await this.initializeIdentity(); |
await this.initializeIdentity(); |
||||
} else { |
} else { |
||||
logger.info( |
logger.debug( |
||||
"[HomeView Settings Trace] 📍 DID unchanged, skipping re-initialization", |
"[HomeView Settings Trace] 📍 DID unchanged, skipping re-initialization", |
||||
); |
); |
||||
} |
} |
||||
@ -756,17 +756,34 @@ export default class HomeView extends Vue { |
|||||
* Called by FeedFilters component when filters change |
* Called by FeedFilters component when filters change |
||||
*/ |
*/ |
||||
async reloadFeedOnChange() { |
async reloadFeedOnChange() { |
||||
const settings = await this.$accountSettings(this.activeDid, { |
logger.debug("[HomeView] 🔄 reloadFeedOnChange() called - refreshing feed"); |
||||
filterFeedByVisible: false, |
|
||||
filterFeedByNearby: false, |
// Get current settings without overriding with defaults |
||||
|
const settings = await this.$accountSettings(this.activeDid); |
||||
|
|
||||
|
logger.debug("[HomeView] 📊 Current filter settings:", { |
||||
|
filterFeedByVisible: settings.filterFeedByVisible, |
||||
|
filterFeedByNearby: settings.filterFeedByNearby, |
||||
trentlarson
commented 2 days ago
Review
I vote we make these all logger.debug (but I won't let that hold up the PR if you disagree). |
|||||
|
searchBoxes: settings.searchBoxes?.length || 0, |
||||
}); |
}); |
||||
|
|
||||
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible; |
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible; |
||||
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby; |
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby; |
||||
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings); |
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings); |
||||
|
|
||||
|
logger.debug("[HomeView] 🎯 Updated filter states:", { |
||||
|
isFeedFilteredByVisible: this.isFeedFilteredByVisible, |
||||
|
isFeedFilteredByNearby: this.isFeedFilteredByNearby, |
||||
|
isAnyFeedFilterOn: this.isAnyFeedFilterOn, |
||||
|
}); |
||||
|
|
||||
this.feedData = []; |
this.feedData = []; |
||||
this.feedPreviousOldestId = undefined; |
this.feedPreviousOldestId = undefined; |
||||
|
|
||||
|
logger.debug("[HomeView] 🧹 Cleared feed data, calling updateAllFeed()"); |
||||
await this.updateAllFeed(); |
await this.updateAllFeed(); |
||||
|
|
||||
|
logger.debug("[HomeView] ✅ Feed refresh completed"); |
||||
} |
} |
||||
|
|
||||
/** |
/** |
||||
@ -845,6 +862,14 @@ export default class HomeView extends Vue { |
|||||
* - this.feedLastViewedClaimId (via updateFeedLastViewedId) |
* - this.feedLastViewedClaimId (via updateFeedLastViewedId) |
||||
*/ |
*/ |
||||
async updateAllFeed() { |
async updateAllFeed() { |
||||
|
logger.debug("[HomeView] 🚀 updateAllFeed() called", { |
||||
|
isFeedLoading: this.isFeedLoading, |
||||
|
currentFeedDataLength: this.feedData.length, |
||||
|
isAnyFeedFilterOn: this.isAnyFeedFilterOn, |
||||
|
isFeedFilteredByVisible: this.isFeedFilteredByVisible, |
||||
|
isFeedFilteredByNearby: this.isFeedFilteredByNearby, |
||||
|
}); |
||||
|
|
||||
this.isFeedLoading = true; |
this.isFeedLoading = true; |
||||
let endOfResults = true; |
let endOfResults = true; |
||||
|
|
||||
@ -853,21 +878,37 @@ export default class HomeView extends Vue { |
|||||
this.apiServer, |
this.apiServer, |
||||
this.feedPreviousOldestId, |
this.feedPreviousOldestId, |
||||
); |
); |
||||
|
|
||||
|
logger.debug("[HomeView] 📡 Retrieved gives from API", { |
||||
|
resultsCount: results.data.length, |
||||
|
endOfResults, |
||||
|
}); |
||||
|
|
||||
if (results.data.length > 0) { |
if (results.data.length > 0) { |
||||
endOfResults = false; |
endOfResults = false; |
||||
// gather any contacts that user has blocked from view |
// gather any contacts that user has blocked from view |
||||
await this.processFeedResults(results.data); |
await this.processFeedResults(results.data); |
||||
await this.updateFeedLastViewedId(results.data); |
await this.updateFeedLastViewedId(results.data); |
||||
|
|
||||
|
logger.debug("[HomeView] 📝 Processed feed results", { |
||||
|
processedCount: this.feedData.length, |
||||
|
}); |
||||
} |
} |
||||
} catch (e) { |
} catch (e) { |
||||
|
logger.error("[HomeView] ❌ Error in updateAllFeed:", e); |
||||
this.handleFeedError(e); |
this.handleFeedError(e); |
||||
} |
} |
||||
|
|
||||
if (this.feedData.length === 0 && !endOfResults) { |
if (this.feedData.length === 0 && !endOfResults) { |
||||
|
logger.debug("[HomeView] 🔄 No results after filtering, retrying..."); |
||||
await this.updateAllFeed(); |
await this.updateAllFeed(); |
||||
} |
} |
||||
|
|
||||
this.isFeedLoading = false; |
this.isFeedLoading = false; |
||||
|
logger.debug("[HomeView] ✅ updateAllFeed() completed", { |
||||
|
finalFeedDataLength: this.feedData.length, |
||||
|
isFeedLoading: this.isFeedLoading, |
||||
|
}); |
||||
} |
} |
||||
|
|
||||
/** |
/** |
||||
@ -892,12 +933,35 @@ export default class HomeView extends Vue { |
|||||
* @param records Array of feed records to process |
* @param records Array of feed records to process |
||||
*/ |
*/ |
||||
private async processFeedResults(records: GiveSummaryRecord[]) { |
private async processFeedResults(records: GiveSummaryRecord[]) { |
||||
|
logger.debug("[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) { |
for (const record of records) { |
||||
const processedRecord = await this.processRecord(record); |
const processedRecord = await this.processRecord(record); |
||||
if (processedRecord) { |
if (processedRecord) { |
||||
this.feedData.push(processedRecord); |
this.feedData.push(processedRecord); |
||||
|
processedCount++; |
||||
|
} else { |
||||
|
filteredCount++; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
|
logger.debug("[HomeView] 📊 Feed processing results:", { |
||||
|
processed: processedCount, |
||||
|
filtered: filteredCount, |
||||
|
total: records.length, |
||||
|
finalFeedLength: this.feedData.length, |
||||
|
}); |
||||
|
|
||||
this.feedPreviousOldestId = records[records.length - 1].jwtId; |
this.feedPreviousOldestId = records[records.length - 1].jwtId; |
||||
} |
} |
||||
|
|
||||
@ -931,7 +995,7 @@ export default class HomeView extends Vue { |
|||||
* - this.feedData (via createFeedRecord) |
* - this.feedData (via createFeedRecord) |
||||
* |
* |
||||
* @param record The record to process |
* @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( |
private async processRecord( |
||||
record: GiveSummaryRecord, |
record: GiveSummaryRecord, |
||||
@ -941,13 +1005,28 @@ export default class HomeView extends Vue { |
|||||
const recipientDid = this.extractRecipientDid(claim); |
const recipientDid = this.extractRecipientDid(claim); |
||||
|
|
||||
const fulfillsPlan = await this.getFulfillsPlan(record); |
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)) { |
if (!this.shouldIncludeRecord(record, fulfillsPlan)) { |
||||
|
logger.debug("[HomeView] ❌ Record filtered out:", record.jwtId); |
||||
return null; |
return null; |
||||
} |
} |
||||
|
|
||||
const provider = this.extractProvider(claim); |
const provider = this.extractProvider(claim); |
||||
const providedByPlan = await this.getProvidedByPlan(provider); |
const providedByPlan = await this.getProvidedByPlan(provider); |
||||
|
|
||||
|
logger.debug("[HomeView] ✅ Record included:", record.jwtId); |
||||
return this.createFeedRecord( |
return this.createFeedRecord( |
||||
record, |
record, |
||||
claim, |
claim, |
||||
@ -1096,6 +1175,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; |
return anyMatch; |
||||
} |
} |
||||
|
|
||||
@ -1531,7 +1626,10 @@ export default class HomeView extends Vue { |
|||||
* Called by template click handler |
* Called by template click handler |
||||
*/ |
*/ |
||||
openFeedFilters() { |
openFeedFilters() { |
||||
(this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange); |
(this.$refs.feedFilters as FeedFilters).open( |
||||
|
this.reloadFeedOnChange, |
||||
|
this.activeDid, |
||||
|
); |
||||
} |
} |
||||
|
|
||||
/** |
/** |
||||
|
Loading…
Reference in new issue
I vote we simplify all these conditionals: if there is no "this.activeDid" then the underlying $updateSettings does the right thing anyway, so you can always pass it in.