Compare commits
29 Commits
imagemagic
...
platformse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
243c3eea32 | ||
|
|
1d0c8ac3cf | ||
|
|
e733089bad | ||
|
|
3c44dc0921 | ||
|
|
1211b87f4e | ||
|
|
76c94bbe08 | ||
|
|
63e1738d87 | ||
|
|
1a06dea491 | ||
|
|
ab23d49145 | ||
|
|
86e9aa75c1 | ||
|
|
cdf5fbdfc6 | ||
| cf44ec1a1d | |||
|
|
f85c190557 | ||
|
|
bc9d3cdda5 | ||
| 1a03dbb24c | |||
| dc8a897004 | |||
| 404fa0e78f | |||
|
|
5f417aeabd | ||
|
|
1542c7bb75 | ||
|
|
68c0459533 | ||
|
|
b761088839 | ||
|
|
e15f540292 | ||
|
|
23b4460376 | ||
|
|
41c243e9f1 | ||
|
|
18fc31d45a | ||
|
|
9196081f34 | ||
|
|
49bf13021f | ||
|
|
2b6a2d3612 | ||
|
|
934e18f728 |
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
225
.cursor/rules/logging_standards.mdc
Normal file
225
.cursor/rules/logging_standards.mdc
Normal 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.
|
||||
|
||||
## Non‑Negotiables (DO THIS)
|
||||
|
||||
- You **MUST** use the project logger; **DO NOT** use any `console.*`
|
||||
methods.
|
||||
- Import exactly as:
|
||||
- `import { logger } from '@/utils/logger'`
|
||||
- If `@` alias is unavailable, compute the correct relative path (do not
|
||||
fail).
|
||||
- Call signatures use **rest parameters**: `logger.info(message, ...args)`
|
||||
- Prefer primitives/IDs and small objects in `...args`; **never build a
|
||||
throwaway object** just to "wrap context".
|
||||
- Production defaults: Web = `warn+`, Electron = `error`, Dev/Capacitor =
|
||||
`info+` (override via `VITE_LOG_LEVEL`).
|
||||
- **Database persistence**: `info|warn|error` are persisted; `debug` is not.
|
||||
Use `logger.toDb(msg, level?)` for DB-only.
|
||||
|
||||
## Available Logger API (Authoritative)
|
||||
|
||||
- `logger.debug(message, ...args)` — verbose internals, timings, input/output
|
||||
shapes
|
||||
- `logger.log(message, ...args)` — synonym of `info` for general info
|
||||
- `logger.info(message, ...args)` — lifecycle, state changes, success paths
|
||||
- `logger.warn(message, ...args)` — recoverable issues, retries, degraded mode
|
||||
- `logger.error(message, ...args)` — failures, thrown exceptions, aborts
|
||||
- `logger.toDb(message, level?)` — DB-only entry (default level = `info`)
|
||||
- `logger.toConsoleAndDb(message, isError)` — console + DB (use sparingly)
|
||||
- `logger.withContext(componentName)` — returns a scoped logger
|
||||
|
||||
## Level Guidelines (Use These Heuristics)
|
||||
|
||||
### DEBUG
|
||||
|
||||
Use for method entry/exit, computed values, filters, loops, retries, and
|
||||
external call payload sizes.
|
||||
|
||||
```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 — Auto‑Rewrites (Apply Every Time)
|
||||
|
||||
- Exact transforms:
|
||||
- `console.debug(...)` → `logger.debug(...)`
|
||||
- `console.log(...)` → `logger.log(...)` (or `logger.info(...)` when
|
||||
clearly stateful)
|
||||
- `console.info(...)` → `logger.info(...)`
|
||||
- `console.warn(...)` → `logger.warn(...)`
|
||||
- `console.error(...)` → `logger.error(...)`
|
||||
|
||||
- Multi-arg handling:
|
||||
- First arg becomes `message` (stringify safely if non-string).
|
||||
- Remaining args map 1:1 to `...args`:
|
||||
`console.info(msg, a, b)` → `logger.info(String(msg), a, b)`
|
||||
|
||||
- Sole `Error`:
|
||||
- `console.error(err)` → `logger.error(err.message, err)`
|
||||
|
||||
- **Object-wrapping cleanup**: Replace `{{ userId, meta }}` wrappers with
|
||||
separate args:
|
||||
`logger.info('User signed in', userId, meta)`
|
||||
|
||||
## DB Logging Rules
|
||||
|
||||
- `debug` **never** persists automatically.
|
||||
- `info|warn|error` persist automatically.
|
||||
- For DB-only events (no console), call `logger.toDb('Message',
|
||||
'info'|'warn'|'error')`.
|
||||
|
||||
## Exceptions (Tightly Scoped)
|
||||
|
||||
Allowed paths (still prefer logger):
|
||||
|
||||
- `**/*.test.*`, `**/*.spec.*`
|
||||
- `scripts/dev/**`, `scripts/migrate/**`
|
||||
|
||||
To intentionally keep `console.*`, add a pragma on the previous line:
|
||||
|
||||
```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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
11
.eslintrc.js
11
.eslintrc.js
@@ -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
182
doc/debug-hook-guide.md
Normal 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
|
||||
474
docs/PlatformServiceMixin-Interface-Consolidation.md
Normal file
474
docs/PlatformServiceMixin-Interface-Consolidation.md
Normal 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
|
||||
@@ -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'],
|
||||
|
||||
28
scripts/check-update-settings.sh
Executable file
28
scripts/check-update-settings.sh
Executable 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
103
scripts/git-hooks/README.md
Normal 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
|
||||
86
scripts/git-hooks/debug-checker.config
Normal file
86
scripts/git-hooks/debug-checker.config
Normal 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
252
scripts/git-hooks/pre-commit
Executable 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
171
scripts/install-debug-hook.sh
Executable 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"
|
||||
110
scripts/migrate-update-settings.js
Normal file
110
scripts/migrate-update-settings.js
Normal 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
117
scripts/test-debug-hook.sh
Executable 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"
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 & 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;
|
||||
|
||||
@@ -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 || "";
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user