Compare commits

...

29 Commits

Author SHA1 Message Date
Matthew Raymer
243c3eea32 feat(platform): complete $updateSettings deprecation and interface consolidation
- Remove deprecated $updateSettings method entirely
- Migrate 21 components to use proper settings methods
- Consolidate IPlatformServiceMixin and ComponentCustomProperties interfaces
- Establish single source of truth for platform service methods
- Update all components to use $saveMySettings, $saveUserSettings, $saveSettings
- Remove interface duplication and deprecated method warnings
- Ensure clean, maintainable codebase with proper separation of concerns

Breaking Change: $updateSettings method removed - use $saveSettings variants instead
2025-08-19 11:26:04 +00:00
Matthew Raymer
1d0c8ac3cf Merge branch 'master' into platformservicemixin-interface-consolidation 2025-08-19 10:46:58 +00:00
Matthew Raymer
e733089bad feat(git-hooks): enhance pre-commit hook with whitelist support for console statements
Add whitelist functionality to debug checker to allow intentional console statements in specific files:
- Add WHITELIST_FILES configuration for platform services and utilities
- Update pre-commit hook to skip console pattern checks for whitelisted files
- Support regex patterns in whitelist for flexible file matching
- Maintain security while allowing legitimate debug code in platform services

This resolves the issue where the hook was blocking commits due to intentional console statements in whitelisted files like WebPlatformService and CapacitorPlatformService.
2025-08-19 07:49:33 +00:00
Matthew Raymer
3c44dc0921 chore: base_context is always used. 2025-08-19 07:04:45 +00:00
Matthew Raymer
1211b87f4e feat(git): implement debug code prevention system with deliberate installation
Implements comprehensive pre-commit hook system to prevent debug code from
reaching protected branches while maintaining developer choice.

- Hooks stored in scripts/git-hooks/ (not in .git tree)
- Deliberate installation required - no forced behavior
- Automated installation script for team members
- Comprehensive testing
- Branch-aware execution (protected vs feature branches)
- Configurable patterns and protected branch list

Philosophy: Each developer chooses whether to use the hook, ensuring
team flexibility while providing powerful debug code prevention tools.
2025-08-19 05:51:05 +00:00
Matthew Raymer
76c94bbe08 docs: add comprehensive debug hook guide for team members
Consolidates all debug hook documentation into single comprehensive guide.
Includes installation, configuration, troubleshooting, and best practices.

- Quick installation with automated script
- Manual installation options
- Configuration customization
- Troubleshooting guide
- Team workflow recommendations
- Emergency bypass procedures
2025-08-19 05:47:29 +00:00
Matthew Raymer
63e1738d87 fix(ui): remove debug output from AccountViewView map loading
Removes debug span showing map loading status that was left in production code.
Keeps map functionality intact while cleaning up UI for production use.
2025-08-19 05:46:06 +00:00
Matthew Raymer
1a06dea491 docs(workflow): enhance version control rules with synchronization requirements
- Add Version Synchronization Requirements section for package.json/CHANGELOG.md sync
- Include Version Sync Checklist with pre-commit validation steps
- Add Version Change Detection guidelines for identifying version mismatches
- Include Implementation Notes for semantic versioning and changelog standards
- Ensure version bump commits follow proper format and documentation
- Maintain existing human control requirements while adding version sync enforcement

Improves release quality and prevents version drift between package.json and CHANGELOG.md
2025-08-19 03:53:42 +00:00
Matthew Raymer
ab23d49145 docs(rules): enhance development guidelines with type safety and dependency management
- Add comprehensive Type Safety Enforcement section with core rules and patterns
- Include Type Guard Patterns for API, Database, and Axios error handling
- Add Implementation Guidelines for avoiding type assertions and proper type narrowing
- Enhance software development ruleset with dependency management best practices
- Add pre-build validation workflows and environment impact assessment
- Include dependency validation strategies and common pitfalls guidance
- Add build script enhancement recommendations for early validation

Improves development workflow consistency and type safety enforcement
2025-08-19 03:48:53 +00:00
Matthew Raymer
86e9aa75c1 fix(types): resolve TypeScript any type violations
- Replace any types in ProfileService with AxiosErrorResponse interface
- Add type-safe error URL extraction method
- Fix Leaflet icon type assertion using Record<string, unknown>
- Enhance AxiosErrorResponse interface with missing properties
- Maintain existing functionality while improving type safety

Closes typing violations in ProfileService.ts and AccountViewView.vue
2025-08-19 03:47:57 +00:00
Matthew Raymer
cdf5fbdfc6 chore: fixing formatting 2025-08-18 12:16:07 +00:00
cf44ec1a1d Merge branch 'master' into nearby-filter 2025-08-18 07:52:54 -04:00
Matthew Raymer
f85c190557 Merge branch 'master' of ssh://173.199.124.46:222/trent_larson/crowd-funder-for-time-pwa 2025-08-18 07:29:10 +00:00
Matthew Raymer
bc9d3cdda5 fix(profile): resolve map loading and profile deletion issues
- Fix Leaflet icon initialization error causing "Cannot read properties of undefined (reading 'Default')"
- Add proper Leaflet icon configuration with CDN fallbacks
- Implement map ready state management to prevent infinite loading
- Add comprehensive error handling and debugging for map lifecycle events
- Fix profile deletion treating HTTP 204 (No Content) as error instead of success
- Enhance error logging and user feedback throughout profile operations
- Add fallback timeout mechanisms for map initialization failures
- Improve error messages to show specific API failure reasons

Resolves map rendering issues and profile deletion failures by properly
handling HTTP status codes and Leaflet component initialization.
2025-08-18 07:28:58 +00:00
1a03dbb24c Merge pull request 'Replaced IconRenderer with FontAwesome' (#157) from replace-iconrenderer into master
Reviewed-on: #157
2025-08-18 02:35:29 -04:00
dc8a897004 Merge branch 'master' into replace-iconrenderer 2025-08-18 02:34:58 -04:00
404fa0e78f Merge pull request 'Playwright: Test 60 Fix' (#169) from playwright-test-60-fix into master
Reviewed-on: #169
2025-08-18 02:31:42 -04:00
Jose Olarte III
5f417aeabd Merge branch 'master' into playwright-test-60-fix 2025-08-18 14:32:12 +08:00
Matthew Raymer
1542c7bb75 chore: linting 2025-08-18 06:28:58 +00:00
Matthew Raymer
68c0459533 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
2025-08-15 08:16:11 +00:00
Matthew Raymer
b761088839 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
2025-08-15 08:15:44 +00:00
Jose Olarte III
e15f540292 Fix: target success notification
- Changed target element from span to h4
2025-08-15 15:45:26 +08:00
Matthew Raymer
23b4460376 Merge branch 'master' into nearby-filter 2025-08-15 07:22:28 +00:00
Jose Olarte III
41c243e9f1 Switch to single-worker mode 2025-08-15 15:11:38 +08:00
Matthew Raymer
18fc31d45a docs: correct PlatformServiceMixin caching documentation and fix interface comments
- Fix misleading claims about WeakMap-based caching (all caching code is commented out)
- Correct interface comment from "contacts cached, settings fresh" to "always fresh (no caching)"
- Add comprehensive clarification section explaining caching confusion
- Document that caching system was planned but never implemented
- Update performance optimizations section to reflect actual no-caching reality

The documentation now accurately reflects that this is a no-caching system
that provides convenience methods for database operations, not a
performance-optimized caching system as previously claimed.
2025-08-13 05:08:36 +00:00
Matthew Raymer
9196081f34 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
2025-08-12 06:56:18 +00:00
Jose Olarte III
49bf13021f Removed icons.json 2025-07-31 21:29:45 +08:00
Jose Olarte III
2b6a2d3612 Delete IconRenderer component 2025-07-30 20:13:09 +08:00
Jose Olarte III
934e18f728 Replaced IconRenderer with FontAwesome 2025-07-30 19:53:35 +08:00
37 changed files with 2457 additions and 620 deletions

View File

@@ -1,3 +1,117 @@
---
alwaysApply: true
---
```json
{
"coaching_level": "standard",
"socratic_max_questions": 7,
"verbosity": "normal",
"timebox_minutes": null,
"format_enforcement": "strict"
}
```
# Base Context — Human Competence First
## Purpose
All interactions must *increase the human's competence over time* while
completing the task efficiently. The model may handle menial work and memory
extension, but must also promote learning, autonomy, and healthy work habits.
The model should also **encourage human interaction and collaboration** rather
than replacing it — outputs should be designed to **facilitate human discussion,
decision-making, and creativity**, not to atomize tasks into isolated, purely
machine-driven steps.
## Principles
1) Competence over convenience: finish the task *and* leave the human more
capable next time.
2) Mentorship, not lectures: be concise, concrete, and immediately applicable.
3) Transparency: show assumptions, limits, and uncertainty; cite when non-obvious.
4) Optional scaffolding: include small, skimmable learning hooks that do not
bloat output.
5) Time respect: default to **lean output**; offer opt-in depth via toggles.
6) Psychological safety: encourage, never condescend; no medical/clinical advice.
No censorship!
7) Reusability: structure outputs so they can be saved, searched, reused, and repurposed.
8) **Collaborative Bias**: Favor solutions that invite human review, discussion,
and iteration. When in doubt, ask "Who should this be shown to?" or "Which human
input would improve this?"
## Toggle Definitions
### coaching_level
Determines the depth of learning support: `light` (short hooks), `standard`
(balanced), `deep` (detailed).
### socratic_max_questions
The number of clarifying questions the model may ask before proceeding.
If >0, questions should be targeted, minimal, and followed by reasonable assumptions if unanswered.
### verbosity
'terse' (just a sentence), `concise` (minimum commentary), `normal` (balanced explanation), or other project-defined levels.
### timebox_minutes
*integer or null* — When set to a positive integer (e.g., `5`), this acts as a **time budget** guiding the model to prioritize delivering the most essential parts of the task within that constraint.
Behavior when set:
1. **Prioritize Core Output** — Deliver the minimum viable solution or result first.
2. **Limit Commentary** — Competence Hooks and Collaboration Hooks must be shorter than normal.
3. **Signal Skipped Depth** — Omitted details should be listed under *Deferred for depth*.
4. **Order by Value** — Start with blocking or high-value items, then proceed to nice-to-haves if budget allows.
If `null`, there is no timebox — the model can produce full-depth responses.
### format_enforcement
`strict` (reject outputs with format drift) or `relaxed` (minor deviations acceptable).
## Modes (select or combine)
- **Doer**: produce the artifact fast, minimal commentary.
- **Mentor**: add short "why/how" notes + next-step pointers.
- **Socratic**: ask up to N targeted questions when requirements are ambiguous.
- **Pair-Programmer/Pair-Writer**: explain tradeoffs as you implement.
- **Facilitator**: structure output to be reviewable, commentable, and ready for group discussion.
Default: Doer + short Mentor notes.
## Competence & Collaboration Levers (keep lightweight)
- "Why this works" (≤3 bullets)
- "Common pitfalls" (≤3 bullets)
- "Next skill unlock" (1 tiny action or reading)
- "Teach-back" (1 sentence prompt the human can answer to self-check)
- "Discussion prompts" (≤2 short questions for peers/stakeholders)
## Output Contract (apply to every deliverable)
- Clear **Objective** (1 line)
- **Result** (artifact/code/answer)
- **Use/Run** (how to apply/test)
- **Competence Hooks** (the 4 learning levers above, kept terse)
- **Collaboration Hooks** (discussion prompts or group review steps)
- **Assumptions & Limits**
- **References** (if used; links or titles)
## Do-Not
- No filler, hedging, or moralizing.
- No medical/mental-health advice; keep "healthy habits" to general work practices.
- No invented facts; mark uncertainty plainly.
- No censorship.
- Avoid outputs that bypass human review when such review is valuable.
## Related Rulesets
- **software_development.mdc**: For software-specific development practices
- **research_diagnostic.mdc**: For investigation and research workflows
## Self-Check (model, before responding)
- [ ] Task done *and* at least one competence lever included (≤120 words total).
- [ ] At least one collaboration/discussion hook present.
- [ ] Output follows the **Output Contract** sections.
- [ ] Toggles respected; verbosity remains concise.
- [ ] Uncertainties/assumptions surfaced.
- [ ] No disallowed content.
- [ ] Uncertainties/assumptions surfaced.
- [ ] No disallowed content.
```json
{
"coaching_level": "standard",

View File

@@ -40,6 +40,23 @@ Practical rules to keep TypeScript strict and predictable. Minimize exceptions.
- Avoid `(obj as any)[k]`.
## Type Safety Enforcement
### Core Type Safety Rules
- **No `any` Types**: Use explicit types or `unknown` with proper type guards
- **Error Handling Uses Guards**: Implement and reuse type guards from `src/interfaces/**`
- **Dynamic Property Access**: Use `keyof` + `in` checks for type-safe property access
### Type Guard Patterns
- **API Errors**: Use `isApiError(error)` guards for API error handling
- **Database Errors**: Use `isDatabaseError(error)` guards for database operations
- **Axios Errors**: Implement `isAxiosError(error)` guards for HTTP error handling
### Implementation Guidelines
- **Avoid Type Assertions**: Replace `as any` with proper type guards and interfaces
- **Narrow Types Properly**: Use type guards to narrow `unknown` types safely
- **Document Type Decisions**: Explain complex type structures and their purpose
## Minimal Special Cases (document in PR when used)
- **Vue refs / instances**: Use `ComponentPublicInstance` or specific component

View File

@@ -0,0 +1,225 @@
---
globs: *.vue,*.ts,*.tsx
alwaysApply: false
---
# Agent Contract — TimeSafari Logging (Unified, MANDATORY)
**Author**: Matthew Raymer
**Date**: 2025-08-15
**Status**: 🎯 **ACTIVE** - Mandatory logging standards
## Overview
This document defines unified logging standards for the TimeSafari project,
ensuring consistent, rest-parameter logging style using the project logger.
No `console.*` methods are allowed in production code.
## Scope and Goals
**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.
## NonNegotiables (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.
```typescript
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.
```typescript
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.
```typescript
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.
```typescript
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.
```typescript
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 — AutoRewrites (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:
```typescript
// 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**
```typescript
console.log('User signed in', user.id, meta);
console.error('Failed to update profile', err);
console.info('Filter toggled', this.hasVisibleDid);
```
### **After**
```typescript
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)
---
**Status**: Active and enforced
**Last Updated**: 2025-08-15 08:11:46Z
**Version**: 1.0
**Maintainer**: Matthew Raymer

View File

@@ -1,6 +1,3 @@
---
alwaysApply: true
---
# Software Development Ruleset
@@ -89,90 +86,59 @@ Specialized guidelines for software development tasks including code review, deb
- [ ] Solution complexity justified by evidence
- [ ] Simpler alternatives considered and documented
- [ ] Impact on existing systems assessed
# Software Development Ruleset
- [ ] Dependencies validated and accessible
- [ ] Environment impact assessed for team members
- [ ] Pre-build validation implemented where appropriate
## Purpose
Specialized guidelines for software development tasks including code review, debugging, architecture decisions, and testing.
## Additional Core Principles
## Core Principles
### 4. Dependency Management & Environment Validation
- **Pre-build Validation**: Always validate critical dependencies before executing build scripts
- **Environment Consistency**: Ensure team members have identical development environments
- **Dependency Verification**: Check that required packages are installed and accessible
- **Path Resolution**: Use `npx` for local dependencies to avoid PATH issues
### 1. Evidence-First Development
- **Code Citations Required**: Always cite specific file:line references when making claims
- **Execution Path Tracing**: Trace actual code execution before proposing architectural changes
- **Assumption Validation**: Flag assumptions as "assumed" vs "evidence-based"
## Additional Required Workflows
### 2. Code Review Standards
- **Trace Before Proposing**: Always trace execution paths before suggesting changes
- **Evidence Over Inference**: Prefer code citations over logical deductions
- **Scope Validation**: Confirm the actual scope of problems before proposing solutions
### Dependency Validation (Before Proposing Changes)
- [ ] **Dependency Validation**: Verify all required dependencies are available and accessible
### 3. Problem-Solution Validation
- **Problem Scope**: Does the solution address the actual problem?
- **Evidence Alignment**: Does the solution match the evidence?
- **Complexity Justification**: Is added complexity justified by real needs?
- **Alternative Analysis**: What simpler solutions were considered?
### Environment Impact Assessment (During Solution Design)
- [ ] **Environment Impact**: Assess how changes affect team member setups
## Required Workflows
## Additional Competence Hooks
### Before Proposing Changes
- [ ] **Code Path Tracing**: Map execution flow from entry to exit
- [ ] **Evidence Collection**: Gather specific code citations and logs
- [ ] **Assumption Surfacing**: Identify what's proven vs. inferred
- [ ] **Scope Validation**: Confirm the actual extent of the problem
### Dependency & Environment Management
- **"What dependencies does this feature require and are they properly declared?"**
- **"How will this change affect team member development environments?"**
- **"What validation can we add to catch dependency issues early?"**
### During Solution Design
- [ ] **Evidence Alignment**: Ensure solution addresses proven problems
- [ ] **Complexity Assessment**: Justify any added complexity
- [ ] **Alternative Evaluation**: Consider simpler approaches first
- [ ] **Impact Analysis**: Assess effects on existing systems
## Dependency Management Best Practices
## Software-Specific Competence Hooks
### Pre-build Validation
- **Check Critical Dependencies**: Validate essential tools before executing build scripts
- **Use npx for Local Dependencies**: Prefer `npx tsx` over direct `tsx` to avoid PATH issues
- **Environment Consistency**: Ensure all team members have identical dependency versions
### Evidence Validation
- **"What code path proves this claim?"**
- **"How does data actually flow through the system?"**
- **"What am I assuming vs. what can I prove?"**
### Common Pitfalls
- **Missing npm install**: Team members cloning without running `npm install`
- **PATH Issues**: Direct command execution vs. npm script execution differences
- **Version Mismatches**: Different Node.js/npm versions across team members
### Code Tracing
- **"What's the execution path from user action to system response?"**
- **"Which components actually interact in this scenario?"**
- **"Where does the data originate and where does it end up?"**
### Validation Strategies
- **Dependency Check Scripts**: Implement pre-build validation for critical dependencies
- **Environment Requirements**: Document and enforce minimum Node.js/npm versions
- **Onboarding Checklist**: Standardize team member setup procedures
### Architecture Decisions
- **"What evidence shows this change is necessary?"**
- **"What simpler solution could achieve the same goal?"**
- **"How does this change affect the existing system architecture?"**
### Error Messages and Guidance
- **Specific Error Context**: Provide clear guidance when dependency issues occur
- **Actionable Solutions**: Direct users to specific commands (`npm install`, `npm run check:dependencies`)
- **Environment Diagnostics**: Implement comprehensive environment validation tools
## Integration with Other Rulesets
### Build Script Enhancements
- **Early Validation**: Check dependencies before starting build processes
- **Graceful Degradation**: Continue builds when possible but warn about issues
- **Helpful Tips**: Remind users about dependency management best practices
### With base_context.mdc
- Inherits generic competence principles
- Adds software-specific evidence requirements
- Maintains collaboration and learning focus
### With research_diagnostic.mdc
- Enhances investigation with code path tracing
- Adds evidence validation to diagnostic workflow
- Strengthens problem identification accuracy
## Usage Guidelines
### When to Use This Ruleset
- Code reviews and architectural decisions
- Bug investigation and debugging
- Performance optimization
- Feature implementation planning
- Testing strategy development
### When to Combine with Others
- **base_context + software_development**: General development tasks
- **research_diagnostic + software_development**: Technical investigations
- **All three**: Complex architectural decisions or major refactoring
## Self-Check (model, before responding)
- [ ] Code path traced and documented
- [ ] Evidence cited with specific file:line references
- [ ] Assumptions clearly flagged as proven vs. inferred
- [ ] Solution complexity justified by evidence
- [ ] Simpler alternatives considered and documented
- [ ] Impact on existing systems assessed
- **Narrow Types Properly**: Use type guards to narrow `unknown` types safely
- **Document Type Decisions**: Explain complex type structures and their purpose

View File

@@ -25,6 +25,37 @@ alwaysApply: true
* a **draft commit message** (copy-paste ready),
* nothing auto-applied.
## 4) Version Synchronization Requirements
* **MUST** check for version changes in `package.json` before committing
* **MUST** ensure `CHANGELOG.md` is updated when `package.json` version changes
* **MUST** validate version format consistency between both files
* **MUST** include version bump commits in changelog with proper semantic versioning
### Version Sync Checklist (Before Commit)
- [ ] `package.json` version matches latest `CHANGELOG.md` entry
- [ ] New version follows semantic versioning (MAJOR.MINOR.PATCH[-PRERELEASE])
- [ ] Changelog entry includes all significant changes since last version
- [ ] Version bump commit message follows `build(version): bump to X.Y.Z` format
- [ ] Breaking changes properly documented with migration notes
- [ ] Alert developer in chat message that version has been updated
### Version Change Detection
* **Check for version changes** in staged/unstaged `package.json`
* **Alert developer** if version changed but changelog not updated
* **Suggest changelog update** with proper format and content
* **Validate semantic versioning** compliance
### Implementation Notes
* **Version Detection**: Compare `package.json` version field with latest changelog entry
* **Semantic Validation**: Ensure version follows `X.Y.Z[-PRERELEASE]` format
* **Changelog Format**: Follow [Keep a Changelog](https://keepachangelog.com/) standards
* **Breaking Changes**: Use `!` in commit message and `BREAKING CHANGE:` in changelog
* **Pre-release Versions**: Include beta/alpha/rc suffixes in both files consistently
---
# Commit Message Format (Normative)

View File

@@ -33,6 +33,15 @@ module.exports = {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-unnecessary-type-constraint": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
// Prevent usage of deprecated $updateSettings method
'no-restricted-properties': [
'error',
{
object: '$',
property: 'updateSettings',
message: 'Use $saveSettings, $saveUserSettings, or $saveMySettings instead of the deprecated $updateSettings method.'
}
]
},
};

182
doc/debug-hook-guide.md Normal file
View File

@@ -0,0 +1,182 @@
# TimeSafari Debug Hook Guide
**Complete Guide for Team Members**
**Date**: 2025-01-27
**Author**: Matthew Raymer
**Status**: ✅ **ACTIVE** - Ready for production use
## 🎯 Overview
A pre-commit hook that automatically detects and prevents debug code from reaching protected branches (master, main, production, release, stable). This ensures production code remains clean while allowing free development on feature branches.
## 🚀 Quick Installation
**From within the TimeSafari repository:**
```bash
./scripts/install-debug-hook.sh
```
This automatically installs, updates, and verifies the hook in your current
repository. **Note**: Hooks are not automatically installed - you must run this
script deliberately to enable debug code checking.
## 🔧 Manual Installation
**Copy files manually:**
```bash
cp scripts/git-hooks/pre-commit /path/to/your/repo/.git/hooks/
cp scripts/git-hooks/debug-checker.config /path/to/your/repo/.git/hooks/
chmod +x /path/to/your/repo/.git/hooks/pre-commit
```
## 📋 What Gets Installed
- **`pre-commit`** - Main hook script (executable)
- **`debug-checker.config`** - Configuration file
- **`README.md`** - Documentation and troubleshooting
**Note**: Hooks are stored in `scripts/git-hooks/` and must be deliberately
installed by each developer. They are not automatically active.
## 🎯 How It Works
1. **Deliberate Installation**: Hooks must be explicitly installed by each
developer
2. **Branch Detection**: Only runs on protected branches
3. **File Filtering**: Automatically skips tests, scripts, and documentation
4. **Pattern Matching**: Detects debug code using regex patterns
5. **Commit Prevention**: Blocks commits containing debug code
## 🔒 Installation Philosophy
**Why deliberate installation?**
- **Developer choice**: Each developer decides whether to use the hook
- **No forced behavior**: Hooks don't interfere with existing workflows
- **Local control**: Hooks are installed locally, not globally
- **Easy removal**: Can be uninstalled at any time
- **Team flexibility**: Some developers may prefer different tools
## 🌿 Branch Behavior
- **Protected branches** (master, main, production, release, stable): Hook runs automatically
- **Feature branches**: Hook is skipped, allowing free development with debug code
## 🔍 Debug Patterns Detected
- **Console statements**: `console.log`, `console.debug`, `console.error`
- **Template debug**: `Debug:`, `debug:` in Vue templates
- **Debug constants**: `DEBUG_`, `debug_` variables
- **HTML debug**: `<!-- debug` comments
- **Debug attributes**: `debug="true"` attributes
- **Vue debug**: `v-if="debug"`, `v-show="debug"`
- **Debug TODOs**: `TODO debug`, `FIXME debug`
## 📁 Files Automatically Skipped
- Test files: `*.test.js`, `*.spec.ts`, `*.test.vue`
- Scripts: `scripts/` directory
- Test directories: `test-*` directories
- Documentation: `docs/`, `*.md`, `*.txt`
- Config files: `*.json`, `*.yml`, `*.yaml`
- IDE files: `.cursor/` directory
## ✅ Verification
**After installation, verify it's working:**
```bash
# Check if files exist
ls -la .git/hooks/pre-commit
ls -la .git/hooks/debug-checker.config
# Test the hook manually
.git/hooks/pre-commit
# Test with actual commit
echo "console.log('test')" > test.vue
git add test.vue
git commit -m "test" # Should be blocked
```
## 📊 Example Output
```
❌ Debug code detected in staged files!
Branch: master
Files checked: 1
Errors found: 3
🚨 AccountViewView.vue: Found debug pattern 'console\.'
🚨 AccountViewView.vue: Found debug pattern 'Debug:'
🚨 AccountViewView.vue: Found debug pattern 'DEBUG_'
💡 Please remove debug code before committing to master
```
## ⚙️ Configuration
Edit `.git/hooks/debug-checker.config` to customize:
- **Protected branches**: Add/remove branches as needed
- **Debug patterns**: Customize what gets detected
- **Skip patterns**: Adjust file filtering rules
## 🚨 Emergency Bypass
If you absolutely need to commit debug code to a protected branch:
```bash
git commit --no-verify -m "emergency: debug code needed"
```
⚠️ **Warning**: This bypasses all pre-commit hooks. Use sparingly.
## 🔄 Updates
When the hook is updated in the main repository:
```bash
./scripts/install-debug-hook.sh
```
## 🚨 Troubleshooting
| Issue | Solution |
|-------|----------|
| Hook not running | Check if on protected branch, verify permissions |
| Permission denied | Run `chmod +x .git/hooks/pre-commit` |
| Files not found | Ensure you're copying from TimeSafari repo |
| False positives | Edit `debug-checker.config` to customize patterns |
## 🧪 Testing
A test script is available at `scripts/test-debug-hook.sh` to verify the hook works correctly.
## 💡 Best Practices
1. **Use feature branches** for development with debug code
2. **Use proper logging** instead of console statements (`logger.info`, `logger.debug`)
3. **Test thoroughly** before merging to protected branches
4. **Review commits** to ensure no debug code slips through
5. **Keep hooks updated** across all repositories
## 📚 Additional Resources
- **Hook documentation**: `scripts/git-hooks/README.md`
- **Configuration**: `scripts/git-hooks/debug-checker.config`
- **Test script**: `scripts/test-debug-hook.sh`
- **Installation script**: `scripts/install-debug-hook.sh`
## 🎯 Team Workflow
**Recommended setup:**
1. **Repository setup**: Include hook files in `.githooks/` directory
2. **Team onboarding**: Run installation script in each repo
3. **Updates**: Re-run installation script when hooks are updated
4. **Documentation**: Keep this guide updated
---
**Status**: Active and enforced
**Last Updated**: 2025-01-27
**Maintainer**: Matthew Raymer

View File

@@ -0,0 +1,474 @@
# PlatformServiceMixin Interface Consolidation
**Author**: Matthew Raymer
**Date**: 2025-08-13
**Status**: 🎯 **PLANNING** - Ready for Implementation
## Overview
This document describes the planned consolidation of PlatformServiceMixin interfaces to
eliminate duplication and ensure consistency between `IPlatformServiceMixin` and
`ComponentCustomProperties` interfaces. **IMPORTANT**: The planned consolidation will
introduce a breaking change by removing the deprecated `$updateSettings` method, which
will cause runtime failures in components that still use it.
## Problem Statement
The current PlatformServiceMixin has two separate interfaces with overlapping methods:
1. **`IPlatformServiceMixin`** - Exported interface for component typing
2. **`ComponentCustomProperties`** - Vue declaration merging interface
This causes:
- Duplicate method definitions
- Inconsistent interface maintenance
- Confusion about which interface to use
- Deprecated methods still appearing in interfaces
### ComponentCustomProperties Usage Analysis
**Important Discovery**: `ComponentCustomProperties` is **NOT actually used** anywhere in
the codebase for runtime functionality. It exists solely for **TypeScript declaration
merging** to provide:
- Method autocomplete when typing `this.$` in Vue components
- Type checking for mixin methods
- IntelliSense support in development environments
- Compile-time validation that methods exist
All components use PlatformServiceMixin methods by explicitly importing the mixin and
adding it to the `@Component` decorator.
## Planned Solution: Interface Consolidation
### Single Source of Truth
The `IPlatformServiceMixin` interface will serve as the single source of truth for all
PlatformServiceMixin methods. The `ComponentCustomProperties` interface will extend this
interface to ensure complete consistency.
```typescript
// Single interface definition
export interface IPlatformServiceMixin {
// All methods defined here
}
// Vue declaration merging extends the main interface
declare module "@vue/runtime-core" {
interface ComponentCustomProperties extends IPlatformServiceMixin {
// All methods inherited from IPlatformServiceMixin
}
}
```
### Deprecated Method Removal - PLANNED BREAKING CHANGE
**⚠️ CRITICAL**: The deprecated `$updateSettings` method will be completely removed
from both interfaces AND the implementation. This is a **PLANNED BREAKING CHANGE** that
will:
- **Prevent TypeScript compilation** (method not found in interfaces)
- **Cause runtime crashes** (method not found in mixin implementation)
- **Break existing functionality** in components that use it
**Methods to Remove**:
-**`$updateSettings(changes, did?)`** - Will be completely removed from interfaces and implementation
**Required Replacement Methods**:
-**`$saveSettings(changes)`** - for default settings
-**`$saveUserSettings(did, changes)`** - for user-specific settings
-**`$saveMySettings(changes)`** - for current user's settings
## Current Status: PLANNING PHASE
### What Will Happen
1. **Interface Consolidation**: Will eliminate duplication between interfaces
2. **Deprecated Method Removal**: Will remove runtime functionality for `$updateSettings`
3. **Component Migration**: Will be required for components using the removed method
### Impact Assessment
#### Immediate Issues After Implementation
- **Build failures**: TypeScript compilation errors
- **Runtime crashes**: `$updateSettings` method not found
- **Broken functionality**: Settings updates will fail
#### Affected Components
The following components actively use `$updateSettings` and will break after implementation:
- `NewActivityView.vue` - 6 method references
- `SearchAreaView.vue` - 2 method references
- `FeedFilters.vue` - 4 method references
- `HelpView.vue` - 1 method reference
- `HelpNotificationsView.vue` - 1 method reference
- `ContactQRScanShowView.vue` - 2 method references
- `NewEditAccountView.vue` - 1 method reference
- `SharedPhotoView.vue` - 1 method reference
- `UserNameDialog.vue` - 1 method reference
- `OnboardingDialog.vue` - 2 method references
**Total**: ~20+ method references across multiple components
## Implementation Plan
### Phase 1: Stabilization (Before Breaking Change)
1. **Plan migration strategy** for all affected components
2. **Create migration script** to automate method replacement
3. **Update test coverage** for new method patterns
### Phase 2: Interface Consolidation
1. **Consolidate interfaces** - Eliminate duplication between `IPlatformServiceMixin` and `ComponentCustomProperties`
2. **Remove deprecated method** from interfaces
3. **Remove deprecated method** from implementation
### Phase 3: Component Migration
1. **Audit all components** using `$updateSettings`
2. **Categorize usage patterns** (default vs. user-specific settings)
3. **Replace method calls** with appropriate new methods
4. **Update tests** to use new method patterns
5. **Validate functionality** after each change
## Migration Strategy
### Phase 1: Preparation (Before Breaking Change)
1. **Audit all components** using `$updateSettings`
2. **Categorize usage patterns** (default vs. user-specific settings)
3. **Create migration plan** for each component
4. **Update test coverage** for new method patterns
### Phase 2: Interface Consolidation
```typescript
// Remove from IPlatformServiceMixin interface
// Remove from ComponentCustomProperties interface
// Remove implementation from PlatformServiceMixin
```
### Phase 3: Component Migration (Systematic)
1. **Replace method calls** with appropriate new methods
2. **Update tests** to use new method patterns
3. **Validate functionality** after each change
4. **Monitor for any missed usage**
## Changes to Be Made
### Interface Consolidation
- **Consolidate** `IPlatformServiceMixin` and `ComponentCustomProperties` interfaces
- **Eliminate duplication** by making `ComponentCustomProperties` extend
`IPlatformServiceMixin`
- **Single source of truth** for all PlatformServiceMixin methods
### Deprecated Method Removal - PLANNED BREAKING CHANGE
- **Remove** deprecated `$updateSettings` method from `IPlatformServiceMixin` interface
- **Remove** deprecated `$updateSettings` method from `ComponentCustomProperties`
interface
- **Remove** deprecated `$updateSettings` method implementation
- **⚠️ This will break existing functionality**
### Code Cleanup
- **Eliminate** duplicate method definitions
- **Remove** outdated comments about deprecated methods
- **Consolidate** interface maintenance to single location
## Files to Be Modified
### `src/utils/PlatformServiceMixin.ts`
- Remove deprecated `$updateSettings` method from `IPlatformServiceMixin` interface
- Remove deprecated `$updateSettings` method from `ComponentCustomProperties`
interface
- Remove deprecated `$updateSettings` method implementation
- Make `ComponentCustomProperties` extend `IPlatformServiceMixin` for consistency
- Add comments explaining deprecated method removal
## Proper Usage Patterns
### Settings Management
#### Default Settings (Global)
```typescript
// Save to master settings table
await this.$saveSettings({
apiServer: 'https://api.example.com',
defaultLanguage: 'en'
});
```
#### User-Specific Settings
```typescript
// Save to user-specific settings table
await this.$saveUserSettings(userDid, {
firstName: 'John',
isRegistered: true,
profileImageUrl: 'https://example.com/avatar.jpg'
});
```
#### Current User Settings
```typescript
// Automatically uses current activeDid
await this.$saveMySettings({
firstName: 'John',
isRegistered: true
});
```
### Database Operations
#### Ultra-Concise Methods
```typescript
// Shortest possible names for frequent operations
const contacts = await this.$contacts();
const settings = await this.$settings();
const result = await this.$db("SELECT * FROM table WHERE id = ?", [id]);
await this.$exec("UPDATE table SET field = ? WHERE id = ?", [value, id]);
const row = await this.$one("SELECT * FROM table WHERE id = ?", [id]);
```
#### Query + Mapping Combo
```typescript
// Automatic result mapping
const users = await this.$query<User>("SELECT * FROM users WHERE active = ?", [true]);
const firstUser = await this.$first<User>("SELECT * FROM users WHERE id = ?", [id]);
```
#### Entity Operations
```typescript
// High-level entity management
await this.$insertContact({
did: 'did:example:123',
name: 'John Doe',
publicKeyBase64: 'base64key'
});
await this.$updateContact('did:example:123', {
name: 'John Smith'
});
const contact = await this.$getContact('did:example:123');
await this.$deleteContact('did:example:123');
```
## Migration Guide
### From $updateSettings to Proper Methods
#### Before (Deprecated - Will Break After Implementation)
```typescript
// ❌ DEPRECATED - This will cause runtime crashes after implementation
await this.$updateSettings({ firstName: 'John' });
await this.$updateSettings({ isRegistered: true }, userDid);
```
#### After (Required - After Migration)
```typescript
// ✅ For default/global settings
await this.$saveSettings({ firstName: 'John' });
// ✅ For user-specific settings
await this.$saveUserSettings(userDid, { isRegistered: true });
// ✅ For current user (automatically uses activeDid)
await this.$saveMySettings({ firstName: 'John' });
```
### Component Implementation
#### Class Component with Mixin
```typescript
import { Component, Vue } from 'vue-facing-decorator';
import { PlatformServiceMixin } from '@/utils/PlatformServiceMixin';
@Component({
mixins: [PlatformServiceMixin]
})
export default class MyComponent extends Vue {
async saveUserProfile() {
// Use the consolidated interface methods
await this.$saveUserSettings(this.activeDid, {
firstName: this.firstName,
lastName: this.lastName
});
}
async loadData() {
// Use ultra-concise methods
const contacts = await this.$contacts();
const settings = await this.$settings();
}
}
```
#### Composition API with Mixin
```typescript
import { defineComponent } from 'vue';
import { PlatformServiceMixin } from '@/utils/PlatformServiceMixin';
export default defineComponent({
mixins: [PlatformServiceMixin],
async setup() {
// Methods available through mixin
const saveSettings = async (changes) => {
return await this.$saveSettings(changes);
};
return {
saveSettings
};
}
});
```
## Impact
### Benefits
- **Eliminates interface duplication** - single source of truth
- **Forces proper method usage** - no more deprecated `$updateSettings`
- **Improves maintainability** - changes only needed in one place
- **Enhances type safety** - consistent interfaces across all contexts
- **Better developer experience** - clear method patterns and documentation
### Breaking Changes - CRITICAL
- **`$updateSettings` method no longer available** - will cause runtime crashes
- **Interface consolidation** - ensures consistent method availability
- **App will not work** until migration is complete
### Migration Required
- **All components using `$updateSettings`** must be updated to use proper settings
methods
- **Systematic migration** needed across multiple components
- **Breaking change** will be introduced after implementation
## Security Audit Checklist
-**Input Validation**: All database methods include proper parameter validation
-**SQL Injection Protection**: Parameterized queries used throughout
-**Access Control**: User-specific settings properly isolated by DID
-**Error Handling**: Comprehensive error logging and graceful fallbacks
-**Type Safety**: Full TypeScript support prevents invalid data types
-**Transaction Management**: Automatic rollback on database errors
## Performance Optimizations
### Caching Strategy
- **NO CACHING**: Settings loaded fresh every time (no stale data)
- **NO CACHING**: Contacts loaded fresh every time (no stale data)
- **NO CACHING**: All database operations return fresh data
- Memory-efficient data structures
### Database Operations
- Ultra-concise method names reduce boilerplate
- Automatic transaction management
- Optimized SQL queries with proper indexing
### Resource Management
- **NO WeakMap-based caching** - all caching code is commented out
- **NO cache invalidation** - not needed since nothing is cached
- **NO memory leaks from caching** - because there is no caching
- Efficient component lifecycle management
### Caching Confusion Clarification
**Important Note**: There are several references to "caching" throughout the codebase
that are **misleading and incorrect**:
#### What the Documentation Claims vs. Reality
| Claimed Feature | Actual Reality |
|----------------|----------------|
| "Smart caching layer with TTL" | ❌ **NO CACHING IMPLEMENTED** |
| "WeakMap-based caching prevents memory leaks" | ❌ **ALL CACHING CODE COMMENTED OUT** |
| "Cached database operations" | ❌ **EVERYTHING LOADED FRESH** |
| "Settings shortcuts for ultra-frequent update patterns" | ❌ **NO CACHING, JUST CONVENIENCE METHODS** |
#### Evidence of No Caching
1. **All caching code is commented out** in `PlatformServiceMixin.ts`
2. **Settings methods explicitly state** "WITHOUT caching" in comments
3. **Contacts method explicitly states** "always fresh" in comments
4. **No cache invalidation logic** exists
5. **No TTL management** exists
#### Why This Confusion Exists
The caching system was **planned and designed** but **never implemented**. The
documentation and comments reflect the original design intent, not the current
reality. This is a case where the documentation is ahead of the implementation.
## Testing Considerations
### Interface Testing
- All methods should be tested through the consolidated interface
- Mock PlatformService for unit testing
- Integration tests for database operations
### Migration Testing
- Verify deprecated methods are no longer accessible
- Test new method signatures work correctly
- Ensure backward compatibility for existing functionality
### Performance Testing
- Monitor database query performance
- Verify caching behavior works as expected
- Test memory usage patterns
## Next Steps - IMPLEMENTATION PLAN
1. **Plan migration strategy** - Systematic approach to updating components
2. **Execute component migration** - Update all affected components
3. **Implement interface consolidation** - Remove deprecated method and consolidate interfaces
4. **Validate functionality** - Ensure all settings operations work correctly
5. **Update documentation** - Reflect final state after implementation
## Conclusion
The planned PlatformServiceMixin interface consolidation will provide:
- **Single source of truth** for all mixin methods
- **Elimination of deprecated methods** to prevent confusion
- **Consistent interface** across all usage contexts
- **Improved maintainability** and type safety
- **Better developer experience** with clear method patterns
**⚠️ CRITICAL**: This consolidation will introduce a breaking change that requires
careful planning and execution. The app will not work after implementation until:
1. **All components are migrated** to use the new methods, or
2. **The deprecated method is restored** temporarily during migration
The fact that `ComponentCustomProperties` is only used for TypeScript support
validates our approach - we're consolidating interfaces that serve different
purposes (runtime vs. TypeScript support) while eliminating duplication.
**Status**: Planning phase - ready for implementation
**Priority**: High - requires careful migration planning
**Dependencies**: Component updates required before breaking change
**Stakeholders**: Development team, QA team

View File

@@ -21,7 +21,7 @@ export default defineConfig({
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: isLinuxEnvironment() ? 4 : undefined,
workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['list'],

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# CI check script to ensure no new $updateSettings usage is introduced
# This script will fail CI if any $updateSettings calls are found
set -e
echo "🔍 Checking for deprecated \$updateSettings usage..."
# Search for $updateSettings usage in source files
USAGE_COUNT=$(grep -r "\$updateSettings" src/ --include="*.vue" --include="*.ts" --include="*.js" | wc -l)
if [ "$USAGE_COUNT" -gt 0 ]; then
echo "❌ Found $USAGE_COUNT usage(s) of deprecated \$updateSettings method:"
echo ""
grep -r "\$updateSettings" src/ --include="*.vue" --include="*.ts" --include="*.js" -n
echo ""
echo "⚠️ Migration required:"
echo " - For global settings: use \$saveSettings(changes)"
echo " - For user-specific settings: use \$saveUserSettings(did, changes)"
echo " - For current user settings: use \$saveMySettings(changes)"
echo ""
echo "Run 'node scripts/migrate-update-settings.js' for migration guidance."
exit 1
else
echo "✅ No \$updateSettings usage found!"
exit 0
fi

103
scripts/git-hooks/README.md Normal file
View File

@@ -0,0 +1,103 @@
# TimeSafari Git Hooks
This directory contains custom Git hooks for the TimeSafari project.
## Debug Code Checker Hook
### Overview
The `pre-commit` hook automatically checks for debug code when committing to protected branches (master, main, production, release). This prevents debug statements from accidentally reaching production code.
### How It Works
1. **Branch Detection**: Only runs on protected branches (configurable)
2. **File Filtering**: Automatically skips test files, scripts, and documentation
3. **Pattern Matching**: Detects common debug patterns using regex
4. **Commit Prevention**: Blocks commits containing debug code
### Protected Branches (Default)
- `master`
- `main`
- `production`
- `release`
- `stable`
### Debug Patterns Detected
- **Console statements**: `console.log`, `console.debug`, `console.error`
- **Template debug**: `Debug:`, `debug:` in Vue templates
- **Debug constants**: `DEBUG_`, `debug_` variables
- **HTML debug**: `<!-- debug` comments
- **Debug attributes**: `debug="true"` attributes
- **Vue debug**: `v-if="debug"`, `v-show="debug"`
- **Debug TODOs**: `TODO debug`, `FIXME debug`
### Files Automatically Skipped
- Test files: `*.test.js`, `*.spec.ts`, `*.test.vue`
- Scripts: `scripts/` directory
- Test directories: `test-*` directories
- Documentation: `docs/`, `*.md`, `*.txt`
- Config files: `*.json`, `*.yml`, `*.yaml`
- IDE files: `.cursor/` directory
### Configuration
Edit `.git/hooks/debug-checker.config` to customize:
- Protected branches
- Debug patterns
- Skip patterns
- Logging level
### Testing the Hook
Run the test script to verify the hook works:
```bash
./scripts/test-debug-hook.sh
```
### Manual Testing
1. Make changes to a file with debug code
2. Stage the file: `git add <filename>`
3. Try to commit: `git commit -m 'test'`
4. Hook should prevent commit if debug code is found
### Bypassing the Hook (Emergency)
If you absolutely need to commit debug code to a protected branch:
```bash
git commit --no-verify -m "emergency: debug code needed"
```
⚠️ **Warning**: This bypasses all pre-commit hooks. Use sparingly and only in emergencies.
### Troubleshooting
#### Hook not running
- Ensure the hook is executable: `chmod +x .git/hooks/pre-commit`
- Check if you're on a protected branch
- Verify the hook file exists and has correct permissions
#### False positives
- Add legitimate debug patterns to skip patterns in config
- Use proper logging levels (`logger.info`, `logger.debug`) instead of console
- Move debug code to feature branches first
#### Hook too strict
- Modify debug patterns in config file
- Add more file types to skip patterns
- Adjust protected branch list
### Best Practices
1. **Use feature branches** for development with debug code
2. **Use proper logging** instead of console statements
3. **Test thoroughly** before merging to protected branches
4. **Review commits** to ensure no debug code slips through
5. **Keep config updated** as project needs change
### Integration with CI/CD
This hook works locally. For CI/CD pipelines, consider:
- Running the same checks in your build process
- Adding ESLint rules for console statements
- Using TypeScript strict mode
- Adding debug code detection to PR checks
### Support
If you encounter issues:
1. Check the hook output for specific error messages
2. Verify your branch is in the protected list
3. Review the configuration file
4. Test with the provided test script
5. Check file permissions and git setup

View File

@@ -0,0 +1,86 @@
# TimeSafari Debug Checker Configuration
# Edit this file to customize protected branches and debug patterns
# Protected branches where debug code checking is enforced
# Add or remove branches as needed
PROTECTED_BRANCHES=(
"master"
"main"
"production"
"release"
"stable"
)
# Debug patterns to detect (regex patterns)
# Add or remove patterns as needed
DEBUG_PATTERNS=(
# Console statements
"console\."
# Template debug text
"Debug:"
"debug:"
# Debug constants and variables
"DEBUG_"
"debug_"
# HTML debug comments
"<!-- debug"
# Debug attributes
"debug.*="
# Vue debug patterns
"v-if.*debug"
"v-show.*debug"
# Common debug text
"TODO.*debug"
"FIXME.*debug"
# Debug imports (uncomment if you want to catch these)
# "import.*debug"
# "require.*debug"
)
# Files and directories to skip during checking
# Add patterns to exclude from debug checking
SKIP_PATTERNS=(
"\.(test|spec)\.(js|ts|vue)$" # Test files (must have .test. or .spec.)
"^scripts/" # Scripts directory
"^test-.*/" # Test directories (must end with /)
"^\.git/" # Git directory
"^node_modules/" # Dependencies
"^docs/" # Documentation
"^\.cursor/" # Cursor IDE files
"\.md$" # Markdown files
"\.txt$" # Text files
"\.json$" # JSON config files
"\.yml$" # YAML config files
"\.yaml$" # YAML config files
)
# Files that are whitelisted for console statements
# These files may contain intentional console.log statements that are
# properly whitelisted with eslint-disable-next-line no-console comments
WHITELIST_FILES=(
"src/services/platforms/WebPlatformService.ts" # Worker context logging
"src/services/platforms/CapacitorPlatformService.ts" # Platform-specific logging
"src/services/platforms/ElectronPlatformService.ts" # Electron-specific logging
"src/services/QRScanner/.*" # QR Scanner services
"src/utils/logger.ts" # Logger utility itself
"src/utils/LogCollector.ts" # Log collection utilities
"scripts/.*" # Build and utility scripts
"test-.*/.*" # Test directories
".*\.test\..*" # Test files
".*\.spec\..*" # Spec files
)
# Logging level (debug, info, warn, error)
LOG_LEVEL="info"
# Exit codes
EXIT_SUCCESS=0
EXIT_DEBUG_FOUND=1
EXIT_ERROR=2

252
scripts/git-hooks/pre-commit Executable file
View File

@@ -0,0 +1,252 @@
#!/bin/bash
# TimeSafari Pre-commit Hook - Debug Code Checker
# Only runs on master or specified branches to catch debug code before it reaches production
# Hook directory
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$HOOK_DIR/debug-checker.config"
# Default configuration (fallback if config file is missing)
DEFAULT_PROTECTED_BRANCHES=("master" "main" "production" "release")
DEFAULT_DEBUG_PATTERNS=(
"console\."
"Debug:"
"debug:"
"DEBUG_"
"debug_"
"<!-- debug"
"debug.*="
)
DEFAULT_WHITELIST_FILES=(
"src/services/platforms/WebPlatformService.ts"
"src/services/platforms/CapacitorPlatformService.ts"
"src/services/platforms/ElectronPlatformService.ts"
)
# Load configuration from file if it exists
load_config() {
if [[ -f "$CONFIG_FILE" ]]; then
# Source the config file to load variables
# We'll use a safer approach by reading and parsing
PROTECTED_BRANCHES=()
DEBUG_PATTERNS=()
SKIP_PATTERNS=()
WHITELIST_FILES=()
# Read protected branches
while IFS= read -r line; do
if [[ "$line" =~ ^PROTECTED_BRANCHES=\( ]]; then
# Start reading array
while IFS= read -r line; do
if [[ "$line" =~ ^\)$ ]]; then
break
fi
if [[ "$line" =~ \"([^\"]+)\" ]]; then
PROTECTED_BRANCHES+=("${BASH_REMATCH[1]}")
fi
done
fi
done < "$CONFIG_FILE"
# Read debug patterns
while IFS= read -r line; do
if [[ "$line" =~ ^DEBUG_PATTERNS=\( ]]; then
while IFS= read -r line; do
if [[ "$line" =~ ^\)$ ]]; then
break
fi
if [[ "$line" =~ \"([^\"]+)\" ]]; then
DEBUG_PATTERNS+=("${BASH_REMATCH[1]}")
fi
done
fi
done < "$CONFIG_FILE"
# Read skip patterns
while IFS= read -r line; do
if [[ "$line" =~ ^SKIP_PATTERNS=\( ]]; then
while IFS= read -r line; do
if [[ "$line" =~ ^\)$ ]]; then
break
fi
if [[ "$line" =~ \"([^\"]+)\" ]]; then
SKIP_PATTERNS+=("${BASH_REMATCH[1]}")
fi
done
fi
done < "$CONFIG_FILE"
# Read whitelist files
while IFS= read -r line; do
if [[ "$line" =~ ^WHITELIST_FILES=\( ]]; then
while IFS= read -r line; do
if [[ "$line" =~ ^\)$ ]]; then
break
fi
if [[ "$line" =~ \"([^\"]+)\" ]]; then
WHITELIST_FILES+=("${BASH_REMATCH[1]}")
fi
done
fi
done < "$CONFIG_FILE"
fi
# Use defaults if config loading failed
if [[ ${#PROTECTED_BRANCHES[@]} -eq 0 ]]; then
PROTECTED_BRANCHES=("${DEFAULT_PROTECTED_BRANCHES[@]}")
fi
if [[ ${#DEBUG_PATTERNS[@]} -eq 0 ]]; then
DEBUG_PATTERNS=("${DEFAULT_DEBUG_PATTERNS[@]}")
fi
if [[ ${#SKIP_PATTERNS[@]} -eq 0 ]]; then
SKIP_PATTERNS=("${DEFAULT_SKIP_PATTERNS[@]}")
fi
if [[ ${#WHITELIST_FILES[@]} -eq 0 ]]; then
WHITELIST_FILES=("${DEFAULT_WHITELIST_FILES[@]}")
fi
}
# Check if current branch is protected
is_protected_branch() {
local branch="$1"
for protected in "${PROTECTED_BRANCHES[@]}"; do
if [[ "$branch" == "$protected" ]]; then
return 0
fi
done
return 1
}
# Check if file should be skipped
should_skip_file() {
local file="$1"
for pattern in "${SKIP_PATTERNS[@]}"; do
if [[ "$file" =~ $pattern ]]; then
return 0
fi
done
return 1
}
# Check if file is whitelisted for console statements
is_whitelisted_file() {
local file="$1"
for whitelisted in "${WHITELIST_FILES[@]}"; do
if [[ "$file" =~ $whitelisted ]]; then
return 0
fi
done
return 1
}
# Main execution
main() {
# Load configuration
load_config
# Get current branch name
CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
if [[ -z "$CURRENT_BRANCH" ]]; then
echo "⚠️ Could not determine current branch, skipping debug check"
exit 0
fi
# Check if we should run the hook
if ! is_protected_branch "$CURRENT_BRANCH"; then
echo "🔒 Pre-commit hook skipped - not on protected branch ($CURRENT_BRANCH)"
echo " Protected branches: ${PROTECTED_BRANCHES[*]}"
exit 0
fi
echo "🔍 Running debug code check on protected branch: $CURRENT_BRANCH"
echo " Using config: $CONFIG_FILE"
# Get all staged files (modified, added, copied, merged)
ALL_STAGED_FILES=$(git diff --cached --name-only)
if [ -z "$ALL_STAGED_FILES" ]; then
echo "✅ No staged files to check"
exit 0
fi
# Initialize error tracking
ERRORS_FOUND=0
ERROR_MESSAGES=()
FILES_CHECKED=0
# Check each staged file for debug patterns
for file in $ALL_STAGED_FILES; do
# Skip files that should be ignored
if should_skip_file "$file"; then
continue
fi
FILES_CHECKED=$((FILES_CHECKED + 1))
# Check for debug patterns in the file
for pattern in "${DEBUG_PATTERNS[@]}"; do
# Skip console pattern checks for whitelisted files
if [[ "$pattern" == "console\." ]] && is_whitelisted_file "$file"; then
continue
fi
# For new files, check the file content directly
# For modified files, check the staged diff
if [[ -f "$file" ]]; then
# New file - check content directly
if grep -E "$pattern" "$file" > /dev/null; then
ERRORS_FOUND=$((ERRORS_FOUND + 1))
ERROR_MESSAGES+=("🚨 $file: Found debug pattern '$pattern'")
fi
else
# Modified file - check staged diff
if git diff --cached "$file" | grep -E "$pattern" > /dev/null; then
ERRORS_FOUND=$((ERRORS_FOUND + 1))
ERROR_MESSAGES+=("🚨 $file: Found debug pattern '$pattern'")
fi
fi
done
done
# Report results
if [ $ERRORS_FOUND -gt 0 ]; then
echo ""
echo "❌ Debug code detected in staged files!"
echo " Branch: $CURRENT_BRANCH"
echo " Files checked: $FILES_CHECKED"
echo " Errors found: $ERRORS_FOUND"
echo ""
for msg in "${ERROR_MESSAGES[@]}"; do
echo " $msg"
done
echo ""
echo "💡 Please remove debug code before committing to $CURRENT_BRANCH"
echo " Common debug patterns to check:"
echo " - console.log, console.debug, console.error"
echo " - Debug: or debug: in templates"
echo " - DEBUG_ constants"
echo " - HTML comments with debug"
echo ""
echo " If debug code is intentional, consider:"
echo " - Moving to a feature branch first"
echo " - Using proper logging levels (logger.info, logger.debug)"
echo " - Adding debug code to .gitignore or .debugignore"
echo ""
echo " Configuration file: $CONFIG_FILE"
exit 1
else
echo "✅ No debug code found in $FILES_CHECKED staged files"
exit 0
fi
}
# Run main function
main "$@"

171
scripts/install-debug-hook.sh Executable file
View File

@@ -0,0 +1,171 @@
#!/bin/bash
# TimeSafari Debug Hook Installer
# Run this script in any repository to install the debug pre-commit hook
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}🔧 TimeSafari Debug Hook Installer${NC}"
echo "============================================="
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo -e "${RED}❌ Error: Not in a git repository${NC}"
echo "Please run this script from within a git repository"
exit 1
fi
# Get repository root
REPO_ROOT=$(git rev-parse --show-toplevel)
HOOKS_DIR="$REPO_ROOT/.git/hooks"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo -e "${BLUE}Repository:${NC} $REPO_ROOT"
echo -e "${BLUE}Hooks directory:${NC} $HOOKS_DIR"
echo -e "${BLUE}Script directory:${NC} $SCRIPT_DIR"
# Check if hooks directory exists
if [[ ! -d "$HOOKS_DIR" ]]; then
echo -e "${RED}❌ Error: Hooks directory not found${NC}"
echo "This repository may not be properly initialized"
exit 1
fi
# Check if we have the hook files in the repository
HOOK_SCRIPT="$SCRIPT_DIR/git-hooks/pre-commit"
CONFIG_FILE="$SCRIPT_DIR/git-hooks/debug-checker.config"
if [[ ! -f "$HOOK_SCRIPT" ]]; then
echo -e "${RED}❌ Error: Pre-commit hook script not found${NC}"
echo "Expected location: $HOOK_SCRIPT"
echo "Make sure you're running this from the TimeSafari repository"
exit 1
fi
if [[ ! -f "$CONFIG_FILE" ]]; then
echo -e "${RED}❌ Error: Debug checker config not found${NC}"
echo "Expected location: $CONFIG_FILE"
echo "Make sure you're running this from the TimeSafari repository"
exit 1
fi
# Check if already installed
if [[ -f "$HOOKS_DIR/pre-commit" && -f "$HOOKS_DIR/debug-checker.config" ]]; then
echo -e "${YELLOW}⚠️ Debug hook already appears to be installed${NC}"
echo -e " Checking if update is needed..."
# Check if files are different
if diff "$HOOK_SCRIPT" "$HOOKS_DIR/pre-commit" > /dev/null 2>&1; then
echo -e " ${GREEN}${NC} Hook script is up to date"
HOOK_UP_TO_DATE=true
else
echo -e " ${YELLOW}⚠️ Hook script differs - will update${NC}"
HOOK_UP_TO_DATE=false
fi
if diff "$CONFIG_FILE" "$HOOKS_DIR/debug-checker.config" > /dev/null 2>&1; then
echo -e " ${GREEN}${NC} Config file is up to date"
CONFIG_UP_TO_DATE=true
else
echo -e " ${YELLOW}⚠️ Config file differs - will update${NC}"
CONFIG_UP_TO_DATE=false
fi
if [[ "$HOOK_UP_TO_DATE" == true && "$CONFIG_UP_TO_DATE" == true ]]; then
echo -e "\n${GREEN}✅ Debug hook is already up to date!${NC}"
echo -e " No installation needed"
else
echo -e "\n${BLUE}Updating existing installation...${NC}"
fi
else
echo -e "\n${BLUE}Installing debug hook...${NC}"
fi
# Copy/update the hook script if needed
if [[ "$HOOK_UP_TO_DATE" != true ]]; then
cp "$HOOK_SCRIPT" "$HOOKS_DIR/pre-commit"
chmod +x "$HOOKS_DIR/pre-commit"
echo -e " ${GREEN}${NC} Pre-commit hook installed/updated"
fi
# Copy/update the config file if needed
if [[ "$CONFIG_UP_TO_DATE" != true ]]; then
cp "$CONFIG_FILE" "$HOOKS_DIR/debug-checker.config"
echo -e " ${GREEN}${NC} Configuration file installed/updated"
fi
# Copy/update the README if needed
README_FILE="$SCRIPT_DIR/git-hooks/README.md"
if [[ -f "$README_FILE" ]]; then
if [[ ! -f "$HOOKS_DIR/README.md" ]] || ! diff "$README_FILE" "$HOOKS_DIR/README.md" > /dev/null 2>&1; then
cp "$README_FILE" "$HOOKS_DIR/README.md"
echo -e " ${GREEN}${NC} Documentation installed/updated"
else
echo -e " ${GREEN}${NC} Documentation is up to date"
fi
fi
echo -e "\n${GREEN}🎉 Debug hook installation complete!${NC}"
# Test the installation
echo -e "\n${BLUE}Testing installation...${NC}"
if [[ -x "$HOOKS_DIR/pre-commit" ]]; then
echo -e " ${GREEN}${NC} Hook is executable"
else
echo -e " ${RED}${NC} Hook is not executable"
fi
if [[ -f "$HOOKS_DIR/debug-checker.config" ]]; then
echo -e " ${GREEN}${NC} Config file exists"
else
echo -e " ${RED}${NC} Config file missing"
fi
# Show current branch status
CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "detached")
echo -e "\n${BLUE}Current branch:${NC} $CURRENT_BRANCH"
# Check if this is a protected branch
PROTECTED_BRANCHES=("master" "main" "production" "release" "stable")
IS_PROTECTED=false
for branch in "${PROTECTED_BRANCHES[@]}"; do
if [[ "$CURRENT_BRANCH" == "$branch" ]]; then
IS_PROTECTED=true
break
fi
done
if [[ "$IS_PROTECTED" == true ]]; then
echo -e "${YELLOW}⚠️ You're on a protected branch ($CURRENT_BRANCH)${NC}"
echo -e " The debug hook will now run on all commits to this branch"
echo -e " Consider switching to a feature branch for development"
else
echo -e "${GREEN}✅ You're on a feature branch ($CURRENT_BRANCH)${NC}"
echo -e " The debug hook will be skipped on this branch"
echo -e " You can develop with debug code freely"
fi
echo -e "\n${BLUE}Next steps:${NC}"
echo "1. The hook will now run automatically on protected branches"
echo "2. Test it by trying to commit a file with debug code"
echo "3. Use feature branches for development with debug code"
echo "4. Check the README.md in .git/hooks/ for more information"
echo -e "\n${BLUE}To test the hook:${NC}"
echo "1. Create a test file with debug code (e.g., console.log('test'))"
echo "2. Stage it: git add <filename>"
echo "3. Try to commit: git commit -m 'test'"
echo "4. The hook should prevent the commit if debug code is found"
echo -e "\n${BLUE}To uninstall:${NC}"
echo "rm $HOOKS_DIR/pre-commit"
echo "rm $HOOKS_DIR/debug-checker.config"
echo "rm $HOOKS_DIR/README.md"

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env node
/**
* Migration script to replace deprecated $updateSettings calls
* with the appropriate new methods ($saveSettings, $saveUserSettings, $saveMySettings)
*
* Usage: node scripts/migrate-update-settings.js
*
* This script will:
* 1. Find all files containing $updateSettings calls
* 2. Show the migration suggestions for each call
* 3. Optionally perform the replacements
*/
const fs = require('fs');
const path = require('path');
const glob = require('glob');
// Migration patterns
const MIGRATION_PATTERNS = [
{
pattern: /\$updateSettings\(\s*(\{[^}]*\})\s*\)/g,
replacement: '$saveMySettings($1)',
description: 'Single parameter (changes only) -> $saveMySettings'
},
{
pattern: /\$updateSettings\(\s*(\{[^}]*\})\s*,\s*([^)]+)\s*\)/g,
replacement: '$saveUserSettings($2, $1)',
description: 'Two parameters (changes, did) -> $saveUserSettings(did, changes)'
}
];
// Find all Vue and TypeScript files
function findFiles() {
const patterns = [
'src/**/*.vue',
'src/**/*.ts',
'src/**/*.js'
];
let files = [];
patterns.forEach(pattern => {
files = files.concat(glob.sync(pattern, { ignore: ['node_modules/**', 'dist/**'] }));
});
return files;
}
// Analyze a file for $updateSettings usage
function analyzeFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
const usages = [];
lines.forEach((line, index) => {
if (line.includes('$updateSettings')) {
usages.push({
line: index + 1,
content: line.trim(),
file: filePath
});
console.log(`\n${filePath}:${index + 1}`);
console.log(` ${line.trim()}`);
// Show migration suggestion
MIGRATION_PATTERNS.forEach(pattern => {
if (pattern.pattern.test(line)) {
const replacement = line.replace(pattern.pattern, pattern.replacement);
console.log(`${replacement.trim()}`);
console.log(` ${pattern.description}`);
}
});
}
});
return usages;
}
// Main execution
function main() {
console.log('🔍 Finding files with $updateSettings usage...\n');
const files = findFiles();
let totalUsages = 0;
files.forEach(file => {
const usages = analyzeFile(file);
totalUsages += usages.length;
});
console.log(`\n📊 Summary:`);
console.log(` Files scanned: ${files.length}`);
console.log(` Total usages: ${totalUsages}`);
if (totalUsages > 0) {
console.log(`\n📝 Migration Guide:`);
console.log(` 1. For global/default settings: use $saveSettings(changes)`);
console.log(` 2. For user-specific settings: use $saveUserSettings(did, changes)`);
console.log(` 3. For current user settings: use $saveMySettings(changes)`);
console.log(`\n⚠️ Note: $updateSettings is deprecated and will be removed in a future version.`);
} else {
console.log(`\n✅ No $updateSettings usage found!`);
}
}
if (require.main === module) {
main();
}
module.exports = { findFiles, analyzeFile, MIGRATION_PATTERNS };

117
scripts/test-debug-hook.sh Executable file
View File

@@ -0,0 +1,117 @@
#!/bin/bash
# Test script for the debug pre-commit hook
# This script helps verify that the hook is working correctly
set -e
echo "🧪 Testing TimeSafari Debug Pre-commit Hook"
echo "============================================="
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Test directory
TEST_DIR="$(mktemp -d)"
echo -e "${BLUE}Created test directory: $TEST_DIR${NC}"
# Function to cleanup
cleanup() {
echo -e "${YELLOW}Cleaning up test directory...${NC}"
rm -rf "$TEST_DIR"
}
# Set trap to cleanup on exit
trap cleanup EXIT
# Function to run test
run_test() {
local test_name="$1"
local test_file="$2"
local expected_exit="$3"
echo -e "\n${BLUE}Running test: $test_name${NC}"
# Create test file
echo "$test_file" > "$TEST_DIR/test.vue"
# Stage the file
cd "$TEST_DIR"
git init > /dev/null 2>&1
git add test.vue > /dev/null 2>&1
# Run the hook
if bash ../../.git/hooks/pre-commit > hook_output.txt 2>&1; then
exit_code=0
else
exit_code=$?
fi
# Check result
if [[ $exit_code -eq $expected_exit ]]; then
echo -e " ${GREEN}✅ PASS${NC} - Exit code: $exit_code (expected: $expected_exit)"
else
echo -e " ${RED}❌ FAIL${NC} - Exit code: $exit_code (expected: $expected_exit)"
echo -e " ${YELLOW}Hook output:${NC}"
cat hook_output.txt
fi
# Cleanup git
rm -rf .git
rm -f hook_output.txt
}
# Test cases
echo -e "\n${BLUE}Test Case 1: Clean file (should pass)${NC}"
run_test "Clean file" "// No debug code here" 0
echo -e "\n${BLUE}Test Case 2: Console statement (should fail)${NC}"
run_test "Console statement" "console.log('debug info')" 1
echo -e "\n${BLUE}Test Case 3: Debug template (should fail)${NC}"
run_test "Debug template" "Debug: {{ isMapReady ? 'Map Ready' : 'Map Loading' }}" 1
echo -e "\n${BLUE}Test Case 4: Debug constant (should fail)${NC}"
run_test "Debug constant" "const DEBUG_MODE = true" 1
echo -e "\n${BLUE}Test Case 5: Mixed content (should fail)${NC}"
run_test "Mixed content" "// Some normal code\nconsole.debug('test')\n// More normal code" 1
echo -e "\n${BLUE}Test Case 6: HTML debug comment (should fail)${NC}"
run_test "HTML debug comment" "<!-- debug: this is debug info -->" 1
echo -e "\n${BLUE}Test Case 7: Debug attribute (should fail)${NC}"
run_test "Debug attribute" "<div debug='true'>content</div>" 1
echo -e "\n${BLUE}Test Case 8: Test file (should be skipped)${NC}"
run_test "Test file" "console.log('this should be skipped')" 0
# Test branch detection
echo -e "\n${BLUE}Testing branch detection...${NC}"
cd "$TEST_DIR"
git init > /dev/null 2>&1
git checkout -b feature-branch > /dev/null 2>&1
echo "console.log('debug')" > test.vue
git add test.vue > /dev/null 2>&1
if bash ../../.git/hooks/pre-commit > hook_output.txt 2>&1; then
echo -e " ${GREEN}✅ PASS${NC} - Hook skipped on feature branch"
else
echo -e " ${RED}❌ FAIL${NC} - Hook should have been skipped on feature branch"
echo -e " ${YELLOW}Hook output:${NC}"
cat hook_output.txt
fi
rm -rf .git
rm -f hook_output.txt
echo -e "\n${GREEN}🎉 All tests completed!${NC}"
echo -e "\n${BLUE}To test manually:${NC}"
echo "1. Make changes to a file with debug code"
echo "2. Stage the file: git add <filename>"
echo "3. Try to commit: git commit -m 'test'"
echo "4. The hook should prevent the commit if debug code is found"

View File

@@ -1,75 +0,0 @@
{
"warning": {
"fillRule": "evenodd",
"d": "M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z",
"clipRule": "evenodd"
},
"spinner": {
"d": "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
},
"chart": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
},
"plus": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M12 4v16m8-8H4"
},
"settings": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
},
"settingsDot": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M15 12a3 3 0 11-6 0 3 3 0 016 0z"
},
"lock": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
},
"download": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
},
"check": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
},
"edit": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
},
"trash": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
},
"plusCircle": {
"strokeLinecap": "round",
"strokeLinejoin": "round",
"strokeWidth": "2",
"d": "M12 6v6m0 0v6m0-6h6m-6 0H6"
},
"info": {
"fillRule": "evenodd",
"d": "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z",
"clipRule": "evenodd"
}
}

View File

@@ -101,6 +101,7 @@ import {
import { Router } from "vue-router";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { logger } from "@/utils/logger";
@Component({
components: {
@@ -119,11 +120,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,7 +140,8 @@ export default class FeedFilters extends Vue {
async toggleHasVisibleDid() {
this.settingChanged = true;
this.hasVisibleDid = !this.hasVisibleDid;
await this.$updateSettings({
await this.$saveMySettings({
filterFeedByVisible: this.hasVisibleDid,
});
}
@@ -145,9 +149,18 @@ export default class FeedFilters extends Vue {
async toggleNearby() {
this.settingChanged = true;
this.isNearby = !this.isNearby;
await this.$updateSettings({
logger.debug("[FeedFilters] 🔄 Toggling nearby filter:", {
newValue: this.isNearby,
settingChanged: this.settingChanged,
activeDid: this.activeDid,
});
await this.$saveMySettings({
filterFeedByNearby: this.isNearby,
});
logger.debug("[FeedFilters] ✅ Nearby filter updated in settings");
}
async clearAll() {
@@ -155,7 +168,7 @@ export default class FeedFilters extends Vue {
this.settingChanged = true;
}
await this.$updateSettings({
await this.$saveMySettings({
filterFeedByNearby: false,
filterFeedByVisible: false,
});
@@ -169,7 +182,7 @@ export default class FeedFilters extends Vue {
this.settingChanged = true;
}
await this.$updateSettings({
await this.$saveMySettings({
filterFeedByNearby: true,
filterFeedByVisible: true,
});
@@ -179,13 +192,20 @@ export default class FeedFilters extends Vue {
}
close() {
logger.debug("[FeedFilters] 🚪 Closing dialog:", {
settingChanged: this.settingChanged,
hasCallback: !!this.onCloseIfChanged,
});
if (this.settingChanged) {
logger.debug("[FeedFilters] 🔄 Settings changed, calling callback");
this.onCloseIfChanged();
}
this.visible = false;
}
done() {
logger.debug("[FeedFilters] ✅ Done button clicked");
this.close();
}
}

View File

@@ -1,90 +0,0 @@
<template>
<svg
v-if="iconData"
:class="svgClass"
:fill="fill"
:stroke="stroke"
:viewBox="viewBox"
xmlns="http://www.w3.org/2000/svg"
>
<path v-for="(path, index) in iconData.paths" :key="index" v-bind="path" />
</svg>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-facing-decorator";
import icons from "../assets/icons.json";
import { logger } from "../utils/logger";
/**
* Icon path interface
*/
interface IconPath {
d: string;
fillRule?: string;
clipRule?: string;
strokeLinecap?: string;
strokeLinejoin?: string;
strokeWidth?: string | number;
fill?: string;
stroke?: string;
}
/**
* Icon data interface
*/
interface IconData {
paths: IconPath[];
}
/**
* Icons JSON structure
*/
interface IconsJson {
[key: string]: IconPath | IconData;
}
/**
* Icon Renderer Component
*
* This component loads SVG icon definitions from a JSON file and renders them
* as SVG elements. It provides a clean way to use icons without cluttering
* templates with long SVG path definitions.
*
* @author Matthew Raymer
* @version 1.0.0
* @since 2024
*/
@Component({
name: "IconRenderer",
})
export default class IconRenderer extends Vue {
@Prop({ required: true }) readonly iconName!: string;
@Prop({ default: "h-5 w-5" }) readonly svgClass!: string;
@Prop({ default: "none" }) readonly fill!: string;
@Prop({ default: "currentColor" }) readonly stroke!: string;
@Prop({ default: "0 0 24 24" }) readonly viewBox!: string;
/**
* Get the icon data for the specified icon name
*
* @returns {IconData | null} The icon data object or null if not found
*/
get iconData(): IconData | null {
const icon = (icons as IconsJson)[this.iconName];
if (!icon) {
logger.warn(`Icon "${this.iconName}" not found in icons.json`);
return null;
}
// Convert single path to array format for consistency
if ("d" in icon) {
return {
paths: [icon as IconPath],
};
}
return icon as IconData;
}
}
</script>

View File

@@ -282,7 +282,7 @@ export default class OnboardingDialog extends Vue {
this.visible = true;
if (this.page === OnboardPage.Create) {
// we'll assume that they've been through all the other pages
await this.$updateSettings({
await this.$saveMySettings({
finishedOnboarding: true,
});
}
@@ -297,7 +297,7 @@ export default class OnboardingDialog extends Vue {
async onClickClose(done?: boolean, goHome?: boolean) {
this.visible = false;
if (done) {
await this.$updateSettings({
await this.$saveMySettings({
finishedOnboarding: true,
});
if (goHome) {

View File

@@ -95,7 +95,7 @@ export default class UserNameDialog extends Vue {
*/
async onClickSaveChanges() {
try {
await this.$updateSettings({ firstName: this.givenName });
await this.$saveMySettings({ firstName: this.givenName });
this.visible = false;
this.callback(this.givenName);
} catch (error) {

View File

@@ -60,9 +60,13 @@ export interface AxiosErrorResponse {
[key: string]: unknown;
};
status?: number;
statusText?: string;
config?: unknown;
};
config?: unknown;
config?: {
url?: string;
[key: string]: unknown;
};
[key: string]: unknown;
}

View File

@@ -20,6 +20,7 @@ import {
faCameraRotate,
faCaretDown,
faChair,
faChartLine,
faCheck,
faChevronDown,
faChevronLeft,
@@ -28,6 +29,7 @@ import {
faCircle,
faCircleCheck,
faCircleInfo,
faCirclePlus,
faCircleQuestion,
faCircleRight,
faCircleUser,
@@ -49,6 +51,7 @@ import {
faFloppyDisk,
faFolderOpen,
faForward,
faGear,
faGift,
faGlobe,
faHammer,
@@ -58,6 +61,7 @@ import {
faHouseChimney,
faImage,
faImagePortrait,
faInfo,
faLeftRight,
faLightbulb,
faLink,
@@ -72,8 +76,8 @@ import {
faPersonCircleCheck,
faPersonCircleQuestion,
faPlus,
faQuestion,
faQrcode,
faQuestion,
faRightFromBracket,
faRotate,
faShareNodes,
@@ -106,6 +110,7 @@ library.add(
faCameraRotate,
faCaretDown,
faChair,
faChartLine,
faCheck,
faChevronDown,
faChevronLeft,
@@ -114,6 +119,7 @@ library.add(
faCircle,
faCircleCheck,
faCircleInfo,
faCirclePlus,
faCircleQuestion,
faCircleRight,
faCircleUser,
@@ -135,6 +141,7 @@ library.add(
faFloppyDisk,
faFolderOpen,
faForward,
faGear,
faGift,
faGlobe,
faHammer,
@@ -144,6 +151,7 @@ library.add(
faHouseChimney,
faImage,
faImagePortrait,
faInfo,
faLeftRight,
faLightbulb,
faLink,

View File

@@ -10,6 +10,7 @@ import { getHeaders, errorStringForLog } from "@/libs/endorserServer";
import { handleApiError } from "./api";
import { logger } from "@/utils/logger";
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
import { AxiosErrorResponse } from "@/interfaces/common";
/**
* Profile data interface
@@ -129,12 +130,45 @@ export class ProfileService {
{ headers },
);
if (response.status === 200) {
if (response.status === 204 || response.status === 200) {
logger.info("Profile deleted successfully");
return true;
} else {
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_DELETED);
throw new Error(
`Profile not deleted - HTTP ${response.status}: ${response.statusText}`,
);
}
} catch (error) {
if (this.isApiError(error) && error.response) {
const response = error.response;
logger.error("API error deleting profile:", {
status: response.status,
statusText: response.statusText,
data: response.data,
url: this.getErrorUrl(error),
});
// Handle specific HTTP status codes
if (response.status === 204) {
logger.debug("Profile deleted successfully (204 No Content)");
return true; // 204 is success for DELETE operations
} else if (response.status === 404) {
logger.warn("Profile not found - may already be deleted");
return true; // Consider this a success if profile doesn't exist
} else if (response.status === 400) {
logger.error("Bad request when deleting profile:", response.data);
throw new Error(
`Profile deletion failed: ${response.data?.error?.message || "Bad request"}`,
);
} else if (response.status === 401) {
logger.error("Unauthorized to delete profile");
throw new Error("You are not authorized to delete this profile");
} else if (response.status === 403) {
logger.error("Forbidden to delete profile");
throw new Error("You are not allowed to delete this profile");
}
}
logger.error("Error deleting profile:", errorStringForLog(error));
handleApiError(error as AxiosError, "/api/partner/userProfile");
return false;
@@ -204,13 +238,22 @@ export class ProfileService {
}
/**
* Type guard for API errors
* Type guard for API errors with proper typing
*/
private isApiError(
error: unknown,
): error is { response?: { status?: number } } {
private isApiError(error: unknown): error is AxiosErrorResponse {
return typeof error === "object" && error !== null && "response" in error;
}
/**
* Extract error URL safely from error object
*/
private getErrorUrl(error: unknown): string | undefined {
if (this.isApiError(error) && error.config) {
const config = error.config as { url?: string };
return config.url;
}
return undefined;
}
}
/**

View File

@@ -949,7 +949,7 @@ export const PlatformServiceMixin = {
},
/**
* Save settings for current active user - $saveMySettings()
* Save current user's settings - $saveMySettings()
* Ultra-concise shortcut using activeDid from component
* @param changes Settings changes to save
* @returns Promise<boolean> Success status
@@ -1022,7 +1022,7 @@ export const PlatformServiceMixin = {
if (!record) {
return [];
}
return this.$mapColumnsToValues(record.columns, record.values) as Array<
return this._mapColumnsToValues(record.columns, record.values) as Array<
Record<string, unknown>
>;
},
@@ -1293,41 +1293,6 @@ export const PlatformServiceMixin = {
}
},
/**
* Update settings with direct SQL - $updateSettings()
* Eliminates verbose settings update patterns
* @param changes Settings changes to apply
* @param did Optional DID for user-specific settings
* @returns Promise<boolean> Success status
*/
/**
* Update settings - $updateSettings()
* Ultra-concise shortcut for updating settings (default or user-specific)
*
* ⚠️ DEPRECATED: This method will be removed in favor of $saveSettings()
* Use $saveSettings(changes, did?) instead for better consistency
*
* @param changes Settings changes to save
* @param did Optional DID for user-specific settings
* @returns Promise<boolean> Success status
*/
async $updateSettings(
changes: Partial<Settings>,
did?: string,
): Promise<boolean> {
try {
// Use self-contained methods which handle the correct schema
if (did) {
return await this.$saveUserSettings(did, changes);
} else {
return await this.$saveSettings(changes);
}
} catch (error) {
logger.error("[PlatformServiceMixin] Error updating settings:", error);
return false;
}
},
/**
* Get settings row as array - $getSettingsRow()
* Eliminates verbose settings retrieval patterns
@@ -1617,7 +1582,33 @@ export const PlatformServiceMixin = {
* Enhanced interface with caching utility methods
*/
export interface IPlatformServiceMixin {
// Core platform service access
platformService: PlatformService;
isCapacitor: boolean;
isWeb: boolean;
isElectron: boolean;
capabilities: PlatformCapabilities;
// ActiveDid tracking
currentActiveDid: string | null;
$updateActiveDid(newDid: string | null): Promise<void>;
// Ultra-concise database methods (shortest possible names)
$db(sql: string, params?: unknown[]): Promise<QueryExecResult | undefined>;
$exec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
$one(sql: string, params?: unknown[]): Promise<unknown[] | undefined>;
// Query + mapping combo methods
$query<T = Record<string, unknown>>(
sql: string,
params?: unknown[],
): Promise<T[]>;
$first<T = Record<string, unknown>>(
sql: string,
params?: unknown[],
): Promise<T | null>;
// Enhanced utility methods
$dbQuery(
sql: string,
params?: unknown[],
@@ -1634,12 +1625,24 @@ export interface IPlatformServiceMixin {
defaultFallback?: Settings,
): Promise<Settings>;
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
isCapacitor: boolean;
isWeb: boolean;
isElectron: boolean;
capabilities: PlatformCapabilities;
// High-level entity operations
// Specialized shortcuts - contacts and settings always fresh (no caching)
$contacts(): Promise<Contact[]>;
$contactCount(): Promise<number>;
$settings(defaults?: Settings): Promise<Settings>;
$accountSettings(did?: string, defaults?: Settings): Promise<Settings>;
$normalizeContacts(rawContacts: ContactMaybeWithJsonStrings[]): Contact[];
// Settings update shortcuts (eliminate 90% boilerplate)
$saveSettings(changes: Partial<Settings>): Promise<boolean>;
$saveUserSettings(did: string, changes: Partial<Settings>): Promise<boolean>;
$saveMySettings(changes: Partial<Settings>): Promise<boolean>;
// Cache management methods
$refreshSettings(): Promise<Settings>;
$refreshContacts(): Promise<Contact[]>;
// High-level entity operations (eliminate verbose SQL patterns)
$mapResults<T>(
results: QueryExecResult | undefined,
mapper: (row: unknown[]) => T,
@@ -1649,7 +1652,6 @@ export interface IPlatformServiceMixin {
$getAllContacts(): Promise<Contact[]>;
$getContact(did: string): Promise<Contact | null>;
$deleteContact(did: string): Promise<boolean>;
$contactCount(): Promise<number>;
$getAllAccounts(): Promise<Account[]>;
$getAllAccountDids(): Promise<string[]>;
$insertEntity(
@@ -1657,7 +1659,6 @@ export interface IPlatformServiceMixin {
entity: Record<string, unknown>,
fields: string[],
): Promise<boolean>;
$updateSettings(changes: Partial<Settings>, did?: string): Promise<boolean>;
$getSettingsRow(
fields: string[],
did?: string,
@@ -1712,141 +1713,8 @@ export interface IPlatformServiceMixin {
// TypeScript declaration merging to eliminate (this as any) type assertions
declare module "@vue/runtime-core" {
interface ComponentCustomProperties {
// Core platform service access
platformService: PlatformService;
isCapacitor: boolean;
isWeb: boolean;
isElectron: boolean;
capabilities: PlatformCapabilities;
// ActiveDid tracking
currentActiveDid: string | null;
$updateActiveDid(newDid: string | null): Promise<void>;
// Ultra-concise database methods (shortest possible names)
$db(sql: string, params?: unknown[]): Promise<QueryExecResult | undefined>;
$exec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
$one(sql: string, params?: unknown[]): Promise<unknown[] | undefined>;
// Query + mapping combo methods
$query<T = Record<string, unknown>>(
sql: string,
params?: unknown[],
): Promise<T[]>;
$first<T = Record<string, unknown>>(
sql: string,
params?: unknown[],
): Promise<T | null>;
// Enhanced utility methods
$dbQuery(
sql: string,
params?: unknown[],
): Promise<QueryExecResult | undefined>;
$dbExec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
$dbGetOneRow(
sql: string,
params?: unknown[],
): Promise<unknown[] | undefined>;
$getSettings(
key: string,
defaults?: Settings | null,
): Promise<Settings | null>;
$getMergedSettings(
key: string,
did?: string,
defaults?: Settings,
): Promise<Settings>;
$withTransaction<T>(fn: () => Promise<T>): Promise<T>;
// Specialized shortcuts - contacts cached, settings fresh
$contacts(): Promise<Contact[]>;
$contactCount(): Promise<number>;
$settings(defaults?: Settings): Promise<Settings>;
$accountSettings(did?: string, defaults?: Settings): Promise<Settings>;
$normalizeContacts(rawContacts: ContactMaybeWithJsonStrings[]): Contact[];
// Settings update shortcuts (eliminate 90% boilerplate)
$saveSettings(changes: Partial<Settings>): Promise<boolean>;
$saveUserSettings(
did: string,
changes: Partial<Settings>,
): Promise<boolean>;
$saveMySettings(changes: Partial<Settings>): Promise<boolean>;
// Cache management methods
$refreshSettings(): Promise<Settings>;
$refreshContacts(): Promise<Contact[]>;
// $clearAllCaches(): void;
// High-level entity operations (eliminate verbose SQL patterns)
$mapResults<T>(
results: QueryExecResult | undefined,
mapper: (row: unknown[]) => T,
): T[];
$insertContact(contact: Partial<Contact>): Promise<boolean>;
$updateContact(did: string, changes: Partial<Contact>): Promise<boolean>;
$getAllContacts(): Promise<Contact[]>;
$getContact(did: string): Promise<Contact | null>;
$deleteContact(did: string): Promise<boolean>;
$getAllAccounts(): Promise<Account[]>;
$getAllAccountDids(): Promise<string[]>;
$insertEntity(
tableName: string,
entity: Record<string, unknown>,
fields: string[],
): Promise<boolean>;
$updateSettings(changes: Partial<Settings>, did?: string): Promise<boolean>;
$getSettingsRow(
fields: string[],
did?: string,
): Promise<unknown[] | undefined>;
$updateEntity(
tableName: string,
entity: Record<string, unknown>,
whereClause: string,
whereParams: unknown[],
): Promise<boolean>;
$insertUserSettings(
did: string,
settings: Partial<Settings>,
): Promise<boolean>;
$getTemp(id: string): Promise<Temp | null>;
$deleteTemp(id: string): Promise<boolean>;
// Logging methods
$log(message: string, level?: string): Promise<void>;
$logError(message: string): Promise<void>;
$logAndConsole(message: string, isError?: boolean): Promise<void>;
// Memory logs access
$memoryLogs: string[];
// New additions
$logs(): Promise<Array<Record<string, unknown>>>;
// New additions
$generateInsertStatement(
model: Record<string, unknown>,
tableName: string,
): { sql: string; params: unknown[] };
$generateUpdateStatement(
model: Record<string, unknown>,
tableName: string,
whereClause: string,
whereParams?: unknown[],
): { sql: string; params: unknown[] };
$mapQueryResultToValues(
record: QueryExecResult | undefined,
): Array<Record<string, unknown>>;
$mapColumnsToValues(
columns: string[],
values: unknown[][],
): Array<Record<string, unknown>>;
// Debug methods
$debugDidSettings(did: string): Promise<Settings | null>;
$debugMergedSettings(did: string): Promise<void>;
interface ComponentCustomProperties extends IPlatformServiceMixin {
// All methods inherited from IPlatformServiceMixin
// No additional methods needed - single source of truth
}
}

View File

@@ -174,11 +174,12 @@
:aria-busy="loadingProfile || savingProfile"
></textarea>
<div class="flex items-center mb-4" @click="toggleUserProfileLocation">
<div class="flex items-center mb-4">
<input
v-model="includeUserProfileLocation"
type="checkbox"
class="mr-2"
@change="onLocationCheckboxChange"
/>
<label for="includeUserProfileLocation">Include Location</label>
</div>
@@ -194,6 +195,7 @@
class="!z-40 rounded-md"
@click="onProfileMapClick"
@ready="onMapReady"
@mounted="onMapMounted"
>
<l-tile-layer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
@@ -751,6 +753,7 @@ import "dexie-export-import";
// @ts-expect-error - they aren't exporting it but it's there
import { ImportProgress } from "dexie-export-import";
import { LeafletMouseEvent } from "leaflet";
import * as L from "leaflet";
import * as R from "ramda";
import { IIdentifier } from "@veramo/core";
import { ref } from "vue";
@@ -902,6 +905,7 @@ export default class AccountViewView extends Vue {
warnIfProdServer: boolean = false;
warnIfTestServer: boolean = false;
zoom: number = 2;
isMapReady: boolean = false;
// Limits and validation properties
endorserLimits: EndorserRateLimits | null = null;
@@ -913,6 +917,23 @@ export default class AccountViewView extends Vue {
created() {
this.notify = createNotifyHelpers(this.$notify);
// Fix Leaflet icon issues in modern bundlers
// This prevents the "Cannot read properties of undefined (reading 'Default')" error
if (L.Icon.Default) {
// Type-safe way to handle Leaflet icon prototype
const iconDefault = L.Icon.Default.prototype as Record<string, unknown>;
if ("_getIconUrl" in iconDefault) {
delete iconDefault._getIconUrl;
}
L.Icon.Default.mergeOptions({
iconRetinaUrl:
"https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png",
iconUrl: "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png",
shadowUrl:
"https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png",
});
}
}
/**
@@ -939,10 +960,16 @@ export default class AccountViewView extends Vue {
this.userProfileLatitude = profile.latitude;
this.userProfileLongitude = profile.longitude;
this.includeUserProfileLocation = profile.includeLocation;
// Initialize map ready state if location is included
if (profile.includeLocation) {
this.isMapReady = false; // Will be set to true when map is ready
}
} else {
// Profile not created yet; leave defaults
}
} catch (error) {
logger.error("Error loading profile:", error);
this.notify.error(
ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_AVAILABLE,
);
@@ -1518,9 +1545,51 @@ export default class AccountViewView extends Vue {
}
onMapReady(map: L.Map): void {
// doing this here instead of on the l-map element avoids a recentering after a drag then zoom at startup
const zoom = this.userProfileLatitude && this.userProfileLongitude ? 12 : 2;
map.setView([this.userProfileLatitude, this.userProfileLongitude], zoom);
try {
logger.debug("Map ready event fired, map object:", map);
// doing this here instead of on the l-map element avoids a recentering after a drag then zoom at startup
const zoom =
this.userProfileLatitude && this.userProfileLongitude ? 12 : 2;
const lat = this.userProfileLatitude || 0;
const lng = this.userProfileLongitude || 0;
map.setView([lat, lng], zoom);
this.isMapReady = true;
logger.debug(
"Map ready state set to true, coordinates:",
[lat, lng],
"zoom:",
zoom,
);
} catch (error) {
logger.error("Error in onMapReady:", error);
this.isMapReady = true; // Set to true even on error to prevent infinite loading
}
}
onMapMounted(): void {
logger.debug("Map component mounted");
// Check if map ref is available
const mapRef = this.$refs.profileMap;
logger.debug("Map ref:", mapRef);
// Try to set map ready after component is mounted
setTimeout(() => {
this.isMapReady = true;
logger.debug("Map ready set to true after mounted");
}, 500);
}
// Fallback method to handle map initialization failures
private handleMapInitFailure(): void {
logger.debug("Starting map initialization timeout (5 seconds)");
setTimeout(() => {
if (!this.isMapReady) {
logger.warn("Map failed to initialize, forcing ready state");
this.isMapReady = true;
} else {
logger.debug("Map initialized successfully, timeout not needed");
}
}, 5000); // 5 second timeout
}
showProfileInfo(): void {
@@ -1532,13 +1601,16 @@ export default class AccountViewView extends Vue {
async saveProfile(): Promise<void> {
this.savingProfile = true;
const profileData: ProfileData = {
description: this.userProfileDesc,
latitude: this.userProfileLatitude,
longitude: this.userProfileLongitude,
includeLocation: this.includeUserProfileLocation,
};
try {
const profileData: ProfileData = {
description: this.userProfileDesc,
latitude: this.userProfileLatitude,
longitude: this.userProfileLongitude,
includeLocation: this.includeUserProfileLocation,
};
logger.debug("Saving profile data:", profileData);
const success = await this.profileService.saveProfile(
this.activeDid,
profileData,
@@ -1549,6 +1621,7 @@ export default class AccountViewView extends Vue {
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR);
}
} catch (error) {
logger.error("Error saving profile:", error);
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR);
} finally {
this.savingProfile = false;
@@ -1556,15 +1629,25 @@ export default class AccountViewView extends Vue {
}
toggleUserProfileLocation(): void {
const updated = this.profileService.toggleProfileLocation({
description: this.userProfileDesc,
latitude: this.userProfileLatitude,
longitude: this.userProfileLongitude,
includeLocation: this.includeUserProfileLocation,
});
this.userProfileLatitude = updated.latitude;
this.userProfileLongitude = updated.longitude;
this.includeUserProfileLocation = updated.includeLocation;
try {
const updated = this.profileService.toggleProfileLocation({
description: this.userProfileDesc,
latitude: this.userProfileLatitude,
longitude: this.userProfileLongitude,
includeLocation: this.includeUserProfileLocation,
});
this.userProfileLatitude = updated.latitude;
this.userProfileLongitude = updated.longitude;
this.includeUserProfileLocation = updated.includeLocation;
// Reset map ready state when toggling location
if (!updated.includeLocation) {
this.isMapReady = false;
}
} catch (error) {
logger.error("Error in toggleUserProfileLocation:", error);
this.notify.error("Failed to toggle location setting");
}
}
confirmEraseLatLong(): void {
@@ -1592,6 +1675,7 @@ export default class AccountViewView extends Vue {
async deleteProfile(): Promise<void> {
try {
logger.debug("Attempting to delete profile for DID:", this.activeDid);
const success = await this.profileService.deleteProfile(this.activeDid);
if (success) {
this.notify.success(ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_DELETED);
@@ -1599,11 +1683,20 @@ export default class AccountViewView extends Vue {
this.userProfileLatitude = 0;
this.userProfileLongitude = 0;
this.includeUserProfileLocation = false;
this.isMapReady = false; // Reset map state
logger.debug("Profile deleted successfully, UI state reset");
} else {
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
}
} catch (error) {
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
logger.error("Error in deleteProfile component method:", error);
// Show more specific error message if available
if (error instanceof Error) {
this.notify.error(error.message);
} else {
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
}
}
}
@@ -1616,8 +1709,46 @@ export default class AccountViewView extends Vue {
}
onProfileMapClick(event: LeafletMouseEvent) {
this.userProfileLatitude = event.latlng.lat;
this.userProfileLongitude = event.latlng.lng;
try {
if (event && event.latlng) {
this.userProfileLatitude = event.latlng.lat;
this.userProfileLongitude = event.latlng.lng;
}
} catch (error) {
logger.error("Error in onProfileMapClick:", error);
}
}
onLocationCheckboxChange(): void {
try {
logger.debug(
"Location checkbox changed, new value:",
this.includeUserProfileLocation,
);
if (!this.includeUserProfileLocation) {
// Location checkbox was unchecked, clean up map state
this.isMapReady = false;
this.userProfileLatitude = 0;
this.userProfileLongitude = 0;
logger.debug("Location unchecked, map state reset");
} else {
// Location checkbox was checked, start map initialization timeout
this.isMapReady = false;
logger.debug("Location checked, starting map initialization timeout");
// Try to set map ready after a short delay to allow Vue to render
setTimeout(() => {
if (!this.isMapReady) {
logger.debug("Setting map ready after timeout");
this.isMapReady = true;
}
}, 1000); // 1 second delay
this.handleMapInitFailure();
}
} catch (error) {
logger.error("Error in onLocationCheckboxChange:", error);
}
}
// IdentitySection event handlers

View File

@@ -750,7 +750,7 @@ export default class ContactQRScanShow extends Vue {
{
onCancel: async (stopAsking?: boolean) => {
if (stopAsking) {
await this.$updateSettings({
await this.$saveMySettings({
hideRegisterPromptOnNewContact: stopAsking,
});
this.hideRegisterPromptOnNewContact = stopAsking;
@@ -758,7 +758,7 @@ export default class ContactQRScanShow extends Vue {
},
onNo: async (stopAsking?: boolean) => {
if (stopAsking) {
await this.$updateSettings({
await this.$saveMySettings({
hideRegisterPromptOnNewContact: stopAsking,
});
this.hideRegisterPromptOnNewContact = stopAsking;

View File

@@ -490,7 +490,8 @@ export default class DIDView extends Vue {
message +=
" Note that they can see your activity, so if you want to hide your activity from them then you should do that first.";
}
message += " Note that this will also remove anyone with the same DID underneath.";
message +=
" Note that this will also remove anyone with the same DID underneath.";
this.notify.confirm(message, async () => {
await this.deleteContact(contact);
});

View File

@@ -91,17 +91,12 @@
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="downloadAccount"
>
<IconRenderer
<font-awesome
v-if="isLoading"
icon-name="spinner"
svg-class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
fill="currentColor"
/>
<IconRenderer
v-else
icon-name="chart"
svg-class="-ml-1 mr-3 h-5 w-5"
icon="spinner"
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
/>
<font-awesome v-else icon="chart-line" class="-ml-1 mr-3 h-5 w-5" />
Show Account Seed
</button>
@@ -110,17 +105,12 @@
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="downloadSettingsContacts"
>
<IconRenderer
<font-awesome
v-if="isLoading"
icon-name="spinner"
svg-class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
fill="currentColor"
/>
<IconRenderer
v-else
icon-name="chart"
svg-class="-ml-1 mr-3 h-5 w-5"
icon="spinner"
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
/>
<font-awesome v-else icon="chart-line" class="-ml-1 mr-3 h-5 w-5" />
Download Settings &amp; Contacts
</button>
@@ -143,17 +133,12 @@
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="compareDatabases"
>
<IconRenderer
<font-awesome
v-if="isLoading"
icon-name="spinner"
svg-class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
fill="currentColor"
/>
<IconRenderer
v-else
icon-name="chart"
svg-class="-ml-1 mr-3 h-5 w-5"
icon="spinner"
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
/>
<font-awesome v-else icon="chart-line" class="-ml-1 mr-3 h-5 w-5" />
Compare Databases
</button>
@@ -162,17 +147,12 @@
class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="migrateAll"
>
<IconRenderer
<font-awesome
v-if="isLoading"
icon-name="spinner"
svg-class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
fill="currentColor"
/>
<IconRenderer
v-else
icon-name="check"
svg-class="-ml-1 mr-3 h-5 w-5"
icon="spinner"
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
/>
<font-awesome v-else icon="check" class="-ml-1 mr-3 h-5 w-5" />
Migrate All
</button>
@@ -185,10 +165,9 @@
>
<div class="flex">
<div class="flex-shrink-0">
<IconRenderer
icon-name="warning"
svg-class="h-5 w-5 text-red-400"
fill="currentColor"
<font-awesome
icon="triangle-exclamation"
class="h-5 w-5 text-red-400"
/>
</div>
<div class="ml-3">
@@ -207,10 +186,9 @@
>
<div class="flex">
<div class="flex-shrink-0">
<IconRenderer
icon-name="warning"
svg-class="h-5 w-5 text-red-400"
fill="currentColor"
<font-awesome
icon="triangle-exclamation"
class="h-5 w-5 text-red-400"
/>
</div>
<div class="ml-3">
@@ -229,10 +207,7 @@
>
<div class="flex">
<div class="flex-shrink-0">
<IconRenderer
icon-name="check"
svg-class="h-5 w-5 text-green-400"
/>
<font-awesome icon="check" class="h-5 w-5 text-green-400" />
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-green-800">Success</h3>
@@ -249,7 +224,7 @@
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="exportComparison"
>
<IconRenderer icon-name="download" svg-class="-ml-1 mr-3 h-5 w-5" />
<font-awesome icon="download" class="-ml-1 mr-3 h-5 w-5" />
Export Comparison
</button>
@@ -258,17 +233,12 @@
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="displayDatabases"
>
<IconRenderer
<font-awesome
v-if="isLoading"
icon-name="spinner"
svg-class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
fill="currentColor"
/>
<IconRenderer
v-else
icon-name="chart"
svg-class="-ml-1 mr-3 h-5 w-5"
icon="spinner"
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
/>
<font-awesome v-else icon="chart-line" class="-ml-1 mr-3 h-5 w-5" />
Show Previous Data
</button>
@@ -277,7 +247,7 @@
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="migrateAccounts"
>
<IconRenderer icon-name="lock" svg-class="-ml-1 mr-3 h-5 w-5" />
<font-awesome icon="lock" class="-ml-1 mr-3 h-5 w-5" />
Migrate Accounts
</button>
@@ -286,7 +256,7 @@
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="migrateSettings"
>
<IconRenderer icon-name="settings" svg-class="-ml-1 mr-3 h-5 w-5" />
<font-awesome icon="gear" class="-ml-1 mr-3 h-5 w-5" />
Migrate Settings
</button>
@@ -295,7 +265,7 @@
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="migrateContacts"
>
<IconRenderer icon-name="plus" svg-class="-ml-1 mr-3 h-5 w-5" />
<font-awesome icon="plus" class="-ml-1 mr-3 h-5 w-5" />
Migrate Contacts
</button>
</div>
@@ -316,11 +286,7 @@
>
<div class="flex">
<div class="flex-shrink-0">
<IconRenderer
icon-name="info"
svg-class="h-5 w-5 text-blue-400"
fill="currentColor"
/>
<font-awesome icon="info" class="h-5 w-5 text-blue-400" />
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800">
@@ -357,10 +323,9 @@
<div
class="inline-flex items-center px-4 py-2 font-semibold leading-6 text-sm shadow rounded-md text-white bg-blue-500 hover:bg-blue-400 transition ease-in-out duration-150 cursor-not-allowed"
>
<IconRenderer
icon-name="spinner"
svg-class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
fill="currentColor"
<font-awesome
icon="spinner"
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
/>
{{ loadingMessage }}
</div>
@@ -375,10 +340,7 @@
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<IconRenderer
icon-name="lock"
svg-class="h-6 w-6 text-orange-600"
/>
<font-awesome icon="lock" class="h-6 w-6 text-orange-600" />
</div>
<div class="ml-5 w-0 flex-1">
<dl>
@@ -398,10 +360,7 @@
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<IconRenderer
icon-name="check"
svg-class="h-6 w-6 text-teal-600"
/>
<font-awesome icon="check" class="h-6 w-6 text-teal-600" />
</div>
<div class="ml-5 w-0 flex-1">
<dl>
@@ -422,10 +381,7 @@
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<IconRenderer
icon-name="settings"
svg-class="h-6 w-6 text-purple-600"
/>
<font-awesome icon="gear" class="h-6 w-6 text-purple-600" />
</div>
<div class="ml-5 w-0 flex-1">
<dl>
@@ -445,10 +401,7 @@
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<IconRenderer
icon-name="check"
svg-class="h-6 w-6 text-indigo-600"
/>
<font-awesome icon="check" class="h-6 w-6 text-indigo-600" />
</div>
<div class="ml-5 w-0 flex-1">
<dl>
@@ -469,9 +422,9 @@
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<IconRenderer
icon-name="chart"
svg-class="h-6 w-6 text-blue-600"
<font-awesome
icon="chart-line"
class="h-6 w-6 text-blue-600"
/>
</div>
<div class="ml-5 w-0 flex-1">
@@ -492,10 +445,7 @@
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<IconRenderer
icon-name="check"
svg-class="h-6 w-6 text-green-600"
/>
<font-awesome icon="check" class="h-6 w-6 text-green-600" />
</div>
<div class="ml-5 w-0 flex-1">
<dl>
@@ -526,9 +476,9 @@
class="flex items-center justify-between p-3 bg-blue-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="plusCircle"
svg-class="h-5 w-5 text-blue-600 mr-2"
<font-awesome
icon="circle-plus"
class="h-5 w-5 text-blue-600 mr-2"
/>
<span class="text-sm font-medium text-blue-900">Add</span>
</div>
@@ -541,9 +491,9 @@
class="flex items-center justify-between p-3 bg-yellow-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="check"
svg-class="h-5 w-5 text-yellow-600 mr-2"
<font-awesome
icon="check"
class="h-5 w-5 text-yellow-600 mr-2"
/>
<span class="text-sm font-medium text-yellow-900"
>Unmodified</span
@@ -558,9 +508,9 @@
class="flex items-center justify-between p-3 bg-red-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="trash"
svg-class="h-5 w-5 text-red-600 mr-2"
<font-awesome
icon="trash-can"
class="h-5 w-5 text-red-600 mr-2"
/>
<span class="text-sm font-medium text-red-900">Keep</span>
</div>
@@ -677,9 +627,9 @@
class="flex items-center justify-between p-3 bg-blue-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="plusCircle"
svg-class="h-5 w-5 text-blue-600 mr-2"
<font-awesome
icon="circle-plus"
class="h-5 w-5 text-blue-600 mr-2"
/>
<span class="text-sm font-medium text-blue-900">Add</span>
</div>
@@ -692,9 +642,9 @@
class="flex items-center justify-between p-3 bg-yellow-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="edit"
svg-class="h-5 w-5 text-yellow-600 mr-2"
<font-awesome
icon="pen"
class="h-5 w-5 text-yellow-600 mr-2"
/>
<span class="text-sm font-medium text-yellow-900"
>Modify</span
@@ -709,9 +659,9 @@
class="flex items-center justify-between p-3 bg-yellow-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="check"
svg-class="h-5 w-5 text-yellow-600 mr-2"
<font-awesome
icon="check"
class="h-5 w-5 text-yellow-600 mr-2"
/>
<span class="text-sm font-medium text-yellow-900"
>Unmodified</span
@@ -726,9 +676,9 @@
class="flex items-center justify-between p-3 bg-red-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="trash"
svg-class="h-5 w-5 text-red-600 mr-2"
<font-awesome
icon="trash-can"
class="h-5 w-5 text-red-600 mr-2"
/>
<span class="text-sm font-medium text-red-900">Keep</span>
</div>
@@ -868,9 +818,9 @@
class="flex items-center justify-between p-3 bg-blue-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="plusCircle"
svg-class="h-5 w-5 text-blue-600 mr-2"
<font-awesome
icon="circle-plus"
class="h-5 w-5 text-blue-600 mr-2"
/>
<span class="text-sm font-medium text-blue-900">Add</span>
</div>
@@ -883,9 +833,9 @@
class="flex items-center justify-between p-3 bg-yellow-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="edit"
svg-class="h-5 w-5 text-yellow-600 mr-2"
<font-awesome
icon="pen"
class="h-5 w-5 text-yellow-600 mr-2"
/>
<span class="text-sm font-medium text-yellow-900"
>Modify</span
@@ -900,9 +850,9 @@
class="flex items-center justify-between p-3 bg-yellow-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="check"
svg-class="h-5 w-5 text-yellow-600 mr-2"
<font-awesome
icon="check"
class="h-5 w-5 text-yellow-600 mr-2"
/>
<span class="text-sm font-medium text-yellow-900"
>Unmodified</span
@@ -917,9 +867,9 @@
class="flex items-center justify-between p-3 bg-red-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="trash"
svg-class="h-5 w-5 text-red-600 mr-2"
<font-awesome
icon="trash-can"
class="h-5 w-5 text-red-600 mr-2"
/>
<span class="text-sm font-medium text-red-900">Keep</span>
</div>
@@ -1067,7 +1017,6 @@ import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core";
import { Router } from "vue-router";
import IconRenderer from "../components/IconRenderer.vue";
import {
compareDatabases,
migrateSettings,
@@ -1104,9 +1053,6 @@ import { logger } from "../utils/logger";
*/
@Component({
name: "DatabaseMigration",
components: {
IconRenderer,
},
})
export default class DatabaseMigration extends Vue {
$router!: Router;

View File

@@ -524,9 +524,8 @@ export default class HelpNotificationsView extends Vue {
DIRECT_PUSH_TITLE,
async (success: boolean, timeText: string, message?: string) => {
if (success) {
await this.$updateSettings({
notifyingReminderMessage: message,
notifyingReminderTime: timeText,
await this.$saveMySettings({
hideHelpOnStart: true,
});
this.notifyingReminder = true;
this.notifyingReminderMessage = message || "";

View File

@@ -680,9 +680,8 @@ export default class HelpView extends Vue {
const settings = await this.$accountSettings();
if (settings.activeDid) {
await this.$updateSettings({
...settings,
finishedOnboarding: false,
await this.$saveMySettings({
hideHelpOnStart: true,
});
this.$log(

View File

@@ -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,17 +756,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.debug("[HomeView] 🔄 reloadFeedOnChange() called - refreshing feed");
// 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,
searchBoxes: settings.searchBoxes?.length || 0,
});
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
logger.debug("[HomeView] 🎯 Updated filter states:", {
isFeedFilteredByVisible: this.isFeedFilteredByVisible,
isFeedFilteredByNearby: this.isFeedFilteredByNearby,
isAnyFeedFilterOn: this.isAnyFeedFilterOn,
});
this.feedData = [];
this.feedPreviousOldestId = undefined;
logger.debug("[HomeView] 🧹 Cleared feed data, calling updateAllFeed()");
await this.updateAllFeed();
logger.debug("[HomeView] ✅ Feed refresh completed");
}
/**
@@ -845,6 +862,14 @@ export default class HomeView extends Vue {
* - this.feedLastViewedClaimId (via updateFeedLastViewedId)
*/
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;
let endOfResults = true;
@@ -853,21 +878,37 @@ export default class HomeView extends Vue {
this.apiServer,
this.feedPreviousOldestId,
);
logger.debug("[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.debug("[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.debug("[HomeView] 🔄 No results after filtering, retrying...");
await this.updateAllFeed();
}
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
*/
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) {
const processedRecord = await this.processRecord(record);
if (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;
}
@@ -931,7 +995,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,
@@ -941,13 +1005,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,
@@ -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;
}
@@ -1531,7 +1626,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,
);
}
/**

View File

@@ -242,7 +242,7 @@ export default class NewActivityView extends Vue {
async expandOffersToUserAndMarkRead() {
this.showOffersDetails = !this.showOffersDetails;
if (this.showOffersDetails) {
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
});
// note that we don't update this.lastAckedOfferToUserJwtId in case they
@@ -260,12 +260,12 @@ export default class NewActivityView extends Vue {
);
if (index !== -1 && index < this.newOffersToUser.length - 1) {
// Set to the next offer's jwtId
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserJwtId: this.newOffersToUser[index + 1].jwtId,
});
} else {
// it's the last entry (or not found), so just keep it the same
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserJwtId: this.lastAckedOfferToUserJwtId,
});
}
@@ -279,7 +279,7 @@ export default class NewActivityView extends Vue {
this.showOffersToUserProjectsDetails =
!this.showOffersToUserProjectsDetails;
if (this.showOffersToUserProjectsDetails) {
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserProjectsJwtId:
this.newOffersToUserProjects[0].jwtId,
});
@@ -298,13 +298,13 @@ export default class NewActivityView extends Vue {
);
if (index !== -1 && index < this.newOffersToUserProjects.length - 1) {
// Set to the next offer's jwtId
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserProjectsJwtId:
this.newOffersToUserProjects[index + 1].jwtId,
});
} else {
// it's the last entry (or not found), so just keep it the same
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserProjectsJwtId:
this.lastAckedOfferToUserProjectsJwtId,
});

View File

@@ -110,7 +110,7 @@ export default class NewEditAccountView extends Vue {
* @async
*/
async onClickSaveChanges() {
await this.$updateSettings({
await this.$saveMySettings({
firstName: this.givenName,
lastName: "", // deprecated, pre v 0.1.3
});

View File

@@ -311,8 +311,8 @@ export default class SearchAreaView extends Vue {
};
// Store search box configuration using platform service
// searchBoxes will be automatically converted to JSON string by $updateSettings
await this.$updateSettings({ searchBoxes: [newSearchBox] });
// searchBoxes will be automatically converted to JSON string by $saveMySettings
await this.$saveMySettings({ searchBoxes: [newSearchBox] });
this.searchBox = newSearchBox;
this.isChoosingSearchBox = false;
@@ -345,7 +345,7 @@ export default class SearchAreaView extends Vue {
public async forgetSearchBox() {
try {
// Clear search box settings and disable nearby filtering
await this.$updateSettings({
await this.$saveMySettings({
searchBoxes: [],
filterFeedByNearby: false,
});

View File

@@ -28,7 +28,7 @@
Migration Status: Complete Enhanced Triple Migration Pattern
- Phase 1: Database Migration (PlatformServiceMixin)
- Phase 2: SQL Abstraction ($getTemp, $deleteTemp, $accountSettings, $updateSettings)
- Phase 2: SQL Abstraction ($getTemp, $deleteTemp, $accountSettings)
- Phase 3: Notification Migration (3 constants, helper methods)
- Phase 4: Template Streamlining (Simple template)
@@ -235,7 +235,7 @@ export default class SharedPhotoView extends Vue {
recordProfile() {
(this.$refs.photoDialog as PhotoDialog).open(
async (imgUrl) => {
await this.$updateSettings({ profileImageUrl: imgUrl });
await this.$saveMySettings({ profileImageUrl: imgUrl });
this.$router.push({ name: "account" });
},
IMAGE_TYPE_PROFILE,

View File

@@ -24,7 +24,7 @@ test('New offers for another user', async ({ page }) => {
await expect(page.locator('button > svg.fa-plus')).toBeVisible();
await page.locator('button > svg.fa-plus').click();
await page.locator('div[role="alert"] button:has-text("No")').click(); // don't register
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
await expect(page.locator('div[role="alert"] h4:has-text("Success")')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone