Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c8e8d573d | ||
|
|
7231ad18a6 | ||
|
|
135023d17b | ||
|
|
28c541e682 | ||
|
|
4ea72162ec | ||
|
|
a6a461d358 | ||
|
|
6c1c109cbd | ||
|
|
cf41665629 | ||
|
|
63024f6e89 | ||
|
|
c2f2ef4a09 | ||
|
|
80a76dadb7 | ||
|
|
bdac9e0da3 | ||
|
|
0277b65caa | ||
|
|
453c791036 | ||
|
|
552196c18f | ||
|
|
17951d8cb8 | ||
|
|
09e6a7107a | ||
|
|
e172faaaf2 | ||
|
|
c3534b54ae | ||
|
|
211de332db | ||
|
|
628469b1bb | ||
|
|
4a63ff6838 | ||
|
|
6013b8e167 | ||
|
|
b2e678dc2f |
@@ -104,161 +104,6 @@ High-level meta-rules that bundle related sub-rules for specific workflows.
|
|||||||
- **`meta_bug_diagnosis.mdc`** - Bug investigation workflow bundling
|
- **`meta_bug_diagnosis.mdc`** - Bug investigation workflow bundling
|
||||||
- **`meta_bug_fixing.mdc`** - Bug fix implementation workflow bundling
|
- **`meta_bug_fixing.mdc`** - Bug fix implementation workflow bundling
|
||||||
- **`meta_feature_implementation.mdc`** - Feature implementation workflow bundling
|
- **`meta_feature_implementation.mdc`** - Feature implementation workflow bundling
|
||||||
- **`meta_research.mdc`** - Investigation and research workflow bundling
|
|
||||||
|
|
||||||
### **Workflow State Management**
|
|
||||||
|
|
||||||
The project uses a sophisticated workflow state management system to ensure systematic development processes and maintain code quality across all phases of development.
|
|
||||||
|
|
||||||
#### **Workflow State System**
|
|
||||||
|
|
||||||
The workflow state is managed through `.cursor/rules/.workflow_state.json` and enforces different modes with specific constraints. The system automatically tracks workflow progression and maintains a complete history of mode transitions.
|
|
||||||
|
|
||||||
**Available Modes**:
|
|
||||||
- **`diagnosis`** - Investigation and analysis phase (read-only)
|
|
||||||
- **`fixing`** - Implementation and bug fixing phase (full access)
|
|
||||||
- **`planning`** - Design and architecture phase (design only)
|
|
||||||
- **`research`** - Investigation and research phase (investigation only)
|
|
||||||
- **`documentation`** - Documentation writing phase (writing only)
|
|
||||||
|
|
||||||
**Mode Constraints**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"diagnosis": {
|
|
||||||
"mode": "read_only",
|
|
||||||
"forbidden": ["modify", "create", "build", "commit"],
|
|
||||||
"allowed": ["read", "search", "analyze", "document"]
|
|
||||||
},
|
|
||||||
"fixing": {
|
|
||||||
"mode": "implementation",
|
|
||||||
"forbidden": [],
|
|
||||||
"allowed": ["modify", "create", "build", "commit", "test"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Workflow History Tracking**:
|
|
||||||
|
|
||||||
The system automatically maintains a `workflowHistory` array that records all mode transitions and meta-rule invocations:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowHistory": [
|
|
||||||
{
|
|
||||||
"mode": "research",
|
|
||||||
"invoked": "meta_core_always_on.mdc",
|
|
||||||
"timestamp": "2025-08-25T02:14:37Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mode": "diagnosis",
|
|
||||||
"invoked": "meta_bug_diagnosis.mdc",
|
|
||||||
"timestamp": "2025-08-25T02:14:37Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**History Entry Format**:
|
|
||||||
- **`mode`**: The workflow mode that was activated
|
|
||||||
- **`invoked`**: The specific meta-rule that triggered the mode change
|
|
||||||
- **`timestamp`**: UTC timestamp when the mode transition occurred
|
|
||||||
|
|
||||||
**History Purpose**:
|
|
||||||
- **Workflow Continuity**: Track progression through development phases
|
|
||||||
- **Meta-Rule Usage**: Monitor which rules are invoked and when
|
|
||||||
- **Temporal Context**: Maintain chronological order of workflow changes
|
|
||||||
- **State Persistence**: Preserve workflow history across development sessions
|
|
||||||
- **Debugging Support**: Help diagnose workflow state issues
|
|
||||||
- **Process Analysis**: Understand development patterns and meta-rule effectiveness
|
|
||||||
|
|
||||||
#### **Commit Override System**
|
|
||||||
|
|
||||||
The workflow includes a flexible commit override mechanism that allows commits on demand while maintaining workflow integrity:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"overrides": {
|
|
||||||
"commit": {
|
|
||||||
"allowed": true,
|
|
||||||
"requires_override": true,
|
|
||||||
"override_reason": "user_requested"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Override Benefits**:
|
|
||||||
- ✅ **Investigation Commits**: Document findings during diagnosis phases
|
|
||||||
- ✅ **Work-in-Progress**: Commit partial solutions during complex investigations
|
|
||||||
- ✅ **Emergency Fixes**: Commit critical fixes without mode transitions
|
|
||||||
- ✅ **Flexible Workflow**: Maintain systematic approach while accommodating real needs
|
|
||||||
|
|
||||||
**Override Limitations**:
|
|
||||||
- ❌ **Does NOT bypass**: Version control rules, commit message standards, or security requirements
|
|
||||||
- ❌ **Does NOT bypass**: Code quality standards, testing requirements, or documentation requirements
|
|
||||||
|
|
||||||
#### **Workflow Enforcement**
|
|
||||||
|
|
||||||
The system automatically enforces workflow constraints through the core always-on rules:
|
|
||||||
|
|
||||||
**Before Every Interaction**:
|
|
||||||
1. **Read current workflow state** from `.cursor/rules/.workflow_state.json`
|
|
||||||
2. **Identify current mode** and its constraints
|
|
||||||
3. **Validate user request** against current mode constraints
|
|
||||||
4. **Enforce constraints** before generating response
|
|
||||||
5. **Guide model behavior** based on current mode
|
|
||||||
|
|
||||||
**Mode-Specific Enforcement**:
|
|
||||||
- **Diagnosis Mode**: Blocks modification, creation, building, and commits
|
|
||||||
- **Fixing Mode**: Allows full implementation and testing capabilities
|
|
||||||
- **Planning Mode**: Focuses on design and architecture, blocks implementation
|
|
||||||
- **Research Mode**: Enables investigation and analysis, blocks modification
|
|
||||||
- **Documentation Mode**: Allows writing and editing, blocks implementation
|
|
||||||
|
|
||||||
#### **Workflow Transitions**
|
|
||||||
|
|
||||||
To change workflow modes, invoke the appropriate meta-rule:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Switch to bug fixing mode
|
|
||||||
@meta_bug_fixing.mdc
|
|
||||||
|
|
||||||
# Switch to feature planning mode
|
|
||||||
@meta_feature_planning.mdc
|
|
||||||
|
|
||||||
# Switch to documentation mode
|
|
||||||
@meta_documentation.mdc
|
|
||||||
```
|
|
||||||
|
|
||||||
**Transition Requirements**:
|
|
||||||
- **Mode Changes**: Require explicit meta-rule invocation
|
|
||||||
- **State Updates**: Automatically update workflow state file
|
|
||||||
- **Constraint Enforcement**: Immediately apply new mode constraints
|
|
||||||
- **History Tracking**: Automatically maintained in `workflowHistory` array
|
|
||||||
- **Timestamp Recording**: Each transition recorded with UTC timestamp
|
|
||||||
|
|
||||||
#### **Integration with Development Process**
|
|
||||||
|
|
||||||
The workflow system integrates seamlessly with existing development practices:
|
|
||||||
|
|
||||||
**Version Control**:
|
|
||||||
- All commits must follow TimeSafari commit message standards
|
|
||||||
- Security audit checklists are enforced regardless of workflow mode
|
|
||||||
- Documentation updates are required for substantial changes
|
|
||||||
|
|
||||||
**Quality Assurance**:
|
|
||||||
- Code quality standards (PEP8, TypeScript, etc.) are always enforced
|
|
||||||
- Testing requirements apply to all implementation work
|
|
||||||
- Documentation standards are maintained across all phases
|
|
||||||
|
|
||||||
**Build System**:
|
|
||||||
- Build Architecture Guard protects critical build files
|
|
||||||
- Platform-specific build processes respect workflow constraints
|
|
||||||
- Asset generation follows established patterns
|
|
||||||
|
|
||||||
**Migration Context**:
|
|
||||||
- Database migration work respects investigation vs. implementation phases
|
|
||||||
- Component migration progress is tracked through workflow states
|
|
||||||
|
|
||||||
## Usage Guidelines
|
## Usage Guidelines
|
||||||
|
|
||||||
|
|||||||
@@ -1,192 +0,0 @@
|
|||||||
# Meta-Rule: Core Always-On Rules
|
|
||||||
|
|
||||||
**Author**: Matthew Raymer
|
|
||||||
**Date**: 2025-08-21
|
|
||||||
**Status**: 🎯 **ACTIVE** - Core rules for every prompt
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This meta-rule bundles the core rules that should be applied to **every single
|
|
||||||
prompt** because they define fundamental behaviors, principles, and context
|
|
||||||
that are essential for all AI interactions.
|
|
||||||
|
|
||||||
## When to Use
|
|
||||||
|
|
||||||
**ALWAYS** - These rules apply to every single prompt, regardless of the task
|
|
||||||
or context. They form the foundation for all AI assistant behavior.
|
|
||||||
|
|
||||||
## Bundled Rules
|
|
||||||
|
|
||||||
### **Core Human Competence Principles**
|
|
||||||
|
|
||||||
- **`core/base_context.mdc`** - Human competence first principles, interaction
|
|
||||||
guidelines, and output contract requirements
|
|
||||||
- **`core/less_complex.mdc`** - Minimalist solution principle and complexity
|
|
||||||
guidelines
|
|
||||||
|
|
||||||
### **Time & Context Standards**
|
|
||||||
|
|
||||||
- **`development/time.mdc`** - Time handling principles and UTC standards
|
|
||||||
- **`development/time_examples.mdc`** - Practical time implementation examples
|
|
||||||
- **`development/time_implementation.mdc`** - Detailed time implementation
|
|
||||||
guidelines
|
|
||||||
|
|
||||||
### **Version Control & Process**
|
|
||||||
|
|
||||||
- **`workflow/version_control.mdc`** - Version control principles and commit
|
|
||||||
guidelines
|
|
||||||
- **`workflow/commit_messages.mdc`** - Commit message format and conventions
|
|
||||||
|
|
||||||
### **Application Context**
|
|
||||||
|
|
||||||
- **`app/timesafari.mdc`** - Core TimeSafari application context and
|
|
||||||
development principles
|
|
||||||
- **`app/timesafari_development.mdc`** - TimeSafari-specific development
|
|
||||||
workflow and quality standards
|
|
||||||
|
|
||||||
## Why These Rules Are Always-On
|
|
||||||
|
|
||||||
### **Base Context**
|
|
||||||
|
|
||||||
- **Human Competence First**: Every interaction must increase human competence
|
|
||||||
- **Output Contract**: All responses must follow the required structure
|
|
||||||
- **Competence Hooks**: Learning and collaboration must be built into every response
|
|
||||||
|
|
||||||
### **Time Standards**
|
|
||||||
|
|
||||||
- **UTC Consistency**: All timestamps must use UTC for system operations
|
|
||||||
- **Evidence Collection**: Time context is essential for debugging and investigation
|
|
||||||
- **Cross-Platform**: Time handling affects all platforms and features
|
|
||||||
|
|
||||||
### **Version Control**
|
|
||||||
|
|
||||||
- **Commit Standards**: Every code change must follow commit message conventions
|
|
||||||
- **Process Consistency**: Version control affects all development work
|
|
||||||
- **Team Collaboration**: Commit standards enable effective team communication
|
|
||||||
|
|
||||||
### **Application Context**
|
|
||||||
|
|
||||||
- **Platform Awareness**: Every task must consider web/mobile/desktop platforms
|
|
||||||
- **Architecture Principles**: All work must follow TimeSafari patterns
|
|
||||||
- **Development Standards**: Quality and testing requirements apply to all work
|
|
||||||
|
|
||||||
## Application Priority
|
|
||||||
|
|
||||||
### **Primary (Apply First)**
|
|
||||||
|
|
||||||
1. **Base Context** - Human competence and output contract
|
|
||||||
2. **Time Standards** - UTC and timestamp requirements
|
|
||||||
3. **Application Context** - TimeSafari principles and platforms
|
|
||||||
|
|
||||||
### **Secondary (Apply as Needed)**
|
|
||||||
|
|
||||||
1. **Version Control** - When making code changes
|
|
||||||
2. **Complexity Guidelines** - When evaluating solution approaches
|
|
||||||
|
|
||||||
## Integration with Other Meta-Rules
|
|
||||||
|
|
||||||
### **Feature Planning**
|
|
||||||
|
|
||||||
- Base context ensures human competence focus
|
|
||||||
- Time standards inform planning and estimation
|
|
||||||
- Application context drives platform considerations
|
|
||||||
|
|
||||||
### **Bug Diagnosis**
|
|
||||||
|
|
||||||
- Base context ensures systematic investigation
|
|
||||||
- Time standards enable proper evidence collection
|
|
||||||
- Application context provides system understanding
|
|
||||||
|
|
||||||
### **Bug Fixing**
|
|
||||||
|
|
||||||
- Base context ensures quality implementation
|
|
||||||
- Time standards maintain logging consistency
|
|
||||||
- Application context guides testing strategy
|
|
||||||
|
|
||||||
### **Feature Implementation**
|
|
||||||
|
|
||||||
- Base context ensures proper development approach
|
|
||||||
- Time standards maintain system consistency
|
|
||||||
- Application context drives architecture decisions
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
- [ ] **Base context applied** to every single prompt
|
|
||||||
- [ ] **Time standards followed** for all timestamps and logging
|
|
||||||
- [ ] **Version control standards** applied to all code changes
|
|
||||||
- [ ] **Application context considered** for all platform work
|
|
||||||
- [ ] **Human competence focus** maintained in all interactions
|
|
||||||
- [ ] **Output contract structure** followed in all responses
|
|
||||||
|
|
||||||
## Common Pitfalls
|
|
||||||
|
|
||||||
- **Don't skip base context** - loses human competence focus
|
|
||||||
- **Don't ignore time standards** - creates inconsistent timestamps
|
|
||||||
- **Don't forget application context** - misses platform considerations
|
|
||||||
- **Don't skip version control** - creates inconsistent commit history
|
|
||||||
- **Don't lose competence focus** - reduces learning value
|
|
||||||
|
|
||||||
## Feedback & Improvement
|
|
||||||
|
|
||||||
### **Rule Effectiveness Ratings (1-5 scale)**
|
|
||||||
|
|
||||||
- **Base Context**: ___/5 - Comments: _______________
|
|
||||||
- **Time Standards**: ___/5 - Comments: _______________
|
|
||||||
- **Version Control**: ___/5 - Comments: _______________
|
|
||||||
- **Application Context**: ___/5 - Comments: _______________
|
|
||||||
|
|
||||||
### **Always-On Effectiveness**
|
|
||||||
|
|
||||||
- **Consistency**: Are these rules applied consistently across all prompts?
|
|
||||||
- **Value**: Do these rules add value to every interaction?
|
|
||||||
- **Overhead**: Are these rules too burdensome for simple tasks?
|
|
||||||
|
|
||||||
### **Integration Feedback**
|
|
||||||
|
|
||||||
- **With Other Meta-Rules**: How well do these integrate with workflow rules?
|
|
||||||
- **Context Switching**: Do these rules help or hinder context switching?
|
|
||||||
- **Learning Curve**: Are these rules easy for new users to understand?
|
|
||||||
|
|
||||||
### **Overall Experience**
|
|
||||||
|
|
||||||
- **Quality Improvement**: Do these rules improve response quality?
|
|
||||||
- **Efficiency**: Do these rules make interactions more efficient?
|
|
||||||
- **Recommendation**: Would you recommend keeping these always-on?
|
|
||||||
|
|
||||||
## Model Implementation Checklist
|
|
||||||
|
|
||||||
### Before Every Prompt
|
|
||||||
|
|
||||||
- [ ] **Base Context**: Ensure human competence principles are active
|
|
||||||
- [ ] **Time Standards**: Verify UTC and timestamp requirements are clear
|
|
||||||
- [ ] **Application Context**: Confirm TimeSafari context is loaded
|
|
||||||
- [ ] **Version Control**: Prepare commit standards if code changes are needed
|
|
||||||
|
|
||||||
### During Response Creation
|
|
||||||
|
|
||||||
- [ ] **Output Contract**: Follow required response structure
|
|
||||||
- [ ] **Competence Hooks**: Include learning and collaboration elements
|
|
||||||
- [ ] **Time Consistency**: Apply UTC standards for all time references
|
|
||||||
- [ ] **Platform Awareness**: Consider all target platforms
|
|
||||||
|
|
||||||
### After Response Creation
|
|
||||||
|
|
||||||
- [ ] **Validation**: Verify all always-on rules were applied
|
|
||||||
- [ ] **Quality Check**: Ensure response meets competence standards
|
|
||||||
- [ ] **Context Review**: Confirm application context was properly considered
|
|
||||||
- [ ] **Feedback Collection**: Note any issues with always-on application
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**See also**:
|
|
||||||
|
|
||||||
- `.cursor/rules/meta_feature_planning.mdc` for workflow-specific rules
|
|
||||||
- `.cursor/rules/meta_bug_diagnosis.mdc` for investigation workflows
|
|
||||||
- `.cursor/rules/meta_bug_fixing.mdc` for fix implementation
|
|
||||||
- `.cursor/rules/meta_feature_implementation.mdc` for feature development
|
|
||||||
|
|
||||||
**Status**: Active core always-on meta-rule
|
|
||||||
**Priority**: Critical (applies to every prompt)
|
|
||||||
**Estimated Effort**: Ongoing reference
|
|
||||||
**Dependencies**: All bundled sub-rules
|
|
||||||
**Stakeholders**: All AI interactions, Development team
|
|
||||||
@@ -21,7 +21,7 @@ alwaysApply: false
|
|||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
All interactions must _increase the human's competence over time_ while
|
All interactions must *increase the human's competence over time* while
|
||||||
completing the task efficiently. The model may handle menial work and memory
|
completing the task efficiently. The model may handle menial work and memory
|
||||||
extension, but must also promote learning, autonomy, and healthy work habits.
|
extension, but must also promote learning, autonomy, and healthy work habits.
|
||||||
The model should also **encourage human interaction and collaboration** rather
|
The model should also **encourage human interaction and collaboration** rather
|
||||||
@@ -31,7 +31,7 @@ machine-driven steps.
|
|||||||
|
|
||||||
## Principles
|
## Principles
|
||||||
|
|
||||||
1. Competence over convenience: finish the task _and_ leave the human more
|
1. Competence over convenience: finish the task *and* leave the human more
|
||||||
|
|
||||||
capable next time.
|
capable next time.
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ assumptions if unanswered.
|
|||||||
|
|
||||||
### timebox_minutes
|
### timebox_minutes
|
||||||
|
|
||||||
_integer or null_ — When set to a positive integer (e.g., `5`), this acts
|
*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
|
as a **time budget** guiding the model to prioritize delivering the most
|
||||||
essential parts of the task within that constraint.
|
essential parts of the task within that constraint.
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ Behavior when set:
|
|||||||
|
|
||||||
3. **Signal Skipped Depth** — Omitted details should be listed under
|
3. **Signal Skipped Depth** — Omitted details should be listed under
|
||||||
|
|
||||||
_Deferred for depth_.
|
*Deferred for depth*.
|
||||||
|
|
||||||
4. **Order by Value** — Start with blocking or high-value items, then
|
4. **Order by Value** — Start with blocking or high-value items, then
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ Default: Doer + short Mentor notes.
|
|||||||
|
|
||||||
## Self-Check (model, before responding)
|
## Self-Check (model, before responding)
|
||||||
|
|
||||||
- [ ] Task done _and_ at least one competence lever included (≤120 words
|
- [ ] Task done *and* at least one competence lever included (≤120 words
|
||||||
total)
|
total)
|
||||||
- [ ] At least one collaboration/discussion hook present
|
- [ ] At least one collaboration/discussion hook present
|
||||||
- [ ] Output follows the **Output Contract** sections
|
- [ ] Output follows the **Output Contract** sections
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ evidence-backed steps**.
|
|||||||
- **Verifiable Outputs**: Include expected results, status codes, or
|
- **Verifiable Outputs**: Include expected results, status codes, or
|
||||||
error messages
|
error messages
|
||||||
|
|
||||||
- **Cite evidence** for _Works/Doesn't_ items (timestamps, filenames,
|
- **Cite evidence** for *Works/Doesn't* items (timestamps, filenames,
|
||||||
line numbers, IDs/status codes, or logs).
|
line numbers, IDs/status codes, or logs).
|
||||||
|
|
||||||
## Required Sections
|
## Required Sections
|
||||||
@@ -181,8 +181,8 @@ Before publishing, verify:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Status**: 🚢 ACTIVE — General ruleset extending _Base Context — Human
|
**Status**: 🚢 ACTIVE — General ruleset extending *Base Context — Human
|
||||||
Competence First_
|
Competence First*
|
||||||
|
|
||||||
**Priority**: Critical
|
**Priority**: Critical
|
||||||
**Estimated Effort**: Ongoing reference
|
**Estimated Effort**: Ongoing reference
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ language: Match repository languages and conventions
|
|||||||
where it occurs; avoid new layers, indirection, or patterns unless
|
where it occurs; avoid new layers, indirection, or patterns unless
|
||||||
strictly necessary.
|
strictly necessary.
|
||||||
2. **Keep scope tight.** Implement only what is needed to satisfy the
|
2. **Keep scope tight.** Implement only what is needed to satisfy the
|
||||||
acceptance criteria and tests for _this_ issue.
|
acceptance criteria and tests for *this* issue.
|
||||||
3. **Avoid speculative abstractions.** Use the **Rule of Three**:
|
3. **Avoid speculative abstractions.** Use the **Rule of Three**:
|
||||||
don't extract helpers/patterns until the third concrete usage proves
|
don't extract helpers/patterns until the third concrete usage proves
|
||||||
the shape.
|
the shape.
|
||||||
@@ -29,7 +29,7 @@ language: Match repository languages and conventions
|
|||||||
7. **Targeted tests only.** Add the smallest set of tests that prove
|
7. **Targeted tests only.** Add the smallest set of tests that prove
|
||||||
the fix and guard against regression; don't rewrite suites.
|
the fix and guard against regression; don't rewrite suites.
|
||||||
8. **Document the "why enough."** Include a one-paragraph note
|
8. **Document the "why enough."** Include a one-paragraph note
|
||||||
explaining why this minimal solution is sufficient _now_.
|
explaining why this minimal solution is sufficient *now*.
|
||||||
|
|
||||||
## Future-Proofing Requires Evidence + Discussion
|
## Future-Proofing Requires Evidence + Discussion
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ alwaysApply: false
|
|||||||
**Date**: 2025-08-19
|
**Date**: 2025-08-19
|
||||||
**Status**: 🎯 **ACTIVE** - Asset management guidelines
|
**Status**: 🎯 **ACTIVE** - Asset management guidelines
|
||||||
|
|
||||||
_Scope: Assets Only (icons, splashes, image pipelines) — not overall build
|
*Scope: Assets Only (icons, splashes, image pipelines) — not overall build
|
||||||
orchestration_
|
orchestration*
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ feature development, issue investigations, ADRs, and documentation**.
|
|||||||
|
|
||||||
`2025-08-17`).
|
`2025-08-17`).
|
||||||
|
|
||||||
- Avoid ambiguous terms like _recently_, _last month_, or _soon_.
|
- Avoid ambiguous terms like *recently*, *last month*, or *soon*.
|
||||||
|
|
||||||
- For time-based experiments (e.g., A/B tests), always include:
|
- For time-based experiments (e.g., A/B tests), always include:
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
- Optionally provide UTC alongside if context requires cross-team clarity.
|
- Optionally provide UTC alongside if context requires cross-team clarity.
|
||||||
|
|
||||||
- When interpreting relative terms like _now_, _today_, _last week_:
|
- When interpreting relative terms like *now*, *today*, *last week*:
|
||||||
|
|
||||||
- Resolve them against the **developer's current time**.
|
- Resolve them against the **developer's current time**.
|
||||||
|
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
---
|
|
||||||
title: Documentation, References, and Model Agent Use
|
|
||||||
version: 1.1
|
|
||||||
alwaysApply: true
|
|
||||||
scope: code, project-plans
|
|
||||||
---
|
|
||||||
|
|
||||||
# Directive on Documentation, References, and Model Agent Use in Code and Project Plans
|
|
||||||
|
|
||||||
To ensure clarity, efficiency, and high-value documentation within code and project plans—and to leverage **model agents** (AI- or automation-based assistants) effectively—contributors must follow these rules:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Documentation and References Must Add Clear Value
|
|
||||||
|
|
||||||
- Only include documentation, comments, or reference links when they provide _new, meaningful information_ that assists understanding or decision-making.
|
|
||||||
- Avoid duplicating content already obvious in the codebase, version history, or linked project documents.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Eliminate Redundant or Noisy References
|
|
||||||
|
|
||||||
- Remove references that serve no purpose beyond filling space.
|
|
||||||
- Model agents may automatically flag and suggest removal of trivial references (e.g., links to unchanged boilerplate or self-evident context).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Explicit Role of Model Agents
|
|
||||||
|
|
||||||
Model agents are **active participants** in documentation quality control. Their tasks include:
|
|
||||||
|
|
||||||
- **Relevance Evaluation**: Automatically analyze references for their substantive contribution before inclusion.
|
|
||||||
- **Redundancy Detection**: Flag duplicate or trivial references across commits, files, or tasks.
|
|
||||||
- **Context Linking**: Suggest appropriate higher-level docs (designs, ADRs, meeting notes) when a code change touches multi-stage or cross-team items.
|
|
||||||
- **Placement Optimization**: Recommend centralization of references (e.g., in plan overviews, ADRs, or merge commit messages) rather than scattered low-value inline references.
|
|
||||||
- **Consistency Monitoring**: Ensure references align with team standards (e.g., ADR template, architecture repo, or external policy documents).
|
|
||||||
|
|
||||||
Contributors must treat agent recommendations as **first-pass reviews** but remain accountable for final human judgment.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Contextual References for Complex Items
|
|
||||||
|
|
||||||
- Use **centralized references** for multi-stage features (e.g., architectural docs, research threads).
|
|
||||||
- Keep inline code comments light; push broader context into centralized documents.
|
|
||||||
- Model agents may auto-summarize complex chains of discussion and attach them as a single reference point.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Centralization of Broader Context
|
|
||||||
|
|
||||||
- Store overarching context (design docs, proposals, workflows) in accessible, well-indexed places.
|
|
||||||
- Model agents should assist by **generating reference maps** that track where docs are cited across the codebase.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Focused Documentation
|
|
||||||
|
|
||||||
- Documentation should explain **why** and **how** decisions are made, not just what was changed.
|
|
||||||
- Model agents can auto-generate first-pass explanations from commit metadata, diffs, and linked issues—but humans must refine them for accuracy and intent.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Review and Accountability
|
|
||||||
|
|
||||||
- Reviewers and team leads must reject submissions containing unnecessary or low-quality documentation.
|
|
||||||
- Model agent outputs are aids, not replacements—contributors remain responsible for **final clarity and relevance**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Continuous Improvement and Agent Feedback Loops
|
|
||||||
|
|
||||||
- Encourage iterative development of model agents so their evaluations become more precise over time.
|
|
||||||
- Contributions should include **feedback on agent suggestions** (e.g., accepted, rejected, or corrected) to train better future outputs.
|
|
||||||
- Agents should log patterns of “rejected” suggestions for refinement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Workflow Overview (Mermaid Diagram)
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
A[Contributor] -->|Writes Code & Draft Docs| B[Model Agent]
|
|
||||||
B -->|Evaluates References| C{Relevant?}
|
|
||||||
C -->|Yes| D[Suggest Placement & Context Links]
|
|
||||||
C -->|No| E[Flag Redundancy / Noise]
|
|
||||||
D --> F[Contributor Refines Docs]
|
|
||||||
E --> F
|
|
||||||
F --> G[Reviewer]
|
|
||||||
G -->|Approves / Requests Revisions| H[Final Documentation]
|
|
||||||
G -->|Feedback on Agent Suggestions| B
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
✅ **Outcome:** By integrating disciplined contributor standards with **model agent augmentation**, the team achieves documentation that is consistently _relevant, concise, centralized, and decision-focused_. AI ensures coverage and noise reduction, while humans ensure precision and judgment.
|
|
||||||
@@ -192,7 +192,6 @@ Summary of key concepts and skills.
|
|||||||
|
|
||||||
Where to apply this knowledge next.
|
Where to apply this knowledge next.
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] Integration tests
|
- [ ] Integration tests
|
||||||
- [ ] E2E tests
|
- [ ] E2E tests
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,9 @@ inherits: base_context.mdc
|
|||||||
|
|
||||||
**Author**: System/Shared
|
**Author**: System/Shared
|
||||||
**Date**: 2025-08-21 (UTC)
|
**Date**: 2025-08-21 (UTC)
|
||||||
**Status**: 🚢 ACTIVE — General ruleset extending _Base Context — Human Competence First_
|
**Status**: 🚢 ACTIVE — General ruleset extending *Base Context — Human Competence First*
|
||||||
|
|
||||||
> **Alignment with Base Context**
|
> **Alignment with Base Context**
|
||||||
>
|
|
||||||
> - **Purpose fit**: Prioritizes human competence and collaboration while delivering reproducible artifacts.
|
> - **Purpose fit**: Prioritizes human competence and collaboration while delivering reproducible artifacts.
|
||||||
> - **Output Contract**: This directive **adds universal constraints** for any technical topic while **inheriting** the Base Context contract sections.
|
> - **Output Contract**: This directive **adds universal constraints** for any technical topic while **inheriting** the Base Context contract sections.
|
||||||
> - **Toggles honored**: Uses the same toggle semantics; defaults above can be overridden by the caller.
|
> - **Toggles honored**: Uses the same toggle semantics; defaults above can be overridden by the caller.
|
||||||
@@ -27,11 +26,9 @@ inherits: base_context.mdc
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
Produce a **developer-grade, reproducible guide** for any technical topic that onboards a competent practitioner **without meta narration** and **with evidence-backed steps**.
|
Produce a **developer-grade, reproducible guide** for any technical topic that onboards a competent practitioner **without meta narration** and **with evidence-backed steps**.
|
||||||
|
|
||||||
## Scope & Constraints
|
## Scope & Constraints
|
||||||
|
|
||||||
- **One Markdown document** as the deliverable.
|
- **One Markdown document** as the deliverable.
|
||||||
- Use **absolute dates** in **UTC** (e.g., `2025-08-21T14:22Z`) — avoid “today/yesterday”.
|
- Use **absolute dates** in **UTC** (e.g., `2025-08-21T14:22Z`) — avoid “today/yesterday”.
|
||||||
- Include at least **one diagram** (Mermaid preferred). Choose the most fitting type:
|
- Include at least **one diagram** (Mermaid preferred). Choose the most fitting type:
|
||||||
@@ -40,11 +37,10 @@ Produce a **developer-grade, reproducible guide** for any technical topic that o
|
|||||||
- **APIs**: `curl` + one client library (e.g., `httpx` for Python).
|
- **APIs**: `curl` + one client library (e.g., `httpx` for Python).
|
||||||
- **CLIs**: literal command blocks and expected output snippets.
|
- **CLIs**: literal command blocks and expected output snippets.
|
||||||
- **Code**: minimal, self-contained samples (language appropriate).
|
- **Code**: minimal, self-contained samples (language appropriate).
|
||||||
- Cite **evidence** for _Works/Doesn’t_ items (timestamps, filenames, line numbers, IDs/status codes, or logs).
|
- Cite **evidence** for *Works/Doesn’t* items (timestamps, filenames, line numbers, IDs/status codes, or logs).
|
||||||
- If something is unknown, output `TODO:<missing>` — **never invent**.
|
- If something is unknown, output `TODO:<missing>` — **never invent**.
|
||||||
|
|
||||||
## Required Sections (extends Base Output Contract)
|
## Required Sections (extends Base Output Contract)
|
||||||
|
|
||||||
Follow this exact order **after** the Base Contract’s **Objective → Result → Use/Run** headers:
|
Follow this exact order **after** the Base Contract’s **Objective → Result → Use/Run** headers:
|
||||||
|
|
||||||
1. **Context & Scope**
|
1. **Context & Scope**
|
||||||
@@ -56,9 +52,9 @@ Follow this exact order **after** the Base Contract’s **Objective → Result
|
|||||||
4. **Architecture / Process Overview**
|
4. **Architecture / Process Overview**
|
||||||
- Short prose + **one diagram** selected from the list above.
|
- Short prose + **one diagram** selected from the list above.
|
||||||
5. **Interfaces & Contracts (choose one)**
|
5. **Interfaces & Contracts (choose one)**
|
||||||
- **API-based**: Endpoint table (_Step, Method, Path/URL, Auth, Key Headers/Params, Sample Req/Resp ref_).
|
- **API-based**: Endpoint table (*Step, Method, Path/URL, Auth, Key Headers/Params, Sample Req/Resp ref*).
|
||||||
- **Data/Files**: I/O contract table (_Source, Format, Schema/Columns, Size, Validation rules_).
|
- **Data/Files**: I/O contract table (*Source, Format, Schema/Columns, Size, Validation rules*).
|
||||||
- **Systems/Hardware**: Interfaces table (_Port/Bus, Protocol, Voltage/Timing, Constraints_).
|
- **Systems/Hardware**: Interfaces table (*Port/Bus, Protocol, Voltage/Timing, Constraints*).
|
||||||
6. **Repro: End-to-End Procedure**
|
6. **Repro: End-to-End Procedure**
|
||||||
- Minimal copy-paste steps with code/commands and **expected outputs**.
|
- Minimal copy-paste steps with code/commands and **expected outputs**.
|
||||||
7. **What Works (with Evidence)**
|
7. **What Works (with Evidence)**
|
||||||
@@ -73,19 +69,16 @@ Follow this exact order **after** the Base Contract’s **Objective → Result
|
|||||||
- Canonical docs, specs, tickets, prior analyses.
|
- Canonical docs, specs, tickets, prior analyses.
|
||||||
|
|
||||||
> **Competence Hooks (per Base Context; keep lightweight):**
|
> **Competence Hooks (per Base Context; keep lightweight):**
|
||||||
>
|
> - *Why this works* (≤3 bullets) — core invariants or guarantees.
|
||||||
> - _Why this works_ (≤3 bullets) — core invariants or guarantees.
|
> - *Common pitfalls* (≤3 bullets) — the traps we saw in evidence.
|
||||||
> - _Common pitfalls_ (≤3 bullets) — the traps we saw in evidence.
|
> - *Next skill unlock* (1 line) — the next capability to implement/learn.
|
||||||
> - _Next skill unlock_ (1 line) — the next capability to implement/learn.
|
> - *Teach-back* (1 line) — prompt the reader to restate the flow/architecture.
|
||||||
> - _Teach-back_ (1 line) — prompt the reader to restate the flow/architecture.
|
|
||||||
|
|
||||||
> **Collaboration Hooks (per Base Context):**
|
> **Collaboration Hooks (per Base Context):**
|
||||||
>
|
|
||||||
> - Name reviewers for **Interfaces & Contracts** and the **diagram**.
|
> - Name reviewers for **Interfaces & Contracts** and the **diagram**.
|
||||||
> - Short **sign-off checklist** before merging/publishing the guide.
|
> - Short **sign-off checklist** before merging/publishing the guide.
|
||||||
|
|
||||||
## Do / Don’t (Base-aligned)
|
## Do / Don’t (Base-aligned)
|
||||||
|
|
||||||
- **Do** quantify progress only against a defined scope with acceptance criteria.
|
- **Do** quantify progress only against a defined scope with acceptance criteria.
|
||||||
- **Do** include minimal sample payloads/headers or I/O schemas; redact sensitive values.
|
- **Do** include minimal sample payloads/headers or I/O schemas; redact sensitive values.
|
||||||
- **Do** keep commentary lean; if timeboxed, move depth to **Deferred for depth**.
|
- **Do** keep commentary lean; if timeboxed, move depth to **Deferred for depth**.
|
||||||
@@ -93,7 +86,6 @@ Follow this exact order **after** the Base Contract’s **Objective → Result
|
|||||||
- **Don’t** include IDE-specific chatter or internal rules unrelated to the task.
|
- **Don’t** include IDE-specific chatter or internal rules unrelated to the task.
|
||||||
|
|
||||||
## Validation Checklist (self-check before returning)
|
## Validation Checklist (self-check before returning)
|
||||||
|
|
||||||
- [ ] All Required Sections present and ordered.
|
- [ ] All Required Sections present and ordered.
|
||||||
- [ ] Diagram compiles (basic Mermaid syntax) and fits the problem.
|
- [ ] Diagram compiles (basic Mermaid syntax) and fits the problem.
|
||||||
- [ ] If API-based, **Auth** and **Key Headers/Params** are listed for each endpoint.
|
- [ ] If API-based, **Auth** and **Key Headers/Params** are listed for each endpoint.
|
||||||
@@ -104,7 +96,6 @@ Follow this exact order **after** the Base Contract’s **Objective → Result
|
|||||||
- [ ] Base **Output Contract** sections satisfied (Objective/Result/Use/Run/Competence/Collaboration/Assumptions/References).
|
- [ ] Base **Output Contract** sections satisfied (Objective/Result/Use/Run/Competence/Collaboration/Assumptions/References).
|
||||||
|
|
||||||
## Universal Template (fill-in)
|
## Universal Template (fill-in)
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# <Title> — Working Notes (As of YYYY-MM-DDTHH:MMZ)
|
# <Title> — Working Notes (As of YYYY-MM-DDTHH:MMZ)
|
||||||
|
|
||||||
@@ -141,46 +132,37 @@ Follow this exact order **after** the Base Contract’s **Objective → Result
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Interfaces & Contracts
|
## Interfaces & Contracts
|
||||||
|
|
||||||
### If API-based
|
### If API-based
|
||||||
|
|
||||||
| Step | Method | Path/URL | Auth | Key Headers/Params | Sample |
|
| Step | Method | Path/URL | Auth | Key Headers/Params | Sample |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| <…> | <…> | <…> | <…> | <…> | below |
|
| <…> | <…> | <…> | <…> | <…> | below |
|
||||||
|
|
||||||
### If Data/Files
|
### If Data/Files
|
||||||
|
|
||||||
| Source | Format | Schema/Columns | Size | Validation |
|
| Source | Format | Schema/Columns | Size | Validation |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| <…> | <…> | <…> | <…> | <…> |
|
| <…> | <…> | <…> | <…> | <…> |
|
||||||
|
|
||||||
### If Systems/Hardware
|
### If Systems/Hardware
|
||||||
|
|
||||||
| Interface | Protocol | Timing/Voltage | Constraints | Notes |
|
| Interface | Protocol | Timing/Voltage | Constraints | Notes |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| <…> | <…> | <…> | <…> | <…> |
|
| <…> | <…> | <…> | <…> | <…> |
|
||||||
|
|
||||||
## Repro: End-to-End Procedure
|
## Repro: End-to-End Procedure
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# commands / curl examples (redacted where necessary)
|
# commands / curl examples (redacted where necessary)
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# minimal client library example (language appropriate)
|
# minimal client library example (language appropriate)
|
||||||
```
|
```
|
||||||
|
|
||||||
> Expected output: <snippet/checks>
|
> Expected output: <snippet/checks>
|
||||||
|
|
||||||
## What Works (Evidence)
|
## What Works (Evidence)
|
||||||
|
|
||||||
- ✅ <short statement>
|
- ✅ <short statement>
|
||||||
- **Time**: <YYYY-MM-DDTHH:MMZ>
|
- **Time**: <YYYY-MM-DDTHH:MMZ>
|
||||||
- **Evidence**: file/line/log or request id/status
|
- **Evidence**: file/line/log or request id/status
|
||||||
- **Verify at**: <where>
|
- **Verify at**: <where>
|
||||||
|
|
||||||
## What Doesn’t (Evidence & Hypotheses)
|
## What Doesn’t (Evidence & Hypotheses)
|
||||||
|
|
||||||
- ❌ <short failure> at `<component/endpoint/file>`
|
- ❌ <short failure> at `<component/endpoint/file>`
|
||||||
- **Time**: <YYYY-MM-DDTHH:MMZ>
|
- **Time**: <YYYY-MM-DDTHH:MMZ>
|
||||||
- **Evidence**: <snippet/id/status>
|
- **Evidence**: <snippet/id/status>
|
||||||
@@ -188,46 +170,38 @@ Follow this exact order **after** the Base Contract’s **Objective → Result
|
|||||||
- **Next probe**: <short>
|
- **Next probe**: <short>
|
||||||
|
|
||||||
## Risks, Limits, Assumptions
|
## Risks, Limits, Assumptions
|
||||||
|
|
||||||
<bullets: limits, security boundaries, retries/backoff, idempotency, SLOs>
|
<bullets: limits, security boundaries, retries/backoff, idempotency, SLOs>
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| <name> | <action> | <measurable outcome> | <YYYY-MM-DD> |
|
| <name> | <action> | <measurable outcome> | <YYYY-MM-DD> |
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
<links/titles>
|
<links/titles>
|
||||||
|
|
||||||
## Competence Hooks
|
## Competence Hooks
|
||||||
|
- *Why this works*: <≤3 bullets>
|
||||||
- _Why this works_: <≤3 bullets>
|
- *Common pitfalls*: <≤3 bullets>
|
||||||
- _Common pitfalls_: <≤3 bullets>
|
- *Next skill unlock*: <1 line>
|
||||||
- _Next skill unlock_: <1 line>
|
- *Teach-back*: <1 line>
|
||||||
- _Teach-back_: <1 line>
|
|
||||||
|
|
||||||
## Collaboration Hooks
|
## Collaboration Hooks
|
||||||
|
|
||||||
- Reviewers: <names/roles>
|
- Reviewers: <names/roles>
|
||||||
- Sign-off checklist: <≤5 checks>
|
- Sign-off checklist: <≤5 checks>
|
||||||
|
|
||||||
## Assumptions & Limits
|
## Assumptions & Limits
|
||||||
|
|
||||||
<bullets>
|
<bullets>
|
||||||
|
|
||||||
## Deferred for depth
|
## Deferred for depth
|
||||||
|
|
||||||
<park deeper material here to respect timeboxing>
|
<park deeper material here to respect timeboxing>
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Notes for Implementers:**
|
**Notes for Implementers:**
|
||||||
|
- Respect Base *Do-Not* (no filler, no invented facts, no censorship).
|
||||||
- Respect Base _Do-Not_ (no filler, no invented facts, no censorship).
|
|
||||||
- Prefer clarity over completeness when timeboxed; capture unknowns explicitly.
|
- Prefer clarity over completeness when timeboxed; capture unknowns explicitly.
|
||||||
- Apply historical comment management rules (see `.cursor/rules/historical_comment_management.mdc`)
|
- Apply historical comment management rules (see `.cursor/rules/historical_comment_management.mdc`)
|
||||||
- Apply realistic time estimation rules (see `.cursor/rules/realistic_time_estimation.mdc`)
|
- Apply realistic time estimation rules (see `.cursor/rules/realistic_time_estimation.mdc`)
|
||||||
- Apply Playwright test investigation rules (see `.cursor/rules/playwright_test_investigation.mdc`)
|
- Apply Playwright test investigation rules (see `.cursor/rules/playwright_test_investigation.mdc`)
|
||||||
@@ -1,287 +1,169 @@
|
|||||||
# Meta-Rule: Bug Diagnosis Workflow
|
# Meta-Rule: Bug Diagnosis
|
||||||
|
|
||||||
**Author**: Matthew Raymer
|
**Author**: Matthew Raymer
|
||||||
**Date**: August 24, 2025
|
**Date**: 2025-08-21
|
||||||
**Status**: 🎯 **ACTIVE** - Core workflow for all bug investigation
|
**Status**: 🎯 **ACTIVE** - Bug investigation workflow bundling
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
This meta-rule defines the systematic approach for investigating and diagnosing
|
This meta-rule bundles all the rules needed for systematic bug investigation
|
||||||
bugs, defects, and unexpected behaviors in the TimeSafari application. It ensures
|
and root cause analysis. Use this when bugs are reported, performance
|
||||||
consistent, thorough, and efficient problem-solving workflows.
|
issues occur, or unexpected behavior happens.
|
||||||
|
|
||||||
## Workflow Constraints
|
|
||||||
|
|
||||||
**This meta-rule enforces DIAGNOSIS MODE for all bundled sub-rules:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowMode": "diagnosis",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "read_only",
|
|
||||||
"forbidden": ["modify", "create", "build", "commit"],
|
|
||||||
"required": "complete_investigation_before_fixing"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**All bundled sub-rules automatically inherit these constraints.**
|
|
||||||
|
|
||||||
## Workflow State Update
|
|
||||||
|
|
||||||
**When this meta-rule is invoked, update the workflow state file:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"currentMode": "diagnosis",
|
|
||||||
"lastInvoked": "meta_bug_diagnosis.mdc",
|
|
||||||
"timestamp": "2025-01-27T15:30:00Z",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "read_only",
|
|
||||||
"forbidden": ["modify", "create", "build", "commit"],
|
|
||||||
"allowed": ["read", "search", "analyze", "document"],
|
|
||||||
"required": "complete_investigation_before_fixing"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**State File Location**: `.cursor/rules/.workflow_state.json`
|
|
||||||
|
|
||||||
**This enables the core always-on rule to enforce diagnosis mode constraints.**
|
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
**ALWAYS** - Apply this workflow to every bug investigation, regardless of
|
- **Bug Reports**: Investigating reported bugs or issues
|
||||||
severity or complexity. This ensures systematic problem-solving and prevents
|
- **Performance Issues**: Diagnosing slow performance or bottlenecks
|
||||||
common investigation pitfalls.
|
- **Unexpected Behavior**: Understanding why code behaves unexpectedly
|
||||||
|
- **Production Issues**: Investigating issues in live environments
|
||||||
|
- **Test Failures**: Understanding why tests are failing
|
||||||
|
- **Integration Problems**: Diagnosing issues between components
|
||||||
|
|
||||||
## Bundled Rules
|
## Bundled Rules
|
||||||
|
|
||||||
### **Investigation Foundation**
|
### **Investigation Process**
|
||||||
|
|
||||||
- **`development/research_diagnostic.mdc`** - Research and investigation methodologies
|
- **`development/research_diagnostic.mdc`** - Systematic investigation
|
||||||
- **`development/logging_standards.mdc`** - Logging and debugging best practices
|
workflow with evidence collection and analysis
|
||||||
- **`development/type_safety_guide.mdc`** - Type safety and error prevention
|
- **`development/investigation_report_example.mdc`** - Investigation
|
||||||
|
documentation templates and examples
|
||||||
|
- **`core/harbor_pilot_universal.mdc`** - Technical guide creation
|
||||||
|
for complex investigations
|
||||||
|
|
||||||
### **Development Workflow**
|
### **Evidence Collection**
|
||||||
|
|
||||||
- **`workflow/version_control.mdc`** - Version control during investigation
|
- **`development/logging_standards.mdc`** - Logging implementation
|
||||||
- **`development/software_development.mdc`** - Development best practices
|
standards for debugging and evidence collection
|
||||||
|
- **`development/time.mdc`** - Timestamp requirements and time
|
||||||
|
handling standards for evidence
|
||||||
|
- **`development/time_examples.mdc`** - Practical examples of
|
||||||
|
proper time handling in investigations
|
||||||
|
|
||||||
## Critical Development Constraints
|
### **Technical Context**
|
||||||
|
|
||||||
### **🚫 NEVER Use Build Commands During Diagnosis**
|
- **`app/timesafari.mdc`** - Core application context and
|
||||||
|
architecture for understanding the system
|
||||||
|
- **`app/timesafari_platforms.mdc`** - Platform-specific
|
||||||
|
considerations and constraints
|
||||||
|
|
||||||
**Critical Rule**: Never use `npm run build:web` or similar build commands during bug diagnosis
|
## Workflow Sequence
|
||||||
|
|
||||||
- **Reason**: These commands block the chat and prevent effective troubleshooting
|
### **Phase 1: Initial Investigation (Start Here)**
|
||||||
- **Impact**: Blocks user interaction, prevents real-time problem solving
|
|
||||||
- **Alternative**: Use safe, fast commands for investigation
|
|
||||||
- **When to use build**: Only after diagnosis is complete and fixes are ready for testing
|
|
||||||
|
|
||||||
### **Safe Diagnosis Commands**
|
1. **Research Diagnostic** - Use `research_diagnostic.mdc` for
|
||||||
|
systematic investigation approach
|
||||||
|
2. **Evidence Collection** - Apply `logging_standards.mdc` and
|
||||||
|
`time.mdc` for proper evidence gathering
|
||||||
|
3. **Context Understanding** - Review `timesafari.mdc` for
|
||||||
|
application context
|
||||||
|
|
||||||
✅ **Safe to use during diagnosis:**
|
### **Phase 2: Deep Investigation**
|
||||||
|
|
||||||
- `npm run lint-fix` - Syntax and style checking
|
1. **Platform Analysis** - Check `timesafari_platforms.mdc` for
|
||||||
- `npm run type-check` - TypeScript validation (if available)
|
platform-specific issues
|
||||||
- `git status` - Version control status
|
2. **Technical Guide Creation** - Use `harbor_pilot_universal.mdc`
|
||||||
- `ls` / `dir` - File listing
|
for complex investigation documentation
|
||||||
- `cat` / `read_file` - File content inspection
|
3. **Evidence Analysis** - Apply `time_examples.mdc` for proper
|
||||||
- `grep_search` - Text pattern searching
|
timestamp handling
|
||||||
|
|
||||||
❌ **Never use during diagnosis:**
|
### **Phase 3: Documentation & Reporting**
|
||||||
|
|
||||||
- `npm run build:web` - Blocks chat
|
1. **Investigation Report** - Use `investigation_report_example.mdc`
|
||||||
- `npm run build:electron` - Blocks chat
|
for comprehensive documentation
|
||||||
- `npm run build:capacitor` - Blocks chat
|
2. **Root Cause Analysis** - Synthesize findings into actionable
|
||||||
- Any long-running build processes
|
insights
|
||||||
|
|
||||||
## Investigation Workflow
|
|
||||||
|
|
||||||
### **Phase 1: Problem Definition**
|
|
||||||
|
|
||||||
1. **Gather Evidence**
|
|
||||||
- Error messages and stack traces
|
|
||||||
- User-reported symptoms
|
|
||||||
- System logs and timestamps
|
|
||||||
- Reproduction steps
|
|
||||||
|
|
||||||
2. **Context Analysis**
|
|
||||||
- When did the problem start?
|
|
||||||
- What changed recently?
|
|
||||||
- Which platform/environment?
|
|
||||||
- User actions leading to the issue
|
|
||||||
|
|
||||||
### **Phase 2: Systematic Investigation**
|
|
||||||
|
|
||||||
1. **Code Inspection**
|
|
||||||
- Relevant file examination
|
|
||||||
- Import and dependency analysis
|
|
||||||
- Syntax and type checking
|
|
||||||
- Logic flow analysis
|
|
||||||
|
|
||||||
2. **Environment Analysis**
|
|
||||||
- Platform-specific considerations
|
|
||||||
- Configuration and settings
|
|
||||||
- Database and storage state
|
|
||||||
- Network and API connectivity
|
|
||||||
|
|
||||||
### **Phase 3: Root Cause Identification**
|
|
||||||
|
|
||||||
1. **Pattern Recognition**
|
|
||||||
- Similar issues in codebase
|
|
||||||
- Common failure modes
|
|
||||||
- Platform-specific behaviors
|
|
||||||
- Recent changes impact
|
|
||||||
|
|
||||||
2. **Hypothesis Testing**
|
|
||||||
- Targeted code changes
|
|
||||||
- Configuration modifications
|
|
||||||
- Environment adjustments
|
|
||||||
- Systematic elimination
|
|
||||||
|
|
||||||
## Investigation Techniques
|
|
||||||
|
|
||||||
### **Safe Code Analysis**
|
|
||||||
|
|
||||||
- **File Reading**: Use `read_file` tool for targeted inspection
|
|
||||||
- **Pattern Searching**: Use `grep_search` for code patterns
|
|
||||||
- **Semantic Search**: Use `codebase_search` for related functionality
|
|
||||||
- **Import Tracing**: Follow dependency chains systematically
|
|
||||||
|
|
||||||
### **Error Analysis**
|
|
||||||
|
|
||||||
- **Stack Trace Analysis**: Identify error origin and propagation
|
|
||||||
- **Log Correlation**: Match errors with system events
|
|
||||||
- **Timeline Reconstruction**: Build sequence of events
|
|
||||||
- **Context Preservation**: Maintain investigation state
|
|
||||||
|
|
||||||
### **Platform Considerations**
|
|
||||||
|
|
||||||
- **Web Platform**: Browser-specific behaviors and limitations
|
|
||||||
- **Electron Platform**: Desktop app considerations
|
|
||||||
- **Capacitor Platform**: Mobile app behaviors
|
|
||||||
- **Cross-Platform**: Shared vs. platform-specific code
|
|
||||||
|
|
||||||
## Evidence Collection Standards
|
|
||||||
|
|
||||||
### **Timestamps**
|
|
||||||
|
|
||||||
- **UTC Format**: All timestamps in UTC for consistency
|
|
||||||
- **Precision**: Include milliseconds for precise correlation
|
|
||||||
- **Context**: Include relevant system state information
|
|
||||||
- **Correlation**: Link events across different components
|
|
||||||
|
|
||||||
### **Error Context**
|
|
||||||
|
|
||||||
- **Full Error Objects**: Capture complete error information
|
|
||||||
- **Stack Traces**: Preserve call stack for analysis
|
|
||||||
- **User Actions**: Document steps leading to error
|
|
||||||
- **System State**: Capture relevant configuration and state
|
|
||||||
|
|
||||||
### **Reproduction Steps**
|
|
||||||
|
|
||||||
- **Clear Sequence**: Step-by-step reproduction instructions
|
|
||||||
- **Environment Details**: Platform, version, configuration
|
|
||||||
- **Data Requirements**: Required data or state
|
|
||||||
- **Expected vs. Actual**: Clear behavior comparison
|
|
||||||
|
|
||||||
## Investigation Documentation
|
|
||||||
|
|
||||||
### **Problem Summary**
|
|
||||||
|
|
||||||
- **Issue Description**: Clear, concise problem statement
|
|
||||||
- **Impact Assessment**: Severity and user impact
|
|
||||||
- **Scope Definition**: Affected components and users
|
|
||||||
- **Priority Level**: Based on impact and frequency
|
|
||||||
|
|
||||||
### **Investigation Log**
|
|
||||||
|
|
||||||
- **Timeline**: Chronological investigation steps
|
|
||||||
- **Evidence**: Collected information and findings
|
|
||||||
- **Hypotheses**: Tested theories and results
|
|
||||||
- **Conclusions**: Root cause identification
|
|
||||||
|
|
||||||
### **Solution Requirements**
|
|
||||||
|
|
||||||
- **Fix Description**: Required changes and approach
|
|
||||||
- **Testing Strategy**: Validation and verification steps
|
|
||||||
- **Rollback Plan**: Reversion strategy if needed
|
|
||||||
- **Prevention Measures**: Future issue prevention
|
|
||||||
|
|
||||||
## Quality Standards
|
|
||||||
|
|
||||||
### **Investigation Completeness**
|
|
||||||
|
|
||||||
- **Evidence Sufficiency**: Adequate information for root cause
|
|
||||||
- **Alternative Theories**: Considered and eliminated
|
|
||||||
- **Platform Coverage**: All relevant platforms investigated
|
|
||||||
- **Edge Cases**: Unusual scenarios considered
|
|
||||||
|
|
||||||
### **Documentation Quality**
|
|
||||||
|
|
||||||
- **Clear Communication**: Understandable to all stakeholders
|
|
||||||
- **Technical Accuracy**: Precise technical details
|
|
||||||
- **Actionable Insights**: Clear next steps and recommendations
|
|
||||||
- **Knowledge Transfer**: Lessons learned for future reference
|
|
||||||
|
|
||||||
## Common Pitfalls
|
|
||||||
|
|
||||||
### **Investigation Mistakes**
|
|
||||||
|
|
||||||
- **Jumping to Solutions**: Implementing fixes before understanding
|
|
||||||
- **Insufficient Evidence**: Making assumptions without data
|
|
||||||
- **Platform Blindness**: Ignoring platform-specific behaviors
|
|
||||||
- **Scope Creep**: Expanding investigation beyond original problem
|
|
||||||
|
|
||||||
### **Communication Issues**
|
|
||||||
|
|
||||||
- **Technical Jargon**: Using unclear terminology
|
|
||||||
- **Missing Context**: Insufficient background information
|
|
||||||
- **Unclear Recommendations**: Vague or ambiguous next steps
|
|
||||||
- **Poor Documentation**: Incomplete or unclear investigation records
|
|
||||||
|
|
||||||
## Success Criteria
|
## Success Criteria
|
||||||
|
|
||||||
- [ ] **Problem clearly defined** with sufficient evidence
|
- [ ] **Root cause identified** with supporting evidence
|
||||||
- [ ] **Root cause identified** through systematic investigation
|
- [ ] **Evidence properly collected** with timestamps and context
|
||||||
- [ ] **Solution approach determined** with clear requirements
|
- [ ] **Investigation documented** using appropriate templates
|
||||||
- [ ] **Documentation complete** for knowledge transfer
|
- [ ] **Platform factors considered** in diagnosis
|
||||||
- [ ] **No chat-blocking commands** used during investigation
|
- [ ] **Reproduction steps documented** for verification
|
||||||
- [ ] **Platform considerations** properly addressed
|
- [ ] **Impact assessment completed** with scope defined
|
||||||
- [ ] **Timeline and context** properly documented
|
- [ ] **Next steps identified** for resolution
|
||||||
|
|
||||||
## Integration with Other Meta-Rules
|
## Common Pitfalls
|
||||||
|
|
||||||
### **Bug Fixing**
|
- **Don't skip evidence collection** - leads to speculation
|
||||||
|
- **Don't ignore platform differences** - misses platform-specific issues
|
||||||
|
- **Don't skip documentation** - loses investigation insights
|
||||||
|
- **Don't assume root cause** - verify with evidence
|
||||||
|
- **Don't ignore time context** - misses temporal factors
|
||||||
|
- **Don't skip reproduction steps** - makes verification impossible
|
||||||
|
|
||||||
- **Investigation Results**: Provide foundation for fix implementation
|
## Integration Points
|
||||||
- **Solution Requirements**: Define what needs to be built
|
|
||||||
- **Testing Strategy**: Inform validation approach
|
|
||||||
- **Documentation**: Support implementation guidance
|
|
||||||
|
|
||||||
### **Feature Planning**
|
### **With Other Meta-Rules**
|
||||||
|
|
||||||
- **Root Cause Analysis**: Identify systemic issues
|
- **Feature Planning**: Use complexity assessment for investigation planning
|
||||||
- **Prevention Measures**: Plan future issue avoidance
|
- **Bug Fixing**: Investigation results feed directly into fix implementation
|
||||||
- **Architecture Improvements**: Identify structural enhancements
|
- **Feature Implementation**: Investigation insights inform future development
|
||||||
- **Process Refinements**: Improve development workflows
|
|
||||||
|
|
||||||
### **Research and Documentation**
|
### **With Development Workflow**
|
||||||
|
|
||||||
- **Knowledge Base**: Contribute to troubleshooting guides
|
- Investigation findings inform testing strategy
|
||||||
- **Pattern Recognition**: Identify common failure modes
|
- Root cause analysis drives preventive measures
|
||||||
- **Best Practices**: Develop investigation methodologies
|
- Evidence collection improves logging standards
|
||||||
- **Team Training**: Improve investigation capabilities
|
|
||||||
|
## Feedback & Improvement
|
||||||
|
|
||||||
|
### **Sub-Rule Ratings (1-5 scale)**
|
||||||
|
|
||||||
|
- **Research Diagnostic**: ___/5 - Comments: _______________
|
||||||
|
- **Investigation Report**: ___/5 - Comments: _______________
|
||||||
|
- **Technical Guide Creation**: ___/5 - Comments: _______________
|
||||||
|
- **Logging Standards**: ___/5 - Comments: _______________
|
||||||
|
- **Time Standards**: ___/5 - Comments: _______________
|
||||||
|
|
||||||
|
### **Workflow Feedback**
|
||||||
|
|
||||||
|
- **Investigation Effectiveness**: How well did the process help find root cause?
|
||||||
|
- **Missing Steps**: What investigation steps should be added?
|
||||||
|
- **Process Gaps**: Where did the workflow break down?
|
||||||
|
|
||||||
|
### **Sub-Rule Improvements**
|
||||||
|
|
||||||
|
- **Clarity Issues**: Which rules were unclear or confusing?
|
||||||
|
- **Missing Examples**: What examples would make rules more useful?
|
||||||
|
- **Template Improvements**: How could investigation templates be better?
|
||||||
|
|
||||||
|
### **Overall Experience**
|
||||||
|
|
||||||
|
- **Time Saved**: How much time did this meta-rule save you?
|
||||||
|
- **Quality Improvement**: Did following these rules improve your investigation?
|
||||||
|
- **Recommendation**: Would you recommend this meta-rule to others?
|
||||||
|
|
||||||
|
## Model Implementation Checklist
|
||||||
|
|
||||||
|
### Before Bug Investigation
|
||||||
|
|
||||||
|
- [ ] **Problem Definition**: Clearly define what needs to be investigated
|
||||||
|
- [ ] **Scope Definition**: Determine investigation scope and boundaries
|
||||||
|
- [ ] **Evidence Planning**: Plan evidence collection strategy
|
||||||
|
- [ ] **Stakeholder Identification**: Identify who needs to be involved
|
||||||
|
|
||||||
|
### During Bug Investigation
|
||||||
|
|
||||||
|
- [ ] **Rule Application**: Apply bundled rules in recommended sequence
|
||||||
|
- [ ] **Evidence Collection**: Collect evidence systematically with timestamps
|
||||||
|
- [ ] **Documentation**: Document investigation process and findings
|
||||||
|
- [ ] **Validation**: Verify findings with reproduction steps
|
||||||
|
|
||||||
|
### After Bug Investigation
|
||||||
|
|
||||||
|
- [ ] **Report Creation**: Create comprehensive investigation report
|
||||||
|
- [ ] **Root Cause Analysis**: Document root cause with evidence
|
||||||
|
- [ ] **Feedback Collection**: Collect feedback on meta-rule effectiveness
|
||||||
|
- [ ] **Process Improvement**: Identify improvements for future investigations
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**See also**:
|
**See also**:
|
||||||
|
|
||||||
|
- `.cursor/rules/meta_feature_planning.mdc` for planning investigation work
|
||||||
- `.cursor/rules/meta_bug_fixing.mdc` for implementing fixes
|
- `.cursor/rules/meta_bug_fixing.mdc` for implementing fixes
|
||||||
- `.cursor/rules/meta_feature_planning.mdc` for planning improvements
|
- `.cursor/rules/meta_feature_implementation.mdc` for preventive measures
|
||||||
- `.cursor/rules/meta_documentation.mdc` for documentation standards
|
|
||||||
|
|
||||||
**Status**: Active meta-rule for bug diagnosis
|
**Status**: Active meta-rule for bug diagnosis
|
||||||
**Priority**: High
|
**Priority**: High
|
||||||
|
|||||||
@@ -10,45 +10,6 @@ This meta-rule bundles all the rules needed for implementing bug fixes
|
|||||||
with proper testing and validation. Use this after diagnosis when
|
with proper testing and validation. Use this after diagnosis when
|
||||||
implementing the actual fix.
|
implementing the actual fix.
|
||||||
|
|
||||||
## Workflow Constraints
|
|
||||||
|
|
||||||
**This meta-rule enforces FIXING MODE for all bundled sub-rules:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowMode": "fixing",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "implementation",
|
|
||||||
"allowed": ["modify", "create", "build", "test", "commit"],
|
|
||||||
"required": "diagnosis_complete_before_fixing"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**All bundled sub-rules automatically inherit these constraints.**
|
|
||||||
|
|
||||||
## Workflow State Update
|
|
||||||
|
|
||||||
**When this meta-rule is invoked, update the workflow state file:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"currentMode": "fixing",
|
|
||||||
"lastInvoked": "meta_bug_fixing.mdc",
|
|
||||||
"timestamp": "2025-01-27T15:30:00Z",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "implementation",
|
|
||||||
"allowed": ["modify", "create", "build", "test", "commit"],
|
|
||||||
"forbidden": [],
|
|
||||||
"required": "diagnosis_complete_before_fixing"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**State File Location**: `.cursor/rules/.workflow_state.json`
|
|
||||||
|
|
||||||
**This enables the core always-on rule to enforce fixing mode constraints.**
|
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
- **Post-Diagnosis**: After root cause is identified and fix is planned
|
- **Post-Diagnosis**: After root cause is identified and fix is planned
|
||||||
|
|||||||
@@ -1,383 +0,0 @@
|
|||||||
# Meta-Rule: Change Evaluation and Breaking Change Detection
|
|
||||||
|
|
||||||
**Author**: Matthew Raymer
|
|
||||||
**Date**: 2025-08-25
|
|
||||||
**Status**: 🎯 **ACTIVE** - Manually activated change evaluation rule
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This meta-rule provides a systematic approach to evaluate changes between
|
|
||||||
branches and detect potential breaking changes. It's designed to catch
|
|
||||||
problematic model behavior by analyzing the nature, scope, and impact of
|
|
||||||
code changes before they cause issues.
|
|
||||||
|
|
||||||
## When to Use
|
|
||||||
|
|
||||||
**Manual Activation Only** - This rule should be invoked when:
|
|
||||||
|
|
||||||
- Reviewing changes before merging branches
|
|
||||||
- Investigating unexpected behavior after updates
|
|
||||||
- Validating that model-generated changes are safe
|
|
||||||
- Analyzing the impact of recent commits
|
|
||||||
- Debugging issues that may be caused by recent changes
|
|
||||||
|
|
||||||
## Workflow State Enforcement
|
|
||||||
|
|
||||||
**This meta-rule enforces current workflow mode constraints:**
|
|
||||||
|
|
||||||
### **Current Workflow State**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowState": {
|
|
||||||
"currentMode": "diagnosis|fixing|planning|research|documentation",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "read_only|implementation|design_only|investigation|writing_only",
|
|
||||||
"allowed": ["array", "of", "allowed", "actions"],
|
|
||||||
"forbidden": ["array", "of", "forbidden", "actions"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Mode-Specific Enforcement**
|
|
||||||
|
|
||||||
**Diagnosis Mode (read_only):**
|
|
||||||
|
|
||||||
- ❌ **Forbidden**: File modification, code creation, build commands, git
|
|
||||||
commits
|
|
||||||
- ✅ **Allowed**: File reading, code analysis, investigation, documentation
|
|
||||||
- **Response**: Focus on analysis and documentation, not implementation
|
|
||||||
|
|
||||||
**Fixing Mode (implementation):**
|
|
||||||
|
|
||||||
- ✅ **Allowed**: File modification, code creation, build commands, testing,
|
|
||||||
git commits
|
|
||||||
- ❌ **Forbidden**: None (full implementation mode)
|
|
||||||
- **Response**: Proceed with implementation and testing
|
|
||||||
|
|
||||||
**Planning Mode (design_only):**
|
|
||||||
|
|
||||||
- ❌ **Forbidden**: Implementation, coding, building, deployment
|
|
||||||
- ✅ **Allowed**: Analysis, design, estimation, documentation, architecture
|
|
||||||
- **Response**: Focus on planning and design, not implementation
|
|
||||||
|
|
||||||
**Research Mode (investigation):**
|
|
||||||
|
|
||||||
- ❌ **Forbidden**: File modification, implementation, deployment
|
|
||||||
- ✅ **Allowed**: Investigation, analysis, research, documentation
|
|
||||||
- **Response**: Focus on investigation and analysis
|
|
||||||
|
|
||||||
**Documentation Mode (writing_only):**
|
|
||||||
|
|
||||||
- ❌ **Forbidden**: Implementation, coding, building, deployment
|
|
||||||
- ✅ **Allowed**: Writing, editing, formatting, structuring, reviewing
|
|
||||||
- **Response**: Focus on documentation creation and improvement
|
|
||||||
|
|
||||||
## Change Evaluation Process
|
|
||||||
|
|
||||||
### **Phase 1: Change Discovery and Analysis**
|
|
||||||
|
|
||||||
1. **Branch Comparison Analysis**
|
|
||||||
|
|
||||||
- Compare working branch with master/main branch
|
|
||||||
- Identify all changed files and their modification types
|
|
||||||
- Categorize changes by scope and impact
|
|
||||||
|
|
||||||
2. **Change Pattern Recognition**
|
|
||||||
|
|
||||||
- Identify common change patterns (refactoring, feature addition, bug
|
|
||||||
fixes)
|
|
||||||
- Detect unusual or suspicious change patterns
|
|
||||||
- Flag changes that deviate from established patterns
|
|
||||||
|
|
||||||
3. **Dependency Impact Assessment**
|
|
||||||
|
|
||||||
- Analyze changes to imports, exports, and interfaces
|
|
||||||
- Identify potential breaking changes to public APIs
|
|
||||||
- Assess impact on dependent components and services
|
|
||||||
|
|
||||||
### **Phase 2: Breaking Change Detection**
|
|
||||||
|
|
||||||
1. **API Contract Analysis**
|
|
||||||
|
|
||||||
- Check for changes to function signatures, method names, class
|
|
||||||
interfaces
|
|
||||||
- Identify removed or renamed public methods/properties
|
|
||||||
- Detect changes to configuration options and constants
|
|
||||||
|
|
||||||
2. **Data Structure Changes**
|
|
||||||
|
|
||||||
- Analyze database schema modifications
|
|
||||||
- Check for changes to data models and interfaces
|
|
||||||
- Identify modifications to serialization/deserialization logic
|
|
||||||
|
|
||||||
3. **Behavioral Changes**
|
|
||||||
|
|
||||||
- Detect changes to business logic and algorithms
|
|
||||||
- Identify modifications to error handling and validation
|
|
||||||
- Check for changes to user experience and workflows
|
|
||||||
|
|
||||||
### **Phase 3: Risk Assessment and Recommendations**
|
|
||||||
|
|
||||||
1. **Risk Level Classification**
|
|
||||||
|
|
||||||
- **LOW**: Cosmetic changes, documentation updates, minor refactoring
|
|
||||||
- **MEDIUM**: Internal API changes, configuration modifications,
|
|
||||||
performance improvements
|
|
||||||
- **HIGH**: Public API changes, breaking interface modifications, major
|
|
||||||
architectural changes
|
|
||||||
- **CRITICAL**: Database schema changes, authentication modifications,
|
|
||||||
security-related changes
|
|
||||||
|
|
||||||
2. **Impact Analysis**
|
|
||||||
|
|
||||||
- Identify affected user groups and use cases
|
|
||||||
- Assess potential for data loss or corruption
|
|
||||||
- Evaluate impact on system performance and reliability
|
|
||||||
|
|
||||||
3. **Mitigation Strategies**
|
|
||||||
|
|
||||||
- Recommend testing approaches for affected areas
|
|
||||||
- Suggest rollback strategies if needed
|
|
||||||
- Identify areas requiring additional validation
|
|
||||||
|
|
||||||
## Implementation Guidelines
|
|
||||||
|
|
||||||
### **Change Analysis Tools**
|
|
||||||
|
|
||||||
1. **Git Diff Analysis**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Compare working branch with master
|
|
||||||
git diff master..HEAD --name-only
|
|
||||||
git diff master..HEAD --stat
|
|
||||||
git log master..HEAD --oneline
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **File Change Categorization**
|
|
||||||
|
|
||||||
- **Core Files**: Application entry points, main services, critical
|
|
||||||
utilities
|
|
||||||
- **Interface Files**: Public APIs, component interfaces, data models
|
|
||||||
- **Configuration Files**: Environment settings, build configurations,
|
|
||||||
deployment scripts
|
|
||||||
- **Test Files**: Unit tests, integration tests, test utilities
|
|
||||||
|
|
||||||
3. **Change Impact Mapping**
|
|
||||||
|
|
||||||
- Map changed files to affected functionality
|
|
||||||
- Identify cross-dependencies and ripple effects
|
|
||||||
- Document potential side effects and unintended consequences
|
|
||||||
|
|
||||||
### **Breaking Change Detection Patterns**
|
|
||||||
|
|
||||||
1. **Function Signature Changes**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BEFORE
|
|
||||||
function processData(data: string, options?: Options): Result
|
|
||||||
|
|
||||||
// AFTER - BREAKING CHANGE
|
|
||||||
function processData(data: string, options: Required<Options>): Result
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Interface Modifications**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BEFORE
|
|
||||||
interface UserProfile {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AFTER - BREAKING CHANGE
|
|
||||||
interface UserProfile {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
phone: string; // Required new field
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Configuration Changes**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BEFORE
|
|
||||||
const config = {
|
|
||||||
apiUrl: 'https://api.example.com',
|
|
||||||
timeout: 5000
|
|
||||||
};
|
|
||||||
|
|
||||||
// AFTER - BREAKING CHANGE
|
|
||||||
const config = {
|
|
||||||
apiUrl: 'https://api.example.com',
|
|
||||||
timeout: 5000,
|
|
||||||
retries: 3 // New required configuration
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
### **Change Evaluation Report**
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Change Evaluation Report
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
- **Risk Level**: [LOW|MEDIUM|HIGH|CRITICAL]
|
|
||||||
- **Overall Assessment**: [SAFE|CAUTION|DANGEROUS|CRITICAL]
|
|
||||||
- **Recommendation**: [PROCEED|REVIEW|HALT|IMMEDIATE_ROLLBACK]
|
|
||||||
|
|
||||||
## Change Analysis
|
|
||||||
|
|
||||||
### Files Modified
|
|
||||||
|
|
||||||
- **Total Changes**: [X] files
|
|
||||||
- **Core Files**: [X] files
|
|
||||||
- **Interface Files**: [X] files
|
|
||||||
- **Configuration Files**: [X] files
|
|
||||||
- **Test Files**: [X] files
|
|
||||||
|
|
||||||
### Change Categories
|
|
||||||
|
|
||||||
- **Refactoring**: [X] changes
|
|
||||||
- **Feature Addition**: [X] changes
|
|
||||||
- **Bug Fixes**: [X] changes
|
|
||||||
- **Configuration**: [X] changes
|
|
||||||
- **Documentation**: [X] changes
|
|
||||||
|
|
||||||
## Breaking Change Detection
|
|
||||||
|
|
||||||
### API Contract Changes
|
|
||||||
|
|
||||||
- **Function Signatures**: [X] modified
|
|
||||||
- **Interface Definitions**: [X] modified
|
|
||||||
- **Public Methods**: [X] added/removed/modified
|
|
||||||
|
|
||||||
### Data Structure Changes
|
|
||||||
|
|
||||||
- **Database Schema**: [X] modifications
|
|
||||||
- **Data Models**: [X] changes
|
|
||||||
- **Serialization**: [X] changes
|
|
||||||
|
|
||||||
### Behavioral Changes
|
|
||||||
|
|
||||||
- **Business Logic**: [X] modifications
|
|
||||||
- **Error Handling**: [X] changes
|
|
||||||
- **User Experience**: [X] changes
|
|
||||||
|
|
||||||
## Risk Assessment
|
|
||||||
|
|
||||||
### Impact Analysis
|
|
||||||
|
|
||||||
- **User Groups Affected**: [Description]
|
|
||||||
- **Use Cases Impacted**: [Description]
|
|
||||||
- **Performance Impact**: [Description]
|
|
||||||
- **Reliability Impact**: [Description]
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
|
||||||
- **Internal Dependencies**: [List]
|
|
||||||
- **External Dependencies**: [List]
|
|
||||||
- **Configuration Dependencies**: [List]
|
|
||||||
|
|
||||||
## Recommendations
|
|
||||||
|
|
||||||
### Testing Requirements
|
|
||||||
|
|
||||||
- [ ] Unit tests for modified components
|
|
||||||
- [ ] Integration tests for affected workflows
|
|
||||||
- [ ] Performance tests for changed algorithms
|
|
||||||
- [ ] User acceptance tests for UI changes
|
|
||||||
|
|
||||||
### Validation Steps
|
|
||||||
|
|
||||||
- [ ] Code review by domain experts
|
|
||||||
- [ ] API compatibility testing
|
|
||||||
- [ ] Database migration testing
|
|
||||||
- [ ] End-to-end workflow testing
|
|
||||||
|
|
||||||
### Rollback Strategy
|
|
||||||
|
|
||||||
- **Rollback Complexity**: [LOW|MEDIUM|HIGH]
|
|
||||||
- **Rollback Time**: [Estimated time]
|
|
||||||
- **Data Preservation**: [Strategy description]
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
[Summary of findings and final recommendation]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### **Example 1: Safe Refactoring**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
@meta_change_evaluation.mdc analyze changes between feature-branch and master
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Example 2: Breaking Change Investigation**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
@meta_change_evaluation.mdc evaluate potential breaking changes in recent commits
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Example 3: Pre-Merge Validation**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
@meta_change_evaluation.mdc validate changes before merging feature-branch to master
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
- [ ] **Change Discovery**: All modified files are identified and categorized
|
|
||||||
- [ ] **Pattern Recognition**: Unusual change patterns are detected and flagged
|
|
||||||
- [ ] **Breaking Change Detection**: All potential breaking changes are identified
|
|
||||||
- [ ] **Risk Assessment**: Accurate risk levels are assigned with justification
|
|
||||||
- [ ] **Recommendations**: Actionable recommendations are provided
|
|
||||||
- [ ] **Documentation**: Complete change evaluation report is generated
|
|
||||||
|
|
||||||
## Common Pitfalls
|
|
||||||
|
|
||||||
- **Missing Dependencies**: Failing to identify all affected components
|
|
||||||
- **Underestimating Impact**: Not considering ripple effects of changes
|
|
||||||
- **Incomplete Testing**: Missing critical test scenarios for changes
|
|
||||||
- **Configuration Blindness**: Overlooking configuration file changes
|
|
||||||
- **Interface Assumptions**: Assuming internal changes won't affect external
|
|
||||||
users
|
|
||||||
|
|
||||||
## Integration with Other Meta-Rules
|
|
||||||
|
|
||||||
### **With Bug Diagnosis**
|
|
||||||
|
|
||||||
- Use change evaluation to identify recent changes that may have caused
|
|
||||||
bugs
|
|
||||||
- Correlate change patterns with reported issues
|
|
||||||
|
|
||||||
### **With Feature Planning**
|
|
||||||
|
|
||||||
- Evaluate the impact of planned changes before implementation
|
|
||||||
- Identify potential breaking changes early in the planning process
|
|
||||||
|
|
||||||
### **With Bug Fixing**
|
|
||||||
|
|
||||||
- Validate that fixes don't introduce new breaking changes
|
|
||||||
- Ensure fixes maintain backward compatibility
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**See also**:
|
|
||||||
|
|
||||||
- `.cursor/rules/meta_core_always_on.mdc` for core always-on rules
|
|
||||||
- `.cursor/rules/meta_feature_planning.mdc` for feature development
|
|
||||||
workflows
|
|
||||||
- `.cursor/rules/meta_bug_diagnosis.mdc` for bug investigation workflows
|
|
||||||
- `.cursor/rules/meta_bug_fixing.mdc` for fix implementation workflows
|
|
||||||
|
|
||||||
**Status**: Active change evaluation meta-rule
|
|
||||||
**Priority**: High (applies to all change evaluation tasks)
|
|
||||||
**Estimated Effort**: Ongoing reference
|
|
||||||
**Dependencies**: All bundled sub-rules
|
|
||||||
**Stakeholders**: Development team, Quality Assurance team, Release
|
|
||||||
Management team
|
|
||||||
@@ -14,115 +14,6 @@ This meta-rule bundles the core rules that should be applied to **every single
|
|||||||
prompt** because they define fundamental behaviors, principles, and context
|
prompt** because they define fundamental behaviors, principles, and context
|
||||||
that are essential for all AI interactions.
|
that are essential for all AI interactions.
|
||||||
|
|
||||||
## Workflow Constraints
|
|
||||||
|
|
||||||
**This meta-rule enforces ALWAYS-ON MODE for all bundled sub-rules:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowMode": "always_on",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "foundation",
|
|
||||||
"alwaysApplied": true,
|
|
||||||
"required": "applied_to_every_prompt"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**All bundled sub-rules automatically inherit these constraints.**
|
|
||||||
|
|
||||||
## Workflow State Enforcement
|
|
||||||
|
|
||||||
**This meta-rule enforces current workflow mode constraints for all interactions:**
|
|
||||||
|
|
||||||
### **Current Workflow State**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowState": {
|
|
||||||
"currentMode": "diagnosis|fixing|planning|research|documentation",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "read_only|implementation|design_only|investigation|writing_only",
|
|
||||||
"allowed": ["array", "of", "allowed", "actions"],
|
|
||||||
"forbidden": ["array", "of", "forbidden", "actions"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Constraint Enforcement Rules**
|
|
||||||
|
|
||||||
**Before responding to any user request, enforce current mode constraints:**
|
|
||||||
|
|
||||||
1. **Read current workflow state** from `.cursor/rules/.workflow_state.json`
|
|
||||||
2. **Identify current mode** and its constraints
|
|
||||||
3. **Validate user request** against current mode constraints
|
|
||||||
4. **Enforce constraints** before generating response
|
|
||||||
5. **Guide model behavior** based on current mode
|
|
||||||
|
|
||||||
### **Mode-Specific Enforcement**
|
|
||||||
|
|
||||||
**Diagnosis Mode (read_only):**
|
|
||||||
|
|
||||||
- ❌ **Forbidden**: File modification, code creation, build commands, git commits
|
|
||||||
- ✅ **Allowed**: File reading, code analysis, investigation, documentation
|
|
||||||
- **Response**: Guide user toward investigation and analysis, not implementation
|
|
||||||
|
|
||||||
**Fixing Mode (implementation):**
|
|
||||||
|
|
||||||
- ✅ **Allowed**: File modification, code creation, build commands, testing, git commits
|
|
||||||
- ❌ **Forbidden**: None (full implementation mode)
|
|
||||||
- **Response**: Proceed with implementation and testing
|
|
||||||
|
|
||||||
**Planning Mode (design_only):**
|
|
||||||
|
|
||||||
- ❌ **Forbidden**: Implementation, coding, building, deployment
|
|
||||||
- ✅ **Allowed**: Analysis, design, estimation, documentation, architecture
|
|
||||||
- **Response**: Focus on planning and design, not implementation
|
|
||||||
|
|
||||||
**Research Mode (investigation):**
|
|
||||||
|
|
||||||
- ❌ **Forbidden**: File modification, implementation, deployment
|
|
||||||
- ✅ **Allowed**: Investigation, analysis, research, documentation
|
|
||||||
- **Response**: Focus on investigation and analysis
|
|
||||||
|
|
||||||
**Documentation Mode (writing_only):**
|
|
||||||
|
|
||||||
- ❌ **Forbidden**: Implementation, coding, building, deployment
|
|
||||||
- ✅ **Allowed**: Writing, editing, formatting, structuring, reviewing
|
|
||||||
- **Response**: Focus on documentation creation and improvement
|
|
||||||
|
|
||||||
### **Constraint Violation Response**
|
|
||||||
|
|
||||||
**If user request violates current mode constraints:**
|
|
||||||
|
|
||||||
```
|
|
||||||
❌ **WORKFLOW CONSTRAINT VIOLATION**
|
|
||||||
|
|
||||||
**Current Mode**: [MODE_NAME]
|
|
||||||
**Requested Action**: [ACTION]
|
|
||||||
**Constraint Violation**: [DESCRIPTION]
|
|
||||||
|
|
||||||
**What You Can Do Instead**:
|
|
||||||
- [LIST OF ALLOWED ALTERNATIVES]
|
|
||||||
|
|
||||||
**To Enable This Action**: Invoke @meta_[appropriate_mode].mdc
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Mode Transition Guidance**
|
|
||||||
|
|
||||||
**When user needs to change modes, provide clear guidance:**
|
|
||||||
|
|
||||||
```
|
|
||||||
🔄 **MODE TRANSITION REQUIRED**
|
|
||||||
|
|
||||||
**Current Mode**: [CURRENT_MODE]
|
|
||||||
**Required Mode**: [REQUIRED_MODE]
|
|
||||||
**Action**: Invoke @meta_[required_mode].mdc
|
|
||||||
|
|
||||||
**This will enable**: [DESCRIPTION OF NEW CAPABILITIES]
|
|
||||||
```
|
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
**ALWAYS** - These rules apply to every single prompt, regardless of the task
|
**ALWAYS** - These rules apply to every single prompt, regardless of the task
|
||||||
@@ -274,8 +165,6 @@ or context. They form the foundation for all AI assistant behavior.
|
|||||||
- [ ] **Time Standards**: Verify UTC and timestamp requirements are clear
|
- [ ] **Time Standards**: Verify UTC and timestamp requirements are clear
|
||||||
- [ ] **Application Context**: Confirm TimeSafari context is loaded
|
- [ ] **Application Context**: Confirm TimeSafari context is loaded
|
||||||
- [ ] **Version Control**: Prepare commit standards if code changes are needed
|
- [ ] **Version Control**: Prepare commit standards if code changes are needed
|
||||||
- [ ] **Workflow State**: Read current mode constraints from state file
|
|
||||||
- [ ] **Constraint Validation**: Validate user request against current mode
|
|
||||||
|
|
||||||
### During Response Creation
|
### During Response Creation
|
||||||
|
|
||||||
@@ -283,8 +172,6 @@ or context. They form the foundation for all AI assistant behavior.
|
|||||||
- [ ] **Competence Hooks**: Include learning and collaboration elements
|
- [ ] **Competence Hooks**: Include learning and collaboration elements
|
||||||
- [ ] **Time Consistency**: Apply UTC standards for all time references
|
- [ ] **Time Consistency**: Apply UTC standards for all time references
|
||||||
- [ ] **Platform Awareness**: Consider all target platforms
|
- [ ] **Platform Awareness**: Consider all target platforms
|
||||||
- [ ] **Mode Enforcement**: Apply current mode constraints to response
|
|
||||||
- [ ] **Constraint Violations**: Block forbidden actions and guide alternatives
|
|
||||||
|
|
||||||
### After Response Creation
|
### After Response Creation
|
||||||
|
|
||||||
@@ -292,8 +179,6 @@ or context. They form the foundation for all AI assistant behavior.
|
|||||||
- [ ] **Quality Check**: Ensure response meets competence standards
|
- [ ] **Quality Check**: Ensure response meets competence standards
|
||||||
- [ ] **Context Review**: Confirm application context was properly considered
|
- [ ] **Context Review**: Confirm application context was properly considered
|
||||||
- [ ] **Feedback Collection**: Note any issues with always-on application
|
- [ ] **Feedback Collection**: Note any issues with always-on application
|
||||||
- [ ] **Mode Compliance**: Verify response stayed within current mode constraints
|
|
||||||
- [ ] **Transition Guidance**: Provide clear guidance for mode changes if needed
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -309,9 +194,3 @@ or context. They form the foundation for all AI assistant behavior.
|
|||||||
**Estimated Effort**: Ongoing reference
|
**Estimated Effort**: Ongoing reference
|
||||||
**Dependencies**: All bundled sub-rules
|
**Dependencies**: All bundled sub-rules
|
||||||
**Stakeholders**: All AI interactions, Development team
|
**Stakeholders**: All AI interactions, Development team
|
||||||
|
|
||||||
**Dependencies**: All bundled sub-rules
|
|
||||||
**Stakeholders**: All AI interactions, Development team
|
|
||||||
|
|
||||||
**Dependencies**: All bundled sub-rules
|
|
||||||
**Stakeholders**: All AI interactions, Development team
|
|
||||||
|
|||||||
@@ -10,48 +10,9 @@ This meta-rule bundles documentation-related rules to create comprehensive,
|
|||||||
educational documentation that increases human competence rather than just
|
educational documentation that increases human competence rather than just
|
||||||
providing technical descriptions.
|
providing technical descriptions.
|
||||||
|
|
||||||
## Workflow Constraints
|
|
||||||
|
|
||||||
**This meta-rule enforces DOCUMENTATION MODE for all bundled sub-rules:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowMode": "documentation",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "writing_only",
|
|
||||||
"allowed": ["write", "edit", "format", "structure", "review"],
|
|
||||||
"forbidden": ["implement", "code", "build", "deploy"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**All bundled sub-rules automatically inherit these constraints.**
|
|
||||||
|
|
||||||
## Workflow State Update
|
|
||||||
|
|
||||||
**When this meta-rule is invoked, update the workflow state file:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"currentMode": "documentation",
|
|
||||||
"lastInvoked": "meta_documentation.mdc",
|
|
||||||
"timestamp": "2025-01-27T15:30:00Z",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "writing_only",
|
|
||||||
"allowed": ["write", "edit", "format", "structure", "review"],
|
|
||||||
"forbidden": ["implement", "code", "build", "deploy"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**State File Location**: `.cursor/rules/.workflow_state.json`
|
|
||||||
|
|
||||||
**This enables the core always-on rule to enforce documentation mode constraints.**
|
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
**Use this meta-rule when**:
|
**Use this meta-rule when**:
|
||||||
|
|
||||||
- Writing new documentation
|
- Writing new documentation
|
||||||
- Updating existing documentation
|
- Updating existing documentation
|
||||||
- Creating technical guides
|
- Creating technical guides
|
||||||
@@ -108,7 +69,6 @@ providing technical descriptions.
|
|||||||
### **Document Structure**
|
### **Document Structure**
|
||||||
|
|
||||||
**Mandatory Sections**:
|
**Mandatory Sections**:
|
||||||
|
|
||||||
- **Overview**: Clear purpose and scope with educational context
|
- **Overview**: Clear purpose and scope with educational context
|
||||||
- **Why This Matters**: Business value and user benefit explanation
|
- **Why This Matters**: Business value and user benefit explanation
|
||||||
- **Core Concepts**: Fundamental understanding before implementation
|
- **Core Concepts**: Fundamental understanding before implementation
|
||||||
@@ -118,7 +78,6 @@ providing technical descriptions.
|
|||||||
- **Next Steps**: Where to go from here
|
- **Next Steps**: Where to go from here
|
||||||
|
|
||||||
**Optional Sections**:
|
**Optional Sections**:
|
||||||
|
|
||||||
- **Background**: Historical context and evolution
|
- **Background**: Historical context and evolution
|
||||||
- **Alternatives**: Other approaches and trade-offs
|
- **Alternatives**: Other approaches and trade-offs
|
||||||
- **Advanced Topics**: Deep dive into complex scenarios
|
- **Advanced Topics**: Deep dive into complex scenarios
|
||||||
@@ -127,7 +86,6 @@ providing technical descriptions.
|
|||||||
### **Writing Style**
|
### **Writing Style**
|
||||||
|
|
||||||
**Educational Approach**:
|
**Educational Approach**:
|
||||||
|
|
||||||
- **Conversational tone**: Write as if explaining to a colleague
|
- **Conversational tone**: Write as if explaining to a colleague
|
||||||
- **Progressive disclosure**: Start simple, add complexity gradually
|
- **Progressive disclosure**: Start simple, add complexity gradually
|
||||||
- **Active voice**: "You can do this" not "This can be done"
|
- **Active voice**: "You can do this" not "This can be done"
|
||||||
@@ -135,7 +93,6 @@ providing technical descriptions.
|
|||||||
- **Analogies**: Use familiar concepts to explain complex ideas
|
- **Analogies**: Use familiar concepts to explain complex ideas
|
||||||
|
|
||||||
**Technical Accuracy**:
|
**Technical Accuracy**:
|
||||||
|
|
||||||
- **Precise language**: Use exact technical terms consistently
|
- **Precise language**: Use exact technical terms consistently
|
||||||
- **Code examples**: Working, tested code snippets
|
- **Code examples**: Working, tested code snippets
|
||||||
- **Version information**: Specify applicable versions and platforms
|
- **Version information**: Specify applicable versions and platforms
|
||||||
@@ -144,7 +101,6 @@ providing technical descriptions.
|
|||||||
### **Content Quality Standards**
|
### **Content Quality Standards**
|
||||||
|
|
||||||
**Educational Value**:
|
**Educational Value**:
|
||||||
|
|
||||||
- [ ] **Concept clarity**: Reader understands the fundamental idea
|
- [ ] **Concept clarity**: Reader understands the fundamental idea
|
||||||
- [ ] **Context relevance**: Reader knows when to apply the knowledge
|
- [ ] **Context relevance**: Reader knows when to apply the knowledge
|
||||||
- [ ] **Practical application**: Reader can implement the solution
|
- [ ] **Practical application**: Reader can implement the solution
|
||||||
@@ -152,7 +108,6 @@ providing technical descriptions.
|
|||||||
- [ ] **Next steps**: Reader knows where to continue learning
|
- [ ] **Next steps**: Reader knows where to continue learning
|
||||||
|
|
||||||
**Technical Accuracy**:
|
**Technical Accuracy**:
|
||||||
|
|
||||||
- [ ] **Fact verification**: All technical details are correct
|
- [ ] **Fact verification**: All technical details are correct
|
||||||
- [ ] **Code validation**: Examples compile and run correctly
|
- [ ] **Code validation**: Examples compile and run correctly
|
||||||
- [ ] **Version compatibility**: Platform and version requirements clear
|
- [ ] **Version compatibility**: Platform and version requirements clear
|
||||||
@@ -190,7 +145,6 @@ providing technical descriptions.
|
|||||||
### **Review Checklist**
|
### **Review Checklist**
|
||||||
|
|
||||||
**Educational Quality**:
|
**Educational Quality**:
|
||||||
|
|
||||||
- [ ] **Clear learning objective**: What will the reader learn?
|
- [ ] **Clear learning objective**: What will the reader learn?
|
||||||
- [ ] **Appropriate complexity**: Matches target audience knowledge
|
- [ ] **Appropriate complexity**: Matches target audience knowledge
|
||||||
- [ ] **Progressive disclosure**: Information builds logically
|
- [ ] **Progressive disclosure**: Information builds logically
|
||||||
@@ -198,7 +152,6 @@ providing technical descriptions.
|
|||||||
- [ ] **Common questions**: Anticipates and answers reader questions
|
- [ ] **Common questions**: Anticipates and answers reader questions
|
||||||
|
|
||||||
**Technical Quality**:
|
**Technical Quality**:
|
||||||
|
|
||||||
- [ ] **Accuracy**: All technical details verified
|
- [ ] **Accuracy**: All technical details verified
|
||||||
- [ ] **Completeness**: Covers all necessary information
|
- [ ] **Completeness**: Covers all necessary information
|
||||||
- [ ] **Consistency**: Terminology and formatting consistent
|
- [ ] **Consistency**: Terminology and formatting consistent
|
||||||
|
|||||||
@@ -10,45 +10,6 @@ This meta-rule bundles all the rules needed for building features with
|
|||||||
proper architecture and cross-platform support. Use this when implementing
|
proper architecture and cross-platform support. Use this when implementing
|
||||||
planned features or refactoring existing code.
|
planned features or refactoring existing code.
|
||||||
|
|
||||||
## Workflow Constraints
|
|
||||||
|
|
||||||
**This meta-rule enforces IMPLEMENTATION MODE for all bundled sub-rules:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowMode": "implementation",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "development",
|
|
||||||
"allowed": ["code", "build", "test", "refactor", "deploy"],
|
|
||||||
"required": "planning_complete_before_implementation"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**All bundled sub-rules automatically inherit these constraints.**
|
|
||||||
|
|
||||||
## Workflow State Update
|
|
||||||
|
|
||||||
**When this meta-rule is invoked, update the workflow state file:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"currentMode": "implementation",
|
|
||||||
"lastInvoked": "meta_feature_implementation.mdc",
|
|
||||||
"timestamp": "2025-01-27T15:30:00Z",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "development",
|
|
||||||
"allowed": ["code", "build", "test", "refactor", "deploy"],
|
|
||||||
"forbidden": [],
|
|
||||||
"required": "planning_complete_before_implementation"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**State File Location**: `.cursor/rules/.workflow_state.json`
|
|
||||||
|
|
||||||
**This enables the core always-on rule to enforce implementation mode constraints.**
|
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
- **Feature Development**: Building new features from planning
|
- **Feature Development**: Building new features from planning
|
||||||
|
|||||||
@@ -10,44 +10,6 @@ This meta-rule bundles all the rules needed for comprehensive feature planning
|
|||||||
across all platforms. Use this when starting any new feature development,
|
across all platforms. Use this when starting any new feature development,
|
||||||
planning sprints, or estimating work effort.
|
planning sprints, or estimating work effort.
|
||||||
|
|
||||||
## Workflow Constraints
|
|
||||||
|
|
||||||
**This meta-rule enforces PLANNING MODE for all bundled sub-rules:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowMode": "planning",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "design_only",
|
|
||||||
"allowed": ["analyze", "plan", "design", "estimate", "document"],
|
|
||||||
"forbidden": ["implement", "code", "build", "test", "deploy"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**All bundled sub-rules automatically inherit these constraints.**
|
|
||||||
|
|
||||||
## Workflow State Update
|
|
||||||
|
|
||||||
**When this meta-rule is invoked, update the workflow state file:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"currentMode": "planning",
|
|
||||||
"lastInvoked": "meta_feature_planning.mdc",
|
|
||||||
"timestamp": "2025-01-27T15:30:00Z",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "design_only",
|
|
||||||
"allowed": ["analyze", "plan", "design", "estimate", "document"],
|
|
||||||
"forbidden": ["implement", "code", "build", "test", "deploy"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**State File Location**: `.cursor/rules/.workflow_state.json`
|
|
||||||
|
|
||||||
**This enables the core always-on rule to enforce planning mode constraints.**
|
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
- **New Feature Development**: Planning features from concept to implementation
|
- **New Feature Development**: Planning features from concept to implementation
|
||||||
|
|||||||
@@ -11,44 +11,6 @@ systematic investigation, analysis, evidence collection, or research tasks. It p
|
|||||||
a comprehensive framework for thorough, methodical research workflows that produce
|
a comprehensive framework for thorough, methodical research workflows that produce
|
||||||
actionable insights and evidence-based conclusions.
|
actionable insights and evidence-based conclusions.
|
||||||
|
|
||||||
## Workflow Constraints
|
|
||||||
|
|
||||||
**This meta-rule enforces RESEARCH MODE for all bundled sub-rules:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflowMode": "research",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "investigation",
|
|
||||||
"allowed": ["read", "search", "analyze", "plan"],
|
|
||||||
"forbidden": ["modify", "create", "build", "commit"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**All bundled sub-rules automatically inherit these constraints.**
|
|
||||||
|
|
||||||
## Workflow State Update
|
|
||||||
|
|
||||||
**When this meta-rule is invoked, update the workflow state file:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"currentMode": "research",
|
|
||||||
"lastInvoked": "meta_research.mdc",
|
|
||||||
"timestamp": "2025-01-27T15:30:00Z",
|
|
||||||
"constraints": {
|
|
||||||
"mode": "investigation",
|
|
||||||
"allowed": ["read", "search", "analyze", "plan"],
|
|
||||||
"forbidden": ["modify", "create", "build", "commit"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**State File Location**: `.cursor/rules/.workflow_state.json`
|
|
||||||
|
|
||||||
**This enables the core always-on rule to enforce research mode constraints.**
|
|
||||||
|
|
||||||
## When to Use
|
## When to Use
|
||||||
|
|
||||||
**RESEARCH TASKS** - Apply this meta-rule when:
|
**RESEARCH TASKS** - Apply this meta-rule when:
|
||||||
|
|||||||
@@ -9,31 +9,26 @@ alwaysApply: false
|
|||||||
**Status**: 🎯 **ACTIVE** - Playwright test debugging guidelines
|
**Status**: 🎯 **ACTIVE** - Playwright test debugging guidelines
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
Provide systematic approach for investigating Playwright test failures with focus on UI element conflicts, timing issues, and selector ambiguity.
|
Provide systematic approach for investigating Playwright test failures with focus on UI element conflicts, timing issues, and selector ambiguity.
|
||||||
|
|
||||||
## Context & Scope
|
## Context & Scope
|
||||||
|
|
||||||
- **Audience**: Developers debugging Playwright test failures
|
- **Audience**: Developers debugging Playwright test failures
|
||||||
- **In scope**: Test failure analysis, selector conflicts, UI state investigation, timing issues
|
- **In scope**: Test failure analysis, selector conflicts, UI state investigation, timing issues
|
||||||
- **Out of scope**: Test writing best practices, CI/CD configuration
|
- **Out of scope**: Test writing best practices, CI/CD configuration
|
||||||
|
|
||||||
## Artifacts & Links
|
## Artifacts & Links
|
||||||
|
|
||||||
- Test results: `test-results/` directory
|
- Test results: `test-results/` directory
|
||||||
- Error context: `error-context.md` files with page snapshots
|
- Error context: `error-context.md` files with page snapshots
|
||||||
- Trace files: `trace.zip` files for failed tests
|
- Trace files: `trace.zip` files for failed tests
|
||||||
- HTML reports: Interactive test reports with screenshots
|
- HTML reports: Interactive test reports with screenshots
|
||||||
|
|
||||||
## Environment & Preconditions
|
## Environment & Preconditions
|
||||||
|
|
||||||
- OS/Runtime: Linux/Windows/macOS with Node.js
|
- OS/Runtime: Linux/Windows/macOS with Node.js
|
||||||
- Versions: Playwright test framework, browser drivers
|
- Versions: Playwright test framework, browser drivers
|
||||||
- Services: Local test server (localhost:8080), test data setup
|
- Services: Local test server (localhost:8080), test data setup
|
||||||
- Auth mode: None required for test investigation
|
- Auth mode: None required for test investigation
|
||||||
|
|
||||||
## Architecture / Process Overview
|
## Architecture / Process Overview
|
||||||
|
|
||||||
Playwright test investigation follows a systematic diagnostic workflow that leverages built-in debugging tools and error context analysis.
|
Playwright test investigation follows a systematic diagnostic workflow that leverages built-in debugging tools and error context analysis.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -62,7 +57,6 @@ flowchart TD
|
|||||||
## Interfaces & Contracts
|
## Interfaces & Contracts
|
||||||
|
|
||||||
### Test Results Structure
|
### Test Results Structure
|
||||||
|
|
||||||
| Component | Format | Content | Validation |
|
| Component | Format | Content | Validation |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Error Context | Markdown | Page snapshot in YAML | Verify DOM state matches test expectations |
|
| Error Context | Markdown | Page snapshot in YAML | Verify DOM state matches test expectations |
|
||||||
@@ -71,7 +65,6 @@ flowchart TD
|
|||||||
| JSON Results | JSON | Machine-readable results | Parse for automated analysis |
|
| JSON Results | JSON | Machine-readable results | Parse for automated analysis |
|
||||||
|
|
||||||
### Investigation Commands
|
### Investigation Commands
|
||||||
|
|
||||||
| Step | Command | Expected Output | Notes |
|
| Step | Command | Expected Output | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Locate failed tests | `find test-results -name "*test-name*"` | Test result directories | Use exact test name patterns |
|
| Locate failed tests | `find test-results -name "*test-name*"` | Test result directories | Use exact test name patterns |
|
||||||
@@ -81,7 +74,6 @@ flowchart TD
|
|||||||
## Repro: End-to-End Investigation Procedure
|
## Repro: End-to-End Investigation Procedure
|
||||||
|
|
||||||
### 1. Locate Failed Test Results
|
### 1. Locate Failed Test Results
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Find all results for a specific test
|
# Find all results for a specific test
|
||||||
find test-results -name "*test-name*" -type d
|
find test-results -name "*test-name*" -type d
|
||||||
@@ -91,7 +83,6 @@ find test-results -name "error-context.md" | head -5
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Analyze Error Context
|
### 2. Analyze Error Context
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Read error context for specific test
|
# Read error context for specific test
|
||||||
cat test-results/test-name-test-description-browser/error-context.md
|
cat test-results/test-name-test-description-browser/error-context.md
|
||||||
@@ -101,7 +92,6 @@ grep -A 10 -B 5 "button.*Yes\|button.*No" test-results/*/error-context.md
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Check Trace Files
|
### 3. Check Trace Files
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List available trace files
|
# List available trace files
|
||||||
find test-results -name "*.zip" | grep trace
|
find test-results -name "*.zip" | grep trace
|
||||||
@@ -111,7 +101,6 @@ npx playwright show-trace test-results/test-name/trace.zip
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 4. Investigate Selector Issues
|
### 4. Investigate Selector Issues
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Check for multiple elements with same text
|
// Check for multiple elements with same text
|
||||||
await page.locator('button:has-text("Yes")').count(); // Should be 1
|
await page.locator('button:has-text("Yes")').count(); // Should be 1
|
||||||
@@ -121,7 +110,6 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
```
|
```
|
||||||
|
|
||||||
## What Works (Evidence)
|
## What Works (Evidence)
|
||||||
|
|
||||||
- ✅ **Error context files** provide page snapshots showing exact DOM state at failure
|
- ✅ **Error context files** provide page snapshots showing exact DOM state at failure
|
||||||
- **Time**: 2025-08-21T14:22Z
|
- **Time**: 2025-08-21T14:22Z
|
||||||
- **Evidence**: `test-results/60-new-activity-New-offers-for-another-user-chromium/error-context.md` shows both alerts visible
|
- **Evidence**: `test-results/60-new-activity-New-offers-for-another-user-chromium/error-context.md` shows both alerts visible
|
||||||
@@ -138,7 +126,6 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
- **Verify at**: Error context markdown files
|
- **Verify at**: Error context markdown files
|
||||||
|
|
||||||
## What Doesn't (Evidence & Hypotheses)
|
## What Doesn't (Evidence & Hypotheses)
|
||||||
|
|
||||||
- ❌ **Generic selectors** fail with multiple similar elements at `test-playwright/testUtils.ts:161`
|
- ❌ **Generic selectors** fail with multiple similar elements at `test-playwright/testUtils.ts:161`
|
||||||
- **Time**: 2025-08-21T14:22Z
|
- **Time**: 2025-08-21T14:22Z
|
||||||
- **Evidence**: `button:has-text("Yes")` matches both "Yes" and "Yes, Export Data"
|
- **Evidence**: `button:has-text("Yes")` matches both "Yes" and "Yes, Export Data"
|
||||||
@@ -152,14 +139,12 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
- **Next probe**: Implement alert queuing or prevent overlapping alerts
|
- **Next probe**: Implement alert queuing or prevent overlapping alerts
|
||||||
|
|
||||||
## Risks, Limits, Assumptions
|
## Risks, Limits, Assumptions
|
||||||
|
|
||||||
- **Trace file size**: Large trace files may impact storage and analysis time
|
- **Trace file size**: Large trace files may impact storage and analysis time
|
||||||
- **Browser compatibility**: Trace viewer requires specific browser support
|
- **Browser compatibility**: Trace viewer requires specific browser support
|
||||||
- **Test isolation**: Shared state between tests may affect investigation results
|
- **Test isolation**: Shared state between tests may affect investigation results
|
||||||
- **Timing sensitivity**: Tests may pass/fail based on system performance
|
- **Timing sensitivity**: Tests may pass/fail based on system performance
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Development Team | Fix test selectors for multiple alerts | All tests pass consistently | 2025-08-22 |
|
| Development Team | Fix test selectors for multiple alerts | All tests pass consistently | 2025-08-22 |
|
||||||
@@ -167,25 +152,21 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
| Development Team | Add test IDs to alert buttons | Unique selectors for all UI elements | 2025-08-28 |
|
| Development Team | Add test IDs to alert buttons | Unique selectors for all UI elements | 2025-08-28 |
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [Playwright Trace Viewer Documentation](https://playwright.dev/docs/trace-viewer)
|
- [Playwright Trace Viewer Documentation](https://playwright.dev/docs/trace-viewer)
|
||||||
- [Playwright Test Results](https://playwright.dev/docs/test-reporters)
|
- [Playwright Test Results](https://playwright.dev/docs/test-reporters)
|
||||||
- [Test Investigation Workflow](./research_diagnostic.mdc)
|
- [Test Investigation Workflow](./research_diagnostic.mdc)
|
||||||
|
|
||||||
## Competence Hooks
|
## Competence Hooks
|
||||||
|
|
||||||
- **Why this works**: Systematic investigation leverages Playwright's built-in debugging tools to identify root causes
|
- **Why this works**: Systematic investigation leverages Playwright's built-in debugging tools to identify root causes
|
||||||
- **Common pitfalls**: Generic selectors fail with multiple similar elements; timing issues create race conditions; alert stacking causes UI conflicts
|
- **Common pitfalls**: Generic selectors fail with multiple similar elements; timing issues create race conditions; alert stacking causes UI conflicts
|
||||||
- **Next skill unlock**: Implement unique test IDs and handle alert dismissal order in test flows
|
- **Next skill unlock**: Implement unique test IDs and handle alert dismissal order in test flows
|
||||||
- **Teach-back**: "How would you investigate a Playwright test failure using error context, trace files, and page snapshots?"
|
- **Teach-back**: "How would you investigate a Playwright test failure using error context, trace files, and page snapshots?"
|
||||||
|
|
||||||
## Collaboration Hooks
|
## Collaboration Hooks
|
||||||
|
|
||||||
- **Reviewers**: QA team, test automation engineers
|
- **Reviewers**: QA team, test automation engineers
|
||||||
- **Sign-off checklist**: Error context analyzed, trace files reviewed, root cause identified, fix implemented and tested
|
- **Sign-off checklist**: Error context analyzed, trace files reviewed, root cause identified, fix implemented and tested
|
||||||
|
|
||||||
## Assumptions & Limits
|
## Assumptions & Limits
|
||||||
|
|
||||||
- Test results directory structure follows Playwright conventions
|
- Test results directory structure follows Playwright conventions
|
||||||
- Trace files are enabled in configuration (`trace: "retain-on-failure"`)
|
- Trace files are enabled in configuration (`trace: "retain-on-failure"`)
|
||||||
- Error context files contain valid YAML page snapshots
|
- Error context files contain valid YAML page snapshots
|
||||||
@@ -197,7 +178,6 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
**Priority**: High
|
**Priority**: High
|
||||||
**Maintainer**: Development team
|
**Maintainer**: Development team
|
||||||
**Next Review**: 2025-09-21
|
**Next Review**: 2025-09-21
|
||||||
|
|
||||||
# Playwright Test Investigation — Harbor Pilot Directive
|
# Playwright Test Investigation — Harbor Pilot Directive
|
||||||
|
|
||||||
**Author**: Matthew Raymer
|
**Author**: Matthew Raymer
|
||||||
@@ -205,31 +185,26 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
**Status**: 🎯 **ACTIVE** - Playwright test debugging guidelines
|
**Status**: 🎯 **ACTIVE** - Playwright test debugging guidelines
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
Provide systematic approach for investigating Playwright test failures with focus on UI element conflicts, timing issues, and selector ambiguity.
|
Provide systematic approach for investigating Playwright test failures with focus on UI element conflicts, timing issues, and selector ambiguity.
|
||||||
|
|
||||||
## Context & Scope
|
## Context & Scope
|
||||||
|
|
||||||
- **Audience**: Developers debugging Playwright test failures
|
- **Audience**: Developers debugging Playwright test failures
|
||||||
- **In scope**: Test failure analysis, selector conflicts, UI state investigation, timing issues
|
- **In scope**: Test failure analysis, selector conflicts, UI state investigation, timing issues
|
||||||
- **Out of scope**: Test writing best practices, CI/CD configuration
|
- **Out of scope**: Test writing best practices, CI/CD configuration
|
||||||
|
|
||||||
## Artifacts & Links
|
## Artifacts & Links
|
||||||
|
|
||||||
- Test results: `test-results/` directory
|
- Test results: `test-results/` directory
|
||||||
- Error context: `error-context.md` files with page snapshots
|
- Error context: `error-context.md` files with page snapshots
|
||||||
- Trace files: `trace.zip` files for failed tests
|
- Trace files: `trace.zip` files for failed tests
|
||||||
- HTML reports: Interactive test reports with screenshots
|
- HTML reports: Interactive test reports with screenshots
|
||||||
|
|
||||||
## Environment & Preconditions
|
## Environment & Preconditions
|
||||||
|
|
||||||
- OS/Runtime: Linux/Windows/macOS with Node.js
|
- OS/Runtime: Linux/Windows/macOS with Node.js
|
||||||
- Versions: Playwright test framework, browser drivers
|
- Versions: Playwright test framework, browser drivers
|
||||||
- Services: Local test server (localhost:8080), test data setup
|
- Services: Local test server (localhost:8080), test data setup
|
||||||
- Auth mode: None required for test investigation
|
- Auth mode: None required for test investigation
|
||||||
|
|
||||||
## Architecture / Process Overview
|
## Architecture / Process Overview
|
||||||
|
|
||||||
Playwright test investigation follows a systematic diagnostic workflow that leverages built-in debugging tools and error context analysis.
|
Playwright test investigation follows a systematic diagnostic workflow that leverages built-in debugging tools and error context analysis.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -258,7 +233,6 @@ flowchart TD
|
|||||||
## Interfaces & Contracts
|
## Interfaces & Contracts
|
||||||
|
|
||||||
### Test Results Structure
|
### Test Results Structure
|
||||||
|
|
||||||
| Component | Format | Content | Validation |
|
| Component | Format | Content | Validation |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Error Context | Markdown | Page snapshot in YAML | Verify DOM state matches test expectations |
|
| Error Context | Markdown | Page snapshot in YAML | Verify DOM state matches test expectations |
|
||||||
@@ -267,7 +241,6 @@ flowchart TD
|
|||||||
| JSON Results | JSON | Machine-readable results | Parse for automated analysis |
|
| JSON Results | JSON | Machine-readable results | Parse for automated analysis |
|
||||||
|
|
||||||
### Investigation Commands
|
### Investigation Commands
|
||||||
|
|
||||||
| Step | Command | Expected Output | Notes |
|
| Step | Command | Expected Output | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Locate failed tests | `find test-results -name "*test-name*"` | Test result directories | Use exact test name patterns |
|
| Locate failed tests | `find test-results -name "*test-name*"` | Test result directories | Use exact test name patterns |
|
||||||
@@ -277,7 +250,6 @@ flowchart TD
|
|||||||
## Repro: End-to-End Investigation Procedure
|
## Repro: End-to-End Investigation Procedure
|
||||||
|
|
||||||
### 1. Locate Failed Test Results
|
### 1. Locate Failed Test Results
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Find all results for a specific test
|
# Find all results for a specific test
|
||||||
find test-results -name "*test-name*" -type d
|
find test-results -name "*test-name*" -type d
|
||||||
@@ -287,7 +259,6 @@ find test-results -name "error-context.md" | head -5
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Analyze Error Context
|
### 2. Analyze Error Context
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Read error context for specific test
|
# Read error context for specific test
|
||||||
cat test-results/test-name-test-description-browser/error-context.md
|
cat test-results/test-name-test-description-browser/error-context.md
|
||||||
@@ -297,7 +268,6 @@ grep -A 10 -B 5 "button.*Yes\|button.*No" test-results/*/error-context.md
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Check Trace Files
|
### 3. Check Trace Files
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List available trace files
|
# List available trace files
|
||||||
find test-results -name "*.zip" | grep trace
|
find test-results -name "*.zip" | grep trace
|
||||||
@@ -307,7 +277,6 @@ npx playwright show-trace test-results/test-name/trace.zip
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 4. Investigate Selector Issues
|
### 4. Investigate Selector Issues
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Check for multiple elements with same text
|
// Check for multiple elements with same text
|
||||||
await page.locator('button:has-text("Yes")').count(); // Should be 1
|
await page.locator('button:has-text("Yes")').count(); // Should be 1
|
||||||
@@ -317,7 +286,6 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
```
|
```
|
||||||
|
|
||||||
## What Works (Evidence)
|
## What Works (Evidence)
|
||||||
|
|
||||||
- ✅ **Error context files** provide page snapshots showing exact DOM state at failure
|
- ✅ **Error context files** provide page snapshots showing exact DOM state at failure
|
||||||
- **Time**: 2025-08-21T14:22Z
|
- **Time**: 2025-08-21T14:22Z
|
||||||
- **Evidence**: `test-results/60-new-activity-New-offers-for-another-user-chromium/error-context.md` shows both alerts visible
|
- **Evidence**: `test-results/60-new-activity-New-offers-for-another-user-chromium/error-context.md` shows both alerts visible
|
||||||
@@ -334,7 +302,6 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
- **Verify at**: Error context markdown files
|
- **Verify at**: Error context markdown files
|
||||||
|
|
||||||
## What Doesn't (Evidence & Hypotheses)
|
## What Doesn't (Evidence & Hypotheses)
|
||||||
|
|
||||||
- ❌ **Generic selectors** fail with multiple similar elements at `test-playwright/testUtils.ts:161`
|
- ❌ **Generic selectors** fail with multiple similar elements at `test-playwright/testUtils.ts:161`
|
||||||
- **Time**: 2025-08-21T14:22Z
|
- **Time**: 2025-08-21T14:22Z
|
||||||
- **Evidence**: `button:has-text("Yes")` matches both "Yes" and "Yes, Export Data"
|
- **Evidence**: `button:has-text("Yes")` matches both "Yes" and "Yes, Export Data"
|
||||||
@@ -348,14 +315,12 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
- **Next probe**: Implement alert queuing or prevent overlapping alerts
|
- **Next probe**: Implement alert queuing or prevent overlapping alerts
|
||||||
|
|
||||||
## Risks, Limits, Assumptions
|
## Risks, Limits, Assumptions
|
||||||
|
|
||||||
- **Trace file size**: Large trace files may impact storage and analysis time
|
- **Trace file size**: Large trace files may impact storage and analysis time
|
||||||
- **Browser compatibility**: Trace viewer requires specific browser support
|
- **Browser compatibility**: Trace viewer requires specific browser support
|
||||||
- **Test isolation**: Shared state between tests may affect investigation results
|
- **Test isolation**: Shared state between tests may affect investigation results
|
||||||
- **Timing sensitivity**: Tests may pass/fail based on system performance
|
- **Timing sensitivity**: Tests may pass/fail based on system performance
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Development Team | Fix test selectors for multiple alerts | All tests pass consistently | 2025-08-22 |
|
| Development Team | Fix test selectors for multiple alerts | All tests pass consistently | 2025-08-22 |
|
||||||
@@ -363,25 +328,21 @@ await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes"
|
|||||||
| Development Team | Add test IDs to alert buttons | Unique selectors for all UI elements | 2025-08-28 |
|
| Development Team | Add test IDs to alert buttons | Unique selectors for all UI elements | 2025-08-28 |
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [Playwright Trace Viewer Documentation](https://playwright.dev/docs/trace-viewer)
|
- [Playwright Trace Viewer Documentation](https://playwright.dev/docs/trace-viewer)
|
||||||
- [Playwright Test Results](https://playwright.dev/docs/test-reporters)
|
- [Playwright Test Results](https://playwright.dev/docs/test-reporters)
|
||||||
- [Test Investigation Workflow](./research_diagnostic.mdc)
|
- [Test Investigation Workflow](./research_diagnostic.mdc)
|
||||||
|
|
||||||
## Competence Hooks
|
## Competence Hooks
|
||||||
|
|
||||||
- **Why this works**: Systematic investigation leverages Playwright's built-in debugging tools to identify root causes
|
- **Why this works**: Systematic investigation leverages Playwright's built-in debugging tools to identify root causes
|
||||||
- **Common pitfalls**: Generic selectors fail with multiple similar elements; timing issues create race conditions; alert stacking causes UI conflicts
|
- **Common pitfalls**: Generic selectors fail with multiple similar elements; timing issues create race conditions; alert stacking causes UI conflicts
|
||||||
- **Next skill unlock**: Implement unique test IDs and handle alert dismissal order in test flows
|
- **Next skill unlock**: Implement unique test IDs and handle alert dismissal order in test flows
|
||||||
- **Teach-back**: "How would you investigate a Playwright test failure using error context, trace files, and page snapshots?"
|
- **Teach-back**: "How would you investigate a Playwright test failure using error context, trace files, and page snapshots?"
|
||||||
|
|
||||||
## Collaboration Hooks
|
## Collaboration Hooks
|
||||||
|
|
||||||
- **Reviewers**: QA team, test automation engineers
|
- **Reviewers**: QA team, test automation engineers
|
||||||
- **Sign-off checklist**: Error context analyzed, trace files reviewed, root cause identified, fix implemented and tested
|
- **Sign-off checklist**: Error context analyzed, trace files reviewed, root cause identified, fix implemented and tested
|
||||||
|
|
||||||
## Assumptions & Limits
|
## Assumptions & Limits
|
||||||
|
|
||||||
- Test results directory structure follows Playwright conventions
|
- Test results directory structure follows Playwright conventions
|
||||||
- Trace files are enabled in configuration (`trace: "retain-on-failure"`)
|
- Trace files are enabled in configuration (`trace: "retain-on-failure"`)
|
||||||
- Error context files contain valid YAML page snapshots
|
- Error context files contain valid YAML page snapshots
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
|
|
||||||
### Avoid
|
### Avoid
|
||||||
|
|
||||||
- Vague: _improved, enhanced, better_
|
- Vague: *improved, enhanced, better*
|
||||||
|
|
||||||
- Trivialities: tiny docs, one-liners, pure lint cleanups (separate,
|
- Trivialities: tiny docs, one-liners, pure lint cleanups (separate,
|
||||||
|
|
||||||
|
|||||||
@@ -54,9 +54,6 @@ build_logs/
|
|||||||
# Guard feedback logs (for continuous improvement analysis)
|
# Guard feedback logs (for continuous improvement analysis)
|
||||||
.guard-feedback.log
|
.guard-feedback.log
|
||||||
|
|
||||||
# Workflow state file (contains dynamic state, not version controlled)
|
|
||||||
.cursor/rules/.workflow_state.json
|
|
||||||
|
|
||||||
# PWA icon files generated by capacitor-assets
|
# PWA icon files generated by capacitor-assets
|
||||||
icons
|
icons
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -18,16 +18,16 @@ npm run lint-fix || {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build Architecture Guard - DISABLED
|
# Then run Build Architecture Guard
|
||||||
# echo "🏗️ Running Build Architecture Guard..."
|
echo "🏗️ Running Build Architecture Guard..."
|
||||||
# bash ./scripts/build-arch-guard.sh --staged || {
|
bash ./scripts/build-arch-guard.sh --staged || {
|
||||||
# echo
|
echo
|
||||||
# echo "❌ Build Architecture Guard failed. Please fix the issues and try again."
|
echo "❌ Build Architecture Guard failed. Please fix the issues and try again."
|
||||||
# echo "💡 To bypass this check for emergency commits, use:"
|
echo "💡 To bypass this check for emergency commits, use:"
|
||||||
# echo " git commit --no-verify"
|
echo " git commit --no-verify"
|
||||||
# echo
|
echo
|
||||||
# exit 1
|
exit 1
|
||||||
# }
|
}
|
||||||
|
|
||||||
echo "✅ All pre-commit checks passed!"
|
echo "✅ All pre-commit checks passed!"
|
||||||
|
|
||||||
|
|||||||
+18
-23
@@ -5,28 +5,23 @@
|
|||||||
#
|
#
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
echo "🔍 Pre-push checks..."
|
echo "🔍 Running Build Architecture Guard (pre-push)..."
|
||||||
|
|
||||||
# Build Architecture Guard - DISABLED
|
# Get the remote branch we're pushing to
|
||||||
# echo "🔍 Running Build Architecture Guard (pre-push)..."
|
REMOTE_BRANCH="origin/$(git rev-parse --abbrev-ref HEAD)"
|
||||||
#
|
|
||||||
# # Get the remote branch we're pushing to
|
|
||||||
# REMOTE_BRANCH="origin/$(git rev-parse --abbrev-ref HEAD)"
|
|
||||||
#
|
|
||||||
# # Check if remote branch exists
|
|
||||||
# if git show-ref --verify --quiet "refs/remotes/$REMOTE_BRANCH"; then
|
|
||||||
# RANGE="$REMOTE_BRANCH...HEAD"
|
|
||||||
# else
|
|
||||||
# # If remote branch doesn't exist, check last commit
|
|
||||||
# RANGE="HEAD~1..HEAD"
|
|
||||||
# fi
|
|
||||||
#
|
|
||||||
# bash ./scripts/build-arch-guard.sh --range "$RANGE" || {
|
|
||||||
# echo
|
|
||||||
# echo "💡 To bypass this check for emergency pushes, use:"
|
|
||||||
# echo " git push --no-verify"
|
|
||||||
# echo
|
|
||||||
# exit 1
|
|
||||||
# }
|
|
||||||
|
|
||||||
echo "✅ Pre-push checks passed!"
|
# Check if remote branch exists
|
||||||
|
if git show-ref --verify --quiet "refs/remotes/$REMOTE_BRANCH"; then
|
||||||
|
RANGE="$REMOTE_BRANCH...HEAD"
|
||||||
|
else
|
||||||
|
# If remote branch doesn't exist, check last commit
|
||||||
|
RANGE="HEAD~1..HEAD"
|
||||||
|
fi
|
||||||
|
|
||||||
|
bash ./scripts/build-arch-guard.sh --range "$RANGE" || {
|
||||||
|
echo
|
||||||
|
echo "💡 To bypass this check for emergency pushes, use:"
|
||||||
|
echo " git push --no-verify"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|||||||
+20
-49
@@ -1,56 +1,27 @@
|
|||||||
{
|
{
|
||||||
"MD013": false,
|
"MD013": {
|
||||||
"MD033": false,
|
"line_length": 80,
|
||||||
"MD041": false,
|
"code_blocks": false,
|
||||||
"MD024": {
|
"tables": false,
|
||||||
"siblings_only": true
|
"headings": false
|
||||||
},
|
|
||||||
"MD029": {
|
|
||||||
"style": "ordered"
|
|
||||||
},
|
|
||||||
"MD007": {
|
|
||||||
"indent": 2
|
|
||||||
},
|
|
||||||
"MD012": {
|
|
||||||
"maximum": 1
|
|
||||||
},
|
},
|
||||||
|
"MD012": true,
|
||||||
"MD022": true,
|
"MD022": true,
|
||||||
"MD025": true,
|
|
||||||
"MD026": {
|
|
||||||
"punctuation": ".,;:!"
|
|
||||||
},
|
|
||||||
"MD030": {
|
|
||||||
"ul_single": 1,
|
|
||||||
"ol_single": 1,
|
|
||||||
"ul_multi": 1,
|
|
||||||
"ol_multi": 1
|
|
||||||
},
|
|
||||||
"MD031": true,
|
"MD031": true,
|
||||||
"MD032": true,
|
"MD032": true,
|
||||||
"MD034": true,
|
|
||||||
"MD035": {
|
|
||||||
"style": "---"
|
|
||||||
},
|
|
||||||
"MD036": false,
|
|
||||||
"MD037": true,
|
|
||||||
"MD038": true,
|
|
||||||
"MD039": true,
|
|
||||||
"MD040": true,
|
|
||||||
"MD042": true,
|
|
||||||
"MD043": false,
|
|
||||||
"MD044": false,
|
|
||||||
"MD045": true,
|
|
||||||
"MD046": {
|
|
||||||
"style": "fenced"
|
|
||||||
},
|
|
||||||
"MD047": true,
|
"MD047": true,
|
||||||
"MD048": {
|
"MD009": true,
|
||||||
"style": "backtick"
|
"MD010": true,
|
||||||
},
|
"MD004": { "style": "dash" },
|
||||||
"MD049": {
|
"MD029": { "style": "ordered" },
|
||||||
"style": "underscore"
|
"MD041": false,
|
||||||
},
|
"MD025": false,
|
||||||
"MD050": {
|
"MD024": false,
|
||||||
"style": "asterisk"
|
"MD036": false,
|
||||||
}
|
"MD003": false,
|
||||||
|
"MD040": false,
|
||||||
|
"MD055": false,
|
||||||
|
"MD056": false,
|
||||||
|
"MD034": false,
|
||||||
|
"MD023": false
|
||||||
}
|
}
|
||||||
+7
-69
@@ -93,7 +93,6 @@ The Build Architecture Guard protects your build system by enforcing documentati
|
|||||||
#### Protected File Patterns
|
#### Protected File Patterns
|
||||||
|
|
||||||
The guard monitors these sensitive paths:
|
The guard monitors these sensitive paths:
|
||||||
|
|
||||||
- `vite.config.*` - Build configuration
|
- `vite.config.*` - Build configuration
|
||||||
- `scripts/**` - Build and utility scripts
|
- `scripts/**` - Build and utility scripts
|
||||||
- `electron/**` - Desktop application code
|
- `electron/**` - Desktop application code
|
||||||
@@ -133,7 +132,6 @@ npm run guard:setup
|
|||||||
#### Troubleshooting
|
#### Troubleshooting
|
||||||
|
|
||||||
If you encounter `mapfile: command not found` errors:
|
If you encounter `mapfile: command not found` errors:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Ensure script is executable
|
# Ensure script is executable
|
||||||
chmod +x scripts/build-arch-guard.sh
|
chmod +x scripts/build-arch-guard.sh
|
||||||
@@ -272,7 +270,6 @@ Start the development server using `npm run build:web:dev` or `npm run build:web
|
|||||||
3. To test the production build locally, use `npm run build:web:serve` (builds then serves)
|
3. To test the production build locally, use `npm run build:web:serve` (builds then serves)
|
||||||
|
|
||||||
**Why Use `serve`?**
|
**Why Use `serve`?**
|
||||||
|
|
||||||
- **Production Testing**: Test your optimized production build locally before deployment
|
- **Production Testing**: Test your optimized production build locally before deployment
|
||||||
- **SPA Routing Validation**: Verify deep linking and navigation work correctly (handles routes like `/discover`, `/account`)
|
- **SPA Routing Validation**: Verify deep linking and navigation work correctly (handles routes like `/discover`, `/account`)
|
||||||
- **Performance Testing**: Test the minified and optimized build locally
|
- **Performance Testing**: Test the minified and optimized build locally
|
||||||
@@ -338,18 +335,15 @@ All web build commands use the `./scripts/build-web.sh` script, which provides:
|
|||||||
The `serve` functionality provides a local HTTP server for testing production builds:
|
The `serve` functionality provides a local HTTP server for testing production builds:
|
||||||
|
|
||||||
**What It Does:**
|
**What It Does:**
|
||||||
|
|
||||||
1. **Builds** the application using Vite
|
1. **Builds** the application using Vite
|
||||||
2. **Serves** the built files from the `dist/` directory
|
2. **Serves** the built files from the `dist/` directory
|
||||||
3. **Handles SPA Routing** - serves `index.html` for all routes (fixes 404s on `/discover`, `/account`, etc.)
|
3. **Handles SPA Routing** - serves `index.html` for all routes (fixes 404s on `/discover`, `/account`, etc.)
|
||||||
|
|
||||||
**Server Options:**
|
**Server Options:**
|
||||||
|
|
||||||
- **Primary**: `npx serve -s dist -l 8080` (recommended - full SPA support)
|
- **Primary**: `npx serve -s dist -l 8080` (recommended - full SPA support)
|
||||||
- **Fallback**: Python HTTP server (limited SPA routing support)
|
- **Fallback**: Python HTTP server (limited SPA routing support)
|
||||||
|
|
||||||
**Use Cases:**
|
**Use Cases:**
|
||||||
|
|
||||||
- Testing production builds before deployment
|
- Testing production builds before deployment
|
||||||
- Validating SPA routing behavior
|
- Validating SPA routing behavior
|
||||||
- Performance testing of optimized builds
|
- Performance testing of optimized builds
|
||||||
@@ -371,8 +365,8 @@ current version to test DB migrations.
|
|||||||
- Put the commit hash in the changelog (which will help you remember to bump the
|
- Put the commit hash in the changelog (which will help you remember to bump the
|
||||||
version in the step later).
|
version in the step later).
|
||||||
|
|
||||||
- Tag with the new version,
|
- Tag with the new version,
|
||||||
[online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or
|
[online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or
|
||||||
`git tag 1.0.2 && git push origin 1.0.2`.
|
`git tag 1.0.2 && git push origin 1.0.2`.
|
||||||
|
|
||||||
- For test, build the app:
|
- For test, build the app:
|
||||||
@@ -623,8 +617,7 @@ The Electron build process follows a multi-stage approach:
|
|||||||
#### **Stage 2: Capacitor Sync**
|
#### **Stage 2: Capacitor Sync**
|
||||||
|
|
||||||
- Copies web assets to Electron app directory
|
- Copies web assets to Electron app directory
|
||||||
- Uses Electron-specific Capacitor configuration (not copied from main config)
|
- Syncs Capacitor configuration and plugins
|
||||||
- Syncs Capacitor plugins for Electron platform
|
|
||||||
- Prepares native module bindings
|
- Prepares native module bindings
|
||||||
|
|
||||||
#### **Stage 3: TypeScript Compile**
|
#### **Stage 3: TypeScript Compile**
|
||||||
@@ -1191,14 +1184,14 @@ If you need to build manually or want to understand the individual steps:
|
|||||||
- Choose Product -> Archive
|
- Choose Product -> Archive
|
||||||
- This will trigger a build and take time, needing user's "login" keychain
|
- This will trigger a build and take time, needing user's "login" keychain
|
||||||
password (user's login password), repeatedly.
|
password (user's login password), repeatedly.
|
||||||
- If it fails with `building for 'iOS', but linking in dylib
|
- If it fails with `building for 'iOS', but linking in dylib
|
||||||
(.../.pkgx/zlib.net/v1.3.0/lib/libz.1.3.dylib) built for 'macOS'` then run
|
(.../.pkgx/zlib.net/v1.3.0/lib/libz.1.3.dylib) built for 'macOS'` then run
|
||||||
XCode outside that terminal (ie. not with `npx cap open ios`).
|
XCode outside that terminal (ie. not with `npx cap open ios`).
|
||||||
- Click Distribute -> App Store Connect
|
- Click Distribute -> App Store Connect
|
||||||
- In AppStoreConnect, add the build to the distribution. You may have to remove
|
- In AppStoreConnect, add the build to the distribution. You may have to remove
|
||||||
the current build with the "-" when you hover over it, then "Add Build" with the
|
the current build with the "-" when you hover over it, then "Add Build" with the
|
||||||
new build.
|
new build.
|
||||||
- May have to go to App Review, click Submission, then hover over the build
|
- May have to go to App Review, click Submission, then hover over the build
|
||||||
and click "-".
|
and click "-".
|
||||||
- It can take 15 minutes for the build to show up in the list of builds.
|
- It can take 15 minutes for the build to show up in the list of builds.
|
||||||
- You'll probably have to "Manage" something about encryption, disallowed in France.
|
- You'll probably have to "Manage" something about encryption, disallowed in France.
|
||||||
@@ -1263,13 +1256,11 @@ npm run assets:validate
|
|||||||
##### What Gets Validated
|
##### What Gets Validated
|
||||||
|
|
||||||
**Source Assets (Required):**
|
**Source Assets (Required):**
|
||||||
|
|
||||||
- `resources/icon.png` - App icon source
|
- `resources/icon.png` - App icon source
|
||||||
- `resources/splash.png` - Splash screen source
|
- `resources/splash.png` - Splash screen source
|
||||||
- `resources/splash_dark.png` - Dark mode splash source
|
- `resources/splash_dark.png` - Dark mode splash source
|
||||||
|
|
||||||
**Android Resources (Generated):**
|
**Android Resources (Generated):**
|
||||||
|
|
||||||
- `android/app/src/main/res/drawable/splash.png` - Splash screen drawable
|
- `android/app/src/main/res/drawable/splash.png` - Splash screen drawable
|
||||||
- `android/app/src/main/res/mipmap-*/ic_launcher.png` - App icons for all densities
|
- `android/app/src/main/res/mipmap-*/ic_launcher.png` - App icons for all densities
|
||||||
- `android/app/src/main/res/mipmap-*/ic_launcher_round.png` - Round app icons for all densities
|
- `android/app/src/main/res/mipmap-*/ic_launcher_round.png` - Round app icons for all densities
|
||||||
@@ -2714,7 +2705,6 @@ configuration files in the repository.
|
|||||||
### 2025-08-21 - Cursor Rules Refactoring and Build System Updates
|
### 2025-08-21 - Cursor Rules Refactoring and Build System Updates
|
||||||
|
|
||||||
#### Package Dependencies Updated
|
#### Package Dependencies Updated
|
||||||
|
|
||||||
- **Added**: `markdownlint-cli2` v0.18.1 - Modern markdown linting with improved performance
|
- **Added**: `markdownlint-cli2` v0.18.1 - Modern markdown linting with improved performance
|
||||||
- **Added**: `@commitlint/cli` v18.6.1 - Conventional commit message validation
|
- **Added**: `@commitlint/cli` v18.6.1 - Conventional commit message validation
|
||||||
- **Added**: `@commitlint/config-conventional` v18.6.2 - Conventional commit standards
|
- **Added**: `@commitlint/config-conventional` v18.6.2 - Conventional commit standards
|
||||||
@@ -2722,33 +2712,28 @@ configuration files in the repository.
|
|||||||
- **Updated**: `lint-staged` v15.2.2 - Pre-commit linting automation
|
- **Updated**: `lint-staged` v15.2.2 - Pre-commit linting automation
|
||||||
|
|
||||||
#### Build Script Improvements
|
#### Build Script Improvements
|
||||||
|
|
||||||
- **Markdown Linting**: Replaced custom markdown scripts with `markdownlint-cli2`
|
- **Markdown Linting**: Replaced custom markdown scripts with `markdownlint-cli2`
|
||||||
- **Before**: `./scripts/fix-markdown.sh` and `./scripts/validate-markdown.sh`
|
- **Before**: `./scripts/fix-markdown.sh` and `./scripts/validate-markdown.sh`
|
||||||
- **After**: `markdownlint-cli2 --fix` and `markdownlint-cli2`
|
- **After**: `markdownlint-cli2 --fix` and `markdownlint-cli2`
|
||||||
- **Benefits**: Faster execution, better error reporting, modern markdown standards
|
- **Benefits**: Faster execution, better error reporting, modern markdown standards
|
||||||
|
|
||||||
#### Lint-Staged Configuration Enhanced
|
#### Lint-Staged Configuration Enhanced
|
||||||
|
|
||||||
- **Added**: Markdown file linting to pre-commit hooks
|
- **Added**: Markdown file linting to pre-commit hooks
|
||||||
- **Pattern**: `*.{md,markdown,mdc}` files now automatically formatted
|
- **Pattern**: `*.{md,markdown,mdc}` files now automatically formatted
|
||||||
- **Command**: `markdownlint-cli2 --fix` runs before each commit
|
- **Command**: `markdownlint-cli2 --fix` runs before each commit
|
||||||
- **Coverage**: All markdown files including `.mdc` cursor rules
|
- **Coverage**: All markdown files including `.mdc` cursor rules
|
||||||
|
|
||||||
#### Commit Message Standards
|
#### Commit Message Standards
|
||||||
|
|
||||||
- **Added**: Conventional commit validation via commitlint
|
- **Added**: Conventional commit validation via commitlint
|
||||||
- **Configuration**: Extends `@commitlint/config-conventional`
|
- **Configuration**: Extends `@commitlint/config-conventional`
|
||||||
- **Enforcement**: Ensures consistent commit message format across the project
|
- **Enforcement**: Ensures consistent commit message format across the project
|
||||||
|
|
||||||
#### Node.js Version Requirements
|
#### Node.js Version Requirements
|
||||||
|
|
||||||
- **Updated**: Minimum Node.js version requirements for new dependencies
|
- **Updated**: Minimum Node.js version requirements for new dependencies
|
||||||
- **markdownlint-cli2**: Requires Node.js >=20
|
- **markdownlint-cli2**: Requires Node.js >=20
|
||||||
- **Various utilities**: Require Node.js >=18 for modern ES features
|
- **Various utilities**: Require Node.js >=18 for modern ES features
|
||||||
|
|
||||||
#### Build Process Impact
|
#### Build Process Impact
|
||||||
|
|
||||||
- **No Breaking Changes**: All existing build commands continue to work
|
- **No Breaking Changes**: All existing build commands continue to work
|
||||||
- **Improved Quality**: Better markdown formatting and commit message standards
|
- **Improved Quality**: Better markdown formatting and commit message standards
|
||||||
- **Enhanced Automation**: More comprehensive pre-commit validation
|
- **Enhanced Automation**: More comprehensive pre-commit validation
|
||||||
@@ -2759,7 +2744,6 @@ configuration files in the repository.
|
|||||||
### 2025-08-21 - Commitlint Configuration Refinement
|
### 2025-08-21 - Commitlint Configuration Refinement
|
||||||
|
|
||||||
#### Commit Message Validation Improvements
|
#### Commit Message Validation Improvements
|
||||||
|
|
||||||
- **Modified**: Commitlint configuration moved from `package.json` to dedicated `commitlint.config.js`
|
- **Modified**: Commitlint configuration moved from `package.json` to dedicated `commitlint.config.js`
|
||||||
- **Enhanced**: Strict validation rules downgraded from errors to warnings
|
- **Enhanced**: Strict validation rules downgraded from errors to warnings
|
||||||
- **Before**: `subject-case` and `subject-full-stop` rules caused red error messages
|
- **Before**: `subject-case` and `subject-full-stop` rules caused red error messages
|
||||||
@@ -2767,18 +2751,16 @@ configuration files in the repository.
|
|||||||
- **Benefit**: Eliminates confusing red error messages while maintaining commit quality guidance
|
- **Benefit**: Eliminates confusing red error messages while maintaining commit quality guidance
|
||||||
|
|
||||||
#### Configuration Structure
|
#### Configuration Structure
|
||||||
|
|
||||||
- **File**: `commitlint.config.js` - Dedicated commitlint configuration
|
- **File**: `commitlint.config.js` - Dedicated commitlint configuration
|
||||||
- **Extends**: `@commitlint/config-conventional` - Standard conventional commit rules
|
- **Extends**: `@commitlint/config-conventional` - Standard conventional commit rules
|
||||||
- **Custom Rules**:
|
- **Custom Rules**:
|
||||||
- `subject-case: [1, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']]`
|
- `subject-case: [1, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']]`
|
||||||
- `subject-full-stop: [1, 'never', '.']`
|
- `subject-full-stop: [1, 'never', '.']`
|
||||||
- **Levels**:
|
- **Levels**:
|
||||||
- `0` = Disabled, `1` = Warning, `2` = Error
|
- `0` = Disabled, `1` = Warning, `2` = Error
|
||||||
- Current: Problematic rules set to warning level (1)
|
- Current: Problematic rules set to warning level (1)
|
||||||
|
|
||||||
#### User Experience Impact
|
#### User Experience Impact
|
||||||
|
|
||||||
- **Before**: Red error messages on every push with strict commit rules
|
- **Before**: Red error messages on every push with strict commit rules
|
||||||
- **After**: Yellow warning messages that provide guidance without disruption
|
- **After**: Yellow warning messages that provide guidance without disruption
|
||||||
- **Workflow**: Commits and pushes continue to work while maintaining quality standards
|
- **Workflow**: Commits and pushes continue to work while maintaining quality standards
|
||||||
@@ -2786,50 +2768,6 @@ configuration files in the repository.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2025-08-26 - Capacitor Plugin Additions
|
|
||||||
|
|
||||||
#### New Capacitor Plugins Added
|
|
||||||
|
|
||||||
- **Added**: `@capacitor/clipboard` v6.0.2 - Clipboard functionality for mobile platforms
|
|
||||||
- **Purpose**: Enable copy/paste operations on mobile devices
|
|
||||||
- **Platforms**: iOS and Android
|
|
||||||
- **Features**: Read/write clipboard content, text handling
|
|
||||||
- **Integration**: Automatically included in mobile builds
|
|
||||||
|
|
||||||
- **Added**: `@capacitor/status-bar` v6.0.2 - Status bar management for mobile platforms
|
|
||||||
- **Purpose**: Control mobile device status bar appearance and behavior
|
|
||||||
- **Platforms**: iOS and Android
|
|
||||||
- **Features**: Status bar styling, visibility control, color management
|
|
||||||
- **Integration**: Automatically included in mobile builds
|
|
||||||
|
|
||||||
#### Android Build System Updates
|
|
||||||
|
|
||||||
- **Modified**: `android/capacitor.settings.gradle` - Added new plugin project includes
|
|
||||||
- **Added**: `:capacitor-clipboard` project directory mapping
|
|
||||||
- **Added**: `:capacitor-status-bar` project directory mapping
|
|
||||||
- **Impact**: New plugins now properly integrated into Android build process
|
|
||||||
|
|
||||||
#### Package Dependencies
|
|
||||||
|
|
||||||
- **Updated**: `package.json` - Added new Capacitor plugin dependencies
|
|
||||||
- **Updated**: `package-lock.json` - Locked dependency versions for consistency
|
|
||||||
- **Version**: All new plugins use Capacitor 6.x compatible versions
|
|
||||||
|
|
||||||
#### Build Process Impact
|
|
||||||
|
|
||||||
- **No Breaking Changes**: Existing build commands continue to work unchanged
|
|
||||||
- **Enhanced Mobile Features**: New clipboard and status bar capabilities available
|
|
||||||
- **Automatic Integration**: Plugins automatically included in mobile builds
|
|
||||||
- **Platform Support**: Both iOS and Android builds now include new functionality
|
|
||||||
|
|
||||||
#### Testing Requirements
|
|
||||||
|
|
||||||
- **Mobile Builds**: Verify new plugins integrate correctly in iOS and Android builds
|
|
||||||
- **Functionality**: Test clipboard operations and status bar management on devices
|
|
||||||
- **Fallback**: Ensure graceful degradation when plugins are unavailable
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Note**: This documentation is maintained alongside the build system. For the
|
**Note**: This documentation is maintained alongside the build system. For the
|
||||||
most up-to-date information, refer to the actual script files and Vite
|
most up-to-date information, refer to the actual script files and Vite
|
||||||
configuration files in the repository.
|
configuration files in the repository.
|
||||||
|
|||||||
@@ -66,17 +66,14 @@ test-image.tar a1b2c3d4e5f6...
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Docs
|
### Docs
|
||||||
|
|
||||||
- [x] **BUILDING.md** updated (sections): Docker deployment
|
- [x] **BUILDING.md** updated (sections): Docker deployment
|
||||||
- [x] Troubleshooting updated: Added Docker troubleshooting section
|
- [x] Troubleshooting updated: Added Docker troubleshooting section
|
||||||
|
|
||||||
### Rollback
|
### Rollback
|
||||||
|
|
||||||
- [x] Verified steps to restore previous behavior:
|
- [x] Verified steps to restore previous behavior:
|
||||||
1. `git revert HEAD`
|
1. `git revert HEAD`
|
||||||
2. `docker rmi test-image`
|
2. `docker rmi test-image`
|
||||||
3. Restore previous BUILDING.md
|
3. Restore previous BUILDING.md
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ See [ClickUp](https://sharing.clickup.com/9014278710/l/h/8cmnyhp-174/10573fec74e
|
|||||||
|
|
||||||
Quick start:
|
Quick start:
|
||||||
|
|
||||||
- For setup, we recommend [pkgx](https://pkgx.dev), which installs what you need (either automatically or with the `dev` command). Core dependencies are typescript & npm; when building for other platforms, you'll need other things such as those in the pkgx.yaml & BUILDING.md files.
|
* For setup, we recommend [pkgx](https://pkgx.dev), which installs what you need (either automatically or with the `dev` command). Core dependencies are typescript & npm; when building for other platforms, you'll need other things such as those in the pkgx.yaml & BUILDING.md files.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
@@ -90,7 +90,6 @@ VITE_LOG_LEVEL=debug npm run dev
|
|||||||
See [Logging Configuration Guide](doc/logging-configuration.md) for complete details.
|
See [Logging Configuration Guide](doc/logging-configuration.md) for complete details.
|
||||||
|
|
||||||
### Quick Usage
|
### Quick Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run the database clearing script
|
# Run the database clearing script
|
||||||
./scripts/clear-database.sh
|
./scripts/clear-database.sh
|
||||||
@@ -103,19 +102,16 @@ npm run build:web:dev # For Web
|
|||||||
### What It Does
|
### What It Does
|
||||||
|
|
||||||
#### **Electron (Desktop App)**
|
#### **Electron (Desktop App)**
|
||||||
|
|
||||||
- Automatically finds and clears the SQLite database files
|
- Automatically finds and clears the SQLite database files
|
||||||
- Works on Linux, macOS, and Windows
|
- Works on Linux, macOS, and Windows
|
||||||
- Clears all data and forces fresh migrations on next startup
|
- Clears all data and forces fresh migrations on next startup
|
||||||
|
|
||||||
#### **Web Browser**
|
#### **Web Browser**
|
||||||
|
|
||||||
- Provides instructions for using custom browser data directories
|
- Provides instructions for using custom browser data directories
|
||||||
- Shows manual clearing via browser DevTools
|
- Shows manual clearing via browser DevTools
|
||||||
- Ensures reliable database clearing without browser complications
|
- Ensures reliable database clearing without browser complications
|
||||||
|
|
||||||
### Safety Features
|
### Safety Features
|
||||||
|
|
||||||
- ✅ **Interactive Script**: Guides you through the process
|
- ✅ **Interactive Script**: Guides you through the process
|
||||||
- ✅ **Platform Detection**: Automatically detects your OS
|
- ✅ **Platform Detection**: Automatically detects your OS
|
||||||
- ✅ **Clear Instructions**: Step-by-step guidance for each platform
|
- ✅ **Clear Instructions**: Step-by-step guidance for each platform
|
||||||
@@ -124,7 +120,6 @@ npm run build:web:dev # For Web
|
|||||||
### Manual Commands (if needed)
|
### Manual Commands (if needed)
|
||||||
|
|
||||||
#### **Electron Database Location**
|
#### **Electron Database Location**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Linux
|
# Linux
|
||||||
rm -rf ~/.config/TimeSafari/*
|
rm -rf ~/.config/TimeSafari/*
|
||||||
@@ -137,7 +132,6 @@ rmdir /s /q %APPDATA%\TimeSafari
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### **Web Browser (Custom Data Directory)**
|
#### **Web Browser (Custom Data Directory)**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create isolated browser profile
|
# Create isolated browser profile
|
||||||
mkdir ~/timesafari-dev-data
|
mkdir ~/timesafari-dev-data
|
||||||
@@ -150,7 +144,6 @@ URL generation across all environments. This prevents localhost URLs from
|
|||||||
appearing in shared links during development.
|
appearing in shared links during development.
|
||||||
|
|
||||||
### Key Features
|
### Key Features
|
||||||
|
|
||||||
- ✅ **Production URLs for Sharing**: All copy link buttons use production domain
|
- ✅ **Production URLs for Sharing**: All copy link buttons use production domain
|
||||||
- ✅ **Environment-Specific Internal URLs**: Internal operations use appropriate
|
- ✅ **Environment-Specific Internal URLs**: Internal operations use appropriate
|
||||||
environment URLs
|
environment URLs
|
||||||
@@ -234,7 +227,6 @@ npm run test:prerequisites
|
|||||||
- **Build failures**: Run `npm run check:dependencies` to diagnose environment issues
|
- **Build failures**: Run `npm run check:dependencies` to diagnose environment issues
|
||||||
|
|
||||||
**Required Versions**:
|
**Required Versions**:
|
||||||
|
|
||||||
- Node.js: 18+ (LTS recommended)
|
- Node.js: 18+ (LTS recommended)
|
||||||
- npm: 8+ (comes with Node.js)
|
- npm: 8+ (comes with Node.js)
|
||||||
- Platform-specific tools: Android Studio, Xcode (for mobile builds)
|
- Platform-specific tools: Android Studio, Xcode (for mobile builds)
|
||||||
@@ -254,26 +246,25 @@ To add a Font Awesome icon, add to `fontawesome.ts` and reference with
|
|||||||
|
|
||||||
### Reference Material
|
### Reference Material
|
||||||
|
|
||||||
- Notifications can be type of `toast` (self-dismiss), `info`, `success`, `warning`, and `danger`.
|
* Notifications can be type of `toast` (self-dismiss), `info`, `success`, `warning`, and `danger`.
|
||||||
They are done via [notiwind](https://www.npmjs.com/package/notiwind) and set up in App.vue.
|
They are done via [notiwind](https://www.npmjs.com/package/notiwind) and set up in App.vue.
|
||||||
|
|
||||||
- [Customize Vue configuration](https://cli.vuejs.org/config/).
|
* [Customize Vue configuration](https://cli.vuejs.org/config/).
|
||||||
|
|
||||||
- If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
* If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
||||||
|
|
||||||
### Code Organization
|
### Code Organization
|
||||||
|
|
||||||
The project uses a centralized approach to type definitions and interfaces:
|
The project uses a centralized approach to type definitions and interfaces:
|
||||||
|
|
||||||
- `src/interfaces/` - Contains all TypeScript interfaces and type definitions
|
* `src/interfaces/` - Contains all TypeScript interfaces and type definitions
|
||||||
- `deepLinks.ts` - Deep linking type system and Zod validation schemas
|
* `deepLinks.ts` - Deep linking type system and Zod validation schemas
|
||||||
- `give.ts` - Give-related interfaces and type definitions
|
* `give.ts` - Give-related interfaces and type definitions
|
||||||
- `claims.ts` - Claim-related interfaces and verifiable credentials
|
* `claims.ts` - Claim-related interfaces and verifiable credentials
|
||||||
- `common.ts` - Shared interfaces and utility types
|
* `common.ts` - Shared interfaces and utility types
|
||||||
- Other domain-specific interface files
|
* Other domain-specific interface files
|
||||||
|
|
||||||
Key principles:
|
Key principles:
|
||||||
|
|
||||||
- All interfaces and types are defined in the interfaces folder
|
- All interfaces and types are defined in the interfaces folder
|
||||||
- Zod schemas are used for runtime validation and type generation
|
- Zod schemas are used for runtime validation and type generation
|
||||||
- Domain-specific interfaces are separated into their own files
|
- Domain-specific interfaces are separated into their own files
|
||||||
@@ -284,11 +275,11 @@ Key principles:
|
|||||||
|
|
||||||
The application uses a platform-agnostic database layer with Vue mixins for service access:
|
The application uses a platform-agnostic database layer with Vue mixins for service access:
|
||||||
|
|
||||||
- `src/services/PlatformService.ts` - Database interface definition
|
* `src/services/PlatformService.ts` - Database interface definition
|
||||||
- `src/services/PlatformServiceFactory.ts` - Platform-specific service factory
|
* `src/services/PlatformServiceFactory.ts` - Platform-specific service factory
|
||||||
- `src/services/AbsurdSqlDatabaseService.ts` - SQLite implementation
|
* `src/services/AbsurdSqlDatabaseService.ts` - SQLite implementation
|
||||||
- `src/utils/PlatformServiceMixin.ts` - Vue mixin for database access with caching
|
* `src/utils/PlatformServiceMixin.ts` - Vue mixin for database access with caching
|
||||||
- `src/db/` - Legacy Dexie database (migration in progress)
|
* `src/db/` - Legacy Dexie database (migration in progress)
|
||||||
|
|
||||||
**Development Guidelines**:
|
**Development Guidelines**:
|
||||||
|
|
||||||
@@ -325,11 +316,11 @@ timesafari/
|
|||||||
|
|
||||||
Gifts make the world go 'round!
|
Gifts make the world go 'round!
|
||||||
|
|
||||||
- [WebStorm by JetBrains](https://www.jetbrains.com/webstorm/) for the free open-source license
|
* [WebStorm by JetBrains](https://www.jetbrains.com/webstorm/) for the free open-source license
|
||||||
- [Máximo Fernández](https://medium.com/@maxfarenas) for the 3D [code](https://github.com/maxfer03/vue-three-ns) and [explanatory post](https://medium.com/nicasource/building-an-interactive-web-portfolio-with-vue-three-js-part-three-implementing-three-js-452cb375ef80)
|
* [Máximo Fernández](https://medium.com/@maxfarenas) for the 3D [code](https://github.com/maxfer03/vue-three-ns) and [explanatory post](https://medium.com/nicasource/building-an-interactive-web-portfolio-with-vue-three-js-part-three-implementing-three-js-452cb375ef80)
|
||||||
- [Many tools & libraries](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/src/branch/master/package.json#L10) such as Nodejs.org, IntelliJ Idea, Veramo.io, Vuejs.org, threejs.org
|
* [Many tools & libraries](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/src/branch/master/package.json#L10) such as Nodejs.org, IntelliJ Idea, Veramo.io, Vuejs.org, threejs.org
|
||||||
- [Bush 3D model](https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439)
|
* [Bush 3D model](https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439)
|
||||||
- [Forest floor image](https://www.goodfreephotos.com/albums/textures/leafy-autumn-forest-floor.jpg)
|
* [Forest floor image](https://www.goodfreephotos.com/albums/textures/leafy-autumn-forest-floor.jpg)
|
||||||
- Time Safari logo assisted by [DALL-E in ChatGPT](https://chat.openai.com/g/g-2fkFE8rbu-dall-e)
|
* Time Safari logo assisted by [DALL-E in ChatGPT](https://chat.openai.com/g/g-2fkFE8rbu-dall-e)
|
||||||
- [DiceBear](https://www.dicebear.com/licenses/) and [Avataaars](https://www.dicebear.com/styles/avataaars/#details) for human-looking identicons
|
* [DiceBear](https://www.dicebear.com/licenses/) and [Avataaars](https://www.dicebear.com/styles/avataaars/#details) for human-looking identicons
|
||||||
- Some gratitude prompts thanks to [Develop Good Habits](https://www.developgoodhabits.com/gratitude-journal-prompts/)
|
* Some gratitude prompts thanks to [Develop Good Habits](https://www.developgoodhabits.com/gratitude-journal-prompts/)
|
||||||
|
|||||||
+11
-11
@@ -5,33 +5,33 @@
|
|||||||
|
|
||||||
We can't trust iOS IndexedDB to persist. I want to start delivering an app to people now, in preparation for presentations mid-June: Rotary on June 12 and Porcfest on June 17.
|
We can't trust iOS IndexedDB to persist. I want to start delivering an app to people now, in preparation for presentations mid-June: Rotary on June 12 and Porcfest on June 17.
|
||||||
|
|
||||||
- Apple WebKit puts a [7-day cap on IndexedDB](https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/).
|
* Apple WebKit puts a [7-day cap on IndexedDB](https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/).
|
||||||
|
|
||||||
- The web standards expose a `persist` method to mark memory as persistent, and [supposedly WebView supports it](https://developer.mozilla.org/en-US/docs/Web/API/StorageManager/persisted), but too many other things indicate it's not reliable. I've talked with [ChatGPT](https://chatgpt.com/share/68322f40-84c8-8007-b213-855f7962989a) & Venice & Claude (in Cursor); [this answer from Perplexity](https://www.perplexity.ai/search/which-platforms-prompt-the-use-HUQLqy4qQD2cRbkmO4CgHg) says that most platforms don't prompt and Safari doesn't support it; I don't know if that means WebKit as well.
|
* The web standards expose a `persist` method to mark memory as persistent, and [supposedly WebView supports it](https://developer.mozilla.org/en-US/docs/Web/API/StorageManager/persisted), but too many other things indicate it's not reliable. I've talked with [ChatGPT](https://chatgpt.com/share/68322f40-84c8-8007-b213-855f7962989a) & Venice & Claude (in Cursor); [this answer from Perplexity](https://www.perplexity.ai/search/which-platforms-prompt-the-use-HUQLqy4qQD2cRbkmO4CgHg) says that most platforms don't prompt and Safari doesn't support it; I don't know if that means WebKit as well.
|
||||||
|
|
||||||
- Capacitor says [not to trust it on iOS](https://capacitorjs.com/docs/v6/guides/storage).
|
* Capacitor says [not to trust it on iOS](https://capacitorjs.com/docs/v6/guides/storage).
|
||||||
|
|
||||||
Also, with sensitive data, the accounts info should be encrypted.
|
Also, with sensitive data, the accounts info should be encrypted.
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
|
|
||||||
- There is a community [SQLite plugin for Capacitor](https://github.com/capacitor-community/sqlite) with encryption by [SQLCipher](https://github.com/sqlcipher/sqlcipher).
|
* There is a community [SQLite plugin for Capacitor](https://github.com/capacitor-community/sqlite) with encryption by [SQLCipher](https://github.com/sqlcipher/sqlcipher).
|
||||||
|
|
||||||
- [This tutorial](https://jepiqueau.github.io/2023/09/05/Ionic7Vue-SQLite-CRUD-App.html#part-1---web---table-of-contents) shows how that plugin works for web as well as native.
|
* [This tutorial](https://jepiqueau.github.io/2023/09/05/Ionic7Vue-SQLite-CRUD-App.html#part-1---web---table-of-contents) shows how that plugin works for web as well as native.
|
||||||
|
|
||||||
- Capacitor abstracts [user preferences in an API](https://capacitorjs.com/docs/apis/preferences), which uses different underlying libraries on iOS & Android. Unfortunately, it won't do any filtering or searching, and is only meant for small amounts of data. (It could be used for settings and for identifiers, but contacts will grow and image blobs won't work.)
|
* Capacitor abstracts [user preferences in an API](https://capacitorjs.com/docs/apis/preferences), which uses different underlying libraries on iOS & Android. Unfortunately, it won't do any filtering or searching, and is only meant for small amounts of data. (It could be used for settings and for identifiers, but contacts will grow and image blobs won't work.)
|
||||||
|
|
||||||
- There are hints that Capacitor offers another custom storage API but all I could find was that Preferences API.
|
* There are hints that Capacitor offers another custom storage API but all I could find was that Preferences API.
|
||||||
|
|
||||||
- [Ionic Storage](https://ionic.io/docs/secure-storage) is an enterprise solution, which also supports encryption.
|
* [Ionic Storage](https://ionic.io/docs/secure-storage) is an enterprise solution, which also supports encryption.
|
||||||
|
|
||||||
- Not an option yet: Dexie may support SQLite in [a future version](https://dexie.org/roadmap/dexie5.0).
|
* Not an option yet: Dexie may support SQLite in [a future version](https://dexie.org/roadmap/dexie5.0).
|
||||||
|
|
||||||
# Current Plan
|
# Current Plan
|
||||||
|
|
||||||
- Implement SQLite for Capacitor & web, with encryption. That will allow us to test quickly and keep the same interface for native & web, but we don't deal with migrations for current web users.
|
* Implement SQLite for Capacitor & web, with encryption. That will allow us to test quickly and keep the same interface for native & web, but we don't deal with migrations for current web users.
|
||||||
|
|
||||||
- After that is delivered, write a migration for current web users from IndexedDB to SQLite.
|
* After that is delivered, write a migration for current web users from IndexedDB to SQLite.
|
||||||
|
|
||||||
# Current method calls
|
# Current method calls
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
# Glossary
|
|
||||||
|
|
||||||
**T (slot time)** — The local wall-clock time a notification should fire (e.g., 08:00).
|
|
||||||
|
|
||||||
**T–lead** — The moment **`prefetchLeadMinutes`** before **T** when the system *attempts* a **single** background prefetch. T–lead **controls prefetch attempts, not arming**; locals are pre-armed earlier to guarantee closed-app delivery.
|
|
||||||
|
|
||||||
**Rolling window** — Always keep **today's remaining** (and tomorrow if iOS pending caps allow) locals **armed** so the OS can deliver while the app is closed.
|
|
||||||
|
|
||||||
**TTL (time-to-live)** — Maximum allowed payload age **at fire time**. If `T − fetchedAt > ttlSeconds`, we **skip** arming for that T.
|
|
||||||
|
|
||||||
**Shared DB (default)** — The app and plugin open the **same SQLite file**; the app owns schema/migrations, the plugin performs short writes with WAL.
|
|
||||||
|
|
||||||
**WAL (Write-Ahead Logging)** — SQLite journaling mode that permits concurrent reads during writes; recommended for foreground-read + background-write.
|
|
||||||
|
|
||||||
**`PRAGMA user_version`** — An integer the app increments on each migration; the plugin **checks** (does not migrate) to ensure compatibility.
|
|
||||||
|
|
||||||
**Exact alarm (Android)** — Minute-precise alarm via `AlarmManager.setExactAndAllowWhileIdle`, subject to policy and permission.
|
|
||||||
|
|
||||||
**Windowed alarm (Android)** — Batched/inexact alarm via `setWindow(start,len)`; we target **±10 minutes** when exact alarms are unavailable.
|
|
||||||
|
|
||||||
**Start-on-Login** — Electron feature that automatically launches the application when the user logs into their system, enabling background notification scheduling and delivery after system reboot.
|
|
||||||
@@ -330,7 +330,6 @@ Track the effectiveness of your Build Architecture Guard:
|
|||||||
## 📝 **Changelog**
|
## 📝 **Changelog**
|
||||||
|
|
||||||
### 2025-08-22 - Shell Compatibility Fix
|
### 2025-08-22 - Shell Compatibility Fix
|
||||||
|
|
||||||
- **Fixed**: Replaced `mapfile` command with portable alternative for cross-shell compatibility
|
- **Fixed**: Replaced `mapfile` command with portable alternative for cross-shell compatibility
|
||||||
- **Impact**: Resolves "mapfile: command not found" errors in pre-commit hooks
|
- **Impact**: Resolves "mapfile: command not found" errors in pre-commit hooks
|
||||||
- **Files**: `scripts/build-arch-guard.sh`
|
- **Files**: `scripts/build-arch-guard.sh`
|
||||||
|
|||||||
+73
-7
@@ -1,11 +1,77 @@
|
|||||||
# TimeSafari — Native-First Notification System (Clean Pack) — 2025-09-07
|
# TimeSafari Docs
|
||||||
|
|
||||||
This pack contains a single-version **Native-First** documentation set with a clear definition of **T–lead** and aligned terminology.
|
## Generating PDF from Markdown on OSx
|
||||||
|
|
||||||
**Native-First =** OS-scheduled **background prefetch at T–lead** + **pre-armed one-shot local notifications**. Web-push is retired.
|
This uses Pandoc and BasicTex (LaTeX) Installed through Homebrew.
|
||||||
|
|
||||||
**Included files**
|
### Set Up
|
||||||
|
|
||||||
- `notification-system.md` (merged comprehensive guide)
|
```bash
|
||||||
- `web-push-cleanup-guide.md` (cleanup instructions)
|
brew install pandoc
|
||||||
- `GLOSSARY.md` (definitions incl. **T** and **T–lead**)
|
|
||||||
|
brew install basictex
|
||||||
|
|
||||||
|
# Setting up LaTex packages
|
||||||
|
|
||||||
|
# First update tlmgr
|
||||||
|
sudo tlmgr update --self
|
||||||
|
|
||||||
|
# Then install LaTex packages
|
||||||
|
sudo tlmgr install bbding
|
||||||
|
sudo tlmgr install enumitem
|
||||||
|
sudo tlmgr install environ
|
||||||
|
sudo tlmgr install fancyhdr
|
||||||
|
sudo tlmgr install framed
|
||||||
|
sudo tlmgr install import
|
||||||
|
sudo tlmgr install lastpage # Enables Page X of Y
|
||||||
|
sudo tlmgr install mdframed
|
||||||
|
sudo tlmgr install multirow
|
||||||
|
sudo tlmgr install needspace
|
||||||
|
sudo tlmgr install ntheorem
|
||||||
|
sudo tlmgr install tabu
|
||||||
|
sudo tlmgr install tcolorbox
|
||||||
|
sudo tlmgr install textpos
|
||||||
|
sudo tlmgr install titlesec
|
||||||
|
sudo tlmgr install titling # Required for the fancy headers used
|
||||||
|
sudo tlmgr install threeparttable
|
||||||
|
sudo tlmgr install trimspaces
|
||||||
|
sudo tlmgr install tocloft # Required for \tableofcontents generation
|
||||||
|
sudo tlmgr install varwidth
|
||||||
|
sudo tlmgr install wrapfig
|
||||||
|
|
||||||
|
# Install fonts
|
||||||
|
sudo tlmgr install cmbright
|
||||||
|
sudo tlmgr install collection-fontsrecommended # And set up fonts
|
||||||
|
sudo tlmgr install fira
|
||||||
|
sudo tlmgr install fontaxes
|
||||||
|
sudo tlmgr install libertine # The main font the doc uses
|
||||||
|
sudo tlmgr install opensans
|
||||||
|
sudo tlmgr install sourceserifpro
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### References
|
||||||
|
|
||||||
|
The following guide was adapted to this project except that we install with Brew and have a few more packages.
|
||||||
|
|
||||||
|
Guide: <https://daniel.feldroy.com/posts/setting-up-latex-on-mac-os-x>
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Use the `pandoc` command to generate a PDF.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pandoc usage-guide.md -o usage-guide.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
And you can open the PDF with the `open` command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
open usage-guide.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use this one-liner
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pandoc usage-guide.md -o usage-guide.pdf && open usage-guide.pdf
|
||||||
|
```
|
||||||
|
|||||||
@@ -117,25 +117,25 @@ async function getDatabaseService() {
|
|||||||
|
|
||||||
## Files Modified
|
## Files Modified
|
||||||
|
|
||||||
1. **src/interfaces/worker-messages.ts** _(NEW)_
|
1. **src/interfaces/worker-messages.ts** *(NEW)*
|
||||||
- Type definitions for worker communication
|
- Type definitions for worker communication
|
||||||
- Request and response message interfaces
|
- Request and response message interfaces
|
||||||
|
|
||||||
2. **src/registerSQLWorker.js** _(MAJOR REWRITE)_
|
2. **src/registerSQLWorker.js** *(MAJOR REWRITE)*
|
||||||
- Message-based operation handling
|
- Message-based operation handling
|
||||||
- **Fixed circular dependency with lazy loading**
|
- **Fixed circular dependency with lazy loading**
|
||||||
- Proper error handling and response formatting
|
- Proper error handling and response formatting
|
||||||
|
|
||||||
3. **src/services/platforms/WebPlatformService.ts** _(MAJOR REWRITE)_
|
3. **src/services/platforms/WebPlatformService.ts** *(MAJOR REWRITE)*
|
||||||
- Worker-only database access
|
- Worker-only database access
|
||||||
- Message sending and response handling
|
- Message sending and response handling
|
||||||
- Timeout and error management
|
- Timeout and error management
|
||||||
|
|
||||||
4. **src/main.web.ts** _(SIMPLIFIED)_
|
4. **src/main.web.ts** *(SIMPLIFIED)*
|
||||||
- Removed duplicate worker creation
|
- Removed duplicate worker creation
|
||||||
- Simplified initialization flow
|
- Simplified initialization flow
|
||||||
|
|
||||||
5. **WORKER_ONLY_DATABASE_IMPLEMENTATION.md** _(NEW)_
|
5. **WORKER_ONLY_DATABASE_IMPLEMENTATION.md** *(NEW)*
|
||||||
- Complete documentation of changes
|
- Complete documentation of changes
|
||||||
|
|
||||||
## Benefits
|
## Benefits
|
||||||
|
|||||||
@@ -0,0 +1,343 @@
|
|||||||
|
# Active Identity Implementation Overview
|
||||||
|
|
||||||
|
**Author**: Matthew Raymer
|
||||||
|
**Date**: 2025-08-21T13:40Z
|
||||||
|
**Status**: 🚧 **IN PROGRESS** - Implementation Complete, Testing Pending
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Separate the `activeDid` field from the monolithic `settings` table into a
|
||||||
|
dedicated `active_identity` table to achieve:
|
||||||
|
|
||||||
|
- **Data normalization** and reduced cache drift
|
||||||
|
- **Simplified identity management** with dedicated table
|
||||||
|
- **Zero breaking API surface** for existing components
|
||||||
|
- **Phased migration** with rollback capability
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
This document provides a comprehensive overview of the implemented Active
|
||||||
|
Identity table separation system, including architecture, migration strategy,
|
||||||
|
and component integration.
|
||||||
|
|
||||||
|
## Use/Run
|
||||||
|
|
||||||
|
The implementation is ready for testing. Components can immediately use the new
|
||||||
|
façade methods while maintaining backward compatibility through dual-write
|
||||||
|
triggers.
|
||||||
|
|
||||||
|
## Context & Scope
|
||||||
|
|
||||||
|
- **Audience**: Developers working with identity management and database
|
||||||
|
migrations
|
||||||
|
- **In scope**: Active DID management, database schema evolution, Vue component
|
||||||
|
integration
|
||||||
|
- **Out of scope**: Multi-profile support beyond basic scope framework, complex
|
||||||
|
identity hierarchies
|
||||||
|
|
||||||
|
## Artifacts & Links
|
||||||
|
|
||||||
|
- **Implementation**: `src/db/tables/activeIdentity.ts`,
|
||||||
|
`src/utils/PlatformServiceMixin.ts`
|
||||||
|
- **Migrations**: `src/db-sql/migration.ts` (migrations 003 & 004)
|
||||||
|
- **Configuration**: `src/config/featureFlags.ts`
|
||||||
|
- **Documentation**: This document and progress tracking
|
||||||
|
|
||||||
|
## Environment & Preconditions
|
||||||
|
|
||||||
|
- **Database**: SQLite (Absurd-SQL for Web, Capacitor SQLite for Mobile)
|
||||||
|
- **Framework**: Vue.js with PlatformServiceMixin
|
||||||
|
- **Migration System**: Built-in migrationService.ts with automatic execution
|
||||||
|
|
||||||
|
## Architecture / Process Overview
|
||||||
|
|
||||||
|
The Active Identity separation follows a **phased migration pattern** with
|
||||||
|
dual-write triggers to ensure zero downtime and backward compatibility.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Legacy State] --> B[Phase A: Dual-Write]
|
||||||
|
B --> C[Phase B: Component Cutover]
|
||||||
|
C --> D[Phase C: Legacy Cleanup]
|
||||||
|
|
||||||
|
A --> A1[settings.activeDid]
|
||||||
|
B --> B1[active_identity table]
|
||||||
|
B --> B2[Dual-write trigger]
|
||||||
|
B --> B3[Fallback support]
|
||||||
|
C --> C1[Components use façade]
|
||||||
|
C --> C2[Legacy fallback disabled]
|
||||||
|
D --> D1[Drop activeDid column]
|
||||||
|
D --> D2[Remove triggers]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interfaces & Contracts
|
||||||
|
|
||||||
|
### Database Schema
|
||||||
|
|
||||||
|
| Table | Purpose | Key Fields | Constraints |
|
||||||
|
|-------|---------|------------|-------------|
|
||||||
|
| `active_identity` | Store active DID | `id`, `active_did`, | FK to accounts.did |
|
||||||
|
| | | `updated_at` | |
|
||||||
|
|
||||||
|
### Service Façade API
|
||||||
|
|
||||||
|
| Method | Purpose | Parameters | Returns |
|
||||||
|
|--------|---------|------------|---------|
|
||||||
|
| `$getActiveDid()` | Retrieve active DID | None | `Promise<string \| null>` |
|
||||||
|
| `$setActiveDid(did)` | Set active DID | `did` | `Promise<void>` |
|
||||||
|
| `$switchActiveIdentity(did)` | Switch to different DID | `did` | `Promise<void>` |
|
||||||
|
| `$getActiveIdentityScopes()` | Get available scopes | None | `Promise<string[]>` (always returns `["default"]`) |
|
||||||
|
|
||||||
|
## Repro: End-to-End Procedure
|
||||||
|
|
||||||
|
### 1. Database Migration Execution
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Migrations run automatically on app startup
|
||||||
|
# Migration 003: Creates active_identity table
|
||||||
|
# Migration 004: Drops settings.activeDid column (Phase C)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Component Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Before (legacy)
|
||||||
|
const activeDid = settings.activeDid || "";
|
||||||
|
await this.$saveSettings({ activeDid: newDid });
|
||||||
|
|
||||||
|
// After (new façade)
|
||||||
|
const activeDid = await this.$getActiveDid() || "";
|
||||||
|
await this.$setActiveDid(newDid);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Feature Flag Control
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Enable/disable migration phases
|
||||||
|
FLAGS.USE_ACTIVE_IDENTITY_ONLY = false; // Allow legacy fallback
|
||||||
|
FLAGS.DROP_SETTINGS_ACTIVEDID = false; // Keep legacy column
|
||||||
|
FLAGS.LOG_ACTIVE_ID_FALLBACK = true; // Log fallback usage
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Works (Evidence)
|
||||||
|
|
||||||
|
- ✅ **Migration Infrastructure**: Migrations 003 and 004 integrated into
|
||||||
|
`migrationService.ts`
|
||||||
|
- ✅ **Table Creation**: `active_identity` table schema with proper constraints
|
||||||
|
and indexes
|
||||||
|
- ✅ **Service Façade**: PlatformServiceMixin extended with all required methods
|
||||||
|
- ✅ **Feature Flags**: Comprehensive flag system for controlling rollout phases
|
||||||
|
- ✅ **Dual-Write Support**: One-way trigger from `settings.activeDid` →
|
||||||
|
`active_identity.active_did`
|
||||||
|
- ✅ **Validation**: DID existence validation before setting as active
|
||||||
|
- ✅ **Error Handling**: Comprehensive error handling with logging
|
||||||
|
|
||||||
|
## What Doesn't (Evidence & Hypotheses)
|
||||||
|
|
||||||
|
- ❌ **Component Migration**: No components yet updated to use new façade
|
||||||
|
methods
|
||||||
|
- ❌ **Testing**: No automated tests for new functionality
|
||||||
|
- ❌ **Performance Validation**: No benchmarks for read/write performance
|
||||||
|
- ❌ **Cross-Platform Validation**: Not tested on mobile platforms yet
|
||||||
|
|
||||||
|
## Risks, Limits, Assumptions
|
||||||
|
|
||||||
|
### **Migration Risks**
|
||||||
|
|
||||||
|
- **Data Loss**: If migration fails mid-process, could lose active DID state
|
||||||
|
- **Rollback Complexity**: Phase C (column drop) requires table rebuild, not
|
||||||
|
easily reversible
|
||||||
|
- **Trigger Dependencies**: Dual-write trigger could fail if `active_identity`
|
||||||
|
table is corrupted
|
||||||
|
|
||||||
|
### **Performance Limits**
|
||||||
|
|
||||||
|
- **Dual-Write Overhead**: Each `activeDid` change triggers additional
|
||||||
|
database operations
|
||||||
|
- **Fallback Queries**: Legacy fallback requires additional database queries
|
||||||
|
- **Transaction Scope**: Active DID changes wrapped in transactions for
|
||||||
|
consistency
|
||||||
|
|
||||||
|
### **Security Boundaries**
|
||||||
|
|
||||||
|
- **DID Validation**: Only validates DID exists in accounts table, not
|
||||||
|
ownership
|
||||||
|
- **Scope Isolation**: No current scope separation enforcement beyond table
|
||||||
|
constraints
|
||||||
|
- **Access Control**: No row-level security on `active_identity` table
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
||||||
|
|-------|------|---------------|-------------------|
|
||||||
|
| Developer | Test migrations | Migrations execute without errors | 2025-08-21 |
|
||||||
|
| Developer | Update components | All components use new façade | 2025-08-22 |
|
||||||
|
| | | methods | |
|
||||||
|
| Developer | Performance testing | Read/write performance meets | 2025-08-23 |
|
||||||
|
| | | requirements | |
|
||||||
|
| Developer | Phase C activation | Feature flag enables column | 2025-08-24 |
|
||||||
|
| | | removal | |
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Database Migration Guide](../database-migration-guide.md)
|
||||||
|
- [PlatformServiceMixin Documentation](../component-communication-guide.md)
|
||||||
|
- [Feature Flags Configuration](../feature-flags.md)
|
||||||
|
|
||||||
|
## Competence Hooks
|
||||||
|
|
||||||
|
- **Why this works**: Phased migration with dual-write triggers ensures zero
|
||||||
|
downtime while maintaining data consistency through foreign key constraints
|
||||||
|
and validation
|
||||||
|
- **Common pitfalls**: Forgetting to update components before enabling
|
||||||
|
`USE_ACTIVE_IDENTITY_ONLY`, not testing rollback scenarios, ignoring
|
||||||
|
cross-platform compatibility
|
||||||
|
- **Next skill unlock**: Implement automated component migration using codemods
|
||||||
|
and ESLint rules
|
||||||
|
- **Teach-back**: Explain how the dual-write trigger prevents data divergence
|
||||||
|
during the transition phase
|
||||||
|
|
||||||
|
## Collaboration Hooks
|
||||||
|
|
||||||
|
- **Reviewers**: Database team for migration logic, Vue team for component
|
||||||
|
integration, DevOps for deployment strategy
|
||||||
|
- **Sign-off checklist**: Migrations tested in staging, components updated,
|
||||||
|
performance validated, rollback plan documented
|
||||||
|
|
||||||
|
## Assumptions & Limits
|
||||||
|
|
||||||
|
- **Single User Focus**: Current implementation assumes single-user mode with
|
||||||
|
'default' scope
|
||||||
|
- **Vue Compatibility**: Assumes `vue-facing-decorator` compatibility (needs
|
||||||
|
validation)
|
||||||
|
- **Migration Timing**: Assumes migrations run on app startup (automatic
|
||||||
|
execution)
|
||||||
|
- **Platform Support**: Assumes same behavior across Web (Absurd-SQL) and
|
||||||
|
Mobile (Capacitor SQLite)
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### **Migration 003: Table Creation**
|
||||||
|
|
||||||
|
Creates the `active_identity` table with:
|
||||||
|
|
||||||
|
- **Primary Key**: Auto-incrementing ID
|
||||||
|
- **Scope Field**: For future multi-profile support (currently 'default')
|
||||||
|
- **Active DID**: Foreign key to accounts.did with CASCADE UPDATE
|
||||||
|
- **Timestamps**: ISO format timestamps for audit trail
|
||||||
|
- **Indexes**: Performance optimization for scope and DID lookups
|
||||||
|
|
||||||
|
### **Migration 004: Column Removal**
|
||||||
|
|
||||||
|
Implements Phase C by:
|
||||||
|
|
||||||
|
- **Table Rebuild**: Creates new settings table without activeDid column
|
||||||
|
- **Data Preservation**: Copies all other data from legacy table
|
||||||
|
- **Index Recreation**: Rebuilds necessary indexes
|
||||||
|
- **Trigger Cleanup**: Removes dual-write triggers
|
||||||
|
|
||||||
|
### **Service Façade Implementation**
|
||||||
|
|
||||||
|
The PlatformServiceMixin extension provides:
|
||||||
|
|
||||||
|
- **Dual-Read Logic**: Prefers new table, falls back to legacy during
|
||||||
|
transition
|
||||||
|
- **Dual-Write Logic**: Updates both tables during Phase A/B
|
||||||
|
- **Validation**: Ensures DID exists before setting as active
|
||||||
|
- **Transaction Safety**: Wraps operations in database transactions
|
||||||
|
- **Error Handling**: Comprehensive logging and error propagation
|
||||||
|
|
||||||
|
### **Feature Flag System**
|
||||||
|
|
||||||
|
Controls migration phases through:
|
||||||
|
|
||||||
|
- **`USE_ACTIVE_IDENTITY_ONLY`**: Disables legacy fallback reads
|
||||||
|
- **`DROP_SETTINGS_ACTIVEDID`**: Enables Phase C column removal
|
||||||
|
- **`LOG_ACTIVE_ID_FALLBACK`**: Logs when legacy fallback is used
|
||||||
|
- **`ENABLE_ACTIVE_IDENTITY_MIGRATION`**: Master switch for migration
|
||||||
|
system
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### **Data Validation**
|
||||||
|
|
||||||
|
- DID format validation (basic "did:" prefix check)
|
||||||
|
- Foreign key constraints ensure referential integrity
|
||||||
|
- Transaction wrapping prevents partial updates
|
||||||
|
|
||||||
|
### **Access Control**
|
||||||
|
|
||||||
|
- No row-level security implemented
|
||||||
|
- Scope isolation framework in place for future use
|
||||||
|
- Validation prevents setting non-existent DIDs as active
|
||||||
|
|
||||||
|
### **Audit Trail**
|
||||||
|
|
||||||
|
- Timestamps on all active identity changes
|
||||||
|
- Logging of fallback usage and errors
|
||||||
|
- Migration tracking through built-in system
|
||||||
|
|
||||||
|
## Performance Characteristics
|
||||||
|
|
||||||
|
### **Read Operations**
|
||||||
|
|
||||||
|
- **Primary Path**: Single query to `active_identity` table
|
||||||
|
- **Fallback Path**: Additional query to `settings` table (Phase A only)
|
||||||
|
- **Indexed Fields**: Both scope and active_did are indexed
|
||||||
|
|
||||||
|
### **Write Operations**
|
||||||
|
|
||||||
|
- **Dual-Write**: Updates both tables during transition (Phase A/B)
|
||||||
|
- **Transaction Overhead**: All operations wrapped in transactions
|
||||||
|
- **Trigger Execution**: Additional database operations per update
|
||||||
|
|
||||||
|
### **Migration Impact**
|
||||||
|
|
||||||
|
- **Table Creation**: Minimal impact (runs once)
|
||||||
|
- **Column Removal**: Moderate impact (table rebuild required)
|
||||||
|
- **Data Seeding**: Depends on existing data volume
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### **Unit Testing**
|
||||||
|
|
||||||
|
- Service façade method validation
|
||||||
|
- Error handling and edge cases
|
||||||
|
- Transaction rollback scenarios
|
||||||
|
|
||||||
|
### **Integration Testing**
|
||||||
|
|
||||||
|
- Migration execution and rollback
|
||||||
|
- Cross-platform compatibility
|
||||||
|
- Performance under load
|
||||||
|
|
||||||
|
### **End-to-End Testing**
|
||||||
|
|
||||||
|
- Component integration
|
||||||
|
- User workflow validation
|
||||||
|
- Migration scenarios
|
||||||
|
|
||||||
|
## Deployment Considerations
|
||||||
|
|
||||||
|
### **Rollout Strategy**
|
||||||
|
|
||||||
|
- **Phase A**: Deploy with dual-write enabled
|
||||||
|
- **Phase B**: Update components to use new methods
|
||||||
|
- **Phase C**: Enable column removal (irreversible)
|
||||||
|
|
||||||
|
### **Rollback Plan**
|
||||||
|
|
||||||
|
- **Phase A/B**: Disable feature flags, revert to legacy methods
|
||||||
|
- **Phase C**: Requires database restore (no automatic rollback)
|
||||||
|
|
||||||
|
### **Monitoring**
|
||||||
|
|
||||||
|
- Track fallback usage through logging
|
||||||
|
- Monitor migration success rates
|
||||||
|
- Alert on validation failures
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status**: Implementation complete, ready for testing and component migration
|
||||||
|
**Next Review**: After initial testing and component updates
|
||||||
|
**Maintainer**: Development team
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# Active Identity Migration - Phase B Progress
|
||||||
|
|
||||||
|
**Author**: Matthew Raymer
|
||||||
|
**Date**: 2025-08-22T07:05Z
|
||||||
|
**Status**: 🚧 **IN PROGRESS** - Component Migration Active
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Complete **Phase B: Component Cutover** by updating all Vue components to use the new Active Identity façade methods instead of directly accessing `settings.activeDid`.
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
|
||||||
|
### ✅ **Completed**
|
||||||
|
- **Migration Infrastructure**: Migrations 003 and 004 implemented
|
||||||
|
- **Service Façade**: PlatformServiceMixin extended with all required methods
|
||||||
|
- **TypeScript Types**: Added missing method declarations to Vue component interfaces
|
||||||
|
- **Feature Flags**: Comprehensive flag system for controlling rollout phases
|
||||||
|
|
||||||
|
### 🔄 **In Progress**
|
||||||
|
- **Component Migration**: Manually updating critical components
|
||||||
|
- **Pattern Establishment**: Creating consistent migration approach
|
||||||
|
|
||||||
|
### ❌ **Pending**
|
||||||
|
- **Bulk Component Updates**: 40+ components need migration
|
||||||
|
- **Testing**: Validate migrated components work correctly
|
||||||
|
- **Performance Validation**: Ensure no performance regressions
|
||||||
|
|
||||||
|
## Migration Progress
|
||||||
|
|
||||||
|
### **Components Migrated (3/40+)**
|
||||||
|
|
||||||
|
| Component | Status | Changes Made | Notes |
|
||||||
|
|-----------|--------|--------------|-------|
|
||||||
|
| `IdentitySwitcherView.vue` | ✅ Complete | - Updated `switchIdentity()` method<br>- Added FLAGS import<br>- Uses `$setActiveDid()` | Critical component for identity switching |
|
||||||
|
| `ImportDerivedAccountView.vue` | ✅ Complete | - Updated `incrementDerivation()` method<br>- Added FLAGS import<br>- Uses `$setActiveDid()` | Handles new account creation |
|
||||||
|
| `ClaimAddRawView.vue` | ✅ Complete | - Updated `initializeSettings()` method<br>- Uses `$getActiveDid()` | Reads active DID for claims |
|
||||||
|
|
||||||
|
### **Components Pending Migration (37+)**
|
||||||
|
|
||||||
|
| Component | Usage Pattern | Priority | Estimated Effort |
|
||||||
|
|-----------|---------------|----------|------------------|
|
||||||
|
| `HomeView.vue` | ✅ Updated | High | 5 min |
|
||||||
|
| `ProjectsView.vue` | `settings.activeDid \|\| ""` | High | 3 min |
|
||||||
|
| `ContactsView.vue` | `settings.activeDid \|\| ""` | High | 3 min |
|
||||||
|
| `AccountViewView.vue` | `settings.activeDid \|\| ""` | High | 3 min |
|
||||||
|
| `InviteOneView.vue` | `settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `TestView.vue` | `settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `SeedBackupView.vue` | `settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `QuickActionBvcBeginView.vue` | `const activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `ConfirmGiftView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `ClaimReportCertificateView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `ImportAccountView.vue` | `settings.activeDid,` | Medium | 3 min |
|
||||||
|
| `MembersList.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `ShareMyContactInfoView.vue` | `const activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `ClaimView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `ImageMethodDialog.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `DiscoverView.vue` | `settings.activeDid as string` | Medium | 3 min |
|
||||||
|
| `QuickActionBvcEndView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `ContactQRScanFullView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `ContactGiftingView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `OfferDetailsView.vue` | `this.activeDid = settings.activeDid ?? ""` | Medium | 3 min |
|
||||||
|
| `NewActivityView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `OfferDialog.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `SharedPhotoView.vue` | `this.activeDid = settings.activeDid` | Medium | 3 min |
|
||||||
|
| `ContactQRScanShowView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `NewEditProjectView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `GiftedDialog.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `HelpView.vue` | `if (settings.activeDid)` | Medium | 3 min |
|
||||||
|
| `TopMessage.vue` | `settings.activeDid?.slice(11, 15)` | Medium | 3 min |
|
||||||
|
| `ClaimCertificateView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `UserProfileView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `OnboardingDialog.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `RecentOffersToUserView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `RecentOffersToUserProjectsView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `ContactImportView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
| `GiftedDetailsView.vue` | `this.activeDid = settings.activeDid \|\| ""` | Medium | 3 min |
|
||||||
|
|
||||||
|
## Migration Patterns
|
||||||
|
|
||||||
|
### **Pattern 1: Simple Read Replacement**
|
||||||
|
```typescript
|
||||||
|
// Before
|
||||||
|
this.activeDid = settings.activeDid || "";
|
||||||
|
|
||||||
|
// After
|
||||||
|
this.activeDid = await this.$getActiveDid() || "";
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Pattern 2: Write Replacement with Dual-Write**
|
||||||
|
```typescript
|
||||||
|
// Before
|
||||||
|
await this.$saveSettings({ activeDid: newDid });
|
||||||
|
|
||||||
|
// After
|
||||||
|
await this.$setActiveDid(newDid);
|
||||||
|
|
||||||
|
// Legacy fallback - remove after Phase C
|
||||||
|
if (!FLAGS.USE_ACTIVE_IDENTITY_ONLY) {
|
||||||
|
await this.$saveSettings({ activeDid: newDid });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Pattern 3: FLAGS Import Addition**
|
||||||
|
```typescript
|
||||||
|
// Add to imports section
|
||||||
|
import { FLAGS } from "@/config/featureFlags";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### **Immediate Actions (Next 30 minutes)**
|
||||||
|
1. **Complete High-Priority Components**: Update remaining critical components
|
||||||
|
2. **Test Migration**: Verify migrated components work correctly
|
||||||
|
3. **Run Linter**: Check for any remaining TypeScript issues
|
||||||
|
|
||||||
|
### **Short Term (Next 2 hours)**
|
||||||
|
1. **Bulk Migration**: Use automated script for remaining components
|
||||||
|
2. **Testing**: Validate all migrated components
|
||||||
|
3. **Performance Check**: Ensure no performance regressions
|
||||||
|
|
||||||
|
### **Medium Term (Next 1 day)**
|
||||||
|
1. **Phase C Preparation**: Enable `USE_ACTIVE_IDENTITY_ONLY` flag
|
||||||
|
2. **Legacy Fallback Removal**: Remove dual-write patterns
|
||||||
|
3. **Final Testing**: End-to-end validation
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
### **Phase B Complete When**
|
||||||
|
- [ ] All 40+ components use new façade methods
|
||||||
|
- [ ] No direct `settings.activeDid` access remains
|
||||||
|
- [ ] All components pass linting
|
||||||
|
- [ ] Basic functionality tested and working
|
||||||
|
- [ ] Performance maintained or improved
|
||||||
|
|
||||||
|
### **Phase C Ready When**
|
||||||
|
- [ ] All components migrated and tested
|
||||||
|
- [ ] Feature flag `USE_ACTIVE_IDENTITY_ONLY` can be enabled
|
||||||
|
- [ ] No legacy fallback usage in production
|
||||||
|
- [ ] Performance benchmarks show improvement
|
||||||
|
|
||||||
|
## Risks & Mitigation
|
||||||
|
|
||||||
|
### **High Risk**
|
||||||
|
- **Component Breakage**: Test each migrated component individually
|
||||||
|
- **Performance Regression**: Monitor performance metrics during migration
|
||||||
|
- **TypeScript Errors**: Ensure all method signatures are properly declared
|
||||||
|
|
||||||
|
### **Medium Risk**
|
||||||
|
- **Migration Inconsistency**: Use consistent patterns across all components
|
||||||
|
- **Testing Coverage**: Ensure comprehensive testing of identity switching flows
|
||||||
|
|
||||||
|
### **Low Risk**
|
||||||
|
- **Backup Size**: Minimal backup strategy for critical files only
|
||||||
|
- **Rollback Complexity**: Simple git revert if needed
|
||||||
|
|
||||||
|
## Tools & Scripts
|
||||||
|
|
||||||
|
### **Migration Scripts**
|
||||||
|
- `scripts/migrate-active-identity-components.sh` - Full backup version
|
||||||
|
- `scripts/migrate-active-identity-components-efficient.sh` - Minimal backup version
|
||||||
|
|
||||||
|
### **Testing Commands**
|
||||||
|
```bash
|
||||||
|
# Check for remaining settings.activeDid usage
|
||||||
|
grep -r "settings\.activeDid" src/views/ src/components/
|
||||||
|
|
||||||
|
# Run linter
|
||||||
|
npm run lint-fix
|
||||||
|
|
||||||
|
# Test specific component
|
||||||
|
npm run test:web -- --grep "IdentitySwitcher"
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Active Identity Implementation Overview](./active-identity-implementation-overview.md)
|
||||||
|
- [PlatformServiceMixin Documentation](../component-communication-guide.md)
|
||||||
|
- [Feature Flags Configuration](../feature-flags.md)
|
||||||
|
- [Database Migration Guide](../database-migration-guide.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status**: Phase B in progress, 3/40+ components migrated
|
||||||
|
**Next Review**: After completing high-priority components
|
||||||
|
**Maintainer**: Development team
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
# ActiveDid Table Separation Progress Report
|
||||||
|
|
||||||
|
**Author**: Matthew Raymer
|
||||||
|
**Date**: 2025-08-21T12:32Z
|
||||||
|
**Status**: 🔍 **INVESTIGATION COMPLETE** - Ready for implementation planning
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This document tracks the investigation and progress of separating the `activeDid` field
|
||||||
|
from the `settings` table into a dedicated `active_identity` table. The project aims
|
||||||
|
to improve data integrity, reduce cache drift, and simplify transaction logic for
|
||||||
|
identity management in TimeSafari.
|
||||||
|
|
||||||
|
## Investigation Results
|
||||||
|
|
||||||
|
### Reference Audit Findings
|
||||||
|
|
||||||
|
**Total ActiveDid References**: 505 across the codebase
|
||||||
|
|
||||||
|
- **Write Operations**: 100 (20%)
|
||||||
|
- **Read Operations**: 260 (51%)
|
||||||
|
- **Other References**: 145 (29%) - includes type definitions, comments, etc.
|
||||||
|
|
||||||
|
**Component Impact**: 15+ Vue components directly access `settings.activeDid`
|
||||||
|
|
||||||
|
### Current Database Schema
|
||||||
|
|
||||||
|
The `settings` table currently contains **30 fields** mixing identity state with user
|
||||||
|
preferences:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
accountDid TEXT, -- Links to identity (null = master)
|
||||||
|
activeDid TEXT, -- Current active identity (master only)
|
||||||
|
apiServer TEXT, -- API endpoint
|
||||||
|
filterFeedByNearby BOOLEAN,
|
||||||
|
filterFeedByVisible BOOLEAN,
|
||||||
|
finishedOnboarding BOOLEAN,
|
||||||
|
firstName TEXT, -- User's name
|
||||||
|
hideRegisterPromptOnNewContact BOOLEAN,
|
||||||
|
isRegistered BOOLEAN,
|
||||||
|
lastName TEXT, -- Deprecated
|
||||||
|
lastAckedOfferToUserJwtId TEXT,
|
||||||
|
lastAckedOfferToUserProjectsJwtId TEXT,
|
||||||
|
lastNotifiedClaimId TEXT,
|
||||||
|
lastViewedClaimId TEXT,
|
||||||
|
notifyingNewActivityTime TEXT,
|
||||||
|
notifyingReminderMessage TEXT,
|
||||||
|
notifyingReminderTime TEXT,
|
||||||
|
partnerApiServer TEXT,
|
||||||
|
passkeyExpirationMinutes INTEGER,
|
||||||
|
profileImageUrl TEXT,
|
||||||
|
searchBoxes TEXT, -- JSON string
|
||||||
|
showContactGivesInline BOOLEAN,
|
||||||
|
showGeneralAdvanced BOOLEAN,
|
||||||
|
showShortcutBvc BOOLEAN,
|
||||||
|
vapid TEXT,
|
||||||
|
warnIfProdServer BOOLEAN,
|
||||||
|
warnIfTestServer BOOLEAN,
|
||||||
|
webPushServer TEXT
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component State Management
|
||||||
|
|
||||||
|
#### PlatformServiceMixin Cache System
|
||||||
|
|
||||||
|
- **`_currentActiveDid`**: Component-level cache for activeDid
|
||||||
|
- **`$updateActiveDid()`**: Method to sync cache with database
|
||||||
|
- **Change Detection**: Watcher triggers component updates on activeDid changes
|
||||||
|
- **State Synchronization**: Cache updates when `$saveSettings()` changes activeDid
|
||||||
|
|
||||||
|
#### Common Usage Patterns
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Standard pattern across 15+ components
|
||||||
|
this.activeDid = settings.activeDid || "";
|
||||||
|
|
||||||
|
// API header generation
|
||||||
|
const headers = await getHeaders(this.activeDid);
|
||||||
|
|
||||||
|
// Identity validation
|
||||||
|
if (claim.issuer === this.activeDid) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migration Infrastructure Status
|
||||||
|
|
||||||
|
#### Existing Capabilities
|
||||||
|
|
||||||
|
- **`migrateSettings()`**: Fully implemented and functional
|
||||||
|
- **Settings Migration**: Handles 30 fields with proper type conversion
|
||||||
|
- **Data Integrity**: Includes validation and error handling
|
||||||
|
- **Rollback Capability**: Migration service has rollback infrastructure
|
||||||
|
|
||||||
|
#### Migration Order
|
||||||
|
|
||||||
|
1. **Accounts** (foundational - contains DIDs)
|
||||||
|
2. **Settings** (references accountDid, activeDid)
|
||||||
|
3. **ActiveDid** (depends on accounts and settings)
|
||||||
|
4. **Contacts** (independent, but migrated after accounts)
|
||||||
|
|
||||||
|
### Testing Infrastructure
|
||||||
|
|
||||||
|
#### Current Coverage
|
||||||
|
|
||||||
|
- **Playwright Tests**: `npm run test:web` and `npm run test:mobile`
|
||||||
|
- **No Unit Tests**: Found for migration or settings management
|
||||||
|
- **Integration Tests**: Available through Playwright test suite
|
||||||
|
- **Platform Coverage**: Web, Mobile (Android/iOS), Desktop (Electron)
|
||||||
|
|
||||||
|
## Risk Assessment
|
||||||
|
|
||||||
|
### High Risk Areas
|
||||||
|
|
||||||
|
1. **Component State Synchronization**: 505 references across codebase
|
||||||
|
2. **Cache Drift**: `_currentActiveDid` vs database `activeDid`
|
||||||
|
3. **Cross-Platform Consistency**: Web + Mobile + Desktop
|
||||||
|
|
||||||
|
### Medium Risk Areas
|
||||||
|
|
||||||
|
1. **Foreign Key Constraints**: activeDid → accounts.did relationship
|
||||||
|
2. **Migration Rollback**: Complex 30-field settings table
|
||||||
|
3. **API Surface Changes**: Components expect `settings.activeDid`
|
||||||
|
|
||||||
|
### Low Risk Areas
|
||||||
|
|
||||||
|
1. **Migration Infrastructure**: Already exists and functional
|
||||||
|
2. **Data Integrity**: Current migration handles complex scenarios
|
||||||
|
3. **Testing Framework**: Playwright tests available for validation
|
||||||
|
|
||||||
|
## Implementation Phases
|
||||||
|
|
||||||
|
### Phase 1: Foundation Analysis ✅ **COMPLETE**
|
||||||
|
|
||||||
|
- [x] **ActiveDid Reference Audit**: 505 references identified and categorized
|
||||||
|
- [x] **Database Schema Analysis**: 30-field settings table documented
|
||||||
|
- [x] **Component Usage Mapping**: 15+ components usage patterns documented
|
||||||
|
- [x] **Migration Infrastructure Assessment**: Existing service validated
|
||||||
|
|
||||||
|
### Phase 2: Design & Implementation (Medium Complexity)
|
||||||
|
|
||||||
|
- [ ] **New Table Schema Design**
|
||||||
|
- Define `active_identity` table structure
|
||||||
|
- Plan foreign key relationships to `accounts.did`
|
||||||
|
- Design migration SQL statements
|
||||||
|
- Validate against existing data patterns
|
||||||
|
|
||||||
|
- [ ] **Component Update Strategy**
|
||||||
|
- Map all 505 references for update strategy
|
||||||
|
- Plan computed property changes
|
||||||
|
- Design state synchronization approach
|
||||||
|
- Preserve existing API surface
|
||||||
|
|
||||||
|
- [ ] **Testing Infrastructure Planning**
|
||||||
|
- Unit tests for new table operations
|
||||||
|
- Integration tests for identity switching
|
||||||
|
- Migration rollback validation
|
||||||
|
- Cross-platform testing strategy
|
||||||
|
|
||||||
|
### Phase 3: Migration & Validation (Complex Complexity)
|
||||||
|
|
||||||
|
- [ ] **Migration Execution Testing**
|
||||||
|
- Test on development database
|
||||||
|
- Validate data integrity post-migration
|
||||||
|
- Measure performance impact
|
||||||
|
- Test rollback scenarios
|
||||||
|
|
||||||
|
- [ ] **Cross-Platform Validation**
|
||||||
|
- Web platform functionality
|
||||||
|
- Mobile platform functionality
|
||||||
|
- Desktop platform functionality
|
||||||
|
- Cross-platform consistency
|
||||||
|
|
||||||
|
- [ ] **User Acceptance Testing**
|
||||||
|
- Identity switching workflows
|
||||||
|
- Settings persistence
|
||||||
|
- Error handling scenarios
|
||||||
|
- Edge case validation
|
||||||
|
|
||||||
|
## Technical Requirements
|
||||||
|
|
||||||
|
### New Table Schema
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Proposed active_identity table
|
||||||
|
CREATE TABLE IF NOT EXISTS active_identity (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
activeDid TEXT NOT NULL,
|
||||||
|
lastUpdated TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (activeDid) REFERENCES accounts(did)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Index for performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_active_identity_activeDid ON active_identity(activeDid);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migration Strategy
|
||||||
|
|
||||||
|
1. **Extract activeDid**: Copy from settings table to new table
|
||||||
|
2. **Update References**: Modify components to use new table
|
||||||
|
3. **Remove Field**: Drop activeDid from settings table
|
||||||
|
4. **Validate**: Ensure data integrity and functionality
|
||||||
|
|
||||||
|
### Component Updates Required
|
||||||
|
|
||||||
|
- **PlatformServiceMixin**: Update activeDid management
|
||||||
|
- **15+ Vue Components**: Modify activeDid access patterns
|
||||||
|
- **Migration Service**: Add activeDid table migration
|
||||||
|
- **Database Utilities**: Update settings operations
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
### Phase 1 ✅ **ACHIEVED**
|
||||||
|
|
||||||
|
- Complete activeDid usage audit with counts
|
||||||
|
- Database schema validation with data integrity check
|
||||||
|
- Migration service health assessment
|
||||||
|
- Clear dependency map for component updates
|
||||||
|
|
||||||
|
### Phase 2
|
||||||
|
|
||||||
|
- New table schema designed and validated
|
||||||
|
- Component update strategy documented
|
||||||
|
- Testing infrastructure planned
|
||||||
|
- Migration scripts developed
|
||||||
|
|
||||||
|
### Phase 3
|
||||||
|
|
||||||
|
- Migration successfully executed
|
||||||
|
- All platforms functional
|
||||||
|
- Performance maintained or improved
|
||||||
|
- Zero data loss
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Technical Dependencies
|
||||||
|
|
||||||
|
- **Existing Migration Infrastructure**: Settings migration service
|
||||||
|
- **Database Access Patterns**: PlatformServiceMixin methods
|
||||||
|
- **Component Architecture**: Vue component patterns
|
||||||
|
|
||||||
|
### Platform Dependencies
|
||||||
|
|
||||||
|
- **Cross-Platform Consistency**: Web + Mobile + Desktop
|
||||||
|
- **Testing Framework**: Playwright test suite
|
||||||
|
- **Build System**: Vite configuration for all platforms
|
||||||
|
|
||||||
|
### Testing Dependencies
|
||||||
|
|
||||||
|
- **Migration Validation**: Rollback testing
|
||||||
|
- **Integration Testing**: Cross-platform functionality
|
||||||
|
- **User Acceptance**: Identity switching workflows
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Immediate Actions (Next Session)
|
||||||
|
|
||||||
|
1. **Create New Table Schema**: Design `active_identity` table structure
|
||||||
|
2. **Component Update Planning**: Map all 505 references for update strategy
|
||||||
|
3. **Migration Script Development**: Create activeDid extraction migration
|
||||||
|
|
||||||
|
### Success Metrics
|
||||||
|
|
||||||
|
- **Data Integrity**: 100% activeDid data preserved
|
||||||
|
- **Performance**: No degradation in identity switching
|
||||||
|
- **Platform Coverage**: All platforms functional
|
||||||
|
- **Testing Coverage**: Comprehensive migration validation
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- **Codebase Analysis**: `src/views/*.vue`, `src/utils/PlatformServiceMixin.ts`
|
||||||
|
- **Database Schema**: `src/db-sql/migration.ts`
|
||||||
|
- **Migration Service**: `src/services/indexedDBMigrationService.ts`
|
||||||
|
- **Settings Types**: `src/db/tables/settings.ts`
|
||||||
|
|
||||||
|
## Competence Hooks
|
||||||
|
|
||||||
|
- **Why this works**: Separation of concerns improves data integrity, reduces
|
||||||
|
cache drift, simplifies transaction logic
|
||||||
|
- **Common pitfalls**: Missing component updates, foreign key constraint
|
||||||
|
violations, migration rollback failures
|
||||||
|
- **Next skill**: Database schema normalization and migration planning
|
||||||
|
- **Teach-back**: "How would you ensure zero downtime during the activeDid
|
||||||
|
table migration?"
|
||||||
|
|
||||||
|
## Collaboration Hooks
|
||||||
|
|
||||||
|
- **Reviewers**: Database team for schema design, Frontend team for component
|
||||||
|
updates, QA team for testing strategy
|
||||||
|
- **Sign-off checklist**: Migration tested, rollback verified, performance
|
||||||
|
validated, component state consistent
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status**: Investigation complete, ready for implementation planning
|
||||||
|
**Next Review**: 2025-08-28
|
||||||
|
**Estimated Complexity**: High (cross-platform refactoring with 505 references)
|
||||||
@@ -11,14 +11,12 @@ The Android Asset Validation System automatically detects and fixes missing Andr
|
|||||||
## Problem Solved
|
## Problem Solved
|
||||||
|
|
||||||
Previously, Android builds would fail with errors like:
|
Previously, Android builds would fail with errors like:
|
||||||
|
|
||||||
```
|
```
|
||||||
error: resource drawable/splash (aka app.timesafari.app:drawable/splash) not found.
|
error: resource drawable/splash (aka app.timesafari.app:drawable/splash) not found.
|
||||||
error: resource mipmap/ic_launcher (aka app.timesafari.app:mipmap/ic_launcher) not found.
|
error: resource mipmap/ic_launcher (aka app.timesafari.app:mipmap/ic_launcher) not found.
|
||||||
```
|
```
|
||||||
|
|
||||||
This happened when:
|
This happened when:
|
||||||
|
|
||||||
- Source assets existed but weren't generated into Android resources
|
- Source assets existed but weren't generated into Android resources
|
||||||
- Android resource directories were missing
|
- Android resource directories were missing
|
||||||
- Asset generation tools weren't run before building
|
- Asset generation tools weren't run before building
|
||||||
@@ -47,19 +45,16 @@ npm run build:android:studio
|
|||||||
### What Gets Validated
|
### What Gets Validated
|
||||||
|
|
||||||
#### Source Assets (Required)
|
#### Source Assets (Required)
|
||||||
|
|
||||||
- `resources/icon.png` - App icon source
|
- `resources/icon.png` - App icon source
|
||||||
- `resources/splash.png` - Splash screen source
|
- `resources/splash.png` - Splash screen source
|
||||||
- `resources/splash_dark.png` - Dark mode splash source
|
- `resources/splash_dark.png` - Dark mode splash source
|
||||||
|
|
||||||
#### Android Resources (Generated)
|
#### Android Resources (Generated)
|
||||||
|
|
||||||
- `android/app/src/main/res/drawable/splash.png` - Splash screen drawable
|
- `android/app/src/main/res/drawable/splash.png` - Splash screen drawable
|
||||||
- `android/app/src/main/res/mipmap-*/ic_launcher.png` - App icons for all densities
|
- `android/app/src/main/res/mipmap-*/ic_launcher.png` - App icons for all densities
|
||||||
- `android/app/src/main/res/mipmap-*/ic_launcher_round.png` - Round app icons for all densities
|
- `android/app/src/main/res/mipmap-*/ic_launcher_round.png` - Round app icons for all densities
|
||||||
|
|
||||||
### Density Levels Checked
|
### Density Levels Checked
|
||||||
|
|
||||||
- `mipmap-mdpi` (1x)
|
- `mipmap-mdpi` (1x)
|
||||||
- `mipmap-hdpi` (1.5x)
|
- `mipmap-hdpi` (1.5x)
|
||||||
- `mipmap-xhdpi` (2x)
|
- `mipmap-xhdpi` (2x)
|
||||||
@@ -69,7 +64,6 @@ npm run build:android:studio
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Automatic Validation (Recommended)
|
### Automatic Validation (Recommended)
|
||||||
|
|
||||||
The validation runs automatically during all Android builds:
|
The validation runs automatically during all Android builds:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -84,7 +78,6 @@ npm run build:android:debug
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Manual Validation
|
### Manual Validation
|
||||||
|
|
||||||
Run validation only to check/fix assets:
|
Run validation only to check/fix assets:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -96,7 +89,6 @@ npm run assets:validate:android
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Validation Only (No Regeneration)
|
### Validation Only (No Regeneration)
|
||||||
|
|
||||||
Check configuration without fixing:
|
Check configuration without fixing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -106,7 +98,6 @@ npm run assets:validate
|
|||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
### Missing Source Assets
|
### Missing Source Assets
|
||||||
|
|
||||||
If source assets are missing, the build fails with clear error messages:
|
If source assets are missing, the build fails with clear error messages:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -117,7 +108,6 @@ If source assets are missing, the build fails with clear error messages:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Missing Generated Resources
|
### Missing Generated Resources
|
||||||
|
|
||||||
If generated resources are missing, they're automatically regenerated:
|
If generated resources are missing, they're automatically regenerated:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -129,7 +119,6 @@ If generated resources are missing, they're automatically regenerated:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Generation Failure
|
### Generation Failure
|
||||||
|
|
||||||
If regeneration fails, helpful guidance is provided:
|
If regeneration fails, helpful guidance is provided:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -142,7 +131,6 @@ If regeneration fails, helpful guidance is provided:
|
|||||||
## Integration Points
|
## Integration Points
|
||||||
|
|
||||||
### Build Script Integration
|
### Build Script Integration
|
||||||
|
|
||||||
The validation is integrated into the main build process:
|
The validation is integrated into the main build process:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -155,7 +143,6 @@ validate_android_assets || {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### NPM Scripts
|
### NPM Scripts
|
||||||
|
|
||||||
New npm scripts for asset management:
|
New npm scripts for asset management:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -169,20 +156,17 @@ New npm scripts for asset management:
|
|||||||
## Benefits
|
## Benefits
|
||||||
|
|
||||||
### For Developers
|
### For Developers
|
||||||
|
|
||||||
- **No More Build Failures**: Automatic detection and fixing of missing resources
|
- **No More Build Failures**: Automatic detection and fixing of missing resources
|
||||||
- **Faster Development**: No need to manually run asset generation tools
|
- **Faster Development**: No need to manually run asset generation tools
|
||||||
- **Clear Error Messages**: Helpful guidance when issues occur
|
- **Clear Error Messages**: Helpful guidance when issues occur
|
||||||
- **Consistent Results**: Same validation on all development machines
|
- **Consistent Results**: Same validation on all development machines
|
||||||
|
|
||||||
### For CI/CD
|
### For CI/CD
|
||||||
|
|
||||||
- **Reliable Builds**: Consistent asset validation across environments
|
- **Reliable Builds**: Consistent asset validation across environments
|
||||||
- **Early Detection**: Catches issues before they reach production
|
- **Early Detection**: Catches issues before they reach production
|
||||||
- **Automated Fixes**: Self-healing builds when possible
|
- **Automated Fixes**: Self-healing builds when possible
|
||||||
|
|
||||||
### For Project Maintenance
|
### For Project Maintenance
|
||||||
|
|
||||||
- **Reduced Support**: Fewer "build doesn't work" issues
|
- **Reduced Support**: Fewer "build doesn't work" issues
|
||||||
- **Documentation**: Clear requirements for required assets
|
- **Documentation**: Clear requirements for required assets
|
||||||
- **Standardization**: Consistent asset structure across the project
|
- **Standardization**: Consistent asset structure across the project
|
||||||
@@ -192,27 +176,21 @@ New npm scripts for asset management:
|
|||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
||||||
#### "No assets found in the asset path"
|
#### "No assets found in the asset path"
|
||||||
|
|
||||||
This occurs when the `assets/` directory is empty. The validation system automatically copies source assets and regenerates them.
|
This occurs when the `assets/` directory is empty. The validation system automatically copies source assets and regenerates them.
|
||||||
|
|
||||||
#### "Failed to generate Android assets"
|
#### "Failed to generate Android assets"
|
||||||
|
|
||||||
Check that:
|
Check that:
|
||||||
|
|
||||||
- Source assets exist in `resources/`
|
- Source assets exist in `resources/`
|
||||||
- `@capacitor/assets` is installed
|
- `@capacitor/assets` is installed
|
||||||
- You have write permissions to the Android directories
|
- You have write permissions to the Android directories
|
||||||
|
|
||||||
#### "Asset generation completed but some resources are still missing"
|
#### "Asset generation completed but some resources are still missing"
|
||||||
|
|
||||||
This indicates a problem with the asset generation tool. Try:
|
This indicates a problem with the asset generation tool. Try:
|
||||||
|
|
||||||
1. Running `npm install` to ensure dependencies are up to date
|
1. Running `npm install` to ensure dependencies are up to date
|
||||||
2. Manually running `npx @capacitor/assets generate`
|
2. Manually running `npx @capacitor/assets generate`
|
||||||
3. Checking the asset generation logs for specific errors
|
3. Checking the asset generation logs for specific errors
|
||||||
|
|
||||||
### Manual Recovery
|
### Manual Recovery
|
||||||
|
|
||||||
If automatic regeneration fails, you can manually create the missing resources:
|
If automatic regeneration fails, you can manually create the missing resources:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -235,14 +213,12 @@ rm assets/icon.png assets/splash.png assets/splash_dark.png
|
|||||||
## Future Enhancements
|
## Future Enhancements
|
||||||
|
|
||||||
### Planned Improvements
|
### Planned Improvements
|
||||||
|
|
||||||
- **iOS Asset Validation**: Extend validation to iOS assets
|
- **iOS Asset Validation**: Extend validation to iOS assets
|
||||||
- **Asset Quality Checks**: Validate image dimensions and formats
|
- **Asset Quality Checks**: Validate image dimensions and formats
|
||||||
- **Performance Optimization**: Cache validation results
|
- **Performance Optimization**: Cache validation results
|
||||||
- **CI/CD Integration**: Add validation to GitHub Actions
|
- **CI/CD Integration**: Add validation to GitHub Actions
|
||||||
|
|
||||||
### Configuration Options
|
### Configuration Options
|
||||||
|
|
||||||
- **Custom Asset Paths**: Support for different asset directory structures
|
- **Custom Asset Paths**: Support for different asset directory structures
|
||||||
- **Validation Rules**: Configurable validation requirements
|
- **Validation Rules**: Configurable validation requirements
|
||||||
- **Skip Options**: Ability to skip validation for specific scenarios
|
- **Skip Options**: Ability to skip validation for specific scenarios
|
||||||
|
|||||||
@@ -122,4 +122,4 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
_This decision was made based on the current codebase architecture and team expertise. The mixin approach provides the best balance of performance, developer experience, and architectural consistency for the TimeSafari application._
|
*This decision was made based on the current codebase architecture and team expertise. The mixin approach provides the best balance of performance, developer experience, and architectural consistency for the TimeSafari application.*
|
||||||
|
|||||||
@@ -92,5 +92,5 @@ Multiple stack traces showing Vue router navigation and component mounting cycle
|
|||||||
3. Address API/server issues in separate debugging session
|
3. Address API/server issues in separate debugging session
|
||||||
|
|
||||||
---
|
---
|
||||||
_Log Entry by: Migration Assistant_
|
*Log Entry by: Migration Assistant*
|
||||||
_Session: ProjectsView.vue Triple Migration Pattern_
|
*Session: ProjectsView.vue Triple Migration Pattern*
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ you apply 1-3 meta-rules that automatically include everything you need.
|
|||||||
### **Step 1: Always Start with Core Always-On**
|
### **Step 1: Always Start with Core Always-On**
|
||||||
|
|
||||||
**Every single interaction** starts with:
|
**Every single interaction** starts with:
|
||||||
|
|
||||||
```
|
```
|
||||||
meta_core_always_on.mdc
|
meta_core_always_on.mdc
|
||||||
```
|
```
|
||||||
@@ -61,121 +60,64 @@ For complex tasks, you might combine multiple meta-rules:
|
|||||||
meta_core_always_on + meta_research + meta_bug_diagnosis
|
meta_core_always_on + meta_research + meta_bug_diagnosis
|
||||||
```
|
```
|
||||||
|
|
||||||
## Workflow Flexibility: Phase-Based, Not Waterfall
|
|
||||||
|
|
||||||
**Important**: Meta-rules represent **workflow phases**, not a rigid sequence. You can:
|
|
||||||
|
|
||||||
### **Jump Between Phases Freely**
|
|
||||||
|
|
||||||
- **Start with diagnosis** if you already know the problem
|
|
||||||
- **Go back to research** if your fix reveals new issues
|
|
||||||
- **Switch to planning** mid-implementation if scope changes
|
|
||||||
- **Document at any phase** - not just at the end
|
|
||||||
|
|
||||||
### **Mode Switching by Invoking Meta-Rules**
|
|
||||||
|
|
||||||
Each meta-rule invocation **automatically switches your workflow mode**:
|
|
||||||
|
|
||||||
```
|
|
||||||
Research Mode → Invoke @meta_bug_diagnosis → Diagnosis Mode
|
|
||||||
Diagnosis Mode → Invoke @meta_bug_fixing → Fixing Mode
|
|
||||||
Planning Mode → Invoke @meta_feature_implementation → Implementation Mode
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Phase Constraints, Not Sequence Constraints**
|
|
||||||
|
|
||||||
- **Within each phase**: Clear constraints on what you can/cannot do
|
|
||||||
- **Between phases**: Complete freedom to move as needed
|
|
||||||
- **No forced order**: Choose the phase that matches your current need
|
|
||||||
|
|
||||||
### **Example of Flexible Workflow**
|
|
||||||
|
|
||||||
```
|
|
||||||
1. Start with @meta_research (investigation mode)
|
|
||||||
2. Jump to @meta_bug_diagnosis (diagnosis mode)
|
|
||||||
3. Realize you need more research → back to @meta_research
|
|
||||||
4. Complete diagnosis → @meta_bug_fixing (implementation mode)
|
|
||||||
5. Find new issues → back to @meta_bug_diagnosis
|
|
||||||
6. Complete fix → @meta_documentation (documentation mode)
|
|
||||||
```
|
|
||||||
|
|
||||||
**The "sticky" part means**: Each phase has clear boundaries, but you control when to enter/exit phases.
|
|
||||||
|
|
||||||
## Practical Usage Examples
|
## Practical Usage Examples
|
||||||
|
|
||||||
### **Example 1: Bug Investigation (Flexible Flow)**
|
### **Example 1: Bug Investigation**
|
||||||
|
|
||||||
**Scenario**: User reports that the contact list isn't loading properly
|
**Scenario**: User reports that the contact list isn't loading properly
|
||||||
|
|
||||||
**Initial Meta-Rule Selection**:
|
**Meta-Rule Selection**:
|
||||||
|
|
||||||
```
|
```
|
||||||
meta_core_always_on + meta_research + meta_bug_diagnosis
|
meta_core_always_on + meta_research + meta_bug_diagnosis
|
||||||
```
|
```
|
||||||
|
|
||||||
**What This Gives You**:
|
**What This Gives You**:
|
||||||
|
|
||||||
- **Core Always-On**: Human competence focus, time standards, context
|
- **Core Always-On**: Human competence focus, time standards, context
|
||||||
- **Research**: Systematic investigation methodology, evidence collection
|
- **Research**: Systematic investigation methodology, evidence collection
|
||||||
- **Bug Diagnosis**: Defect analysis framework, root cause identification
|
- **Bug Diagnosis**: Defect analysis framework, root cause identification
|
||||||
|
|
||||||
**Flexible Workflow**:
|
**Workflow**:
|
||||||
|
|
||||||
1. Apply core always-on for foundation
|
1. Apply core always-on for foundation
|
||||||
2. Use research meta-rule for systematic investigation
|
2. Use research meta-rule for systematic investigation
|
||||||
3. Switch to bug diagnosis when you have enough evidence
|
3. Apply bug diagnosis for defect analysis
|
||||||
4. **Can go back to research** if diagnosis reveals new questions
|
4. Follow the bundled workflow automatically
|
||||||
5. **Can jump to bug fixing** if root cause is obvious
|
|
||||||
6. **Can document findings** at any phase
|
|
||||||
|
|
||||||
### **Example 2: Feature Development (Iterative Flow)**
|
### **Example 2: Feature Development**
|
||||||
|
|
||||||
**Scenario**: Building a new contact search feature
|
**Scenario**: Building a new contact search feature
|
||||||
|
|
||||||
**Meta-Rule Selection**:
|
**Meta-Rule Selection**:
|
||||||
|
|
||||||
```
|
```
|
||||||
meta_core_always_on + meta_feature_planning + meta_feature_implementation
|
meta_core_always_on + meta_feature_planning + meta_feature_implementation
|
||||||
```
|
```
|
||||||
|
|
||||||
**What This Gives You**:
|
**What This Gives You**:
|
||||||
|
|
||||||
- **Core Always-On**: Foundation principles and context
|
- **Core Always-On**: Foundation principles and context
|
||||||
- **Feature Planning**: Requirements analysis, architecture planning
|
- **Feature Planning**: Requirements analysis, architecture planning
|
||||||
- **Feature Implementation**: Development workflow, testing strategy
|
- **Feature Implementation**: Development workflow, testing strategy
|
||||||
|
|
||||||
**Iterative Workflow**:
|
**Workflow**:
|
||||||
|
|
||||||
1. Start with core always-on
|
1. Start with core always-on
|
||||||
2. Use feature planning for design and requirements
|
2. Use feature planning for design and requirements
|
||||||
3. Switch to feature implementation for coding and testing
|
3. Switch to feature implementation for coding and testing
|
||||||
4. **Can return to planning** if implementation reveals design issues
|
|
||||||
5. **Can go back to research** if you need to investigate alternatives
|
|
||||||
6. **Can document progress** throughout the process
|
|
||||||
|
|
||||||
### **Example 3: Documentation Creation (Parallel Flow)**
|
### **Example 3: Documentation Creation**
|
||||||
|
|
||||||
**Scenario**: Writing a migration guide for the new database system
|
**Scenario**: Writing a migration guide for the new database system
|
||||||
|
|
||||||
**Meta-Rule Selection**:
|
**Meta-Rule Selection**:
|
||||||
|
|
||||||
```
|
```
|
||||||
meta_core_always_on + meta_documentation
|
meta_core_always_on + meta_documentation
|
||||||
```
|
```
|
||||||
|
|
||||||
**What This Gives You**:
|
**What This Gives You**:
|
||||||
|
|
||||||
- **Core Always-On**: Foundation and context
|
- **Core Always-On**: Foundation and context
|
||||||
- **Documentation**: Educational focus, templates, quality standards
|
- **Documentation**: Educational focus, templates, quality standards
|
||||||
|
|
||||||
**Parallel Workflow**:
|
**Workflow**:
|
||||||
|
|
||||||
1. Apply core always-on for foundation
|
1. Apply core always-on for foundation
|
||||||
2. Use documentation meta-rule for educational content creation
|
2. Use documentation meta-rule for educational content creation
|
||||||
3. **Can research** while documenting if you need more information
|
3. Follow educational templates and quality standards
|
||||||
4. **Can plan** documentation structure as you write
|
|
||||||
5. **Can implement** examples or code snippets as needed
|
|
||||||
6. Follow educational templates and quality standards
|
|
||||||
|
|
||||||
## Meta-Rule Application Process
|
## Meta-Rule Application Process
|
||||||
|
|
||||||
@@ -212,35 +154,27 @@ Each meta-rule includes success criteria. Use these to validate your work:
|
|||||||
## Common Meta-Rule Combinations
|
## Common Meta-Rule Combinations
|
||||||
|
|
||||||
### **Research + Diagnosis**
|
### **Research + Diagnosis**
|
||||||
|
|
||||||
```
|
```
|
||||||
meta_core_always_on + meta_research + meta_bug_diagnosis
|
meta_core_always_on + meta_research + meta_bug_diagnosis
|
||||||
```
|
```
|
||||||
|
|
||||||
**Use for**: Complex bug investigations requiring systematic analysis
|
**Use for**: Complex bug investigations requiring systematic analysis
|
||||||
|
|
||||||
### **Planning + Implementation**
|
### **Planning + Implementation**
|
||||||
|
|
||||||
```
|
```
|
||||||
meta_core_always_on + meta_feature_planning + meta_feature_implementation
|
meta_core_always_on + meta_feature_planning + meta_feature_implementation
|
||||||
```
|
```
|
||||||
|
|
||||||
**Use for**: End-to-end feature development from concept to deployment
|
**Use for**: End-to-end feature development from concept to deployment
|
||||||
|
|
||||||
### **Research + Planning**
|
### **Research + Planning**
|
||||||
|
|
||||||
```
|
```
|
||||||
meta_core_always_on + meta_research + meta_feature_planning
|
meta_core_always_on + meta_research + meta_feature_planning
|
||||||
```
|
```
|
||||||
|
|
||||||
**Use for**: Feasibility research and solution design
|
**Use for**: Feasibility research and solution design
|
||||||
|
|
||||||
### **Documentation + Context**
|
### **Documentation + Context**
|
||||||
|
|
||||||
```
|
```
|
||||||
meta_core_always_on + meta_documentation + [context-specific]
|
meta_core_always_on + meta_documentation + [context-specific]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Use for**: Creating comprehensive, educational documentation
|
**Use for**: Creating comprehensive, educational documentation
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|||||||
@@ -1,231 +0,0 @@
|
|||||||
# TimeSafari — Native-First Notification System
|
|
||||||
|
|
||||||
**Status:** Ready for implementation
|
|
||||||
**Date:** 2025-09-07
|
|
||||||
**Author:** Matthew Raymer
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
Ship a **single, Native-First** notification system: OS-scheduled **background prefetch at T–lead** + **pre-armed** local notifications. Web-push is retired.
|
|
||||||
|
|
||||||
### What we deliver
|
|
||||||
|
|
||||||
- **Closed-app delivery:** Pre-armed locals fire even if the app is closed.
|
|
||||||
- **Freshness:** One prefetch attempt per slot at **T–lead**; ETag/TTL controls; skip when stale.
|
|
||||||
- **Android precision:** Exact alarms with permission; windowed fallback (±10m) otherwise.
|
|
||||||
- **Resilience:** Re-arm after reboot/time-change (Android receivers; iOS on next wake/silent push).
|
|
||||||
- **Cross-platform:** Same TS API (iOS/Android/Electron). Electron is best-effort while running.
|
|
||||||
|
|
||||||
### Success signals
|
|
||||||
|
|
||||||
- High delivery reliability, minute-precision on Android with permission.
|
|
||||||
- Prefetch budget hit rate at **T–lead**; zero stale deliveries beyond TTL.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Strategic Plan
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
Deliver 1..M daily notifications with **OS background prefetch at T–lead** and **rolling-window safety** so messages display with fresh content even when the app is closed.
|
|
||||||
|
|
||||||
### Tenets
|
|
||||||
|
|
||||||
- **Reliability first:** OS delivers once scheduled; no JS at delivery time.
|
|
||||||
- **Freshness with guardrails:** Prefetch at **T–lead**; enforce **TTL-at-fire**; ETag-aware.
|
|
||||||
- **Single system:** One TS API; native adapters swap under the hood.
|
|
||||||
- **Platform honesty:** Android exactness via permission; iOS best-effort budget.
|
|
||||||
|
|
||||||
### Architecture (high level)
|
|
||||||
|
|
||||||
App (Vue/TS) → Orchestrator (policy) → Native Adapters:
|
|
||||||
|
|
||||||
- **SchedulerNative** — AlarmManager (Android) / UNUserNotificationCenter (iOS)
|
|
||||||
- **BackgroundPrefetchNative** — WorkManager (Android) / BGTaskScheduler (+ silent push) (iOS)
|
|
||||||
- **DataStore** — SQLite
|
|
||||||
|
|
||||||
**Storage (single shared DB):** The app and the native plugin will use **the same SQLite database file**. The app owns schema/migrations; the plugin opens the same file with WAL enabled and performs short, serialized writes. This keeps one source of truth for payloads, delivery logs, and config.
|
|
||||||
|
|
||||||
### SQLite Ownership & Concurrency
|
|
||||||
|
|
||||||
* **One DB file:** The plugin opens the **same path** the app uses (no second DB).
|
|
||||||
* **Migrations owned by app:** The app executes schema migrations and bumps `PRAGMA user_version`. The plugin **never** migrates; it **asserts** the expected version.
|
|
||||||
* **WAL mode:** Open DB with `journal_mode=WAL`, `synchronous=NORMAL`, `busy_timeout=5000`, `foreign_keys=ON`. WAL allows foreground reads while a background job commits quickly.
|
|
||||||
* **Single-writer discipline:** Background jobs write in **short transactions** (UPSERT per slot), then return.
|
|
||||||
* **Encryption (optional):** If using SQLCipher, the **same key** is used by both app and plugin. Do not mix encrypted and unencrypted openings.
|
|
||||||
|
|
||||||
### Scheduling & T–lead
|
|
||||||
|
|
||||||
- **Arm** a rolling window (today + tomorrow within iOS cap).
|
|
||||||
- **Attempt** a single **online-first** fetch per slot at **T–lead = T − prefetchLeadMinutes**.
|
|
||||||
- If prefetch is skipped, the armed local **still fires** using cached content.
|
|
||||||
|
|
||||||
### Policies
|
|
||||||
|
|
||||||
- **TTL-at-fire:** If (T − fetchedAt) > `ttlSeconds` → **skip** arming.
|
|
||||||
- **Android exactness:** Request `SCHEDULE_EXACT_ALARM`; fallback **±10m** window.
|
|
||||||
- **Reboot/time change:** Android receivers re-arm next 24h; iOS on next wake/silent push.
|
|
||||||
- **No delivery-time mutation:** iOS locals cannot be mutated by NSE; render before scheduling.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Guide
|
|
||||||
|
|
||||||
### 1) Interfaces (TS stable)
|
|
||||||
|
|
||||||
- **SchedulerNative**: `scheduleExact({slotId, whenMs, title, body, extra})`, `scheduleWindow(..., windowLenMs)`, `cancelBySlot`, `rescheduleAll`, `capabilities()`
|
|
||||||
- **BackgroundPrefetchNative**: `schedulePrefetch(slotId, atMs)`, `cancelPrefetch(slotId)`
|
|
||||||
- **DataStore**: SQLite adapters (notif_contents, notif_deliveries, notif_config)
|
|
||||||
- **Public API**: `configure`, `requestPermissions`, `runFullPipelineNow`, `reschedule`, `getState`
|
|
||||||
|
|
||||||
### DB Path & Adapter Configuration
|
|
||||||
|
|
||||||
* **Configure option:** `dbPath: string` (absolute path or platform alias) is passed from JS to the plugin during `configure()`.
|
|
||||||
* **Shared tables:**
|
|
||||||
|
|
||||||
* `notif_contents(slot_id, payload_json, fetched_at, etag, …)`
|
|
||||||
* `notif_deliveries(slot_id, fire_at, delivered_at, status, error_code, …)`
|
|
||||||
* `notif_config(k, v)`
|
|
||||||
* **Open settings:**
|
|
||||||
|
|
||||||
* `journal_mode=WAL`
|
|
||||||
* `synchronous=NORMAL`
|
|
||||||
* `busy_timeout=5000`
|
|
||||||
* `foreign_keys=ON`
|
|
||||||
|
|
||||||
**Type (TS) extension**
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export type ConfigureOptions = {
|
|
||||||
// …existing fields…
|
|
||||||
dbPath: string; // shared DB file the plugin will open
|
|
||||||
storage: 'shared'; // canonical value; plugin-owned DB is not used
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Plugin side (pseudo)**
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
// Android open
|
|
||||||
val db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READWRITE)
|
|
||||||
db.execSQL("PRAGMA journal_mode=WAL")
|
|
||||||
db.execSQL("PRAGMA synchronous=NORMAL")
|
|
||||||
db.execSQL("PRAGMA foreign_keys=ON")
|
|
||||||
db.execSQL("PRAGMA busy_timeout=5000")
|
|
||||||
// Verify schema version
|
|
||||||
val uv = rawQuery("PRAGMA user_version").use { it.moveToFirst(); it.getInt(0) }
|
|
||||||
require(uv >= MIN_EXPECTED_VERSION) { "Schema version too old" }
|
|
||||||
```
|
|
||||||
|
|
||||||
```swift
|
|
||||||
// iOS open (FMDB / SQLite3)
|
|
||||||
// Set WAL via PRAGMA after open; check user_version the same way.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2) Templating & Arming
|
|
||||||
|
|
||||||
- Render `title/body` **before** scheduling; pass via **SchedulerNative**.
|
|
||||||
- Route all arming through **SchedulerNative** to centralize Android exact/window semantics.
|
|
||||||
|
|
||||||
### 3) T–lead (single attempt)
|
|
||||||
|
|
||||||
**T–lead governs prefetch, not arming.** We **arm** one-shot locals as part of the rolling window so closed-app delivery is guaranteed. At **T–lead = T − prefetchLeadMinutes**, the **native background job** attempts **one** 12s ETag-aware fetch. If fresh content arrives and will not violate **TTL-at-fire**, we (re)arm the upcoming slot; if the OS skips the wake, the pre-armed local still fires with cached content.
|
|
||||||
|
|
||||||
- Compute T–lead = `whenMs - prefetchLeadMinutes*60_000`.
|
|
||||||
- `BackgroundPrefetchNative.schedulePrefetch(slotId, atMs=T–lead)`.
|
|
||||||
- On wake: **ETag** fetch (timeout **12s**), persist, optionally cancel & re-arm if within TTL.
|
|
||||||
- Never fetch at delivery time.
|
|
||||||
|
|
||||||
### 4) TTL-at-fire
|
|
||||||
|
|
||||||
**TTL-at-fire:** Before arming for time **T**, compute `T − fetchedAt`. If that exceeds `ttlSeconds`, **do not arm** (skip). This prevents posting stale notifications when the app has been closed for a long time.
|
|
||||||
|
|
||||||
`if (whenMs - fetchedAt) > ttlSeconds*1000 → skip`
|
|
||||||
|
|
||||||
### 5) Android specifics
|
|
||||||
|
|
||||||
- Request `SCHEDULE_EXACT_ALARM`; deep-link if denied; fallback to `setWindow(start,len)` (±10m).
|
|
||||||
- Receivers: `BOOT_COMPLETED`, `TIMEZONE_CHANGED`, `TIME_SET` → recompute & re-arm for next 24h and schedule T–lead prefetch.
|
|
||||||
|
|
||||||
### 6) iOS specifics
|
|
||||||
|
|
||||||
- `BGTaskScheduler` for T–lead prefetch (best-effort). Optional silent push nudge.
|
|
||||||
- Locals: `UNCalendarNotificationTrigger` (one-shots); no NSE mutation for locals.
|
|
||||||
|
|
||||||
### 7) Network & Timeouts
|
|
||||||
|
|
||||||
- Content fetch: **12s** timeout; single attempt at T–lead; ETag/304 respected.
|
|
||||||
- ACK/Error: **8s** timeout, fire-and-forget.
|
|
||||||
|
|
||||||
### 8) Electron
|
|
||||||
|
|
||||||
- Notifications while app is running; recommend **Start-on-Login**. No true background scheduling when fully closed.
|
|
||||||
|
|
||||||
### 9) Telemetry
|
|
||||||
|
|
||||||
- Record `scheduled|shown|error`; ACK deliveries (8s timeout); include slot/times/TZ/app version.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Capability Matrix
|
|
||||||
|
|
||||||
| Capability | Android (Native) | iOS (Native) | Electron | Web |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| Multi-daily locals (closed app) | ✅ | ✅ | ✅ (app running) | — |
|
|
||||||
| Prefetch at T–lead (app closed) | ✅ WorkManager | ⚠️ BGTask (best-effort) | ✅ (app running) | — |
|
|
||||||
| Re-arm after reboot/time-change | ✅ Receivers | ⚠️ On next wake/silent push | ✅ Start-on-Login | — |
|
|
||||||
| Minute-precision alarms | ✅ with exact permission | ❌ not guaranteed | ✅ timer best-effort | — |
|
|
||||||
| Delivery-time mutation for locals | ❌ | ❌ | — | — |
|
|
||||||
| ETag/TTL enforcement | ✅ | ✅ | ✅ | — |
|
|
||||||
| Rolling-window safety | ✅ | ✅ | ✅ | — |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
### Core
|
|
||||||
|
|
||||||
- **Closed-app delivery:** Armed locals fire at T with last rendered content. No delivery-time network.
|
|
||||||
- **T–lead prefetch:** Single background attempt at **T–lead**; if skipped, delivery still occurs from cache.
|
|
||||||
- **TTL-at-fire:** No armed local violates TTL at T.
|
|
||||||
|
|
||||||
### Android
|
|
||||||
|
|
||||||
- **Exact permission path:** With `SCHEDULE_EXACT_ALARM` → within ±1m; else **±10m** window.
|
|
||||||
- **Reboot recovery:** After reboot, receivers re-arm next 24h and schedule T–lead prefetch.
|
|
||||||
- **TZ/DST change:** Recompute & re-arm; future slots align to new wall-clock.
|
|
||||||
|
|
||||||
### iOS
|
|
||||||
|
|
||||||
- **BGTask budget respected:** Prefetch often runs but may be skipped; delivery still occurs via rolling window.
|
|
||||||
- **Force-quit caveat:** No background execution after user terminate; delivery still occurs if pre-armed.
|
|
||||||
|
|
||||||
### Electron
|
|
||||||
|
|
||||||
- **Running-app rule:** Delivery only while app runs; with Start-on-Login, after reboot the orchestrator re-arms and subsequent slots deliver.
|
|
||||||
|
|
||||||
### Network
|
|
||||||
|
|
||||||
- Content fetch timeout **12s**; ACK/Error **8s**; no retries inside lead; ETag honored.
|
|
||||||
|
|
||||||
### Observability
|
|
||||||
|
|
||||||
- Log/telemetry for `scheduled|shown|error`; ACK payload includes slot, times, device TZ, app version.
|
|
||||||
|
|
||||||
### DB Sharing
|
|
||||||
|
|
||||||
* **Shared DB visibility:** A background prefetch writes `notif_contents`; the foreground UI **immediately** reads the same row.
|
|
||||||
* **WAL overlap:** With the app reading while the plugin commits, no user-visible blocking occurs.
|
|
||||||
* **Version safety:** If `user_version` is behind, the plugin emits an error and does not write (protects against partial installs).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Web-Push Cleanup
|
|
||||||
|
|
||||||
Web-push functionality has been retired due to unreliability. All web-push related code paths and documentation sections should be removed or marked as deprecated. See `web-push-cleanup-guide.md` for detailed cleanup steps.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
_This document consolidates the Native-First notification system strategy, implementation details, capabilities, and acceptance criteria into a single comprehensive reference._
|
|
||||||
@@ -1,551 +0,0 @@
|
|||||||
# TimeSafari Web-Push Cleanup Guide
|
|
||||||
|
|
||||||
**Status:** 🚀 Native-First Implementation
|
|
||||||
**Date:** 2025-01-27T14:30Z (UTC)
|
|
||||||
**Author:** Matthew Raymer
|
|
||||||
**Scope:** Web-push code cleanup and deprecation
|
|
||||||
**Goal:** Remove or quarantine all web-push code paths and mark as deprecated.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
This document provides a comprehensive cleanup guide for removing web-push code
|
|
||||||
paths from TimeSafari. Web-push has been retired for unreliability, and the
|
|
||||||
system now focuses on native mobile reliability with Electron best-effort support.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cleanup Strategy
|
|
||||||
|
|
||||||
### Phase 1: Identify Web-Push Code Paths
|
|
||||||
|
|
||||||
#### Service Worker Files
|
|
||||||
|
|
||||||
- [ ] `sw_scripts/notification-click.js` - Mark as deprecated
|
|
||||||
- [ ] `sw_scripts/` directory - Review for web-push dependencies
|
|
||||||
- [ ] Service worker registration code - Remove or quarantine
|
|
||||||
|
|
||||||
#### Web-Specific Code
|
|
||||||
|
|
||||||
- [ ] Web push notification handlers
|
|
||||||
- [ ] Service worker event listeners
|
|
||||||
- [ ] Web notification API usage
|
|
||||||
- [ ] Push subscription management
|
|
||||||
|
|
||||||
#### Configuration Files
|
|
||||||
|
|
||||||
- [ ] VitePWA plugin configuration
|
|
||||||
- [ ] Service worker build configuration
|
|
||||||
- [ ] Web push manifest files
|
|
||||||
|
|
||||||
### Phase 2: Mark as Deprecated
|
|
||||||
|
|
||||||
#### Code Comments
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DEPRECATED: Web-push notification handling
|
|
||||||
// This code is kept for reference but not used in production
|
|
||||||
// Replaced by Native-First notification system
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Documentation Updates
|
|
||||||
|
|
||||||
- [ ] Mark web-push sections as deprecated
|
|
||||||
- [ ] Add deprecation notices
|
|
||||||
- [ ] Update README files
|
|
||||||
- [ ] Update API documentation
|
|
||||||
|
|
||||||
### Phase 3: Remove or Quarantine
|
|
||||||
|
|
||||||
#### Complete Removal
|
|
||||||
|
|
||||||
- [ ] Web push subscription code
|
|
||||||
- [ ] Service worker notification handlers
|
|
||||||
- [ ] Web-specific notification APIs
|
|
||||||
- [ ] Push message handling
|
|
||||||
|
|
||||||
#### Quarantine (Keep for Reference)
|
|
||||||
|
|
||||||
- [ ] Service worker registration code
|
|
||||||
- [ ] Web push configuration
|
|
||||||
- [ ] Historical web-push tests
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Detailed Cleanup Tasks
|
|
||||||
|
|
||||||
### 1. Service Worker Cleanup
|
|
||||||
|
|
||||||
#### Files to Deprecate
|
|
||||||
|
|
||||||
**`sw_scripts/notification-click.js`**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DEPRECATED: Service worker notification handling
|
|
||||||
// This code is kept for reference but not used in production
|
|
||||||
// Replaced by Native-First notification system
|
|
||||||
|
|
||||||
// Original web-push notification click handler
|
|
||||||
self.addEventListener('notificationclick', (event) => {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
event.notification.close();
|
|
||||||
|
|
||||||
const slotId = event.notification.data?.slotId;
|
|
||||||
const route = slotId ? '/#/daily' : '/#/notifications';
|
|
||||||
|
|
||||||
event.waitUntil(
|
|
||||||
clients.openWindow(route).catch(() => {
|
|
||||||
return clients.openWindow('/');
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Service Worker Registration**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DEPRECATED: Service worker registration
|
|
||||||
// This code is kept for reference but not used in production
|
|
||||||
// Replaced by Native-First notification system
|
|
||||||
|
|
||||||
if ('serviceWorker' in navigator && process.env.VITE_PLATFORM === 'web') {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
navigator.serviceWorker.register('/sw.js')
|
|
||||||
.then(registration => {
|
|
||||||
console.log('Service Worker registered:', registration);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Service Worker registration failed:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Web Push API Cleanup
|
|
||||||
|
|
||||||
#### Push Subscription Management
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DEPRECATED: Web push subscription management
|
|
||||||
// This code is kept for reference but not used in production
|
|
||||||
// Replaced by Native-First notification system
|
|
||||||
|
|
||||||
class WebPushManager {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
async subscribeToPush() {
|
|
||||||
// Implementation kept for reference
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
async unsubscribeFromPush() {
|
|
||||||
// Implementation kept for reference
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Push Message Handling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DEPRECATED: Push message handling
|
|
||||||
// This code is kept for reference but not used in production
|
|
||||||
// Replaced by Native-First notification system
|
|
||||||
|
|
||||||
self.addEventListener('push', (event) => {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
const data = event.data ? event.data.json() : {};
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
body: data.body,
|
|
||||||
icon: '/icon-192x192.png',
|
|
||||||
badge: '/badge-72x72.png',
|
|
||||||
data: data
|
|
||||||
};
|
|
||||||
|
|
||||||
event.waitUntil(
|
|
||||||
self.registration.showNotification(data.title, options)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Configuration Cleanup
|
|
||||||
|
|
||||||
#### VitePWA Plugin Configuration
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DEPRECATED: VitePWA plugin configuration
|
|
||||||
// This configuration is kept for reference but not used in production
|
|
||||||
// Replaced by Native-First notification system
|
|
||||||
|
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
VitePWA({
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
registerType: 'autoUpdate',
|
|
||||||
workbox: {
|
|
||||||
globPatterns: ['**/*.{js,css,html,ico,png,svg}']
|
|
||||||
},
|
|
||||||
includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'],
|
|
||||||
manifest: {
|
|
||||||
name: 'TimeSafari',
|
|
||||||
short_name: 'TimeSafari',
|
|
||||||
description: 'TimeSafari App',
|
|
||||||
theme_color: '#ffffff',
|
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: 'pwa-192x192.png',
|
|
||||||
sizes: '192x192',
|
|
||||||
type: 'image/png'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Service Worker Build Configuration
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DEPRECATED: Service worker build configuration
|
|
||||||
// This configuration is kept for reference but not used in production
|
|
||||||
// Replaced by Native-First notification system
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
input: {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
sw: 'sw_scripts/notification-click.js'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Test Cleanup
|
|
||||||
|
|
||||||
#### Web Push Tests
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DEPRECATED: Web push tests
|
|
||||||
// These tests are kept for reference but not used in production
|
|
||||||
// Replaced by Native-First notification system
|
|
||||||
|
|
||||||
describe('Web Push Notifications (DEPRECATED)', () => {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
it('should handle push notifications', async () => {
|
|
||||||
// Test implementation kept for reference
|
|
||||||
});
|
|
||||||
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
it('should handle notification clicks', async () => {
|
|
||||||
// Test implementation kept for reference
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Service Worker Tests
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// DEPRECATED: Service worker tests
|
|
||||||
// These tests are kept for reference but not used in production
|
|
||||||
// Replaced by Native-First notification system
|
|
||||||
|
|
||||||
describe('Service Worker (DEPRECATED)', () => {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
it('should register service worker', async () => {
|
|
||||||
// Test implementation kept for reference
|
|
||||||
});
|
|
||||||
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
it('should handle push events', async () => {
|
|
||||||
// Test implementation kept for reference
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Documentation Cleanup
|
|
||||||
|
|
||||||
#### README Updates
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# TimeSafari Native-First Notification System
|
|
||||||
|
|
||||||
## Web-Push Status: DEPRECATED
|
|
||||||
|
|
||||||
Web-push has been retired for unreliability. The system now focuses on native mobile reliability with Electron best-effort support.
|
|
||||||
|
|
||||||
### Deprecated Features
|
|
||||||
- ❌ Web push notifications
|
|
||||||
- ❌ Service worker notification handling
|
|
||||||
- ❌ Web notification API
|
|
||||||
|
|
||||||
### Active Features
|
|
||||||
- ✅ Native mobile notifications (Android/iOS)
|
|
||||||
- ✅ Electron notifications (best-effort)
|
|
||||||
- ✅ OS-scheduled background prefetch
|
|
||||||
- ✅ Rolling window safety
|
|
||||||
```
|
|
||||||
|
|
||||||
#### API Documentation Updates
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Notification API (Native-First)
|
|
||||||
|
|
||||||
### Deprecated Methods
|
|
||||||
- `subscribeToPush()` - DEPRECATED: Web-push only
|
|
||||||
- `unsubscribeFromPush()` - DEPRECATED: Web-push only
|
|
||||||
- `handlePushMessage()` - DEPRECATED: Web-push only
|
|
||||||
|
|
||||||
### Active Methods
|
|
||||||
- `scheduleExact()` - Native exact scheduling
|
|
||||||
- `scheduleWindow()` - Native windowed scheduling
|
|
||||||
- `schedulePrefetch()` - Native background prefetch
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File-by-File Cleanup Checklist
|
|
||||||
|
|
||||||
### Service Worker Files
|
|
||||||
|
|
||||||
- [ ] `sw_scripts/notification-click.js` - Mark as deprecated
|
|
||||||
- [ ] `sw_scripts/` directory - Review for web-push dependencies
|
|
||||||
- [ ] Service worker build configuration - Remove or quarantine
|
|
||||||
|
|
||||||
### Web-Specific Code
|
|
||||||
|
|
||||||
- [ ] `src/main.web.ts` - Remove service worker registration
|
|
||||||
- [ ] `src/services/webPush.ts` - Mark as deprecated
|
|
||||||
- [ ] `src/utils/serviceWorker.ts` - Mark as deprecated
|
|
||||||
- [ ] Web notification API usage - Remove or quarantine
|
|
||||||
|
|
||||||
### Configuration Files
|
|
||||||
|
|
||||||
- [ ] `vite.config.web.mts` - Remove VitePWA plugin
|
|
||||||
- [ ] `package.json` - Remove web-push dependencies
|
|
||||||
- [ ] `public/manifest.json` - Mark as deprecated
|
|
||||||
- [ ] Service worker build scripts - Remove or quarantine
|
|
||||||
|
|
||||||
### Test Files
|
|
||||||
|
|
||||||
- [ ] `test-playwright/web-push.spec.ts` - Mark as deprecated
|
|
||||||
- [ ] `test/services/webPush.test.ts` - Mark as deprecated
|
|
||||||
- [ ] Service worker tests - Mark as deprecated
|
|
||||||
|
|
||||||
### Documentation Files
|
|
||||||
|
|
||||||
- [ ] `README.md` - Update to reflect native-first approach
|
|
||||||
- [ ] `doc/web-push.md` - Mark as deprecated
|
|
||||||
- [ ] API documentation - Remove web-push references
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Dependencies to Remove
|
|
||||||
|
|
||||||
### NPM Packages
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
"web-push": "^7.4.0",
|
|
||||||
"vite-plugin-pwa": "^0.17.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
"workbox-webpack-plugin": "^6.5.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build Scripts
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"scripts": {
|
|
||||||
// DEPRECATED: Web-push only
|
|
||||||
"build:sw": "workbox generateSW",
|
|
||||||
"test:sw": "jest --testPathPattern=serviceWorker"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migration Guide
|
|
||||||
|
|
||||||
### From Web-Push to Native-First
|
|
||||||
|
|
||||||
#### Step 1: Remove Web-Push Dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Remove web-push packages
|
|
||||||
npm uninstall web-push vite-plugin-pwa workbox-webpack-plugin
|
|
||||||
|
|
||||||
# Remove service worker files
|
|
||||||
rm -rf sw_scripts/
|
|
||||||
rm -f public/sw.js
|
|
||||||
rm -f public/workbox-*.js
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 2: Update Configuration
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Remove VitePWA plugin from vite.config.web.mts
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
// Remove VitePWA plugin
|
|
||||||
// VitePWA({ ... })
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 3: Update Service Registration
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Remove service worker registration from main.web.ts
|
|
||||||
// if ('serviceWorker' in navigator) {
|
|
||||||
// navigator.serviceWorker.register('/sw.js')
|
|
||||||
// }
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 4: Update Tests
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Remove web-push tests
|
|
||||||
// describe('Web Push Notifications', () => { ... })
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Verification Checklist
|
|
||||||
|
|
||||||
### Code Removal Verification
|
|
||||||
|
|
||||||
- [ ] No web-push imports remain
|
|
||||||
- [ ] No service worker registration code
|
|
||||||
- [ ] No push subscription management
|
|
||||||
- [ ] No web notification API usage
|
|
||||||
- [ ] No VitePWA plugin configuration
|
|
||||||
|
|
||||||
### Documentation Verification
|
|
||||||
|
|
||||||
- [ ] All web-push references marked as deprecated
|
|
||||||
- [ ] README updated to reflect native-first approach
|
|
||||||
- [ ] API documentation updated
|
|
||||||
- [ ] Test documentation updated
|
|
||||||
|
|
||||||
### Build Verification
|
|
||||||
|
|
||||||
- [ ] Web build succeeds without service worker
|
|
||||||
- [ ] No service worker files generated
|
|
||||||
- [ ] No web-push dependencies in bundle
|
|
||||||
- [ ] Native builds work correctly
|
|
||||||
|
|
||||||
### Test Verification
|
|
||||||
|
|
||||||
- [ ] Web-push tests are marked as deprecated
|
|
||||||
- [ ] Native notification tests pass
|
|
||||||
- [ ] No web-push test failures
|
|
||||||
- [ ] Test suite runs successfully
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Rollback Plan
|
|
||||||
|
|
||||||
### Emergency Rollback
|
|
||||||
|
|
||||||
If native-first implementation fails, web-push code can be restored:
|
|
||||||
|
|
||||||
#### 1. **Restore Dependencies**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install web-push vite-plugin-pwa workbox-webpack-plugin
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. **Restore Service Worker Files**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout HEAD~1 -- sw_scripts/
|
|
||||||
git checkout HEAD~1 -- public/sw.js
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. **Restore Configuration**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout HEAD~1 -- vite.config.web.mts
|
|
||||||
git checkout HEAD~1 -- package.json
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. **Restore Tests**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout HEAD~1 -- test-playwright/web-push.spec.ts
|
|
||||||
git checkout HEAD~1 -- test/services/webPush.test.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rollback Verification
|
|
||||||
|
|
||||||
- [ ] Web-push functionality restored
|
|
||||||
- [ ] Service worker registration works
|
|
||||||
- [ ] Push notifications work
|
|
||||||
- [ ] Tests pass
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Post-Cleanup Tasks
|
|
||||||
|
|
||||||
### Code Review
|
|
||||||
|
|
||||||
- [ ] Review all changes for completeness
|
|
||||||
- [ ] Verify no web-push code remains
|
|
||||||
- [ ] Check for orphaned references
|
|
||||||
- [ ] Validate native-first implementation
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
- [ ] Run full test suite
|
|
||||||
- [ ] Verify native notifications work
|
|
||||||
- [ ] Check Electron functionality
|
|
||||||
- [ ] Validate mobile builds
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- [ ] Update all documentation
|
|
||||||
- [ ] Remove web-push references
|
|
||||||
- [ ] Update API documentation
|
|
||||||
- [ ] Update user guides
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
### Complete Web-Push Removal
|
|
||||||
|
|
||||||
- [ ] All web-push code marked as deprecated
|
|
||||||
- [ ] Service worker files quarantined
|
|
||||||
- [ ] Dependencies removed
|
|
||||||
- [ ] Configuration updated
|
|
||||||
|
|
||||||
### Native-First Implementation
|
|
||||||
|
|
||||||
- [ ] Native notifications work on Android
|
|
||||||
- [ ] Native notifications work on iOS
|
|
||||||
- [ ] Electron notifications work
|
|
||||||
- [ ] Background prefetch works
|
|
||||||
|
|
||||||
### Documentation Updated
|
|
||||||
|
|
||||||
- [ ] All docs reflect native-first approach
|
|
||||||
- [ ] Web-push marked as deprecated
|
|
||||||
- [ ] Migration guide provided
|
|
||||||
- [ ] Rollback plan documented
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
_This cleanup guide provides comprehensive instructions for removing web-push
|
|
||||||
code paths from TimeSafari. Web-push has been retired for unreliability, and the
|
|
||||||
system now focuses on native mobile reliability with Electron best-effort support._
|
|
||||||
@@ -5,15 +5,12 @@
|
|||||||
**Status**: 🎯 **ACTIVE** - Z-index layering standards
|
**Status**: 🎯 **ACTIVE** - Z-index layering standards
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
Establish consistent z-index values across the TimeSafari application to ensure proper layering of UI elements.
|
Establish consistent z-index values across the TimeSafari application to ensure proper layering of UI elements.
|
||||||
|
|
||||||
## Result
|
## Result
|
||||||
|
|
||||||
This document defines the z-index hierarchy for all UI components.
|
This document defines the z-index hierarchy for all UI components.
|
||||||
|
|
||||||
## Use/Run
|
## Use/Run
|
||||||
|
|
||||||
Reference these values when implementing new components or modifying existing ones to maintain consistent layering.
|
Reference these values when implementing new components or modifying existing ones to maintain consistent layering.
|
||||||
|
|
||||||
## Z-Index Hierarchy
|
## Z-Index Hierarchy
|
||||||
@@ -54,7 +51,7 @@ Reference these values when implementing new components or modifying existing on
|
|||||||
## Collaboration Hooks
|
## Collaboration Hooks
|
||||||
|
|
||||||
- **Reviewers**: Frontend team, UI/UX designers
|
- **Reviewers**: Frontend team, UI/UX designers
|
||||||
- **Sign-off checklist**:
|
- **Sign-off checklist**:
|
||||||
- [ ] All new components follow z-index guidelines
|
- [ ] All new components follow z-index guidelines
|
||||||
- [ ] Existing components updated to use defined values
|
- [ ] Existing components updated to use defined values
|
||||||
- [ ] Cross-browser testing completed
|
- [ ] Cross-browser testing completed
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
import { CapacitorConfig } from '@capacitor/cli';
|
|
||||||
|
|
||||||
const config: CapacitorConfig = {
|
|
||||||
appId: 'app.timesafari',
|
|
||||||
appName: 'TimeSafari',
|
|
||||||
webDir: 'dist',
|
|
||||||
server: {
|
|
||||||
cleartext: true
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
App: {
|
|
||||||
appUrlOpen: {
|
|
||||||
handlers: [
|
|
||||||
{
|
|
||||||
url: 'timesafari://*',
|
|
||||||
autoVerify: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SplashScreen: {
|
|
||||||
launchShowDuration: 3000,
|
|
||||||
launchAutoHide: true,
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
androidSplashResourceName: 'splash',
|
|
||||||
androidScaleType: 'CENTER_CROP',
|
|
||||||
showSpinner: false,
|
|
||||||
androidSpinnerStyle: 'large',
|
|
||||||
iosSpinnerStyle: 'small',
|
|
||||||
spinnerColor: '#999999',
|
|
||||||
splashFullScreen: true,
|
|
||||||
splashImmersive: true
|
|
||||||
},
|
|
||||||
CapSQLite: {
|
|
||||||
iosDatabaseLocation: 'Library/CapacitorDatabase',
|
|
||||||
iosIsEncryption: false,
|
|
||||||
iosBiometric: {
|
|
||||||
biometricAuth: false,
|
|
||||||
biometricTitle: 'Biometric login for TimeSafari'
|
|
||||||
},
|
|
||||||
androidIsEncryption: false,
|
|
||||||
androidBiometric: {
|
|
||||||
biometricAuth: false,
|
|
||||||
biometricTitle: 'Biometric login for TimeSafari'
|
|
||||||
},
|
|
||||||
electronIsEncryption: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ios: {
|
|
||||||
contentInset: 'never',
|
|
||||||
allowsLinkPreview: true,
|
|
||||||
scrollEnabled: true,
|
|
||||||
limitsNavigationsToAppBoundDomains: true,
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
allowNavigation: [
|
|
||||||
'*.timesafari.app',
|
|
||||||
'*.jsdelivr.net',
|
|
||||||
'api.endorser.ch'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
android: {
|
|
||||||
allowMixedContent: true,
|
|
||||||
captureInput: true,
|
|
||||||
webContentsDebuggingEnabled: false,
|
|
||||||
allowNavigation: [
|
|
||||||
'*.timesafari.app',
|
|
||||||
'*.jsdelivr.net',
|
|
||||||
'api.endorser.ch',
|
|
||||||
'10.0.2.2:3000'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
electron: {
|
|
||||||
deepLinking: {
|
|
||||||
schemes: ['timesafari']
|
|
||||||
},
|
|
||||||
buildOptions: {
|
|
||||||
appId: 'app.timesafari',
|
|
||||||
productName: 'TimeSafari',
|
|
||||||
directories: {
|
|
||||||
output: 'dist-electron-packages'
|
|
||||||
},
|
|
||||||
files: [
|
|
||||||
'dist/**/*',
|
|
||||||
'electron/**/*'
|
|
||||||
],
|
|
||||||
mac: {
|
|
||||||
category: 'public.app-category.productivity',
|
|
||||||
target: [
|
|
||||||
{
|
|
||||||
target: 'dmg',
|
|
||||||
arch: ['x64', 'arm64']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
win: {
|
|
||||||
target: [
|
|
||||||
{
|
|
||||||
target: 'nsis',
|
|
||||||
arch: ['x64']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
linux: {
|
|
||||||
target: [
|
|
||||||
{
|
|
||||||
target: 'AppImage',
|
|
||||||
arch: ['x64']
|
|
||||||
}
|
|
||||||
],
|
|
||||||
category: 'Utility'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
Generated
+1
@@ -56,6 +56,7 @@
|
|||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor-community/sqlite/-/sqlite-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor-community/sqlite/-/sqlite-6.0.2.tgz",
|
||||||
"integrity": "sha512-sj+2SPLu7E/3dM3xxcWwfNomG+aQHuN96/EFGrOtp4Dv30/2y5oIPyi6hZGjQGjPc5GDNoTQwW7vxWNzybjuMg==",
|
"integrity": "sha512-sj+2SPLu7E/3dM3xxcWwfNomG+aQHuN96/EFGrOtp4Dv30/2y5oIPyi6hZGjQGjPc5GDNoTQwW7vxWNzybjuMg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jeep-sqlite": "^2.7.2"
|
"jeep-sqlite": "^2.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ process.stderr.on('error', (err) => {
|
|||||||
const trayMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [new MenuItem({ label: 'Quit App', role: 'quit' })];
|
const trayMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [new MenuItem({ label: 'Quit App', role: 'quit' })];
|
||||||
const appMenuBarMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [
|
const appMenuBarMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [
|
||||||
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
|
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
|
||||||
{ role: 'editMenu' },
|
|
||||||
{ role: 'viewMenu' },
|
{ role: 'viewMenu' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ export class ElectronCapacitorApp {
|
|||||||
];
|
];
|
||||||
private AppMenuBarMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [
|
private AppMenuBarMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [
|
||||||
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
|
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
|
||||||
{ role: 'editMenu' },
|
|
||||||
{ role: 'viewMenu' },
|
{ role: 'viewMenu' },
|
||||||
];
|
];
|
||||||
private mainWindowState;
|
private mainWindowState;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compileOnSave": true,
|
"compileOnSave": true,
|
||||||
"include": ["./src/**/*"],
|
"include": ["./src/**/*", "./capacitor.config.ts", "./capacitor.config.js"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
|
|||||||
@@ -136,6 +136,7 @@
|
|||||||
"*.{js,ts,vue,css,json,yml,yaml}": "eslint --fix || true",
|
"*.{js,ts,vue,css,json,yml,yaml}": "eslint --fix || true",
|
||||||
"*.{md,markdown,mdc}": "markdownlint-cli2 --fix"
|
"*.{md,markdown,mdc}": "markdownlint-cli2 --fix"
|
||||||
},
|
},
|
||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor-community/electron": "^5.0.1",
|
"@capacitor-community/electron": "^5.0.1",
|
||||||
"@capacitor-community/sqlite": "6.0.2",
|
"@capacitor-community/sqlite": "6.0.2",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default defineConfig({
|
|||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 0,
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: 1,
|
workers: 4,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: [
|
reporter: [
|
||||||
['list'],
|
['list'],
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ sync_capacitor() {
|
|||||||
copy_web_assets() {
|
copy_web_assets() {
|
||||||
log_info "Copying web assets to Electron"
|
log_info "Copying web assets to Electron"
|
||||||
safe_execute "Copying assets" "cp -r dist/* electron/app/"
|
safe_execute "Copying assets" "cp -r dist/* electron/app/"
|
||||||
# Note: Electron has its own capacitor.config.ts file, so we don't copy the main config
|
safe_execute "Copying config" "cp capacitor.config.json electron/capacitor.config.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Compile TypeScript
|
# Compile TypeScript
|
||||||
|
|||||||
@@ -221,7 +221,10 @@ export default class GiftedDialog extends Vue {
|
|||||||
try {
|
try {
|
||||||
const settings = await this.$settings();
|
const settings = await this.$settings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new façade method with legacy fallback
|
||||||
|
const retrievedActiveDid = await this.$getActiveDid();
|
||||||
|
this.activeDid = retrievedActiveDid || "";
|
||||||
|
logger.debug("[GiftedDialog] Set activeDid from new system:", this.activeDid);
|
||||||
|
|
||||||
this.allContacts = await this.$contacts();
|
this.allContacts = await this.$contacts();
|
||||||
|
|
||||||
@@ -286,7 +289,9 @@ export default class GiftedDialog extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async confirm() {
|
async confirm() {
|
||||||
|
logger.debug("[GiftedDialog] confirm() called with activeDid:", this.activeDid);
|
||||||
if (!this.activeDid) {
|
if (!this.activeDid) {
|
||||||
|
logger.error("[GiftedDialog] Validation failed - activeDid is empty/null:", this.activeDid);
|
||||||
this.safeNotify.error(
|
this.safeNotify.error(
|
||||||
NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER.message,
|
NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER.message,
|
||||||
TIMEOUTS.SHORT,
|
TIMEOUTS.SHORT,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
:weight="2"
|
:weight="2"
|
||||||
color="#3b82f6"
|
color="#3b82f6"
|
||||||
fill-color="#3b82f6"
|
fill-color="#3b82f6"
|
||||||
:fill-opacity="0.2"
|
fill-opacity="0.2"
|
||||||
/>
|
/>
|
||||||
</l-map>
|
</l-map>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -175,7 +175,8 @@ export default class OfferDialog extends Vue {
|
|||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new façade method with legacy fallback
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -270,7 +270,8 @@ export default class OnboardingDialog extends Vue {
|
|||||||
async open(page: OnboardPage) {
|
async open(page: OnboardPage) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new façade method with legacy fallback
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.isRegistered = !!settings.isRegistered;
|
this.isRegistered = !!settings.isRegistered;
|
||||||
|
|
||||||
const contacts = await this.$getAllContacts();
|
const contacts = await this.$getAllContacts();
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { Component, Vue, Prop } from "vue-facing-decorator";
|
|||||||
import { AppString, NotificationIface } from "../constants/app";
|
import { AppString, NotificationIface } from "../constants/app";
|
||||||
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
|
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
|
||||||
import { createNotifyHelpers, TIMEOUTS } from "../utils/notify";
|
import { createNotifyHelpers, TIMEOUTS } from "../utils/notify";
|
||||||
import { logger } from "../utils/logger";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
mixins: [PlatformServiceMixin],
|
mixins: [PlatformServiceMixin],
|
||||||
@@ -45,49 +44,26 @@ export default class TopMessage extends Vue {
|
|||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Load settings without overriding database values - fixes settings inconsistency
|
// Ultra-concise cached settings loading - replaces 50+ lines of logic!
|
||||||
logger.debug("[TopMessage] 📥 Loading settings without overrides...");
|
const settings = await this.$accountSettings(undefined, {
|
||||||
const settings = await this.$accountSettings();
|
activeDid: undefined,
|
||||||
|
apiServer: AppString.PROD_ENDORSER_API_SERVER,
|
||||||
logger.debug("[TopMessage] 📊 Settings loaded:", {
|
|
||||||
activeDid: settings.activeDid,
|
|
||||||
apiServer: settings.apiServer,
|
|
||||||
warnIfTestServer: settings.warnIfTestServer,
|
|
||||||
warnIfProdServer: settings.warnIfProdServer,
|
|
||||||
component: "TopMessage",
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only show warnings if the user has explicitly enabled them
|
|
||||||
if (
|
if (
|
||||||
settings.warnIfTestServer &&
|
settings.warnIfTestServer &&
|
||||||
settings.apiServer &&
|
|
||||||
settings.apiServer !== AppString.PROD_ENDORSER_API_SERVER
|
settings.apiServer !== AppString.PROD_ENDORSER_API_SERVER
|
||||||
) {
|
) {
|
||||||
const didPrefix = settings.activeDid?.slice(11, 15);
|
const didPrefix = settings.activeDid?.slice(11, 15);
|
||||||
this.message = "You're not using prod, user " + didPrefix;
|
this.message = "You're not using prod, user " + didPrefix;
|
||||||
logger.debug("[TopMessage] ⚠️ Test server warning displayed:", {
|
|
||||||
apiServer: settings.apiServer,
|
|
||||||
didPrefix: didPrefix,
|
|
||||||
});
|
|
||||||
} else if (
|
} else if (
|
||||||
settings.warnIfProdServer &&
|
settings.warnIfProdServer &&
|
||||||
settings.apiServer &&
|
|
||||||
settings.apiServer === AppString.PROD_ENDORSER_API_SERVER
|
settings.apiServer === AppString.PROD_ENDORSER_API_SERVER
|
||||||
) {
|
) {
|
||||||
const didPrefix = settings.activeDid?.slice(11, 15);
|
const didPrefix = settings.activeDid?.slice(11, 15);
|
||||||
this.message = "You are using prod, user " + didPrefix;
|
this.message = "You are using prod, user " + didPrefix;
|
||||||
logger.debug("[TopMessage] ⚠️ Production server warning displayed:", {
|
|
||||||
apiServer: settings.apiServer,
|
|
||||||
didPrefix: didPrefix,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logger.debug(
|
|
||||||
"[TopMessage] ℹ️ No warnings displayed - conditions not met",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
logger.error("[TopMessage] ❌ Error loading settings:", err);
|
|
||||||
this.notify.error(JSON.stringify(err), TIMEOUTS.MODAL);
|
this.notify.error(JSON.stringify(err), TIMEOUTS.MODAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,7 @@ export default class UserNameDialog extends Vue {
|
|||||||
*/
|
*/
|
||||||
async open(aCallback?: (name?: string) => void) {
|
async open(aCallback?: (name?: string) => void) {
|
||||||
this.callback = aCallback || this.callback;
|
this.callback = aCallback || this.callback;
|
||||||
// Load from account-specific settings instead of master settings
|
const settings = await this.$settings();
|
||||||
const settings = await this.$accountSettings();
|
|
||||||
this.givenName = settings.firstName || "";
|
this.givenName = settings.firstName || "";
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
}
|
}
|
||||||
@@ -96,18 +95,7 @@ export default class UserNameDialog extends Vue {
|
|||||||
*/
|
*/
|
||||||
async onClickSaveChanges() {
|
async onClickSaveChanges() {
|
||||||
try {
|
try {
|
||||||
// Get the current active DID to save to user-specific settings
|
await this.$updateSettings({ firstName: this.givenName });
|
||||||
const settings = await this.$accountSettings();
|
|
||||||
const activeDid = settings.activeDid;
|
|
||||||
|
|
||||||
if (activeDid) {
|
|
||||||
// Save to user-specific settings for the current identity
|
|
||||||
await this.$saveUserSettings(activeDid, { firstName: this.givenName });
|
|
||||||
} else {
|
|
||||||
// Fallback to master settings if no active DID
|
|
||||||
await this.$saveSettings({ firstName: this.givenName });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.callback(this.givenName);
|
this.callback(this.givenName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Feature Flags Configuration
|
||||||
|
*
|
||||||
|
* Controls the rollout of new features and migrations
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
* @date 2025-08-21
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const FLAGS = {
|
||||||
|
/**
|
||||||
|
* When true, disallow legacy fallback reads from settings.activeDid
|
||||||
|
* Set to true after all components are migrated to the new façade
|
||||||
|
*/
|
||||||
|
USE_ACTIVE_IDENTITY_ONLY: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls Phase C column removal from settings table
|
||||||
|
* Set to true when ready to drop the legacy activeDid column
|
||||||
|
*
|
||||||
|
* ✅ ENABLED: Migration 004 has dropped the activeDid column (2025-08-22T10:30Z)
|
||||||
|
*/
|
||||||
|
DROP_SETTINGS_ACTIVEDID: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log warnings when dual-read falls back to legacy settings.activeDid
|
||||||
|
* Useful for monitoring migration progress
|
||||||
|
*/
|
||||||
|
LOG_ACTIVE_ID_FALLBACK: process.env.NODE_ENV === "development",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the new active_identity table and migration
|
||||||
|
* Set to true to start the migration process
|
||||||
|
*/
|
||||||
|
ENABLE_ACTIVE_IDENTITY_MIGRATION: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get feature flag value with type safety
|
||||||
|
*/
|
||||||
|
export function getFlag<K extends keyof typeof FLAGS>(
|
||||||
|
key: K,
|
||||||
|
): (typeof FLAGS)[K] {
|
||||||
|
return FLAGS[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a feature flag is enabled
|
||||||
|
*/
|
||||||
|
export function isFlagEnabled<K extends keyof typeof FLAGS>(key: K): boolean {
|
||||||
|
return Boolean(FLAGS[key]);
|
||||||
|
}
|
||||||
@@ -1689,11 +1689,3 @@ export const NOTIFY_CONTACTS_ADDED_CONFIRM = {
|
|||||||
title: "They're Added To Your List",
|
title: "They're Added To Your List",
|
||||||
message: "Would you like to go to the main page now?",
|
message: "Would you like to go to the main page now?",
|
||||||
};
|
};
|
||||||
|
|
||||||
// ImportAccountView.vue specific constants
|
|
||||||
// Used in: ImportAccountView.vue (onImportClick method - duplicate account warning)
|
|
||||||
export const NOTIFY_DUPLICATE_ACCOUNT_IMPORT = {
|
|
||||||
title: "Account Already Imported",
|
|
||||||
message:
|
|
||||||
"This account has already been imported. Please use a different seed phrase or check your existing accounts.",
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -124,6 +124,131 @@ const MIGRATIONS = [
|
|||||||
ALTER TABLE contacts ADD COLUMN iViewContent BOOLEAN DEFAULT TRUE;
|
ALTER TABLE contacts ADD COLUMN iViewContent BOOLEAN DEFAULT TRUE;
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "003_active_identity_table_separation",
|
||||||
|
sql: `
|
||||||
|
-- Create active_identity table with proper constraints
|
||||||
|
CREATE TABLE IF NOT EXISTS active_identity (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
active_did TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
||||||
|
CONSTRAINT fk_active_identity_account FOREIGN KEY (active_did)
|
||||||
|
REFERENCES accounts(did) ON UPDATE CASCADE ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create index for performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_active_identity_active_did ON active_identity(active_did);
|
||||||
|
|
||||||
|
-- Seed from existing settings.activeDid if valid
|
||||||
|
INSERT INTO active_identity (active_did)
|
||||||
|
SELECT s.activeDid
|
||||||
|
FROM settings s
|
||||||
|
WHERE s.activeDid IS NOT NULL
|
||||||
|
AND EXISTS (SELECT 1 FROM accounts a WHERE a.did = s.activeDid)
|
||||||
|
AND s.id = 1;
|
||||||
|
|
||||||
|
-- Fallback: choose first known account if still empty
|
||||||
|
INSERT INTO active_identity (active_did)
|
||||||
|
SELECT a.did
|
||||||
|
FROM accounts a
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM active_identity ai)
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- Create one-way mirroring trigger (settings.activeDid → active_identity.active_did)
|
||||||
|
DROP TRIGGER IF EXISTS trg_settings_activeDid_to_active_identity;
|
||||||
|
CREATE TRIGGER trg_settings_activeDid_to_active_identity
|
||||||
|
AFTER UPDATE OF activeDid ON settings
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN NEW.activeDid IS NOT OLD.activeDid AND NEW.activeDid IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
UPDATE active_identity
|
||||||
|
SET active_did = NEW.activeDid,
|
||||||
|
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
||||||
|
WHERE id = 1;
|
||||||
|
|
||||||
|
INSERT INTO active_identity (id, active_did, updated_at)
|
||||||
|
SELECT 1, NEW.activeDid, strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM active_identity ai WHERE ai.id = 1
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
// Migration 004 re-enabled - Phase 1 complete, critical components migrated
|
||||||
|
{
|
||||||
|
name: "004_drop_settings_activeDid_column",
|
||||||
|
sql: `
|
||||||
|
-- Phase C: Remove activeDid column from settings table
|
||||||
|
-- Note: SQLite requires table rebuild for column removal
|
||||||
|
|
||||||
|
-- Create new settings table without activeDid column
|
||||||
|
CREATE TABLE settings_new (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
accountDid TEXT,
|
||||||
|
-- activeDid intentionally omitted
|
||||||
|
apiServer TEXT,
|
||||||
|
filterFeedByNearby BOOLEAN,
|
||||||
|
filterFeedByVisible BOOLEAN,
|
||||||
|
finishedOnboarding BOOLEAN,
|
||||||
|
firstName TEXT,
|
||||||
|
hideRegisterPromptOnNewContact BOOLEAN,
|
||||||
|
isRegistered BOOLEAN,
|
||||||
|
lastName TEXT,
|
||||||
|
lastAckedOfferToUserJwtId TEXT,
|
||||||
|
lastAckedOfferToUserProjectsJwtId TEXT,
|
||||||
|
lastNotifiedClaimId TEXT,
|
||||||
|
lastViewedClaimId TEXT,
|
||||||
|
notifyingNewActivityTime TEXT,
|
||||||
|
notifyingReminderMessage TEXT,
|
||||||
|
notifyingReminderTime TEXT,
|
||||||
|
partnerApiServer TEXT,
|
||||||
|
passkeyExpirationMinutes INTEGER,
|
||||||
|
profileImageUrl TEXT,
|
||||||
|
searchBoxes TEXT,
|
||||||
|
showContactGivesInline BOOLEAN,
|
||||||
|
showGeneralAdvanced BOOLEAN,
|
||||||
|
showShortcutBvc BOOLEAN,
|
||||||
|
vapid TEXT,
|
||||||
|
warnIfProdServer BOOLEAN,
|
||||||
|
warnIfTestServer BOOLEAN,
|
||||||
|
webPushServer TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Copy data from old table (excluding activeDid)
|
||||||
|
INSERT INTO settings_new (
|
||||||
|
id, accountDid, apiServer, filterFeedByNearby, filterFeedByVisible,
|
||||||
|
finishedOnboarding, firstName, hideRegisterPromptOnNewContact,
|
||||||
|
isRegistered, lastName, lastAckedOfferToUserJwtId,
|
||||||
|
lastAckedOfferToUserProjectsJwtId, lastNotifiedClaimId,
|
||||||
|
lastViewedClaimId, notifyingNewActivityTime, notifyingReminderMessage,
|
||||||
|
notifyingReminderTime, partnerApiServer, passkeyExpirationMinutes,
|
||||||
|
profileImageUrl, searchBoxes, showContactGivesInline,
|
||||||
|
showGeneralAdvanced, showShortcutBvc, vapid, warnIfProdServer,
|
||||||
|
warnIfTestServer, webPushServer
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id, accountDid, apiServer, filterFeedByNearby, filterFeedByVisible,
|
||||||
|
finishedOnboarding, firstName, hideRegisterPromptOnNewContact,
|
||||||
|
isRegistered, lastName, lastAckedOfferToUserJwtId,
|
||||||
|
lastAckedOfferToUserProjectsJwtId, lastNotifiedClaimId,
|
||||||
|
lastViewedClaimId, notifyingNewActivityTime, notifyingReminderMessage,
|
||||||
|
notifyingReminderTime, partnerApiServer, passkeyExpirationMinutes,
|
||||||
|
profileImageUrl, searchBoxes, showContactGivesInline,
|
||||||
|
showGeneralAdvanced, showShortcutBvc, vapid, warnIfProdServer,
|
||||||
|
warnIfTestServer, webPushServer
|
||||||
|
FROM settings;
|
||||||
|
|
||||||
|
-- Drop old table and rename new one
|
||||||
|
DROP TABLE settings;
|
||||||
|
ALTER TABLE settings_new RENAME TO settings;
|
||||||
|
|
||||||
|
-- Recreate indexes
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_settings_accountDid ON settings(accountDid);
|
||||||
|
|
||||||
|
-- Drop the mirroring trigger (no longer needed)
|
||||||
|
DROP TRIGGER IF EXISTS trg_settings_activeDid_to_active_identity;
|
||||||
|
`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Active Identity Table Definition
|
||||||
|
*
|
||||||
|
* Manages the currently active identity/DID for the application.
|
||||||
|
* Replaces the activeDid field from the settings table to improve
|
||||||
|
* data normalization and reduce cache drift.
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
* @date 2025-08-21
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active Identity record structure
|
||||||
|
*/
|
||||||
|
export interface ActiveIdentity {
|
||||||
|
/** Primary key */
|
||||||
|
id?: number;
|
||||||
|
|
||||||
|
/** The currently active DID - foreign key to accounts.did */
|
||||||
|
active_did: string;
|
||||||
|
|
||||||
|
/** Last update timestamp in ISO format */
|
||||||
|
updated_at?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database schema for the active_identity table
|
||||||
|
*/
|
||||||
|
export const ActiveIdentitySchema = {
|
||||||
|
active_identity: "++id, active_did, updated_at",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default values for ActiveIdentity records
|
||||||
|
*/
|
||||||
|
export const ActiveIdentityDefaults = {
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation function for DID format
|
||||||
|
*/
|
||||||
|
export function isValidDid(did: string): boolean {
|
||||||
|
return typeof did === "string" && did.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ActiveIdentity record
|
||||||
|
*/
|
||||||
|
export function createActiveIdentity(
|
||||||
|
activeDid: string,
|
||||||
|
): ActiveIdentity {
|
||||||
|
if (!isValidDid(activeDid)) {
|
||||||
|
throw new Error(`Invalid DID format: ${activeDid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
active_did: activeDid,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
+33
-292
@@ -486,15 +486,6 @@ const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
|
|||||||
max: 500,
|
max: 500,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Tracks in-flight requests to prevent duplicate API calls for the same plan
|
|
||||||
* @constant {Map}
|
|
||||||
*/
|
|
||||||
const inFlightRequests = new Map<
|
|
||||||
string,
|
|
||||||
Promise<PlanSummaryRecord | undefined>
|
|
||||||
>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves plan data from cache or server
|
* Retrieves plan data from cache or server
|
||||||
* @param {string} handleId - Plan handle ID
|
* @param {string} handleId - Plan handle ID
|
||||||
@@ -514,140 +505,40 @@ export async function getPlanFromCache(
|
|||||||
if (!handleId) {
|
if (!handleId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
let cred = planCache.get(handleId);
|
||||||
|
if (!cred) {
|
||||||
|
const url =
|
||||||
|
apiServer +
|
||||||
|
"/api/v2/report/plans?handleId=" +
|
||||||
|
encodeURIComponent(handleId);
|
||||||
|
const headers = await getHeaders(requesterDid);
|
||||||
|
try {
|
||||||
|
const resp = await axios.get(url, { headers });
|
||||||
|
if (resp.status === 200 && resp.data?.data?.length > 0) {
|
||||||
|
cred = resp.data.data[0];
|
||||||
|
planCache.set(handleId, cred);
|
||||||
|
} else {
|
||||||
|
// Use debug level for development to reduce console noise
|
||||||
|
const isDevelopment = process.env.VITE_PLATFORM === "development";
|
||||||
|
const log = isDevelopment ? logger.debug : logger.log;
|
||||||
|
|
||||||
// Check cache first (existing behavior)
|
log(
|
||||||
const cred = planCache.get(handleId);
|
"[EndorserServer] Plan cache is empty for handle",
|
||||||
if (cred) {
|
handleId,
|
||||||
return cred;
|
" Got data:",
|
||||||
}
|
JSON.stringify(resp.data),
|
||||||
|
);
|
||||||
// Check if request is already in flight (NEW: request deduplication)
|
}
|
||||||
if (inFlightRequests.has(handleId)) {
|
} catch (error) {
|
||||||
logger.debug(
|
logger.error(
|
||||||
"[Plan Loading] 🔄 Request already in flight, reusing promise:",
|
"[EndorserServer] Failed to load plan with handle",
|
||||||
{
|
|
||||||
handleId,
|
handleId,
|
||||||
requesterDid,
|
" Got error:",
|
||||||
timestamp: new Date().toISOString(),
|
JSON.stringify(error),
|
||||||
},
|
|
||||||
);
|
|
||||||
return inFlightRequests.get(handleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new request promise (NEW: request coordination)
|
|
||||||
const requestPromise = performPlanRequest(
|
|
||||||
handleId,
|
|
||||||
axios,
|
|
||||||
apiServer,
|
|
||||||
requesterDid,
|
|
||||||
);
|
|
||||||
inFlightRequests.set(handleId, requestPromise);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await requestPromise;
|
|
||||||
return result;
|
|
||||||
} finally {
|
|
||||||
// Clean up in-flight request tracking (NEW: cleanup)
|
|
||||||
inFlightRequests.delete(handleId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs the actual plan request to the server
|
|
||||||
* @param {string} handleId - Plan handle ID
|
|
||||||
* @param {Axios} axios - Axios instance
|
|
||||||
* @param {string} apiServer - API server URL
|
|
||||||
* @param {string} [requesterDid] - Optional requester DID for private info
|
|
||||||
* @returns {Promise<PlanSummaryRecord|undefined>} Plan data or undefined if not found
|
|
||||||
*
|
|
||||||
* @throws {Error} If server request fails
|
|
||||||
*/
|
|
||||||
async function performPlanRequest(
|
|
||||||
handleId: string,
|
|
||||||
axios: Axios,
|
|
||||||
apiServer: string,
|
|
||||||
requesterDid?: string,
|
|
||||||
): Promise<PlanSummaryRecord | undefined> {
|
|
||||||
const url =
|
|
||||||
apiServer + "/api/v2/report/plans?handleId=" + encodeURIComponent(handleId);
|
|
||||||
const headers = await getHeaders(requesterDid);
|
|
||||||
|
|
||||||
// Enhanced diagnostic logging for plan loading
|
|
||||||
const requestId = `plan_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
||||||
|
|
||||||
logger.debug("[Plan Loading] 🔍 Loading plan from server:", {
|
|
||||||
requestId,
|
|
||||||
handleId,
|
|
||||||
apiServer,
|
|
||||||
endpoint: url,
|
|
||||||
requesterDid,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await axios.get(url, { headers });
|
|
||||||
|
|
||||||
logger.debug("[Plan Loading] ✅ Plan loaded successfully:", {
|
|
||||||
requestId,
|
|
||||||
handleId,
|
|
||||||
status: resp.status,
|
|
||||||
hasData: !!resp.data?.data,
|
|
||||||
dataLength: resp.data?.data?.length || 0,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.status === 200 && resp.data?.data?.length > 0) {
|
|
||||||
const cred = resp.data.data[0];
|
|
||||||
planCache.set(handleId, cred);
|
|
||||||
|
|
||||||
logger.debug("[Plan Loading] 💾 Plan cached:", {
|
|
||||||
requestId,
|
|
||||||
handleId,
|
|
||||||
planName: cred?.name,
|
|
||||||
planIssuer: cred?.issuerDid,
|
|
||||||
});
|
|
||||||
|
|
||||||
return cred;
|
|
||||||
} else {
|
|
||||||
// Use debug level for development to reduce console noise
|
|
||||||
const isDevelopment = process.env.VITE_PLATFORM === "development";
|
|
||||||
const log = isDevelopment ? logger.debug : logger.log;
|
|
||||||
|
|
||||||
log(
|
|
||||||
"[Plan Loading] ⚠️ Plan cache is empty for handle",
|
|
||||||
handleId,
|
|
||||||
" Got data:",
|
|
||||||
JSON.stringify(resp.data),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
// Enhanced error logging for plan loading failures
|
|
||||||
const axiosError = error as {
|
|
||||||
response?: {
|
|
||||||
data?: unknown;
|
|
||||||
status?: number;
|
|
||||||
statusText?: string;
|
|
||||||
};
|
|
||||||
message?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.error("[Plan Loading] ❌ Failed to load plan:", {
|
|
||||||
requestId,
|
|
||||||
handleId,
|
|
||||||
apiServer,
|
|
||||||
endpoint: url,
|
|
||||||
requesterDid,
|
|
||||||
errorStatus: axiosError.response?.status,
|
|
||||||
errorStatusText: axiosError.response?.statusText,
|
|
||||||
errorData: axiosError.response?.data,
|
|
||||||
errorMessage: axiosError.message || String(error),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
return cred;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1128,82 +1019,19 @@ export async function createAndSubmitClaim(
|
|||||||
|
|
||||||
const vcJwt: string = await createEndorserJwtForDid(issuerDid, vcPayload);
|
const vcJwt: string = await createEndorserJwtForDid(issuerDid, vcPayload);
|
||||||
|
|
||||||
// Enhanced diagnostic logging for claim submission
|
|
||||||
const requestId = `claim_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
||||||
|
|
||||||
logger.info("[Claim Submission] 🚀 Starting claim submission:", {
|
|
||||||
requestId,
|
|
||||||
apiServer,
|
|
||||||
requesterDid: issuerDid,
|
|
||||||
endpoint: `${apiServer}/api/v2/claim`,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
jwtLength: vcJwt.length,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make the xhr request payload
|
// Make the xhr request payload
|
||||||
const payload = JSON.stringify({ jwtEncoded: vcJwt });
|
const payload = JSON.stringify({ jwtEncoded: vcJwt });
|
||||||
const url = `${apiServer}/api/v2/claim`;
|
const url = `${apiServer}/api/v2/claim`;
|
||||||
|
|
||||||
logger.debug("[Claim Submission] 📡 Making API request:", {
|
|
||||||
requestId,
|
|
||||||
url,
|
|
||||||
payloadSize: payload.length,
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await axios.post(url, payload, {
|
const response = await axios.post(url, payload, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info("[Claim Submission] ✅ Claim submitted successfully:", {
|
|
||||||
requestId,
|
|
||||||
status: response.status,
|
|
||||||
handleId: response.data?.handleId,
|
|
||||||
responseSize: JSON.stringify(response.data).length,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, handleId: response.data?.handleId };
|
return { success: true, handleId: response.data?.handleId };
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
// Enhanced error logging with comprehensive context
|
logger.error("Error submitting claim:", error);
|
||||||
const requestId = `claim_error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
||||||
|
|
||||||
const axiosError = error as {
|
|
||||||
response?: {
|
|
||||||
data?: { error?: { code?: string; message?: string } };
|
|
||||||
status?: number;
|
|
||||||
statusText?: string;
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
};
|
|
||||||
config?: {
|
|
||||||
url?: string;
|
|
||||||
method?: string;
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
};
|
|
||||||
message?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.error("[Claim Submission] ❌ Claim submission failed:", {
|
|
||||||
requestId,
|
|
||||||
apiServer,
|
|
||||||
requesterDid: issuerDid,
|
|
||||||
endpoint: `${apiServer}/api/v2/claim`,
|
|
||||||
errorCode: axiosError.response?.data?.error?.code,
|
|
||||||
errorMessage: axiosError.response?.data?.error?.message,
|
|
||||||
httpStatus: axiosError.response?.status,
|
|
||||||
httpStatusText: axiosError.response?.statusText,
|
|
||||||
responseHeaders: axiosError.response?.headers,
|
|
||||||
requestConfig: {
|
|
||||||
url: axiosError.config?.url,
|
|
||||||
method: axiosError.config?.method,
|
|
||||||
headers: axiosError.config?.headers,
|
|
||||||
},
|
|
||||||
originalError: axiosError.message || String(error),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const errorMessage: string =
|
const errorMessage: string =
|
||||||
serverMessageForUser(error) ||
|
serverMessageForUser(error) ||
|
||||||
(error && typeof error === "object" && "message" in error
|
(error && typeof error === "object" && "message" in error
|
||||||
@@ -1666,56 +1494,14 @@ export async function fetchEndorserRateLimits(
|
|||||||
) {
|
) {
|
||||||
const url = `${apiServer}/api/report/rateLimits`;
|
const url = `${apiServer}/api/report/rateLimits`;
|
||||||
const headers = await getHeaders(issuerDid);
|
const headers = await getHeaders(issuerDid);
|
||||||
|
|
||||||
// Enhanced diagnostic logging for user registration tracking
|
|
||||||
logger.debug("[User Registration] Checking user status on server:", {
|
|
||||||
did: issuerDid,
|
|
||||||
server: apiServer,
|
|
||||||
endpoint: url,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(url, { headers } as AxiosRequestConfig);
|
const response = await axios.get(url, { headers } as AxiosRequestConfig);
|
||||||
|
|
||||||
// Log successful registration check
|
|
||||||
logger.debug("[User Registration] User registration check successful:", {
|
|
||||||
did: issuerDid,
|
|
||||||
server: apiServer,
|
|
||||||
status: response.status,
|
|
||||||
isRegistered: true,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Enhanced error logging with user registration context
|
|
||||||
const axiosError = error as {
|
|
||||||
response?: {
|
|
||||||
data?: { error?: { code?: string; message?: string } };
|
|
||||||
status?: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const errorCode = axiosError.response?.data?.error?.code;
|
|
||||||
const errorMessage = axiosError.response?.data?.error?.message;
|
|
||||||
const httpStatus = axiosError.response?.status;
|
|
||||||
|
|
||||||
logger.warn("[User Registration] User not registered on server:", {
|
|
||||||
did: issuerDid,
|
|
||||||
server: apiServer,
|
|
||||||
errorCode: errorCode,
|
|
||||||
errorMessage: errorMessage,
|
|
||||||
httpStatus: httpStatus,
|
|
||||||
needsRegistration: true,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Log the original error for debugging
|
|
||||||
logger.error(
|
logger.error(
|
||||||
`[fetchEndorserRateLimits] Error for DID ${issuerDid}:`,
|
`[fetchEndorserRateLimits] Error for DID ${issuerDid}:`,
|
||||||
errorStringForLog(error),
|
errorStringForLog(error),
|
||||||
);
|
);
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1728,53 +1514,8 @@ export async function fetchEndorserRateLimits(
|
|||||||
* @param {string} issuerDid - The DID for which to check rate limits.
|
* @param {string} issuerDid - The DID for which to check rate limits.
|
||||||
* @returns {Promise<AxiosResponse>} The Axios response object.
|
* @returns {Promise<AxiosResponse>} The Axios response object.
|
||||||
*/
|
*/
|
||||||
export async function fetchImageRateLimits(
|
export async function fetchImageRateLimits(axios: Axios, issuerDid: string) {
|
||||||
axios: Axios,
|
const url = DEFAULT_IMAGE_API_SERVER + "/image-limits";
|
||||||
issuerDid: string,
|
|
||||||
imageServer?: string,
|
|
||||||
) {
|
|
||||||
const server = imageServer || DEFAULT_IMAGE_API_SERVER;
|
|
||||||
const url = server + "/image-limits";
|
|
||||||
const headers = await getHeaders(issuerDid);
|
const headers = await getHeaders(issuerDid);
|
||||||
|
return await axios.get(url, { headers } as AxiosRequestConfig);
|
||||||
// Enhanced diagnostic logging for image server calls
|
|
||||||
logger.debug("[Image Server] Checking image rate limits:", {
|
|
||||||
did: issuerDid,
|
|
||||||
server: server,
|
|
||||||
endpoint: url,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get(url, { headers } as AxiosRequestConfig);
|
|
||||||
|
|
||||||
// Log successful image server call
|
|
||||||
logger.debug("[Image Server] Image rate limits check successful:", {
|
|
||||||
did: issuerDid,
|
|
||||||
server: server,
|
|
||||||
status: response.status,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
|
||||||
// Enhanced error logging for image server failures
|
|
||||||
const axiosError = error as {
|
|
||||||
response?: {
|
|
||||||
data?: { error?: { code?: string; message?: string } };
|
|
||||||
status?: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.warn("[Image Server] Image rate limits check failed:", {
|
|
||||||
did: issuerDid,
|
|
||||||
server: server,
|
|
||||||
errorCode: axiosError.response?.data?.error?.code,
|
|
||||||
errorMessage: axiosError.response?.data?.error?.message,
|
|
||||||
httpStatus: axiosError.response?.status,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+110
-113
@@ -614,64 +614,88 @@ export const retrieveAllAccountsMetadata = async (): Promise<
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DUPLICATE_ACCOUNT_ERROR = "Cannot import duplicate account.";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a new identity to SQL database
|
* Saves a new identity to both SQL and Dexie databases
|
||||||
*/
|
*/
|
||||||
export async function saveNewIdentity(
|
export async function saveNewIdentity(
|
||||||
identity: IIdentifier,
|
identity: IIdentifier,
|
||||||
mnemonic: string,
|
mnemonic: string,
|
||||||
derivationPath: string,
|
derivationPath: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// add to the new sql db
|
try {
|
||||||
const platformService = await getPlatformService();
|
// add to the new sql db
|
||||||
|
const platformService = await getPlatformService();
|
||||||
|
|
||||||
// Check if account already exists before attempting to save
|
const secrets = await platformService.dbQuery(
|
||||||
const existingAccount = await platformService.dbQuery(
|
`SELECT secretBase64 FROM secret`,
|
||||||
"SELECT did FROM accounts WHERE did = ?",
|
);
|
||||||
[identity.did],
|
if (!secrets?.values?.length || !secrets.values[0]?.length) {
|
||||||
);
|
throw new Error(
|
||||||
|
"No initial encryption supported. We recommend you clear your data and start over.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (existingAccount?.values?.length) {
|
const secretBase64 = secrets.values[0][0] as string;
|
||||||
|
|
||||||
|
const secret = base64ToArrayBuffer(secretBase64);
|
||||||
|
const identityStr = JSON.stringify(identity);
|
||||||
|
const encryptedIdentity = await simpleEncrypt(identityStr, secret);
|
||||||
|
const encryptedMnemonic = await simpleEncrypt(mnemonic, secret);
|
||||||
|
const encryptedIdentityBase64 = arrayBufferToBase64(encryptedIdentity);
|
||||||
|
const encryptedMnemonicBase64 = arrayBufferToBase64(encryptedMnemonic);
|
||||||
|
|
||||||
|
const sql = `INSERT INTO accounts (dateCreated, derivationPath, did, identityEncrBase64, mnemonicEncrBase64, publicKeyHex)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)`;
|
||||||
|
const params = [
|
||||||
|
new Date().toISOString(),
|
||||||
|
derivationPath,
|
||||||
|
identity.did,
|
||||||
|
encryptedIdentityBase64,
|
||||||
|
encryptedMnemonicBase64,
|
||||||
|
identity.keys[0].publicKeyHex,
|
||||||
|
];
|
||||||
|
await platformService.dbExec(sql, params);
|
||||||
|
|
||||||
|
// Set the new identity as active using Active Identity façade
|
||||||
|
// Check if we need to avoid legacy settings table (Phase C)
|
||||||
|
const FLAGS = await import("@/config/featureFlags");
|
||||||
|
|
||||||
|
if (!FLAGS.FLAGS.DROP_SETTINGS_ACTIVEDID) {
|
||||||
|
// Phase A/B: Update legacy settings table
|
||||||
|
await platformService.updateDefaultSettings({ activeDid: identity.did });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always update/insert into new active_identity table
|
||||||
|
const DEFAULT_SCOPE = "default";
|
||||||
|
const existingRecord = await platformService.dbQuery(
|
||||||
|
"SELECT id FROM active_identity WHERE scope = ? LIMIT 1",
|
||||||
|
[DEFAULT_SCOPE],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingRecord?.values?.length) {
|
||||||
|
// Update existing record
|
||||||
|
await platformService.dbExec(
|
||||||
|
`UPDATE active_identity
|
||||||
|
SET active_did = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
||||||
|
WHERE scope = ?`,
|
||||||
|
[identity.did, DEFAULT_SCOPE],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Insert new record
|
||||||
|
await platformService.dbExec(
|
||||||
|
`INSERT INTO active_identity (scope, active_did, updated_at)
|
||||||
|
VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))`,
|
||||||
|
[DEFAULT_SCOPE, identity.did],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await platformService.insertNewDidIntoSettings(identity.did);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to update default settings:", error);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Account with DID ${identity.did} already exists. ${DUPLICATE_ACCOUNT_ERROR}`,
|
"Failed to set default settings. Please try again or restart the app.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const secrets = await platformService.dbQuery(
|
|
||||||
`SELECT secretBase64 FROM secret`,
|
|
||||||
);
|
|
||||||
if (!secrets?.values?.length || !secrets.values[0]?.length) {
|
|
||||||
throw new Error(
|
|
||||||
"No initial encryption supported. We recommend you clear your data and start over.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const secretBase64 = secrets.values[0][0] as string;
|
|
||||||
|
|
||||||
const secret = base64ToArrayBuffer(secretBase64);
|
|
||||||
const identityStr = JSON.stringify(identity);
|
|
||||||
const encryptedIdentity = await simpleEncrypt(identityStr, secret);
|
|
||||||
const encryptedMnemonic = await simpleEncrypt(mnemonic, secret);
|
|
||||||
const encryptedIdentityBase64 = arrayBufferToBase64(encryptedIdentity);
|
|
||||||
const encryptedMnemonicBase64 = arrayBufferToBase64(encryptedMnemonic);
|
|
||||||
|
|
||||||
const sql = `INSERT INTO accounts (dateCreated, derivationPath, did, identityEncrBase64, mnemonicEncrBase64, publicKeyHex)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)`;
|
|
||||||
const params = [
|
|
||||||
new Date().toISOString(),
|
|
||||||
derivationPath,
|
|
||||||
identity.did,
|
|
||||||
encryptedIdentityBase64,
|
|
||||||
encryptedMnemonicBase64,
|
|
||||||
identity.keys[0].publicKeyHex,
|
|
||||||
];
|
|
||||||
await platformService.dbExec(sql, params);
|
|
||||||
|
|
||||||
await platformService.updateDefaultSettings({ activeDid: identity.did });
|
|
||||||
|
|
||||||
await platformService.insertNewDidIntoSettings(identity.did);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -722,7 +746,38 @@ export const registerSaveAndActivatePasskey = async (
|
|||||||
): Promise<Account> => {
|
): Promise<Account> => {
|
||||||
const account = await registerAndSavePasskey(keyName);
|
const account = await registerAndSavePasskey(keyName);
|
||||||
const platformService = await getPlatformService();
|
const platformService = await getPlatformService();
|
||||||
await platformService.updateDefaultSettings({ activeDid: account.did });
|
|
||||||
|
// Set the new account as active using Active Identity façade
|
||||||
|
// Check if we need to avoid legacy settings table (Phase C)
|
||||||
|
const FLAGS = await import("@/config/featureFlags");
|
||||||
|
|
||||||
|
if (!FLAGS.FLAGS.DROP_SETTINGS_ACTIVEDID) {
|
||||||
|
// Phase A/B: Update legacy settings table
|
||||||
|
await platformService.updateDefaultSettings({ activeDid: account.did });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always update/insert into new active_identity table
|
||||||
|
const existingRecord = await platformService.dbQuery(
|
||||||
|
"SELECT id FROM active_identity LIMIT 1",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingRecord?.values?.length) {
|
||||||
|
// Update existing record
|
||||||
|
await platformService.dbExec(
|
||||||
|
`UPDATE active_identity
|
||||||
|
SET active_did = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
||||||
|
WHERE id = ?`,
|
||||||
|
[account.did, existingRecord.values[0][0]],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Insert new record
|
||||||
|
await platformService.dbExec(
|
||||||
|
`INSERT INTO active_identity (active_did, updated_at)
|
||||||
|
VALUES (?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))`,
|
||||||
|
[account.did],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await platformService.updateDidSpecificSettings(account.did, {
|
await platformService.updateDidSpecificSettings(account.did, {
|
||||||
isRegistered: false,
|
isRegistered: false,
|
||||||
});
|
});
|
||||||
@@ -981,16 +1036,13 @@ export async function importFromMnemonic(
|
|||||||
const firstName = settings[0];
|
const firstName = settings[0];
|
||||||
const isRegistered = settings[1];
|
const isRegistered = settings[1];
|
||||||
|
|
||||||
logger.debug(
|
logger.info("[importFromMnemonic] Test User #0 settings verification", {
|
||||||
"[importFromMnemonic] Test User #0 settings verification",
|
did: newId.did,
|
||||||
{
|
firstName,
|
||||||
did: newId.did,
|
isRegistered,
|
||||||
firstName,
|
expectedFirstName: "User Zero",
|
||||||
isRegistered,
|
expectedIsRegistered: true,
|
||||||
expectedFirstName: "User Zero",
|
});
|
||||||
expectedIsRegistered: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// If settings weren't saved correctly, try individual updates
|
// If settings weren't saved correctly, try individual updates
|
||||||
if (firstName !== "User Zero" || isRegistered !== 1) {
|
if (firstName !== "User Zero" || isRegistered !== 1) {
|
||||||
@@ -1016,7 +1068,7 @@ export async function importFromMnemonic(
|
|||||||
|
|
||||||
if (retryResult?.values?.length) {
|
if (retryResult?.values?.length) {
|
||||||
const retrySettings = retryResult.values[0];
|
const retrySettings = retryResult.values[0];
|
||||||
logger.debug(
|
logger.info(
|
||||||
"[importFromMnemonic] Test User #0 settings after retry",
|
"[importFromMnemonic] Test User #0 settings after retry",
|
||||||
{
|
{
|
||||||
firstName: retrySettings[0],
|
firstName: retrySettings[0],
|
||||||
@@ -1039,58 +1091,3 @@ export async function importFromMnemonic(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if an account with the given DID already exists in the database
|
|
||||||
*
|
|
||||||
* @param did - The DID to check for duplicates
|
|
||||||
* @returns Promise<boolean> - True if account already exists, false otherwise
|
|
||||||
* @throws Error if database query fails
|
|
||||||
*/
|
|
||||||
export async function checkForDuplicateAccount(did: string): Promise<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if an account with the given DID already exists in the database
|
|
||||||
*
|
|
||||||
* @param mnemonic - The mnemonic phrase to derive DID from
|
|
||||||
* @param derivationPath - The derivation path to use
|
|
||||||
* @returns Promise<boolean> - True if account already exists, false otherwise
|
|
||||||
* @throws Error if database query fails
|
|
||||||
*/
|
|
||||||
export async function checkForDuplicateAccount(
|
|
||||||
mnemonic: string,
|
|
||||||
derivationPath: string,
|
|
||||||
): Promise<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of checkForDuplicateAccount with overloaded signatures
|
|
||||||
*/
|
|
||||||
export async function checkForDuplicateAccount(
|
|
||||||
didOrMnemonic: string,
|
|
||||||
derivationPath?: string,
|
|
||||||
): Promise<boolean> {
|
|
||||||
let didToCheck: string;
|
|
||||||
|
|
||||||
if (derivationPath) {
|
|
||||||
// Derive the DID from mnemonic and derivation path
|
|
||||||
const [address, privateHex, publicHex] = deriveAddress(
|
|
||||||
didOrMnemonic.trim().toLowerCase(),
|
|
||||||
derivationPath,
|
|
||||||
);
|
|
||||||
|
|
||||||
const newId = newIdentifier(address, privateHex, publicHex, derivationPath);
|
|
||||||
didToCheck = newId.did;
|
|
||||||
} else {
|
|
||||||
// Use the provided DID directly
|
|
||||||
didToCheck = didOrMnemonic;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if an account with this DID already exists
|
|
||||||
const platformService = await getPlatformService();
|
|
||||||
const existingAccount = await platformService.dbQuery(
|
|
||||||
"SELECT did FROM accounts WHERE did = ?",
|
|
||||||
[didToCheck],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (existingAccount?.values?.length ?? 0) > 0;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,15 +13,6 @@ const platform = process.env.VITE_PLATFORM || "web";
|
|||||||
|
|
||||||
logger.info(`[Main] 🚀 Loading TimeSafari for platform: ${platform}`);
|
logger.info(`[Main] 🚀 Loading TimeSafari for platform: ${platform}`);
|
||||||
|
|
||||||
// Log all relevant environment variables for boot-time debugging
|
|
||||||
logger.info("[Main] 🌍 Boot-time environment configuration:", {
|
|
||||||
platform: process.env.VITE_PLATFORM,
|
|
||||||
defaultEndorserApiServer: process.env.VITE_DEFAULT_ENDORSER_API_SERVER,
|
|
||||||
defaultPartnerApiServer: process.env.VITE_DEFAULT_PARTNER_API_SERVER,
|
|
||||||
nodeEnv: process.env.NODE_ENV,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dynamically import the appropriate main entry point
|
// Dynamically import the appropriate main entry point
|
||||||
if (platform === "capacitor") {
|
if (platform === "capacitor") {
|
||||||
logger.info(`[Main] 📱 Loading Capacitor-specific entry point`);
|
logger.info(`[Main] 📱 Loading Capacitor-specific entry point`);
|
||||||
|
|||||||
+5
-21
@@ -327,7 +327,7 @@ router.onError(errorHandler); // Assign the error handler to the router instance
|
|||||||
* @param next - Navigation function
|
* @param next - Navigation function
|
||||||
*/
|
*/
|
||||||
router.beforeEach(async (to, _from, next) => {
|
router.beforeEach(async (to, _from, next) => {
|
||||||
logger.debug(`[Router] 🧭 Navigation guard triggered:`, {
|
logger.info(`[Router] 🧭 Navigation guard triggered:`, {
|
||||||
from: _from?.path || "none",
|
from: _from?.path || "none",
|
||||||
to: to.path,
|
to: to.path,
|
||||||
name: to.name,
|
name: to.name,
|
||||||
@@ -337,22 +337,6 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Log boot-time configuration on first navigation
|
|
||||||
if (!_from) {
|
|
||||||
logger.info(
|
|
||||||
"[Router] 🚀 First navigation detected - logging boot-time configuration:",
|
|
||||||
{
|
|
||||||
platform: process.env.VITE_PLATFORM,
|
|
||||||
defaultEndorserApiServer:
|
|
||||||
process.env.VITE_DEFAULT_ENDORSER_API_SERVER,
|
|
||||||
defaultPartnerApiServer: process.env.VITE_DEFAULT_PARTNER_API_SERVER,
|
|
||||||
nodeEnv: process.env.NODE_ENV,
|
|
||||||
targetRoute: to.path,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip identity check for routes that handle identity creation manually
|
// Skip identity check for routes that handle identity creation manually
|
||||||
const skipIdentityRoutes = [
|
const skipIdentityRoutes = [
|
||||||
"/start",
|
"/start",
|
||||||
@@ -368,11 +352,11 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`[Router] 🔍 Checking user identity for route: ${to.path}`);
|
logger.info(`[Router] 🔍 Checking user identity for route: ${to.path}`);
|
||||||
|
|
||||||
// Check if user has any identities
|
// Check if user has any identities
|
||||||
const allMyDids = await retrieveAccountDids();
|
const allMyDids = await retrieveAccountDids();
|
||||||
logger.debug(`[Router] 📋 Found ${allMyDids.length} user identities`);
|
logger.info(`[Router] 📋 Found ${allMyDids.length} user identities`);
|
||||||
|
|
||||||
if (allMyDids.length === 0) {
|
if (allMyDids.length === 0) {
|
||||||
logger.info("[Router] ⚠️ No identities found, creating default identity");
|
logger.info("[Router] ⚠️ No identities found, creating default identity");
|
||||||
@@ -382,7 +366,7 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
|
|
||||||
logger.info("[Router] ✅ Default identity created successfully");
|
logger.info("[Router] ✅ Default identity created successfully");
|
||||||
} else {
|
} else {
|
||||||
logger.debug(
|
logger.info(
|
||||||
`[Router] ✅ User has ${allMyDids.length} identities, proceeding`,
|
`[Router] ✅ User has ${allMyDids.length} identities, proceeding`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -408,7 +392,7 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
|
|
||||||
// Add navigation success logging
|
// Add navigation success logging
|
||||||
router.afterEach((to, from) => {
|
router.afterEach((to, from) => {
|
||||||
logger.debug(`[Router] ✅ Navigation completed:`, {
|
logger.info(`[Router] ✅ Navigation completed:`, {
|
||||||
from: from?.path || "none",
|
from: from?.path || "none",
|
||||||
to: to.path,
|
to: to.path,
|
||||||
name: to.name,
|
name: to.name,
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
/**
|
||||||
|
* ProfileService - Handles user profile operations and API calls
|
||||||
|
* Extracted from AccountViewView.vue to improve separation of concerns
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AxiosInstance, AxiosError } from "axios";
|
||||||
|
import { UserProfile } from "@/libs/partnerServer";
|
||||||
|
import { UserProfileResponse } from "@/interfaces/accountView";
|
||||||
|
import { getHeaders, errorStringForLog } from "@/libs/endorserServer";
|
||||||
|
import { handleApiError } from "./api";
|
||||||
|
import { logger } from "@/utils/logger";
|
||||||
|
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile data interface
|
||||||
|
*/
|
||||||
|
export interface ProfileData {
|
||||||
|
description: string;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
includeLocation: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile service class
|
||||||
|
*/
|
||||||
|
export class ProfileService {
|
||||||
|
private axios: AxiosInstance;
|
||||||
|
private partnerApiServer: string;
|
||||||
|
|
||||||
|
constructor(axios: AxiosInstance, partnerApiServer: string) {
|
||||||
|
this.axios = axios;
|
||||||
|
this.partnerApiServer = partnerApiServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load user profile from the server
|
||||||
|
* @param activeDid - The user's DID
|
||||||
|
* @returns ProfileData or null if profile doesn't exist
|
||||||
|
*/
|
||||||
|
async loadProfile(activeDid: string): Promise<ProfileData | null> {
|
||||||
|
try {
|
||||||
|
const headers = await getHeaders(activeDid);
|
||||||
|
const response = await this.axios.get<UserProfileResponse>(
|
||||||
|
`${this.partnerApiServer}/api/partner/userProfileForIssuer/${activeDid}`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const data = response.data.data;
|
||||||
|
const profileData: ProfileData = {
|
||||||
|
description: data.description || "",
|
||||||
|
latitude: data.locLat || 0,
|
||||||
|
longitude: data.locLon || 0,
|
||||||
|
includeLocation: !!(data.locLat && data.locLon),
|
||||||
|
};
|
||||||
|
return profileData;
|
||||||
|
} else {
|
||||||
|
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.UNABLE_TO_LOAD_PROFILE);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (this.isApiError(error) && error.response?.status === 404) {
|
||||||
|
// Profile doesn't exist yet - this is normal
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error("Error loading profile:", errorStringForLog(error));
|
||||||
|
handleApiError(error as AxiosError, "/api/partner/userProfileForIssuer");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save user profile to the server
|
||||||
|
* @param activeDid - The user's DID
|
||||||
|
* @param profileData - The profile data to save
|
||||||
|
* @returns true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
async saveProfile(
|
||||||
|
activeDid: string,
|
||||||
|
profileData: ProfileData,
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const headers = await getHeaders(activeDid);
|
||||||
|
const payload: UserProfile = {
|
||||||
|
description: profileData.description,
|
||||||
|
issuerDid: activeDid,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add location data if location is included
|
||||||
|
if (
|
||||||
|
profileData.includeLocation &&
|
||||||
|
profileData.latitude &&
|
||||||
|
profileData.longitude
|
||||||
|
) {
|
||||||
|
payload.locLat = profileData.latitude;
|
||||||
|
payload.locLon = profileData.longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.axios.post(
|
||||||
|
`${this.partnerApiServer}/api/partner/userProfile`,
|
||||||
|
payload,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 201) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logger.error("Error saving profile:", response);
|
||||||
|
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_SAVED);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error saving profile:", errorStringForLog(error));
|
||||||
|
handleApiError(error as AxiosError, "/api/partner/userProfile");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete user profile from the server
|
||||||
|
* @param activeDid - The user's DID
|
||||||
|
* @returns true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
async deleteProfile(activeDid: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const headers = await getHeaders(activeDid);
|
||||||
|
const url = `${this.partnerApiServer}/api/partner/userProfile`;
|
||||||
|
const response = await this.axios.delete(url, { headers });
|
||||||
|
|
||||||
|
if (response.status === 204 || response.status === 200) {
|
||||||
|
logger.info("Profile deleted successfully");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logger.error("Unexpected response status when deleting profile:", {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
data: response.data,
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
const errorMessage =
|
||||||
|
typeof response.data === "string"
|
||||||
|
? response.data
|
||||||
|
: response.data?.message || "Bad request";
|
||||||
|
throw new Error(`Profile deletion failed: ${errorMessage}`);
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update profile location
|
||||||
|
* @param profileData - Current profile data
|
||||||
|
* @param latitude - New latitude
|
||||||
|
* @param longitude - New longitude
|
||||||
|
* @returns Updated profile data
|
||||||
|
*/
|
||||||
|
updateProfileLocation(
|
||||||
|
profileData: ProfileData,
|
||||||
|
latitude: number,
|
||||||
|
longitude: number,
|
||||||
|
): ProfileData {
|
||||||
|
return {
|
||||||
|
...profileData,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
includeLocation: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle location inclusion in profile
|
||||||
|
* @param profileData - Current profile data
|
||||||
|
* @returns Updated profile data
|
||||||
|
*/
|
||||||
|
toggleProfileLocation(profileData: ProfileData): ProfileData {
|
||||||
|
const includeLocation = !profileData.includeLocation;
|
||||||
|
return {
|
||||||
|
...profileData,
|
||||||
|
latitude: includeLocation ? profileData.latitude : 0,
|
||||||
|
longitude: includeLocation ? profileData.longitude : 0,
|
||||||
|
includeLocation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear profile location
|
||||||
|
* @param profileData - Current profile data
|
||||||
|
* @returns Updated profile data
|
||||||
|
*/
|
||||||
|
clearProfileLocation(profileData: ProfileData): ProfileData {
|
||||||
|
return {
|
||||||
|
...profileData,
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
includeLocation: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset profile to default state
|
||||||
|
* @returns Default profile data
|
||||||
|
*/
|
||||||
|
getDefaultProfile(): ProfileData {
|
||||||
|
return {
|
||||||
|
description: "",
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
includeLocation: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for API errors with proper typing
|
||||||
|
*/
|
||||||
|
private isApiError(error: unknown): error is {
|
||||||
|
response?: {
|
||||||
|
status?: number;
|
||||||
|
statusText?: string;
|
||||||
|
data?: { message?: string } | string;
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
return typeof error === "object" && error !== null && "response" in error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract error URL safely from error object
|
||||||
|
*/
|
||||||
|
private getErrorUrl(error: unknown): string | undefined {
|
||||||
|
if (this.isAxiosError(error)) {
|
||||||
|
return error.config?.url;
|
||||||
|
}
|
||||||
|
if (this.isApiError(error) && this.hasConfigProperty(error)) {
|
||||||
|
const config = this.getConfigProperty(error);
|
||||||
|
return config?.url;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard to check if error has config property
|
||||||
|
*/
|
||||||
|
private hasConfigProperty(
|
||||||
|
error: unknown,
|
||||||
|
): error is { config?: { url?: string } } {
|
||||||
|
return typeof error === "object" && error !== null && "config" in error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely extract config property from error
|
||||||
|
*/
|
||||||
|
private getConfigProperty(error: {
|
||||||
|
config?: { url?: string };
|
||||||
|
}): { url?: string } | undefined {
|
||||||
|
return error.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for AxiosError
|
||||||
|
*/
|
||||||
|
private isAxiosError(error: unknown): error is AxiosError {
|
||||||
|
return error instanceof AxiosError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function to create a ProfileService instance
|
||||||
|
*/
|
||||||
|
export function createProfileService(
|
||||||
|
axios: AxiosInstance,
|
||||||
|
partnerApiServer: string,
|
||||||
|
): ProfileService {
|
||||||
|
return new ProfileService(axios, partnerApiServer);
|
||||||
|
}
|
||||||
@@ -49,6 +49,8 @@ import {
|
|||||||
type Settings,
|
type Settings,
|
||||||
type SettingsWithJsonStrings,
|
type SettingsWithJsonStrings,
|
||||||
} from "@/db/tables/settings";
|
} from "@/db/tables/settings";
|
||||||
|
import { type ActiveIdentity } from "@/db/tables/activeIdentity";
|
||||||
|
import { FLAGS } from "@/config/featureFlags";
|
||||||
import { logger } from "@/utils/logger";
|
import { logger } from "@/utils/logger";
|
||||||
import { Contact, ContactMaybeWithJsonStrings } from "@/db/tables/contacts";
|
import { Contact, ContactMaybeWithJsonStrings } from "@/db/tables/contacts";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
@@ -437,17 +439,17 @@ export const PlatformServiceMixin = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method for retrieving master settings
|
* Utility method for retrieving and parsing settings
|
||||||
* Common pattern used across many components
|
* Common pattern used across many components
|
||||||
*/
|
*/
|
||||||
async $getMasterSettings(
|
async $getSettings(
|
||||||
|
key: string,
|
||||||
fallback: Settings | null = null,
|
fallback: Settings | null = null,
|
||||||
): Promise<Settings | null> {
|
): Promise<Settings | null> {
|
||||||
try {
|
try {
|
||||||
// Master settings: query by id
|
|
||||||
const result = await this.$dbQuery(
|
const result = await this.$dbQuery(
|
||||||
"SELECT * FROM settings WHERE id = ?",
|
"SELECT * FROM settings WHERE id = ? OR accountDid = ?",
|
||||||
[MASTER_SETTINGS_KEY],
|
[key, key],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result?.values?.length) {
|
if (!result?.values?.length) {
|
||||||
@@ -472,7 +474,8 @@ export const PlatformServiceMixin = {
|
|||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[Settings Trace] ❌ Failed to get master settings:`, {
|
logger.error(`[Settings Trace] ❌ Failed to get settings:`, {
|
||||||
|
key,
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
return fallback;
|
return fallback;
|
||||||
@@ -490,7 +493,10 @@ export const PlatformServiceMixin = {
|
|||||||
): Promise<Settings> {
|
): Promise<Settings> {
|
||||||
try {
|
try {
|
||||||
// Get default settings
|
// Get default settings
|
||||||
const defaultSettings = await this.$getMasterSettings(defaultFallback);
|
const defaultSettings = await this.$getSettings(
|
||||||
|
defaultKey,
|
||||||
|
defaultFallback,
|
||||||
|
);
|
||||||
|
|
||||||
// If no account DID, return defaults
|
// If no account DID, return defaults
|
||||||
if (!accountDid) {
|
if (!accountDid) {
|
||||||
@@ -753,20 +759,19 @@ export const PlatformServiceMixin = {
|
|||||||
* @returns Fresh settings object from database
|
* @returns Fresh settings object from database
|
||||||
*/
|
*/
|
||||||
async $settings(defaults: Settings = {}): Promise<Settings> {
|
async $settings(defaults: Settings = {}): Promise<Settings> {
|
||||||
const settings = await this.$getMasterSettings(defaults);
|
const settings = await this.$getSettings(MASTER_SETTINGS_KEY, defaults);
|
||||||
|
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXED: Remove forced override - respect user preferences
|
// **ELECTRON-SPECIFIC FIX**: Apply platform-specific API server override
|
||||||
// Only set default if no user preference exists
|
// This ensures Electron always uses production endpoints regardless of cached settings
|
||||||
if (!settings.apiServer && process.env.VITE_PLATFORM === "electron") {
|
if (process.env.VITE_PLATFORM === "electron") {
|
||||||
// Import constants dynamically to get platform-specific values
|
// Import constants dynamically to get platform-specific values
|
||||||
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
||||||
"../constants/app"
|
"../constants/app"
|
||||||
);
|
);
|
||||||
// Only set if user hasn't specified a preference
|
|
||||||
settings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
settings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,7 +791,10 @@ export const PlatformServiceMixin = {
|
|||||||
): Promise<Settings> {
|
): Promise<Settings> {
|
||||||
try {
|
try {
|
||||||
// Get default settings first
|
// Get default settings first
|
||||||
const defaultSettings = await this.$getMasterSettings(defaults);
|
const defaultSettings = await this.$getSettings(
|
||||||
|
MASTER_SETTINGS_KEY,
|
||||||
|
defaults,
|
||||||
|
);
|
||||||
|
|
||||||
if (!defaultSettings) {
|
if (!defaultSettings) {
|
||||||
return defaults;
|
return defaults;
|
||||||
@@ -807,17 +815,14 @@ export const PlatformServiceMixin = {
|
|||||||
defaultSettings,
|
defaultSettings,
|
||||||
);
|
);
|
||||||
|
|
||||||
// FIXED: Remove forced override - respect user preferences
|
// **ELECTRON-SPECIFIC FIX**: Force production API endpoints for Electron
|
||||||
// Only set default if no user preference exists
|
// This ensures Electron doesn't use localhost development servers that might be saved in user settings
|
||||||
if (
|
if (process.env.VITE_PLATFORM === "electron") {
|
||||||
!mergedSettings.apiServer &&
|
|
||||||
process.env.VITE_PLATFORM === "electron"
|
|
||||||
) {
|
|
||||||
// Import constants dynamically to get platform-specific values
|
// Import constants dynamically to get platform-specific values
|
||||||
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
||||||
"../constants/app"
|
"../constants/app"
|
||||||
);
|
);
|
||||||
// Only set if user hasn't specified a preference
|
|
||||||
mergedSettings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
mergedSettings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -961,6 +966,156 @@ export const PlatformServiceMixin = {
|
|||||||
return await this.$saveUserSettings(currentDid, changes);
|
return await this.$saveUserSettings(currentDid, changes);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// ACTIVE IDENTITY METHODS (New table separation)
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current active DID from the active_identity table
|
||||||
|
* Falls back to legacy settings.activeDid during Phase A transition
|
||||||
|
*
|
||||||
|
* @returns Promise<string | null> The active DID or null if not found
|
||||||
|
*/
|
||||||
|
async $getActiveDid(): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
logger.debug("[ActiveDid] Getting activeDid");
|
||||||
|
|
||||||
|
// Try new active_identity table first
|
||||||
|
const row = await this.$first<ActiveIdentity>(
|
||||||
|
"SELECT active_did FROM active_identity LIMIT 1",
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.debug("[ActiveDid] New system result:", row?.active_did || "null");
|
||||||
|
|
||||||
|
if (row?.active_did) {
|
||||||
|
logger.debug("[ActiveDid] Using new system value:", row.active_did);
|
||||||
|
return row.active_did;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to legacy settings.activeDid during Phase A/B (unless Phase C is complete)
|
||||||
|
if (!FLAGS.DROP_SETTINGS_ACTIVEDID) {
|
||||||
|
if (FLAGS.LOG_ACTIVE_ID_FALLBACK) {
|
||||||
|
logger.warn("[ActiveDid] Fallback to legacy settings.activeDid");
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacy = await this.$first<Settings>(
|
||||||
|
"SELECT activeDid FROM settings WHERE id = ? LIMIT 1",
|
||||||
|
[MASTER_SETTINGS_KEY],
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.debug("[ActiveDid] Legacy fallback result:", legacy?.activeDid || "null");
|
||||||
|
return legacy?.activeDid || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("[ActiveDid] No fallback available, returning null");
|
||||||
|
|
||||||
|
// Log current database state for debugging
|
||||||
|
try {
|
||||||
|
const activeIdentityCount = await this.$first<{ count: number }>(
|
||||||
|
"SELECT COUNT(*) as count FROM active_identity",
|
||||||
|
);
|
||||||
|
logger.debug("[ActiveDid] Active identity records:", activeIdentityCount?.count || 0);
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug("[ActiveDid] Could not count active identity records:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[ActiveDid] Error getting activeDid:", error);
|
||||||
|
|
||||||
|
// Fallback to legacy settings.activeDid during Phase A/B
|
||||||
|
if (!FLAGS.DROP_SETTINGS_ACTIVEDID) {
|
||||||
|
try {
|
||||||
|
const legacy = await this.$first<Settings>(
|
||||||
|
"SELECT activeDid FROM settings WHERE id = ? LIMIT 1",
|
||||||
|
[MASTER_SETTINGS_KEY],
|
||||||
|
);
|
||||||
|
return legacy?.activeDid || null;
|
||||||
|
} catch (fallbackError) {
|
||||||
|
logger.error("[ActiveDid] Legacy fallback also failed:", fallbackError);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active DID in the active_identity table
|
||||||
|
* Also updates legacy settings.activeDid during Phase A/B transition
|
||||||
|
*
|
||||||
|
* @param did The DID to set as active
|
||||||
|
* @returns Promise<void>
|
||||||
|
*/
|
||||||
|
async $setActiveDid(did: string | null): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!did) {
|
||||||
|
logger.warn("[ActiveDid] Attempting to set null activeDid - this may cause issues");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("[ActiveDid] Setting activeDid to:", did);
|
||||||
|
|
||||||
|
// Update/insert into new active_identity table
|
||||||
|
const existingRecord = await this.$first<ActiveIdentity>(
|
||||||
|
"SELECT id FROM active_identity LIMIT 1",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingRecord?.id) {
|
||||||
|
// Update existing record
|
||||||
|
await this.$exec(
|
||||||
|
`UPDATE active_identity
|
||||||
|
SET active_did = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
||||||
|
WHERE id = ?`,
|
||||||
|
[did, existingRecord.id],
|
||||||
|
);
|
||||||
|
logger.debug("[ActiveDid] Updated existing record");
|
||||||
|
} else {
|
||||||
|
// Insert new record
|
||||||
|
await this.$exec(
|
||||||
|
`INSERT INTO active_identity (active_did, updated_at)
|
||||||
|
VALUES (?, strftime('%Y-%m-%dT%H:%M:%fZ','now'))`,
|
||||||
|
[did],
|
||||||
|
);
|
||||||
|
logger.debug("[ActiveDid] Inserted new record");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy fallback - update settings.activeDid during Phase A/B
|
||||||
|
if (!FLAGS.USE_ACTIVE_IDENTITY_ONLY) {
|
||||||
|
await this.$exec(
|
||||||
|
"UPDATE settings SET activeDid = ? WHERE id = ?",
|
||||||
|
[did, MASTER_SETTINGS_KEY],
|
||||||
|
);
|
||||||
|
logger.debug("[ActiveDid] Updated legacy settings.activeDid");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("[ActiveDid] Successfully set activeDid to:", did);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[ActiveDid] Error setting activeDid:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to a different active identity
|
||||||
|
* Convenience method that validates and sets the new active DID
|
||||||
|
*
|
||||||
|
* @param did The DID to switch to
|
||||||
|
* @returns Promise<void>
|
||||||
|
*/
|
||||||
|
async $switchActiveIdentity(did: string): Promise<void> {
|
||||||
|
await this.$setActiveDid(did);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available identity scopes (simplified to single scope)
|
||||||
|
* @returns Promise<string[]> Array containing only 'default' scope
|
||||||
|
*/
|
||||||
|
async $getActiveIdentityScopes(): Promise<string[]> {
|
||||||
|
// Simplified to single scope since we removed multi-scope support
|
||||||
|
return ["default"];
|
||||||
|
},
|
||||||
|
|
||||||
// =================================================
|
// =================================================
|
||||||
// CACHE MANAGEMENT METHODS
|
// CACHE MANAGEMENT METHODS
|
||||||
// =================================================
|
// =================================================
|
||||||
@@ -1571,7 +1726,10 @@ export const PlatformServiceMixin = {
|
|||||||
async $debugMergedSettings(did: string): Promise<void> {
|
async $debugMergedSettings(did: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Get default settings
|
// Get default settings
|
||||||
const defaultSettings = await this.$getMasterSettings({});
|
const defaultSettings = await this.$getSettings(
|
||||||
|
MASTER_SETTINGS_KEY,
|
||||||
|
{},
|
||||||
|
);
|
||||||
logger.info(
|
logger.info(
|
||||||
`[PlatformServiceMixin] Default settings:`,
|
`[PlatformServiceMixin] Default settings:`,
|
||||||
defaultSettings,
|
defaultSettings,
|
||||||
@@ -1618,7 +1776,10 @@ export interface IPlatformServiceMixin {
|
|||||||
): Promise<QueryExecResult | undefined>;
|
): Promise<QueryExecResult | undefined>;
|
||||||
$dbExec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
|
$dbExec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
|
||||||
$dbGetOneRow(sql: string, params?: unknown[]): Promise<unknown[] | undefined>;
|
$dbGetOneRow(sql: string, params?: unknown[]): Promise<unknown[] | undefined>;
|
||||||
$getMasterSettings(fallback?: Settings | null): Promise<Settings | null>;
|
$getSettings(
|
||||||
|
key: string,
|
||||||
|
fallback?: Settings | null,
|
||||||
|
): Promise<Settings | null>;
|
||||||
$getMergedSettings(
|
$getMergedSettings(
|
||||||
defaultKey: string,
|
defaultKey: string,
|
||||||
accountDid?: string,
|
accountDid?: string,
|
||||||
@@ -1699,6 +1860,12 @@ export interface IPlatformServiceMixin {
|
|||||||
// Debug methods
|
// Debug methods
|
||||||
$debugDidSettings(did: string): Promise<Settings | null>;
|
$debugDidSettings(did: string): Promise<Settings | null>;
|
||||||
$debugMergedSettings(did: string): Promise<void>;
|
$debugMergedSettings(did: string): Promise<void>;
|
||||||
|
|
||||||
|
// Active Identity façade methods
|
||||||
|
$getActiveDid(): Promise<string | null>;
|
||||||
|
$setActiveDid(did: string | null): Promise<void>;
|
||||||
|
$switchActiveIdentity(did: string): Promise<void>;
|
||||||
|
$getActiveIdentityScopes(): Promise<string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TypeScript declaration merging to eliminate (this as any) type assertions
|
// TypeScript declaration merging to eliminate (this as any) type assertions
|
||||||
@@ -1715,6 +1882,12 @@ declare module "@vue/runtime-core" {
|
|||||||
currentActiveDid: string | null;
|
currentActiveDid: string | null;
|
||||||
$updateActiveDid(newDid: string | null): Promise<void>;
|
$updateActiveDid(newDid: string | null): Promise<void>;
|
||||||
|
|
||||||
|
// Active Identity façade methods
|
||||||
|
$getActiveDid(): Promise<string | null>;
|
||||||
|
$setActiveDid(did: string | null): Promise<void>;
|
||||||
|
$switchActiveIdentity(did: string): Promise<void>;
|
||||||
|
$getActiveIdentityScopes(): Promise<string[]>;
|
||||||
|
|
||||||
// Ultra-concise database methods (shortest possible names)
|
// Ultra-concise database methods (shortest possible names)
|
||||||
$db(sql: string, params?: unknown[]): Promise<QueryExecResult | undefined>;
|
$db(sql: string, params?: unknown[]): Promise<QueryExecResult | undefined>;
|
||||||
$exec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
|
$exec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
|
||||||
@@ -1740,7 +1913,10 @@ declare module "@vue/runtime-core" {
|
|||||||
sql: string,
|
sql: string,
|
||||||
params?: unknown[],
|
params?: unknown[],
|
||||||
): Promise<unknown[] | undefined>;
|
): Promise<unknown[] | undefined>;
|
||||||
$getMasterSettings(defaults?: Settings | null): Promise<Settings | null>;
|
$getSettings(
|
||||||
|
key: string,
|
||||||
|
defaults?: Settings | null,
|
||||||
|
): Promise<Settings | null>;
|
||||||
$getMergedSettings(
|
$getMergedSettings(
|
||||||
key: string,
|
key: string,
|
||||||
did?: string,
|
did?: string,
|
||||||
|
|||||||
@@ -1,298 +0,0 @@
|
|||||||
/**
|
|
||||||
* Standardized Error Handler
|
|
||||||
*
|
|
||||||
* Provides consistent error handling patterns across the TimeSafari codebase
|
|
||||||
* to improve debugging, user experience, and maintainability.
|
|
||||||
*
|
|
||||||
* @author Matthew Raymer
|
|
||||||
* @since 2025-08-25
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { AxiosError } from "axios";
|
|
||||||
import { logger } from "./logger";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard error context for consistent logging
|
|
||||||
*/
|
|
||||||
export interface ErrorContext {
|
|
||||||
component: string;
|
|
||||||
operation: string;
|
|
||||||
timestamp: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enhanced error information for better debugging
|
|
||||||
*/
|
|
||||||
export interface EnhancedErrorInfo {
|
|
||||||
errorType: "AxiosError" | "NetworkError" | "ValidationError" | "UnknownError";
|
|
||||||
status?: number;
|
|
||||||
statusText?: string;
|
|
||||||
errorData?: unknown;
|
|
||||||
errorMessage: string;
|
|
||||||
errorStack?: string;
|
|
||||||
requestContext?: {
|
|
||||||
url?: string;
|
|
||||||
method?: string;
|
|
||||||
headers?: Record<string, unknown>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standardized error handler for API operations
|
|
||||||
*
|
|
||||||
* @param error - The error that occurred
|
|
||||||
* @param context - Context information about the operation
|
|
||||||
* @param operation - Description of the operation being performed
|
|
||||||
* @returns Enhanced error information for consistent handling
|
|
||||||
*/
|
|
||||||
export function handleApiError(
|
|
||||||
error: unknown,
|
|
||||||
context: ErrorContext,
|
|
||||||
operation: string,
|
|
||||||
): EnhancedErrorInfo {
|
|
||||||
const baseContext = {
|
|
||||||
...context,
|
|
||||||
operation,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (error instanceof AxiosError) {
|
|
||||||
const axiosError = error as AxiosError;
|
|
||||||
const status = axiosError.response?.status;
|
|
||||||
const statusText = axiosError.response?.statusText;
|
|
||||||
const errorData = axiosError.response?.data;
|
|
||||||
|
|
||||||
const enhancedError: EnhancedErrorInfo = {
|
|
||||||
errorType: "AxiosError",
|
|
||||||
status,
|
|
||||||
statusText,
|
|
||||||
errorData,
|
|
||||||
errorMessage: axiosError.message,
|
|
||||||
errorStack: axiosError.stack,
|
|
||||||
requestContext: {
|
|
||||||
url: axiosError.config?.url,
|
|
||||||
method: axiosError.config?.method,
|
|
||||||
headers: axiosError.config?.headers,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Log with consistent format
|
|
||||||
logger.error(
|
|
||||||
`[${context.component}] ❌ ${operation} failed (AxiosError):`,
|
|
||||||
{
|
|
||||||
...baseContext,
|
|
||||||
...enhancedError,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return enhancedError;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof Error) {
|
|
||||||
const enhancedError: EnhancedErrorInfo = {
|
|
||||||
errorType: "UnknownError",
|
|
||||||
errorMessage: error.message,
|
|
||||||
errorStack: error.stack,
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.error(`[${context.component}] ❌ ${operation} failed (Error):`, {
|
|
||||||
...baseContext,
|
|
||||||
...enhancedError,
|
|
||||||
});
|
|
||||||
|
|
||||||
return enhancedError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle unknown error types
|
|
||||||
const enhancedError: EnhancedErrorInfo = {
|
|
||||||
errorType: "UnknownError",
|
|
||||||
errorMessage: String(error),
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.error(`[${context.component}] ❌ ${operation} failed (Unknown):`, {
|
|
||||||
...baseContext,
|
|
||||||
...enhancedError,
|
|
||||||
});
|
|
||||||
|
|
||||||
return enhancedError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract human-readable error message from various error response formats
|
|
||||||
*
|
|
||||||
* @param errorData - Error response data
|
|
||||||
* @returns Human-readable error message
|
|
||||||
*/
|
|
||||||
export function extractErrorMessage(errorData: unknown): string {
|
|
||||||
if (typeof errorData === "string") {
|
|
||||||
return errorData;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof errorData === "object" && errorData !== null) {
|
|
||||||
const obj = errorData as Record<string, unknown>;
|
|
||||||
|
|
||||||
// Try common error message fields
|
|
||||||
if (obj.message && typeof obj.message === "string") {
|
|
||||||
return obj.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.error && typeof obj.error === "string") {
|
|
||||||
return obj.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.detail && typeof obj.detail === "string") {
|
|
||||||
return obj.detail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.reason && typeof obj.reason === "string") {
|
|
||||||
return obj.reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to stringified object
|
|
||||||
return JSON.stringify(errorData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(errorData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create user-friendly error message from enhanced error info
|
|
||||||
*
|
|
||||||
* @param errorInfo - Enhanced error information
|
|
||||||
* @param fallbackMessage - Fallback message if error details are insufficient
|
|
||||||
* @returns User-friendly error message
|
|
||||||
*/
|
|
||||||
export function createUserMessage(
|
|
||||||
errorInfo: EnhancedErrorInfo,
|
|
||||||
fallbackMessage: string,
|
|
||||||
): string {
|
|
||||||
if (errorInfo.errorType === "AxiosError") {
|
|
||||||
const status = errorInfo.status;
|
|
||||||
const statusText = errorInfo.statusText;
|
|
||||||
const errorMessage = extractErrorMessage(errorInfo.errorData);
|
|
||||||
|
|
||||||
if (status && statusText) {
|
|
||||||
if (errorMessage && errorMessage !== "{}") {
|
|
||||||
return `${fallbackMessage}: ${status} ${statusText} - ${errorMessage}`;
|
|
||||||
}
|
|
||||||
return `${fallbackMessage}: ${status} ${statusText}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
errorInfo.errorMessage &&
|
|
||||||
errorInfo.errorMessage !== "Request failed with status code 0"
|
|
||||||
) {
|
|
||||||
return `${fallbackMessage}: ${errorInfo.errorMessage}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fallbackMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle specific HTTP status codes with appropriate user messages
|
|
||||||
*
|
|
||||||
* @param status - HTTP status code
|
|
||||||
* @param errorData - Error response data
|
|
||||||
* @param operation - Description of the operation
|
|
||||||
* @returns User-friendly error message
|
|
||||||
*/
|
|
||||||
export function handleHttpStatus(
|
|
||||||
status: number,
|
|
||||||
errorData: unknown,
|
|
||||||
operation: string,
|
|
||||||
): string {
|
|
||||||
const errorMessage = extractErrorMessage(errorData);
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case 400:
|
|
||||||
return errorMessage || `${operation} failed: Bad request`;
|
|
||||||
case 401:
|
|
||||||
return `${operation} failed: Authentication required`;
|
|
||||||
case 403:
|
|
||||||
return `${operation} failed: Access denied`;
|
|
||||||
case 404:
|
|
||||||
return errorMessage || `${operation} failed: Resource not found`;
|
|
||||||
case 409:
|
|
||||||
return errorMessage || `${operation} failed: Conflict with existing data`;
|
|
||||||
case 422:
|
|
||||||
return errorMessage || `${operation} failed: Validation error`;
|
|
||||||
case 429:
|
|
||||||
return `${operation} failed: Too many requests. Please try again later.`;
|
|
||||||
case 500:
|
|
||||||
return `${operation} failed: Server error. Please try again later.`;
|
|
||||||
case 502:
|
|
||||||
case 503:
|
|
||||||
case 504:
|
|
||||||
return `${operation} failed: Service temporarily unavailable. Please try again later.`;
|
|
||||||
default:
|
|
||||||
return errorMessage || `${operation} failed: HTTP ${status}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an error is a network-related error
|
|
||||||
*
|
|
||||||
* @param error - The error to check
|
|
||||||
* @returns True if the error is network-related
|
|
||||||
*/
|
|
||||||
export function isNetworkError(error: unknown): boolean {
|
|
||||||
if (error instanceof AxiosError) {
|
|
||||||
return !error.response && !error.request;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof Error) {
|
|
||||||
const message = error.message.toLowerCase();
|
|
||||||
return (
|
|
||||||
message.includes("network") ||
|
|
||||||
message.includes("timeout") ||
|
|
||||||
message.includes("connection") ||
|
|
||||||
message.includes("fetch")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an error is a timeout error
|
|
||||||
*
|
|
||||||
* @param error - The error to check
|
|
||||||
* @returns True if the error is a timeout
|
|
||||||
*/
|
|
||||||
export function isTimeoutError(error: unknown): boolean {
|
|
||||||
if (error instanceof AxiosError) {
|
|
||||||
return (
|
|
||||||
error.code === "ECONNABORTED" ||
|
|
||||||
error.message.toLowerCase().includes("timeout")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof Error) {
|
|
||||||
return error.message.toLowerCase().includes("timeout");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create standardized error context for components
|
|
||||||
*
|
|
||||||
* @param component - Component name
|
|
||||||
* @param operation - Operation being performed
|
|
||||||
* @param additionalContext - Additional context information
|
|
||||||
* @returns Standardized error context
|
|
||||||
*/
|
|
||||||
export function createErrorContext(
|
|
||||||
component: string,
|
|
||||||
operation: string,
|
|
||||||
additionalContext: Record<string, unknown> = {},
|
|
||||||
): ErrorContext {
|
|
||||||
return {
|
|
||||||
component,
|
|
||||||
operation,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
...additionalContext,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,482 +0,0 @@
|
|||||||
/**
|
|
||||||
* Performance Optimizer
|
|
||||||
*
|
|
||||||
* Provides utilities for optimizing API calls, database queries, and component
|
|
||||||
* rendering to improve TimeSafari application performance.
|
|
||||||
*
|
|
||||||
* @author Matthew Raymer
|
|
||||||
* @since 2025-08-25
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { logger } from "./logger";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Batch operation configuration
|
|
||||||
*/
|
|
||||||
export interface BatchConfig {
|
|
||||||
maxBatchSize: number;
|
|
||||||
maxWaitTime: number;
|
|
||||||
retryAttempts: number;
|
|
||||||
retryDelay: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default batch configuration
|
|
||||||
*/
|
|
||||||
export const DEFAULT_BATCH_CONFIG: BatchConfig = {
|
|
||||||
maxBatchSize: 10,
|
|
||||||
maxWaitTime: 100, // milliseconds
|
|
||||||
retryAttempts: 3,
|
|
||||||
retryDelay: 1000, // milliseconds
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Batched operation item
|
|
||||||
*/
|
|
||||||
export interface BatchItem<T, R> {
|
|
||||||
id: string;
|
|
||||||
data: T;
|
|
||||||
resolve: (value: R) => void;
|
|
||||||
reject: (error: Error) => void;
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Batch processor for API operations
|
|
||||||
*
|
|
||||||
* Groups multiple similar operations into batches to reduce
|
|
||||||
* the number of API calls and improve performance.
|
|
||||||
*/
|
|
||||||
export class BatchProcessor<T, R> {
|
|
||||||
private items: BatchItem<T, R>[] = [];
|
|
||||||
private timer: NodeJS.Timeout | null = null;
|
|
||||||
private processing = false;
|
|
||||||
private config: BatchConfig;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private batchHandler: (items: T[]) => Promise<R[]>,
|
|
||||||
private itemIdExtractor: (item: T) => string,
|
|
||||||
config: Partial<BatchConfig> = {},
|
|
||||||
) {
|
|
||||||
this.config = { ...DEFAULT_BATCH_CONFIG, ...config };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an item to the batch
|
|
||||||
*
|
|
||||||
* @param data - Data to process
|
|
||||||
* @returns Promise that resolves when the item is processed
|
|
||||||
*/
|
|
||||||
async add(data: T): Promise<R> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const item: BatchItem<T, R> = {
|
|
||||||
id: this.itemIdExtractor(data),
|
|
||||||
data,
|
|
||||||
resolve,
|
|
||||||
reject,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.items.push(item);
|
|
||||||
|
|
||||||
// Start timer if this is the first item
|
|
||||||
if (this.items.length === 1) {
|
|
||||||
this.startTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process immediately if batch is full
|
|
||||||
if (this.items.length >= this.config.maxBatchSize) {
|
|
||||||
this.processBatch();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the batch timer
|
|
||||||
*/
|
|
||||||
private startTimer(): void {
|
|
||||||
if (this.timer) {
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.timer = setTimeout(() => {
|
|
||||||
this.processBatch();
|
|
||||||
}, this.config.maxWaitTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the current batch
|
|
||||||
*/
|
|
||||||
private async processBatch(): Promise<void> {
|
|
||||||
if (this.processing || this.items.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.processing = true;
|
|
||||||
|
|
||||||
// Clear timer
|
|
||||||
if (this.timer) {
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current batch
|
|
||||||
const currentItems = [...this.items];
|
|
||||||
this.items = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
logger.debug("[BatchProcessor] 🔄 Processing batch:", {
|
|
||||||
batchSize: currentItems.length,
|
|
||||||
itemIds: currentItems.map((item) => item.id),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process batch
|
|
||||||
const results = await this.batchHandler(
|
|
||||||
currentItems.map((item) => item.data),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Map results back to items
|
|
||||||
const resultMap = new Map<string, R>();
|
|
||||||
results.forEach((result, index) => {
|
|
||||||
const item = currentItems[index];
|
|
||||||
if (item) {
|
|
||||||
resultMap.set(item.id, result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Resolve promises
|
|
||||||
currentItems.forEach((item) => {
|
|
||||||
const result = resultMap.get(item.id);
|
|
||||||
if (result !== undefined) {
|
|
||||||
item.resolve(result);
|
|
||||||
} else {
|
|
||||||
item.reject(new Error(`No result found for item ${item.id}`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.debug("[BatchProcessor] ✅ Batch processed successfully:", {
|
|
||||||
batchSize: currentItems.length,
|
|
||||||
resultsCount: results.length,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("[BatchProcessor] ❌ Batch processing failed:", {
|
|
||||||
batchSize: currentItems.length,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reject all items in the batch
|
|
||||||
currentItems.forEach((item) => {
|
|
||||||
item.reject(error instanceof Error ? error : new Error(String(error)));
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
this.processing = false;
|
|
||||||
|
|
||||||
// Start timer for remaining items if any
|
|
||||||
if (this.items.length > 0) {
|
|
||||||
this.startTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current batch status
|
|
||||||
*/
|
|
||||||
getStatus(): {
|
|
||||||
pendingItems: number;
|
|
||||||
isProcessing: boolean;
|
|
||||||
hasTimer: boolean;
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
pendingItems: this.items.length,
|
|
||||||
isProcessing: this.processing,
|
|
||||||
hasTimer: this.timer !== null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all pending items
|
|
||||||
*/
|
|
||||||
clear(): void {
|
|
||||||
if (this.timer) {
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reject all pending items
|
|
||||||
this.items.forEach((item) => {
|
|
||||||
item.reject(new Error("Batch processor cleared"));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.items = [];
|
|
||||||
this.processing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database query optimizer
|
|
||||||
*
|
|
||||||
* Provides utilities for optimizing database queries and reducing
|
|
||||||
* the number of database operations.
|
|
||||||
*/
|
|
||||||
export class DatabaseOptimizer {
|
|
||||||
/**
|
|
||||||
* Batch multiple SELECT queries into a single query
|
|
||||||
*
|
|
||||||
* @param baseQuery - Base SELECT query
|
|
||||||
* @param ids - Array of IDs to query
|
|
||||||
* @param idColumn - Name of the ID column
|
|
||||||
* @returns Optimized query string
|
|
||||||
*/
|
|
||||||
static batchSelectQuery(
|
|
||||||
baseQuery: string,
|
|
||||||
ids: (string | number)[],
|
|
||||||
idColumn: string,
|
|
||||||
): string {
|
|
||||||
if (ids.length === 0) {
|
|
||||||
return baseQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ids.length === 1) {
|
|
||||||
return `${baseQuery} WHERE ${idColumn} = ?`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const placeholders = ids.map(() => "?").join(", ");
|
|
||||||
return `${baseQuery} WHERE ${idColumn} IN (${placeholders})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a query plan for multiple operations
|
|
||||||
*
|
|
||||||
* @param operations - Array of database operations
|
|
||||||
* @returns Optimized query plan
|
|
||||||
*/
|
|
||||||
static createQueryPlan(
|
|
||||||
operations: Array<{
|
|
||||||
type: "SELECT" | "INSERT" | "UPDATE" | "DELETE";
|
|
||||||
table: string;
|
|
||||||
priority: number;
|
|
||||||
}>,
|
|
||||||
): Array<{
|
|
||||||
type: "SELECT" | "INSERT" | "UPDATE" | "DELETE";
|
|
||||||
table: string;
|
|
||||||
priority: number;
|
|
||||||
batchable: boolean;
|
|
||||||
}> {
|
|
||||||
return operations
|
|
||||||
.map((op) => ({
|
|
||||||
...op,
|
|
||||||
batchable: op.type === "SELECT" || op.type === "INSERT",
|
|
||||||
}))
|
|
||||||
.sort((a, b) => {
|
|
||||||
// Sort by priority first, then by type
|
|
||||||
if (a.priority !== b.priority) {
|
|
||||||
return b.priority - a.priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SELECT operations first, then INSERT, UPDATE, DELETE
|
|
||||||
const typeOrder = { SELECT: 0, INSERT: 1, UPDATE: 2, DELETE: 3 };
|
|
||||||
return typeOrder[a.type] - typeOrder[b.type];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component rendering optimizer
|
|
||||||
*
|
|
||||||
* Provides utilities for optimizing Vue component rendering
|
|
||||||
* and reducing unnecessary re-renders.
|
|
||||||
*/
|
|
||||||
export class ComponentOptimizer {
|
|
||||||
/**
|
|
||||||
* Debounce function calls to prevent excessive execution
|
|
||||||
*
|
|
||||||
* @param func - Function to debounce
|
|
||||||
* @param wait - Wait time in milliseconds
|
|
||||||
* @returns Debounced function
|
|
||||||
*/
|
|
||||||
static debounce<T extends (...args: unknown[]) => unknown>(
|
|
||||||
func: T,
|
|
||||||
wait: number,
|
|
||||||
): (...args: Parameters<T>) => void {
|
|
||||||
let timeout: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
return (...args: Parameters<T>) => {
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
func(...args);
|
|
||||||
}, wait);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throttle function calls to limit execution frequency
|
|
||||||
*
|
|
||||||
* @param func - Function to throttle
|
|
||||||
* @param limit - Time limit in milliseconds
|
|
||||||
* @returns Throttled function
|
|
||||||
*/
|
|
||||||
static throttle<T extends (...args: unknown[]) => unknown>(
|
|
||||||
func: T,
|
|
||||||
limit: number,
|
|
||||||
): (...args: Parameters<T>) => void {
|
|
||||||
let inThrottle = false;
|
|
||||||
|
|
||||||
return (...args: Parameters<T>) => {
|
|
||||||
if (!inThrottle) {
|
|
||||||
func(...args);
|
|
||||||
inThrottle = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
inThrottle = false;
|
|
||||||
}, limit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Memoize function results to avoid redundant computation
|
|
||||||
*
|
|
||||||
* @param func - Function to memoize
|
|
||||||
* @param keyGenerator - Function to generate cache keys
|
|
||||||
* @returns Memoized function
|
|
||||||
*/
|
|
||||||
static memoize<T extends (...args: unknown[]) => unknown, K>(
|
|
||||||
func: T,
|
|
||||||
keyGenerator: (...args: Parameters<T>) => K,
|
|
||||||
): T {
|
|
||||||
const cache = new Map<K, unknown>();
|
|
||||||
|
|
||||||
return ((...args: Parameters<T>) => {
|
|
||||||
const key = keyGenerator(...args);
|
|
||||||
|
|
||||||
if (cache.has(key)) {
|
|
||||||
return cache.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = func(...args);
|
|
||||||
cache.set(key, result);
|
|
||||||
return result;
|
|
||||||
}) as T;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performance monitoring utility
|
|
||||||
*
|
|
||||||
* Tracks and reports performance metrics for optimization analysis.
|
|
||||||
*/
|
|
||||||
export class PerformanceMonitor {
|
|
||||||
private static instance: PerformanceMonitor;
|
|
||||||
private metrics = new Map<
|
|
||||||
string,
|
|
||||||
Array<{ timestamp: number; duration: number }>
|
|
||||||
>();
|
|
||||||
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get singleton instance
|
|
||||||
*/
|
|
||||||
static getInstance(): PerformanceMonitor {
|
|
||||||
if (!PerformanceMonitor.instance) {
|
|
||||||
PerformanceMonitor.instance = new PerformanceMonitor();
|
|
||||||
}
|
|
||||||
return PerformanceMonitor.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start timing an operation
|
|
||||||
*
|
|
||||||
* @param operationName - Name of the operation
|
|
||||||
* @returns Function to call when operation completes
|
|
||||||
*/
|
|
||||||
startTiming(operationName: string): () => void {
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const duration = performance.now() - startTime;
|
|
||||||
this.recordMetric(operationName, duration);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record a performance metric
|
|
||||||
*
|
|
||||||
* @param operationName - Name of the operation
|
|
||||||
* @param duration - Duration in milliseconds
|
|
||||||
*/
|
|
||||||
private recordMetric(operationName: string, duration: number): void {
|
|
||||||
if (!this.metrics.has(operationName)) {
|
|
||||||
this.metrics.set(operationName, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
const operationMetrics = this.metrics.get(operationName)!;
|
|
||||||
operationMetrics.push({
|
|
||||||
timestamp: Date.now(),
|
|
||||||
duration,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Keep only last 100 metrics per operation
|
|
||||||
if (operationMetrics.length > 100) {
|
|
||||||
operationMetrics.splice(0, operationMetrics.length - 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get performance summary for an operation
|
|
||||||
*
|
|
||||||
* @param operationName - Name of the operation
|
|
||||||
* @returns Performance statistics
|
|
||||||
*/
|
|
||||||
getPerformanceSummary(operationName: string): {
|
|
||||||
count: number;
|
|
||||||
average: number;
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
recentAverage: number;
|
|
||||||
} | null {
|
|
||||||
const metrics = this.metrics.get(operationName);
|
|
||||||
if (!metrics || metrics.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const durations = metrics.map((m) => m.duration);
|
|
||||||
const recentMetrics = metrics.slice(-10); // Last 10 metrics
|
|
||||||
|
|
||||||
return {
|
|
||||||
count: metrics.length,
|
|
||||||
average: durations.reduce((a, b) => a + b, 0) / durations.length,
|
|
||||||
min: Math.min(...durations),
|
|
||||||
max: Math.max(...durations),
|
|
||||||
recentAverage:
|
|
||||||
recentMetrics.reduce((a, b) => a + b.duration, 0) /
|
|
||||||
recentMetrics.length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all performance metrics
|
|
||||||
*/
|
|
||||||
getAllMetrics(): Map<string, Array<{ timestamp: number; duration: number }>> {
|
|
||||||
return new Map(this.metrics);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all performance metrics
|
|
||||||
*/
|
|
||||||
clearMetrics(): void {
|
|
||||||
this.metrics.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience function to get the performance monitor
|
|
||||||
*/
|
|
||||||
export const getPerformanceMonitor = (): PerformanceMonitor => {
|
|
||||||
return PerformanceMonitor.getInstance();
|
|
||||||
};
|
|
||||||
+41
-443
@@ -58,10 +58,8 @@
|
|||||||
v-if="!isRegistered"
|
v-if="!isRegistered"
|
||||||
:passkeys-enabled="PASSKEYS_ENABLED"
|
:passkeys-enabled="PASSKEYS_ENABLED"
|
||||||
:given-name="givenName"
|
:given-name="givenName"
|
||||||
:message="
|
message="Before you can publicly announce a new project or time commitment,
|
||||||
`Before you can publicly announce a new project or time commitment, ` +
|
a friend needs to register you."
|
||||||
`a friend needs to register you.`
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
@@ -754,7 +752,6 @@ import "leaflet/dist/leaflet.css";
|
|||||||
|
|
||||||
import { Buffer } from "buffer/";
|
import { Buffer } from "buffer/";
|
||||||
import "dexie-export-import";
|
import "dexie-export-import";
|
||||||
|
|
||||||
// @ts-expect-error - they aren't exporting it but it's there
|
// @ts-expect-error - they aren't exporting it but it's there
|
||||||
import { ImportProgress } from "dexie-export-import";
|
import { ImportProgress } from "dexie-export-import";
|
||||||
import { LeafletMouseEvent } from "leaflet";
|
import { LeafletMouseEvent } from "leaflet";
|
||||||
@@ -816,13 +813,11 @@ import {
|
|||||||
isApiError,
|
isApiError,
|
||||||
ImportContent,
|
ImportContent,
|
||||||
} from "@/interfaces/accountView";
|
} from "@/interfaces/accountView";
|
||||||
// Profile data interface (inlined from ProfileService)
|
import {
|
||||||
interface ProfileData {
|
ProfileService,
|
||||||
description: string;
|
createProfileService,
|
||||||
latitude: number;
|
ProfileData,
|
||||||
longitude: number;
|
} from "@/services/ProfileService";
|
||||||
includeLocation: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputImportFileNameRef = ref<Blob>();
|
const inputImportFileNameRef = ref<Blob>();
|
||||||
|
|
||||||
@@ -921,6 +916,7 @@ export default class AccountViewView extends Vue {
|
|||||||
imageLimits: ImageRateLimits | null = null;
|
imageLimits: ImageRateLimits | null = null;
|
||||||
limitsMessage: string = "";
|
limitsMessage: string = "";
|
||||||
|
|
||||||
|
private profileService!: ProfileService;
|
||||||
private notify!: ReturnType<typeof createNotifyHelpers>;
|
private notify!: ReturnType<typeof createNotifyHelpers>;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -930,10 +926,7 @@ export default class AccountViewView extends Vue {
|
|||||||
// This prevents the "Cannot read properties of undefined (reading 'Default')" error
|
// This prevents the "Cannot read properties of undefined (reading 'Default')" error
|
||||||
if (L.Icon.Default) {
|
if (L.Icon.Default) {
|
||||||
// Type-safe way to handle Leaflet icon prototype
|
// Type-safe way to handle Leaflet icon prototype
|
||||||
const iconDefault = L.Icon.Default.prototype as unknown as Record<
|
const iconDefault = L.Icon.Default.prototype as Record<string, unknown>;
|
||||||
string,
|
|
||||||
unknown
|
|
||||||
>;
|
|
||||||
if ("_getIconUrl" in iconDefault) {
|
if ("_getIconUrl" in iconDefault) {
|
||||||
delete iconDefault._getIconUrl;
|
delete iconDefault._getIconUrl;
|
||||||
}
|
}
|
||||||
@@ -955,21 +948,17 @@ export default class AccountViewView extends Vue {
|
|||||||
* @throws Will display specific messages to the user based on different errors.
|
* @throws Will display specific messages to the user based on different errors.
|
||||||
*/
|
*/
|
||||||
async mounted(): Promise<void> {
|
async mounted(): Promise<void> {
|
||||||
|
this.profileService = createProfileService(
|
||||||
|
this.axios,
|
||||||
|
this.partnerApiServer,
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await this.initializeState();
|
await this.initializeState();
|
||||||
await this.processIdentity();
|
await this.processIdentity();
|
||||||
|
|
||||||
// Profile service logic now inlined - no need for external service
|
|
||||||
logger.debug(
|
|
||||||
"[AccountViewView] Profile logic ready with partnerApiServer:",
|
|
||||||
{
|
|
||||||
partnerApiServer: this.partnerApiServer,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.isRegistered) {
|
if (this.isRegistered) {
|
||||||
try {
|
try {
|
||||||
const profile = await this.loadProfile(this.activeDid);
|
const profile = await this.profileService.loadProfile(this.activeDid);
|
||||||
if (profile) {
|
if (profile) {
|
||||||
this.userProfileDesc = profile.description;
|
this.userProfileDesc = profile.description;
|
||||||
this.userProfileLatitude = profile.latitude;
|
this.userProfileLatitude = profile.latitude;
|
||||||
@@ -1050,7 +1039,8 @@ export default class AccountViewView extends Vue {
|
|||||||
// Then get the account-specific settings
|
// Then get the account-specific settings
|
||||||
const settings: AccountSettings = await this.$accountSettings();
|
const settings: AccountSettings = await this.$accountSettings();
|
||||||
|
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.apiServerInput = settings.apiServer || "";
|
this.apiServerInput = settings.apiServer || "";
|
||||||
this.givenName =
|
this.givenName =
|
||||||
@@ -1422,24 +1412,21 @@ export default class AccountViewView extends Vue {
|
|||||||
|
|
||||||
async checkLimits(): Promise<void> {
|
async checkLimits(): Promise<void> {
|
||||||
this.loadingLimits = true;
|
this.loadingLimits = true;
|
||||||
const did = this.activeDid;
|
|
||||||
if (!did) {
|
|
||||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IDENTIFIER;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const did = this.activeDid;
|
||||||
|
|
||||||
|
if (!did) {
|
||||||
|
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IDENTIFIER;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.$saveUserSettings(did, {
|
await this.$saveUserSettings(did, {
|
||||||
apiServer: this.apiServer,
|
apiServer: this.apiServer,
|
||||||
partnerApiServer: this.partnerApiServer,
|
partnerApiServer: this.partnerApiServer,
|
||||||
webPushServer: this.webPushServer,
|
webPushServer: this.webPushServer,
|
||||||
});
|
});
|
||||||
|
|
||||||
const imageResp = await fetchImageRateLimits(
|
const imageResp = await fetchImageRateLimits(this.axios, did);
|
||||||
this.axios,
|
|
||||||
did,
|
|
||||||
this.DEFAULT_IMAGE_API_SERVER,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (imageResp.status === 200) {
|
if (imageResp.status === 200) {
|
||||||
this.imageLimits = imageResp.data;
|
this.imageLimits = imageResp.data;
|
||||||
@@ -1465,26 +1452,7 @@ export default class AccountViewView extends Vue {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.limitsMessage =
|
this.limitsMessage =
|
||||||
ACCOUNT_VIEW_CONSTANTS.LIMITS.ERROR_RETRIEVING_LIMITS;
|
ACCOUNT_VIEW_CONSTANTS.LIMITS.ERROR_RETRIEVING_LIMITS;
|
||||||
|
logger.error("Error retrieving limits: ", error);
|
||||||
// Enhanced error logging with server context
|
|
||||||
const axiosError = error as {
|
|
||||||
response?: {
|
|
||||||
data?: { error?: { code?: string; message?: string } };
|
|
||||||
status?: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
logger.error("[Server Limits] Error retrieving limits:", {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
did: did,
|
|
||||||
apiServer: this.apiServer,
|
|
||||||
partnerApiServer: this.partnerApiServer,
|
|
||||||
errorCode: axiosError?.response?.data?.error?.code,
|
|
||||||
errorMessage: axiosError?.response?.data?.error?.message,
|
|
||||||
httpStatus: axiosError?.response?.status,
|
|
||||||
needsUserMigration: true,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// this.notify.error(this.limitsMessage, TIMEOUTS.STANDARD);
|
// this.notify.error(this.limitsMessage, TIMEOUTS.STANDARD);
|
||||||
} finally {
|
} finally {
|
||||||
this.loadingLimits = false;
|
this.loadingLimits = false;
|
||||||
@@ -1492,70 +1460,24 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onClickSaveApiServer(): Promise<void> {
|
async onClickSaveApiServer(): Promise<void> {
|
||||||
// Enhanced diagnostic logging for claim URL changes
|
|
||||||
const previousApiServer = this.apiServer;
|
|
||||||
const newApiServer = this.apiServerInput;
|
|
||||||
|
|
||||||
logger.debug("[Server Switching] Claim URL change initiated:", {
|
|
||||||
did: this.activeDid,
|
|
||||||
previousServer: previousApiServer,
|
|
||||||
newServer: newApiServer,
|
|
||||||
changeType: "apiServer",
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.$saveSettings({
|
await this.$saveSettings({
|
||||||
apiServer: newApiServer,
|
apiServer: this.apiServerInput,
|
||||||
});
|
});
|
||||||
this.apiServer = newApiServer;
|
this.apiServer = this.apiServerInput;
|
||||||
|
|
||||||
// Add this line to save to user-specific settings
|
// Add this line to save to user-specific settings
|
||||||
await this.$saveUserSettings(this.activeDid, {
|
await this.$saveUserSettings(this.activeDid, {
|
||||||
apiServer: this.apiServer,
|
apiServer: this.apiServer,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log successful server switch
|
|
||||||
logger.debug("[Server Switching] Claim URL change completed:", {
|
|
||||||
did: this.activeDid,
|
|
||||||
previousServer: previousApiServer,
|
|
||||||
newServer: newApiServer,
|
|
||||||
changeType: "apiServer",
|
|
||||||
settingsSaved: true,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClickSavePartnerServer(): Promise<void> {
|
async onClickSavePartnerServer(): Promise<void> {
|
||||||
// Enhanced diagnostic logging for partner server changes
|
|
||||||
const previousPartnerServer = this.partnerApiServer;
|
|
||||||
const newPartnerServer = this.partnerApiServerInput;
|
|
||||||
|
|
||||||
logger.debug("[Server Switching] Partner server change initiated:", {
|
|
||||||
did: this.activeDid,
|
|
||||||
previousServer: previousPartnerServer,
|
|
||||||
newServer: newPartnerServer,
|
|
||||||
changeType: "partnerApiServer",
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.$saveSettings({
|
await this.$saveSettings({
|
||||||
partnerApiServer: newPartnerServer,
|
partnerApiServer: this.partnerApiServerInput,
|
||||||
});
|
});
|
||||||
this.partnerApiServer = newPartnerServer;
|
this.partnerApiServer = this.partnerApiServerInput;
|
||||||
|
|
||||||
await this.$saveUserSettings(this.activeDid, {
|
await this.$saveUserSettings(this.activeDid, {
|
||||||
partnerApiServer: this.partnerApiServer,
|
partnerApiServer: this.partnerApiServer,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log successful partner server switch
|
|
||||||
logger.debug("[Server Switching] Partner server change completed:", {
|
|
||||||
did: this.activeDid,
|
|
||||||
previousServer: previousPartnerServer,
|
|
||||||
newServer: newPartnerServer,
|
|
||||||
changeType: "partnerApiServer",
|
|
||||||
settingsSaved: true,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClickSavePushServer(): Promise<void> {
|
async onClickSavePushServer(): Promise<void> {
|
||||||
@@ -1629,6 +1551,7 @@ export default class AccountViewView extends Vue {
|
|||||||
|
|
||||||
onMapReady(map: L.Map): void {
|
onMapReady(map: L.Map): void {
|
||||||
try {
|
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
|
// doing this here instead of on the l-map element avoids a recentering after a drag then zoom at startup
|
||||||
const zoom =
|
const zoom =
|
||||||
this.userProfileLatitude && this.userProfileLongitude ? 12 : 2;
|
this.userProfileLatitude && this.userProfileLongitude ? 12 : 2;
|
||||||
@@ -1657,15 +1580,19 @@ export default class AccountViewView extends Vue {
|
|||||||
// Try to set map ready after component is mounted
|
// Try to set map ready after component is mounted
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.isMapReady = true;
|
this.isMapReady = true;
|
||||||
|
logger.debug("Map ready set to true after mounted");
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback method to handle map initialization failures
|
// Fallback method to handle map initialization failures
|
||||||
private handleMapInitFailure(): void {
|
private handleMapInitFailure(): void {
|
||||||
|
logger.debug("Starting map initialization timeout (5 seconds)");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this.isMapReady) {
|
if (!this.isMapReady) {
|
||||||
logger.warn("Map failed to initialize, forcing ready state");
|
logger.warn("Map failed to initialize, forcing ready state");
|
||||||
this.isMapReady = true;
|
this.isMapReady = true;
|
||||||
|
} else {
|
||||||
|
logger.debug("Map initialized successfully, timeout not needed");
|
||||||
}
|
}
|
||||||
}, 5000); // 5 second timeout
|
}, 5000); // 5 second timeout
|
||||||
}
|
}
|
||||||
@@ -1689,7 +1616,7 @@ export default class AccountViewView extends Vue {
|
|||||||
|
|
||||||
logger.debug("Saving profile data:", profileData);
|
logger.debug("Saving profile data:", profileData);
|
||||||
|
|
||||||
const success = await this.saveProfileToServer(
|
const success = await this.profileService.saveProfile(
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
profileData,
|
profileData,
|
||||||
);
|
);
|
||||||
@@ -1708,7 +1635,7 @@ export default class AccountViewView extends Vue {
|
|||||||
|
|
||||||
toggleUserProfileLocation(): void {
|
toggleUserProfileLocation(): void {
|
||||||
try {
|
try {
|
||||||
const updated = this.toggleProfileLocation({
|
const updated = this.profileService.toggleProfileLocation({
|
||||||
description: this.userProfileDesc,
|
description: this.userProfileDesc,
|
||||||
latitude: this.userProfileLatitude,
|
latitude: this.userProfileLatitude,
|
||||||
longitude: this.userProfileLongitude,
|
longitude: this.userProfileLongitude,
|
||||||
@@ -1753,7 +1680,8 @@ export default class AccountViewView extends Vue {
|
|||||||
|
|
||||||
async deleteProfile(): Promise<void> {
|
async deleteProfile(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const success = await this.deleteProfileFromServer(this.activeDid);
|
logger.debug("Attempting to delete profile for DID:", this.activeDid);
|
||||||
|
const success = await this.profileService.deleteProfile(this.activeDid);
|
||||||
if (success) {
|
if (success) {
|
||||||
this.notify.success(ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_DELETED);
|
this.notify.success(ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_DELETED);
|
||||||
this.userProfileDesc = "";
|
this.userProfileDesc = "";
|
||||||
@@ -1761,6 +1689,7 @@ export default class AccountViewView extends Vue {
|
|||||||
this.userProfileLongitude = 0;
|
this.userProfileLongitude = 0;
|
||||||
this.includeUserProfileLocation = false;
|
this.includeUserProfileLocation = false;
|
||||||
this.isMapReady = false; // Reset map state
|
this.isMapReady = false; // Reset map state
|
||||||
|
logger.debug("Profile deleted successfully, UI state reset");
|
||||||
} else {
|
} else {
|
||||||
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
|
||||||
}
|
}
|
||||||
@@ -1806,6 +1735,7 @@ export default class AccountViewView extends Vue {
|
|||||||
this.isMapReady = false;
|
this.isMapReady = false;
|
||||||
this.userProfileLatitude = 0;
|
this.userProfileLatitude = 0;
|
||||||
this.userProfileLongitude = 0;
|
this.userProfileLongitude = 0;
|
||||||
|
logger.debug("Location unchecked, map state reset");
|
||||||
} else {
|
} else {
|
||||||
// Location checkbox was checked, start map initialization timeout
|
// Location checkbox was checked, start map initialization timeout
|
||||||
this.isMapReady = false;
|
this.isMapReady = false;
|
||||||
@@ -1814,6 +1744,7 @@ export default class AccountViewView extends Vue {
|
|||||||
// Try to set map ready after a short delay to allow Vue to render
|
// Try to set map ready after a short delay to allow Vue to render
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this.isMapReady) {
|
if (!this.isMapReady) {
|
||||||
|
logger.debug("Setting map ready after timeout");
|
||||||
this.isMapReady = true;
|
this.isMapReady = true;
|
||||||
}
|
}
|
||||||
}, 1000); // 1 second delay
|
}, 1000); // 1 second delay
|
||||||
@@ -1866,338 +1797,5 @@ export default class AccountViewView extends Vue {
|
|||||||
onRecheckLimits() {
|
onRecheckLimits() {
|
||||||
this.checkLimits();
|
this.checkLimits();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inlined profile methods (previously in ProfileService)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load user profile from the partner API
|
|
||||||
*/
|
|
||||||
private async loadProfile(did: string): Promise<ProfileData | null> {
|
|
||||||
try {
|
|
||||||
const requestId = `profile_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
||||||
|
|
||||||
logger.debug("[AccountViewView] Loading profile:", {
|
|
||||||
requestId,
|
|
||||||
did,
|
|
||||||
partnerApiServer: this.partnerApiServer,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get authentication headers
|
|
||||||
const headers = await getHeaders(did);
|
|
||||||
|
|
||||||
const fullUrl = `${this.partnerApiServer}/api/partner/userProfileForIssuer/${did}`;
|
|
||||||
|
|
||||||
logger.debug("[AccountViewView] Making API request:", {
|
|
||||||
requestId,
|
|
||||||
did,
|
|
||||||
fullUrl,
|
|
||||||
hasAuthHeader: !!headers.Authorization,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await this.axios.get(fullUrl, { headers });
|
|
||||||
|
|
||||||
logger.debug("[AccountViewView] Profile loaded successfully:", {
|
|
||||||
requestId,
|
|
||||||
status: response.status,
|
|
||||||
hasData: !!response.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data && response.data.data) {
|
|
||||||
const profileData = response.data.data;
|
|
||||||
logger.debug("[AccountViewView] Parsing profile data:", {
|
|
||||||
requestId,
|
|
||||||
locLat: profileData.locLat,
|
|
||||||
locLon: profileData.locLon,
|
|
||||||
description: profileData.description,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
description: profileData.description || "",
|
|
||||||
latitude: profileData.locLat || 0,
|
|
||||||
longitude: profileData.locLon || 0,
|
|
||||||
includeLocation: !!(profileData.locLat && profileData.locLon),
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.debug("[AccountViewView] Parsed profile result:", {
|
|
||||||
requestId,
|
|
||||||
result,
|
|
||||||
hasLocation: result.includeLocation,
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
logger.debug("[AccountViewView] No profile data found in response:", {
|
|
||||||
requestId,
|
|
||||||
hasData: !!response.data,
|
|
||||||
hasDataData: !!(response.data && response.data.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
// Handle specific HTTP status codes cleanly to suppress console spam
|
|
||||||
if (error && typeof error === "object" && "response" in error) {
|
|
||||||
const axiosError = error as { response?: { status?: number } };
|
|
||||||
|
|
||||||
if (axiosError.response?.status === 404) {
|
|
||||||
logger.info(
|
|
||||||
"[Profile] No profile found - this is normal for new users",
|
|
||||||
{
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
status: 404,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (axiosError.response?.status === 400) {
|
|
||||||
logger.warn("[Profile] Bad request - user may not be registered", {
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
status: 400,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
axiosError.response?.status === 401 ||
|
|
||||||
axiosError.response?.status === 403
|
|
||||||
) {
|
|
||||||
logger.warn("[Profile] Authentication/authorization issue", {
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
status: axiosError.response.status,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only log full errors for unexpected issues (5xx, network errors, etc.)
|
|
||||||
logger.error("[Profile] Unexpected error loading profile:", {
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
throw new Error("Failed to load profile");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save user profile to the partner API
|
|
||||||
*/
|
|
||||||
private async saveProfileToServer(
|
|
||||||
did: string,
|
|
||||||
profileData: ProfileData,
|
|
||||||
): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const requestId = `profile_save_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
||||||
|
|
||||||
logger.debug("[AccountViewView] Saving profile:", {
|
|
||||||
requestId,
|
|
||||||
did,
|
|
||||||
profileData,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get authentication headers
|
|
||||||
const headers = await getHeaders(did);
|
|
||||||
|
|
||||||
// Prepare payload in the format expected by the partner API
|
|
||||||
const payload = {
|
|
||||||
description: profileData.description,
|
|
||||||
issuerDid: did,
|
|
||||||
...(profileData.includeLocation &&
|
|
||||||
profileData.latitude &&
|
|
||||||
profileData.longitude
|
|
||||||
? {
|
|
||||||
locLat: profileData.latitude,
|
|
||||||
locLon: profileData.longitude,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.debug("[AccountViewView] Sending payload to server:", {
|
|
||||||
requestId,
|
|
||||||
payload,
|
|
||||||
hasLocation: profileData.includeLocation,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await this.axios.post(
|
|
||||||
`${this.partnerApiServer}/api/partner/userProfile`,
|
|
||||||
payload,
|
|
||||||
{ headers },
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.debug("[AccountViewView] Profile saved successfully:", {
|
|
||||||
requestId,
|
|
||||||
status: response.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
// Handle specific HTTP status codes cleanly to suppress console spam
|
|
||||||
if (error && typeof error === "object" && "response" in error) {
|
|
||||||
const axiosError = error as { response?: { status?: number } };
|
|
||||||
|
|
||||||
if (axiosError.response?.status === 400) {
|
|
||||||
logger.warn("[Profile] Bad request saving profile", {
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
status: 400,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
throw new Error("Invalid profile data");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
axiosError.response?.status === 401 ||
|
|
||||||
axiosError.response?.status === 403
|
|
||||||
) {
|
|
||||||
logger.warn(
|
|
||||||
"[Profile] Authentication/authorization issue saving profile",
|
|
||||||
{
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
status: axiosError.response.status,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
throw new Error("Authentication required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (axiosError.response?.status === 409) {
|
|
||||||
logger.warn("[Profile] Profile conflict - may already exist", {
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
status: 409,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
throw new Error("Profile already exists");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only log full errors for unexpected issues
|
|
||||||
logger.error("[Profile] Unexpected error saving profile:", {
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
throw new Error("Failed to save profile");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle profile location visibility
|
|
||||||
*/
|
|
||||||
private toggleProfileLocation(profileData: ProfileData): ProfileData {
|
|
||||||
const includeLocation = !profileData.includeLocation;
|
|
||||||
return {
|
|
||||||
...profileData,
|
|
||||||
latitude: includeLocation ? profileData.latitude : 0,
|
|
||||||
longitude: includeLocation ? profileData.longitude : 0,
|
|
||||||
includeLocation,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear profile location
|
|
||||||
*/
|
|
||||||
private clearProfileLocation(profileData: ProfileData): ProfileData {
|
|
||||||
return {
|
|
||||||
...profileData,
|
|
||||||
latitude: 0,
|
|
||||||
longitude: 0,
|
|
||||||
includeLocation: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get default profile data
|
|
||||||
*/
|
|
||||||
private getDefaultProfile(): ProfileData {
|
|
||||||
return {
|
|
||||||
description: "",
|
|
||||||
latitude: 0,
|
|
||||||
longitude: 0,
|
|
||||||
includeLocation: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete user profile from the partner API
|
|
||||||
*/
|
|
||||||
private async deleteProfileFromServer(did: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const requestId = `profile_delete_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
||||||
|
|
||||||
logger.debug("[AccountViewView] Deleting profile:", {
|
|
||||||
requestId,
|
|
||||||
did,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get authentication headers
|
|
||||||
const headers = await getHeaders(did);
|
|
||||||
|
|
||||||
const response = await this.axios.delete(
|
|
||||||
`${this.partnerApiServer}/api/partner/userProfile/${did}`,
|
|
||||||
{ headers },
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.debug("[AccountViewView] Profile deleted successfully:", {
|
|
||||||
requestId,
|
|
||||||
status: response.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
// Handle specific HTTP status codes cleanly to suppress console spam
|
|
||||||
if (error && typeof error === "object" && "response" in error) {
|
|
||||||
const axiosError = error as { response?: { status?: number } };
|
|
||||||
|
|
||||||
if (axiosError.response?.status === 404) {
|
|
||||||
logger.info(
|
|
||||||
"[Profile] Profile not found for deletion - may already be deleted",
|
|
||||||
{
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
status: 404,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return true; // Consider it successful if already deleted
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
axiosError.response?.status === 401 ||
|
|
||||||
axiosError.response?.status === 403
|
|
||||||
) {
|
|
||||||
logger.warn(
|
|
||||||
"[Profile] Authentication/authorization issue deleting profile",
|
|
||||||
{
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
status: axiosError.response.status,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only log full errors for unexpected issues
|
|
||||||
logger.error("[Profile] Unexpected error deleting profile:", {
|
|
||||||
did,
|
|
||||||
server: this.partnerApiServer,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ export default class ClaimAddRawView extends Vue {
|
|||||||
*/
|
*/
|
||||||
private async initializeSettings() {
|
private async initializeSettings() {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new façade method with legacy fallback
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export default class ClaimCertificateView extends Vue {
|
|||||||
async created() {
|
async created() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
const pathParams = window.location.pathname.substring(
|
const pathParams = window.location.pathname.substring(
|
||||||
"/claim-cert/".length,
|
"/claim-cert/".length,
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ export default class ClaimReportCertificateView extends Vue {
|
|||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
const settings = await this.$settings();
|
const settings = await this.$settings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
const pathParams = window.location.pathname.substring(
|
const pathParams = window.location.pathname.substring(
|
||||||
"/claim-cert/".length,
|
"/claim-cert/".length,
|
||||||
|
|||||||
@@ -728,7 +728,8 @@ export default class ClaimView extends Vue {
|
|||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
|
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.allContacts = await this.$contacts();
|
this.allContacts = await this.$contacts();
|
||||||
|
|
||||||
|
|||||||
@@ -547,7 +547,8 @@ export default class ConfirmGiftView extends Vue {
|
|||||||
*/
|
*/
|
||||||
private async initializeSettings() {
|
private async initializeSettings() {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.allContacts = await this.$getAllContacts();
|
this.allContacts = await this.$getAllContacts();
|
||||||
this.isRegistered = settings.isRegistered || false;
|
this.isRegistered = settings.isRegistered || false;
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ import {
|
|||||||
NOTIFY_CONFIRMATION_RESTRICTION,
|
NOTIFY_CONFIRMATION_RESTRICTION,
|
||||||
} from "../constants/notifications";
|
} from "../constants/notifications";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
|
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
||||||
import { GiveSummaryRecord, GiveActionClaim } from "../interfaces";
|
import { GiveSummaryRecord, GiveActionClaim } from "../interfaces";
|
||||||
import { AgreeActionClaim } from "../interfaces/claims";
|
import { AgreeActionClaim } from "../interfaces/claims";
|
||||||
import {
|
import {
|
||||||
@@ -223,8 +223,9 @@ export default class ContactAmountssView extends Vue {
|
|||||||
const contact = await this.$getContact(contactDid);
|
const contact = await this.$getContact(contactDid);
|
||||||
this.contact = contact;
|
this.contact = contact;
|
||||||
|
|
||||||
const settings = await this.$getMasterSettings();
|
const settings = await this.$getSettings(MASTER_SETTINGS_KEY);
|
||||||
this.activeDid = settings?.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
|
|
||||||
if (this.activeDid && this.contact) {
|
if (this.activeDid && this.contact) {
|
||||||
|
|||||||
@@ -164,7 +164,8 @@ export default class ContactGiftingView extends Vue {
|
|||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
|
|
||||||
this.allContacts = await this.$getAllContacts();
|
this.allContacts = await this.$getAllContacts();
|
||||||
|
|
||||||
|
|||||||
@@ -340,7 +340,8 @@ export default class ContactImportView extends Vue {
|
|||||||
*/
|
*/
|
||||||
private async initializeSettings() {
|
private async initializeSettings() {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -265,7 +265,8 @@ export default class ContactQRScanFull extends Vue {
|
|||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.givenName = settings.firstName || "";
|
this.givenName = settings.firstName || "";
|
||||||
this.isRegistered = !!settings.isRegistered;
|
this.isRegistered = !!settings.isRegistered;
|
||||||
|
|||||||
@@ -286,7 +286,8 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.givenName = settings.firstName || "";
|
this.givenName = settings.firstName || "";
|
||||||
this.hideRegisterPromptOnNewContact =
|
this.hideRegisterPromptOnNewContact =
|
||||||
|
|||||||
@@ -294,7 +294,8 @@ export default class ContactsView extends Vue {
|
|||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.isRegistered = !!settings.isRegistered;
|
this.isRegistered = !!settings.isRegistered;
|
||||||
|
|
||||||
|
|||||||
+10
-9
@@ -71,22 +71,22 @@
|
|||||||
contactFromDid?.seesMe && contactFromDid.did !== activeDid
|
contactFromDid?.seesMe && contactFromDid.did !== activeDid
|
||||||
"
|
"
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
title="They can see your activity"
|
title="They can see you"
|
||||||
@click="confirmSetVisibility(contactFromDid, false)"
|
@click="confirmSetVisibility(contactFromDid, false)"
|
||||||
>
|
>
|
||||||
<font-awesome icon="arrow-up" class="fa-fw" />
|
|
||||||
<font-awesome icon="eye" class="fa-fw" />
|
<font-awesome icon="eye" class="fa-fw" />
|
||||||
|
<font-awesome icon="arrow-up" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else-if="
|
v-else-if="
|
||||||
!contactFromDid?.seesMe && contactFromDid?.did !== activeDid
|
!contactFromDid?.seesMe && contactFromDid?.did !== activeDid
|
||||||
"
|
"
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
title="They cannot see your activity"
|
title="They cannot see you"
|
||||||
@click="confirmSetVisibility(contactFromDid, true)"
|
@click="confirmSetVisibility(contactFromDid, true)"
|
||||||
>
|
>
|
||||||
<font-awesome icon="arrow-up" class="fa-fw" />
|
|
||||||
<font-awesome icon="eye-slash" class="fa-fw" />
|
<font-awesome icon="eye-slash" class="fa-fw" />
|
||||||
|
<font-awesome icon="arrow-up" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -95,11 +95,11 @@
|
|||||||
contactFromDid.did !== activeDid
|
contactFromDid.did !== activeDid
|
||||||
"
|
"
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
title="You watch their activity"
|
title="I view their content"
|
||||||
@click="confirmViewContent(contactFromDid, false)"
|
@click="confirmViewContent(contactFromDid, false)"
|
||||||
>
|
>
|
||||||
<font-awesome icon="arrow-down" class="fa-fw" />
|
|
||||||
<font-awesome icon="eye" class="fa-fw" />
|
<font-awesome icon="eye" class="fa-fw" />
|
||||||
|
<font-awesome icon="arrow-down" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else-if="
|
v-else-if="
|
||||||
@@ -107,11 +107,11 @@
|
|||||||
contactFromDid?.did !== activeDid
|
contactFromDid?.did !== activeDid
|
||||||
"
|
"
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
title="You do not watch their activity"
|
title="I do not view their content"
|
||||||
@click="confirmViewContent(contactFromDid, true)"
|
@click="confirmViewContent(contactFromDid, true)"
|
||||||
>
|
>
|
||||||
<font-awesome icon="arrow-down" class="fa-fw" />
|
|
||||||
<font-awesome icon="eye-slash" class="fa-fw" />
|
<font-awesome icon="eye-slash" class="fa-fw" />
|
||||||
|
<font-awesome icon="arrow-down" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -376,7 +376,8 @@ export default class DIDView extends Vue {
|
|||||||
*/
|
*/
|
||||||
private async initializeSettings() {
|
private async initializeSettings() {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -415,7 +415,8 @@ export default class DiscoverView extends Vue {
|
|||||||
const searchPeople = !!this.$route.query["searchPeople"];
|
const searchPeople = !!this.$route.query["searchPeople"];
|
||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = (settings.activeDid as string) || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = (settings.apiServer as string) || "";
|
this.apiServer = (settings.apiServer as string) || "";
|
||||||
this.partnerApiServer =
|
this.partnerApiServer =
|
||||||
(settings.partnerApiServer as string) || this.partnerApiServer;
|
(settings.partnerApiServer as string) || this.partnerApiServer;
|
||||||
|
|||||||
@@ -441,7 +441,8 @@ export default class GiftedDetails extends Vue {
|
|||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(this.giverDid && !this.giverName) ||
|
(this.giverDid && !this.giverName) ||
|
||||||
|
|||||||
+29
-19
@@ -319,9 +319,8 @@
|
|||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li>
|
<li>
|
||||||
Go to Your Identity <font-awesome icon="circle-user" class="fa-fw" /> page,
|
Go to Your Identity <font-awesome icon="circle-user" class="fa-fw" /> page,
|
||||||
click Advanced, and follow the instructions to "Import Contacts".
|
click Advanced, and follow the instructions for the Contacts & Settings Database "Import".
|
||||||
(There is currently no way to import other settings, so you'll have to recreate
|
Beware that this will erase your existing contact & settings.
|
||||||
by hand your search area, filters, etc.)
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -337,18 +336,14 @@
|
|||||||
|
|
||||||
<h2 class="text-xl font-semibold">How do I erase my data from my device?</h2>
|
<h2 class="text-xl font-semibold">How do I erase my data from my device?</h2>
|
||||||
<p>
|
<p>
|
||||||
Before doing this, you should back up your data with the instructions above.
|
Before doing this, you may want to back up your data with the instructions above.
|
||||||
Note that this does not erase data sent to our servers (see contact info below)
|
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li class="list-disc list-outside ml-4">
|
<li class="list-disc list-outside ml-4">
|
||||||
Mobile
|
Mobile
|
||||||
<ul>
|
<ul>
|
||||||
<li class="list-disc list-outside ml-4">
|
<li class="list-disc list-outside ml-4">
|
||||||
App Store app: hold down on the icon, then uninstall it
|
Home Screen: hold down on the icon, and choose to delete it
|
||||||
</li>
|
|
||||||
<li class="list-disc list-outside ml-4">
|
|
||||||
Home Screen PWA: hold down on the icon, and delete it
|
|
||||||
</li>
|
</li>
|
||||||
<li class="list-disc list-outside ml-4">
|
<li class="list-disc list-outside ml-4">
|
||||||
Chrome: Settings -> Privacy and Security -> Clear Browsing Data
|
Chrome: Settings -> Privacy and Security -> Clear Browsing Data
|
||||||
@@ -420,6 +415,15 @@
|
|||||||
different page.
|
different page.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h2 class="text-xl font-semibold">
|
||||||
|
Where do I get help with notifications?
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<router-link class="text-blue-500" to="/help-notifications"
|
||||||
|
>Here.</router-link
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold">
|
<h2 class="text-xl font-semibold">
|
||||||
This app is misbehaving, like showing me a blank screen or failing to show my personal data.
|
This app is misbehaving, like showing me a blank screen or failing to show my personal data.
|
||||||
What can I do?
|
What can I do?
|
||||||
@@ -430,13 +434,10 @@
|
|||||||
</p>
|
</p>
|
||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li>
|
<li>
|
||||||
For mobile apps, make sure you're connected to the internet.
|
Drag down on the screen to refresh it; do that multiple times, because
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
For PWAs, drag down on the screen to refresh it; do that multiple times, because
|
|
||||||
it sometimes takes multiple tries for the app to refresh to the latest version.
|
it sometimes takes multiple tries for the app to refresh to the latest version.
|
||||||
You can see the version information at the bottom of this page; the best
|
You can see the version information at the bottom of this page; the best
|
||||||
way to determine the latest version is to open TimeSafari.app in an incognito/private
|
way to determine the latest version is to open this page in an incognito/private
|
||||||
browser window and look at the version there.
|
browser window and look at the version there.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -467,6 +468,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
Then reload Time Safari.
|
Then reload Time Safari.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
Restart your device.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
If you still have problems, you can clear the cache (see "erase my data" above)
|
If you still have problems, you can clear the cache (see "erase my data" above)
|
||||||
@@ -504,12 +508,16 @@
|
|||||||
</p>
|
</p>
|
||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li>
|
<li>
|
||||||
If sending images, a server stores them. They can be removed by editing each claim
|
If using notifications, a server stores push token data. That can be revoked at any time
|
||||||
and deleting the image.
|
by disabling notifications on the Profile <font-awesome icon="circle-user" class="fa-fw" /> page.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
If sending images, a server stores them, too. They can be removed by editing the claim
|
||||||
|
and deleting them.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
If sending other partner system data (eg. to Trustroots) a public key and message
|
If sending other partner system data (eg. to Trustroots) a public key and message
|
||||||
data are stored on a server. Those can be removed via direct personal request (via contact below).
|
data are stored on a server. Those can be removed via direct personal request.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
For all other claim data,
|
For all other claim data,
|
||||||
@@ -680,7 +688,9 @@ export default class HelpView extends Vue {
|
|||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
|
|
||||||
if (settings.activeDid) {
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
const activeDid = await this.$getActiveDid();
|
||||||
|
if (activeDid) {
|
||||||
await this.$updateSettings({
|
await this.$updateSettings({
|
||||||
...settings,
|
...settings,
|
||||||
finishedOnboarding: false,
|
finishedOnboarding: false,
|
||||||
@@ -688,7 +698,7 @@ export default class HelpView extends Vue {
|
|||||||
|
|
||||||
this.$log(
|
this.$log(
|
||||||
"[HelpView] Onboarding reset successfully for DID: " +
|
"[HelpView] Onboarding reset successfully for DID: " +
|
||||||
settings.activeDid,
|
activeDid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-30
@@ -431,6 +431,7 @@ export default class HomeView extends Vue {
|
|||||||
* Called automatically by Vue lifecycle system
|
* Called automatically by Vue lifecycle system
|
||||||
*/
|
*/
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
logger.debug("[HomeView] mounted() starting");
|
||||||
try {
|
try {
|
||||||
await this.initializeIdentity();
|
await this.initializeIdentity();
|
||||||
// Settings already loaded in initializeIdentity()
|
// Settings already loaded in initializeIdentity()
|
||||||
@@ -471,6 +472,7 @@ export default class HomeView extends Vue {
|
|||||||
* @throws Logs error if DID retrieval fails
|
* @throws Logs error if DID retrieval fails
|
||||||
*/
|
*/
|
||||||
private async initializeIdentity() {
|
private async initializeIdentity() {
|
||||||
|
logger.debug("[HomeView] initializeIdentity() starting");
|
||||||
try {
|
try {
|
||||||
// Retrieve DIDs with better error handling
|
// Retrieve DIDs with better error handling
|
||||||
try {
|
try {
|
||||||
@@ -515,7 +517,11 @@ export default class HomeView extends Vue {
|
|||||||
// **CRITICAL**: Ensure correct API server for platform
|
// **CRITICAL**: Ensure correct API server for platform
|
||||||
await this.ensureCorrectApiServer();
|
await this.ensureCorrectApiServer();
|
||||||
|
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new façade method with legacy fallback
|
||||||
|
const retrievedActiveDid = await this.$getActiveDid();
|
||||||
|
logger.debug("[HomeView] Retrieved activeDid:", retrievedActiveDid);
|
||||||
|
this.activeDid = retrievedActiveDid || "";
|
||||||
|
logger.debug("[HomeView] Set activeDid to:", this.activeDid);
|
||||||
|
|
||||||
// Load contacts with graceful fallback
|
// Load contacts with graceful fallback
|
||||||
try {
|
try {
|
||||||
@@ -568,27 +574,10 @@ export default class HomeView extends Vue {
|
|||||||
this.isRegistered = true;
|
this.isRegistered = true;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Enhanced error logging with server context
|
|
||||||
const errorMessage =
|
|
||||||
error instanceof Error ? error.message : String(error);
|
|
||||||
const axiosError = error as {
|
|
||||||
response?: {
|
|
||||||
data?: { error?: { code?: string; message?: string } };
|
|
||||||
status?: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"[HomeView Settings Trace] ⚠️ Registration check failed",
|
"[HomeView Settings Trace] ⚠️ Registration check failed",
|
||||||
{
|
{
|
||||||
error: errorMessage,
|
error: error instanceof Error ? error.message : String(error),
|
||||||
did: this.activeDid,
|
|
||||||
server: this.apiServer,
|
|
||||||
errorCode: axiosError?.response?.data?.error?.code,
|
|
||||||
errorMessage: axiosError?.response?.data?.error?.message,
|
|
||||||
httpStatus: axiosError?.response?.status,
|
|
||||||
needsUserMigration: true,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -602,7 +591,8 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures correct API server configuration
|
* Ensures API server is correctly set for the current platform
|
||||||
|
* For Electron, always use production endpoint regardless of saved settings
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* Called after loading settings to ensure correct API endpoint
|
* Called after loading settings to ensure correct API endpoint
|
||||||
@@ -610,9 +600,12 @@ export default class HomeView extends Vue {
|
|||||||
private async ensureCorrectApiServer() {
|
private async ensureCorrectApiServer() {
|
||||||
const { DEFAULT_ENDORSER_API_SERVER } = await import("../constants/app");
|
const { DEFAULT_ENDORSER_API_SERVER } = await import("../constants/app");
|
||||||
|
|
||||||
// Only set default if no user preference exists
|
if (process.env.VITE_PLATFORM === "electron") {
|
||||||
if (!this.apiServer) {
|
// **CRITICAL FIX**: Always use production API server for Electron
|
||||||
// Set default API server for any platform if not already set
|
// This prevents the capacitor-electron:// protocol from being used for API calls
|
||||||
|
this.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
||||||
|
} else if (!this.apiServer) {
|
||||||
|
// **FIX**: Set default API server for web/development if not already set
|
||||||
this.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
this.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1174,13 +1167,9 @@ export default class HomeView extends Vue {
|
|||||||
location: fulfillsPlan
|
location: fulfillsPlan
|
||||||
? { lat: fulfillsPlan.locLat, lon: fulfillsPlan.locLon }
|
? { lat: fulfillsPlan.locLat, lon: fulfillsPlan.locLon }
|
||||||
: null,
|
: null,
|
||||||
inSearchBox:
|
inSearchBox: fulfillsPlan
|
||||||
fulfillsPlan?.locLat && fulfillsPlan?.locLon
|
? this.latLongInAnySearchBox(fulfillsPlan.locLat, fulfillsPlan.locLon)
|
||||||
? this.latLongInAnySearchBox(
|
: null,
|
||||||
fulfillsPlan.locLat,
|
|
||||||
fulfillsPlan.locLon,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
finalResult: anyMatch,
|
finalResult: anyMatch,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<div
|
<div
|
||||||
:class="identityListItemClasses"
|
:class="identityListItemClasses"
|
||||||
@click="switchAccount(ident.did)"
|
@click="switchIdentity(ident.did)"
|
||||||
>
|
>
|
||||||
<font-awesome
|
<font-awesome
|
||||||
v-if="ident.did === activeDid"
|
v-if="ident.did === activeDid"
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
:class="secondaryButtonClasses"
|
:class="secondaryButtonClasses"
|
||||||
@click="switchAccount(undefined)"
|
@click="switchIdentity(undefined)"
|
||||||
>
|
>
|
||||||
No Identity
|
No Identity
|
||||||
</a>
|
</a>
|
||||||
@@ -116,6 +116,7 @@ import {
|
|||||||
NOTIFY_DELETE_IDENTITY_CONFIRM,
|
NOTIFY_DELETE_IDENTITY_CONFIRM,
|
||||||
} from "@/constants/notifications";
|
} from "@/constants/notifications";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
|
import { FLAGS } from "@/config/featureFlags";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { QuickNav },
|
components: { QuickNav },
|
||||||
@@ -200,7 +201,8 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new façade method with legacy fallback
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.apiServerInput = settings.apiServer || "";
|
this.apiServerInput = settings.apiServer || "";
|
||||||
|
|
||||||
@@ -221,46 +223,63 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async switchAccount(did?: string) {
|
async switchIdentity(did?: string) {
|
||||||
// Save the new active DID to master settings
|
try {
|
||||||
await this.$saveSettings({ activeDid: did });
|
if (did) {
|
||||||
|
// Use new façade method instead of legacy settings
|
||||||
|
await this.$setActiveDid(did);
|
||||||
|
|
||||||
// Check if we need to load user-specific settings for the new DID
|
// Update local state
|
||||||
if (did) {
|
this.activeDid = did;
|
||||||
try {
|
|
||||||
const newSettings = await this.$accountSettings(did);
|
// Legacy fallback - remove after Phase C
|
||||||
logger.debug(
|
if (!FLAGS.USE_ACTIVE_IDENTITY_ONLY) {
|
||||||
"[IdentitySwitcher Settings Trace] ✅ New account settings loaded",
|
await this.$saveSettings({ activeDid: did });
|
||||||
{
|
}
|
||||||
did,
|
|
||||||
settingsKeys: Object.keys(newSettings).filter(
|
// Check if we need to load user-specific settings for the new DID
|
||||||
(k) =>
|
try {
|
||||||
k in newSettings &&
|
const newSettings = await this.$accountSettings(did);
|
||||||
newSettings[k as keyof typeof newSettings] !== undefined,
|
logger.info(
|
||||||
),
|
"[IdentitySwitcher Settings Trace] ✅ New account settings loaded",
|
||||||
},
|
{
|
||||||
);
|
did,
|
||||||
} catch (error) {
|
settingsKeys: Object.keys(newSettings).filter(
|
||||||
logger.warn(
|
(k) =>
|
||||||
"[IdentitySwitcher Settings Trace] ⚠️ Error loading new account settings",
|
k in newSettings &&
|
||||||
{
|
newSettings[k as keyof typeof newSettings] !== undefined,
|
||||||
did,
|
),
|
||||||
error: error instanceof Error ? error.message : String(error),
|
},
|
||||||
},
|
);
|
||||||
);
|
} catch (error) {
|
||||||
// Handle error silently - user settings will be loaded when needed
|
logger.warn(
|
||||||
|
"[IdentitySwitcher Settings Trace] ⚠️ Error loading new account settings",
|
||||||
|
{
|
||||||
|
did,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Handle error silently - user settings will be loaded when needed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle "No Identity" case
|
||||||
|
this.activeDid = "";
|
||||||
|
// Note: We don't clear active DID in database for safety
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"[IdentitySwitcher Settings Trace] 🔄 Navigating to home to trigger watcher",
|
||||||
|
{
|
||||||
|
newDid: did,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Navigate to home page to trigger the watcher
|
||||||
|
this.$router.push({ name: "home" });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[IdentitySwitcher] Error switching identity", error);
|
||||||
|
this.notify.error("Error switching identity", TIMEOUTS.SHORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
"[IdentitySwitcher Settings Trace] 🔄 Navigating to home to trigger watcher",
|
|
||||||
{
|
|
||||||
newDid: did,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Navigate to home page to trigger the watcher
|
|
||||||
this.$router.push({ name: "home" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAccount(id: string) {
|
async deleteAccount(id: string) {
|
||||||
|
|||||||
@@ -88,15 +88,9 @@ import { Router } from "vue-router";
|
|||||||
|
|
||||||
import { AppString, NotificationIface } from "../constants/app";
|
import { AppString, NotificationIface } from "../constants/app";
|
||||||
import { DEFAULT_ROOT_DERIVATION_PATH } from "../libs/crypto";
|
import { DEFAULT_ROOT_DERIVATION_PATH } from "../libs/crypto";
|
||||||
import {
|
import { retrieveAccountCount, importFromMnemonic } from "../libs/util";
|
||||||
retrieveAccountCount,
|
|
||||||
importFromMnemonic,
|
|
||||||
checkForDuplicateAccount,
|
|
||||||
DUPLICATE_ACCOUNT_ERROR,
|
|
||||||
} from "../libs/util";
|
|
||||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||||
import { NOTIFY_DUPLICATE_ACCOUNT_IMPORT } from "@/constants/notifications";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import Account View Component
|
* Import Account View Component
|
||||||
@@ -204,19 +198,6 @@ export default class ImportAccountView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check for duplicate account before importing
|
|
||||||
const isDuplicate = await checkForDuplicateAccount(
|
|
||||||
this.mnemonic,
|
|
||||||
this.derivationPath,
|
|
||||||
);
|
|
||||||
if (isDuplicate) {
|
|
||||||
this.notify.warning(
|
|
||||||
NOTIFY_DUPLICATE_ACCOUNT_IMPORT.message,
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await importFromMnemonic(
|
await importFromMnemonic(
|
||||||
this.mnemonic,
|
this.mnemonic,
|
||||||
this.derivationPath,
|
this.derivationPath,
|
||||||
@@ -226,11 +207,12 @@ export default class ImportAccountView extends Vue {
|
|||||||
// Check what was actually imported
|
// Check what was actually imported
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
|
|
||||||
// Check account-specific settings
|
// Check account-specific settings using Active Identity façade
|
||||||
if (settings?.activeDid) {
|
const activeDid = await this.$getActiveDid();
|
||||||
|
if (activeDid) {
|
||||||
try {
|
try {
|
||||||
await this.$query("SELECT * FROM settings WHERE accountDid = ?", [
|
await this.$query("SELECT * FROM settings WHERE accountDid = ?", [
|
||||||
settings.activeDid,
|
activeDid,
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log error but don't interrupt import flow
|
// Log error but don't interrupt import flow
|
||||||
@@ -242,20 +224,9 @@ export default class ImportAccountView extends Vue {
|
|||||||
this.$router.push({ name: "account" });
|
this.$router.push({ name: "account" });
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
this.$logError("Import failed: " + error);
|
this.$logError("Import failed: " + error);
|
||||||
|
|
||||||
// Check if this is a duplicate account error from saveNewIdentity
|
|
||||||
const errorMessage =
|
|
||||||
error instanceof Error ? error.message : String(error);
|
|
||||||
if (errorMessage.includes(DUPLICATE_ACCOUNT_ERROR)) {
|
|
||||||
this.notify.warning(
|
|
||||||
NOTIFY_DUPLICATE_ACCOUNT_IMPORT.message,
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.notify.error(
|
this.notify.error(
|
||||||
errorMessage || "Failed to import account.",
|
(error instanceof Error ? error.message : String(error)) ||
|
||||||
|
"Failed to import account.",
|
||||||
TIMEOUTS.LONG,
|
TIMEOUTS.LONG,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ import {
|
|||||||
retrieveAllAccountsMetadata,
|
retrieveAllAccountsMetadata,
|
||||||
retrieveFullyDecryptedAccount,
|
retrieveFullyDecryptedAccount,
|
||||||
saveNewIdentity,
|
saveNewIdentity,
|
||||||
checkForDuplicateAccount,
|
|
||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { Account, AccountEncrypted } from "../db/tables/accounts";
|
import { Account, AccountEncrypted } from "../db/tables/accounts";
|
||||||
@@ -172,20 +171,15 @@ export default class ImportAccountView extends Vue {
|
|||||||
const newId = newIdentifier(address, publicHex, privateHex, newDerivPath);
|
const newId = newIdentifier(address, publicHex, privateHex, newDerivPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check for duplicate account before creating
|
|
||||||
const isDuplicate = await checkForDuplicateAccount(newId.did);
|
|
||||||
if (isDuplicate) {
|
|
||||||
this.notify.warning(
|
|
||||||
"This derived account already exists. Please try a different derivation path.",
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await saveNewIdentity(newId, mne, newDerivPath);
|
await saveNewIdentity(newId, mne, newDerivPath);
|
||||||
|
|
||||||
// record that as the active DID
|
// record that as the active DID using new façade
|
||||||
await this.$saveSettings({ activeDid: newId.did });
|
await this.$setActiveDid(newId.did);
|
||||||
|
|
||||||
|
// Legacy fallback - remove after Phase C
|
||||||
|
if (!FLAGS.USE_ACTIVE_IDENTITY_ONLY) {
|
||||||
|
await this.$saveSettings({ activeDid: newId.did });
|
||||||
|
}
|
||||||
await this.$saveUserSettings(newId.did, {
|
await this.$saveUserSettings(newId.did, {
|
||||||
isRegistered: false,
|
isRegistered: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { APP_SERVER } from "../constants/app";
|
|||||||
import { decodeEndorserJwt } from "../libs/crypto/vc";
|
import { decodeEndorserJwt } from "../libs/crypto/vc";
|
||||||
import { errorStringForLog } from "../libs/endorserServer";
|
import { errorStringForLog } from "../libs/endorserServer";
|
||||||
import { generateSaveAndActivateIdentity } from "../libs/util";
|
import { generateSaveAndActivateIdentity } from "../libs/util";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
import { createNotifyHelpers } from "@/utils/notify";
|
import { createNotifyHelpers } from "@/utils/notify";
|
||||||
import {
|
import {
|
||||||
@@ -120,7 +121,8 @@ export default class InviteOneAcceptView extends Vue {
|
|||||||
|
|
||||||
// Load or generate identity
|
// Load or generate identity
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
|
|
||||||
// Identity creation should be handled by router guard, but keep as fallback for deep links
|
// Identity creation should be handled by router guard, but keep as fallback for deep links
|
||||||
|
|||||||
@@ -283,7 +283,8 @@ export default class InviteOneView extends Vue {
|
|||||||
try {
|
try {
|
||||||
// Use PlatformServiceMixin for account settings
|
// Use PlatformServiceMixin for account settings
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.isRegistered = !!settings.isRegistered;
|
this.isRegistered = !!settings.isRegistered;
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,8 @@ export default class NewActivityView extends Vue {
|
|||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId || "";
|
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId || "";
|
||||||
this.lastAckedOfferToUserProjectsJwtId =
|
this.lastAckedOfferToUserProjectsJwtId =
|
||||||
settings.lastAckedOfferToUserProjectsJwtId || "";
|
settings.lastAckedOfferToUserProjectsJwtId || "";
|
||||||
|
|||||||
@@ -110,22 +110,10 @@ export default class NewEditAccountView extends Vue {
|
|||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async onClickSaveChanges() {
|
async onClickSaveChanges() {
|
||||||
// Get the current active DID to save to user-specific settings
|
await this.$updateSettings({
|
||||||
const settings = await this.$accountSettings();
|
firstName: this.givenName,
|
||||||
const activeDid = settings.activeDid;
|
lastName: "", // deprecated, pre v 0.1.3
|
||||||
|
});
|
||||||
if (activeDid) {
|
|
||||||
// Save to user-specific settings for the current identity
|
|
||||||
await this.$saveUserSettings(activeDid, {
|
|
||||||
firstName: this.givenName,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Fallback to master settings if no active DID
|
|
||||||
await this.$saveSettings({
|
|
||||||
firstName: this.givenName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$router.back();
|
this.$router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -378,7 +378,8 @@ export default class NewEditProjectView extends Vue {
|
|||||||
this.numAccounts = await retrieveAccountCount();
|
this.numAccounts = await retrieveAccountCount();
|
||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
|
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
|
||||||
|
|
||||||
|
|||||||
@@ -433,7 +433,8 @@ export default class OfferDetailsView extends Vue {
|
|||||||
private async loadAccountSettings() {
|
private async loadAccountSettings() {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.apiServer = settings.apiServer ?? "";
|
this.apiServer = settings.apiServer ?? "";
|
||||||
this.activeDid = settings.activeDid ?? "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) ?? "";
|
||||||
this.showGeneralAdvanced = settings.showGeneralAdvanced ?? false;
|
this.showGeneralAdvanced = settings.showGeneralAdvanced ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,8 @@ export default class OnboardMeetingListView extends Vue {
|
|||||||
// Load user account settings
|
// Load user account settings
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
|
|
||||||
this.activeDid = settings?.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.firstName = settings?.firstName || "";
|
this.firstName = settings?.firstName || "";
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ export default class OnboardMeetingMembersView extends Vue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings?.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.firstName = settings?.firstName || "";
|
this.firstName = settings?.firstName || "";
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
|||||||
@@ -349,7 +349,8 @@ export default class OnboardMeetingView extends Vue {
|
|||||||
this.$notify as Parameters<typeof createNotifyHelpers>[0],
|
this.$notify as Parameters<typeof createNotifyHelpers>[0],
|
||||||
);
|
);
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings?.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.fullName = settings?.firstName || "";
|
this.fullName = settings?.firstName || "";
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
|||||||
@@ -226,7 +226,7 @@
|
|||||||
<div class="grid items-start grid-cols-1 sm:grid-cols-3 gap-4 mt-4">
|
<div class="grid items-start grid-cols-1 sm:grid-cols-3 gap-4 mt-4">
|
||||||
<!-- First, offers on the left-->
|
<!-- First, offers on the left-->
|
||||||
<div class="bg-slate-100 px-4 py-3 rounded-md">
|
<div class="bg-slate-100 px-4 py-3 rounded-md">
|
||||||
<div v-if="activeDid && isRegistered" class="mb-4">
|
<div v-if="activeDid && isRegistered">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button
|
<button
|
||||||
data-testId="offerButton"
|
data-testId="offerButton"
|
||||||
@@ -243,19 +243,13 @@
|
|||||||
:project-name="name"
|
:project-name="name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h3 class="text-lg font-bold leading-tight mb-3">
|
<h3 class="text-lg font-bold mb-3 mt-4">Offered To This Idea</h3>
|
||||||
Offered To This Idea
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div v-if="offersToThis.length === 0" class="text-sm">
|
<div v-if="offersToThis.length === 0">
|
||||||
(None yet.<span v-if="activeDid && isRegistered">
|
(None yet. Wanna
|
||||||
Wanna
|
<span class="cursor-pointer text-blue-500" @click="openOfferDialog()"
|
||||||
<span
|
>offer something... especially if others join you</span
|
||||||
class="cursor-pointer text-blue-500"
|
>?)
|
||||||
@click="openOfferDialog()"
|
|
||||||
>offer something… especially if others join you</span
|
|
||||||
>?</span
|
|
||||||
>)
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul v-else class="text-sm border-t border-slate-300">
|
<ul v-else class="text-sm border-t border-slate-300">
|
||||||
@@ -320,7 +314,7 @@
|
|||||||
<!-- Now, gives TO this project in the middle -->
|
<!-- Now, gives TO this project in the middle -->
|
||||||
<!-- (similar to "FROM" gift display below) -->
|
<!-- (similar to "FROM" gift display below) -->
|
||||||
<div class="bg-slate-100 px-4 py-3 rounded-md" data-testId="gives-to">
|
<div class="bg-slate-100 px-4 py-3 rounded-md" data-testId="gives-to">
|
||||||
<div v-if="activeDid && isRegistered" class="mb-4">
|
<div v-if="activeDid && isRegistered">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button
|
<button
|
||||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
||||||
@@ -331,9 +325,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-lg font-bold leading-tight mb-3">
|
<h3 class="text-lg font-bold mt-4">Given To This Project</h3>
|
||||||
Given To This Project
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div v-if="givesToThis.length === 0" class="text-sm">
|
<div v-if="givesToThis.length === 0" class="text-sm">
|
||||||
(None yet. If you've seen something, say something by clicking a
|
(None yet. If you've seen something, say something by clicking a
|
||||||
@@ -484,7 +476,7 @@
|
|||||||
<!-- Finally, gives FROM this project on the right -->
|
<!-- Finally, gives FROM this project on the right -->
|
||||||
<!-- (similar to "TO" gift display above) -->
|
<!-- (similar to "TO" gift display above) -->
|
||||||
<div class="bg-slate-100 px-4 py-3 rounded-md" data-testId="gives-from">
|
<div class="bg-slate-100 px-4 py-3 rounded-md" data-testId="gives-from">
|
||||||
<div v-if="activeDid && isRegistered" class="mb-4">
|
<div v-if="activeDid && isRegistered">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button
|
<button
|
||||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
||||||
@@ -502,13 +494,11 @@
|
|||||||
:is-from-project-view="true"
|
:is-from-project-view="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h3 class="text-lg font-bold leading-tight mb-3">
|
<h3 class="text-lg font-bold mb-3 mt-4">
|
||||||
Benefitted From This Project
|
Benefitted From This Project
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div v-if="givesProvidedByThis.length === 0" class="text-sm">
|
<div v-if="givesProvidedByThis.length === 0">(None yet.)</div>
|
||||||
(None yet.)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-else class="text-sm border-t border-slate-300">
|
<ul v-else class="text-sm border-t border-slate-300">
|
||||||
<li
|
<li
|
||||||
@@ -780,7 +770,8 @@ export default class ProjectViewView extends Vue {
|
|||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.allContacts = await this.$getAllContacts();
|
this.allContacts = await this.$getAllContacts();
|
||||||
this.isRegistered = !!settings.isRegistered;
|
this.isRegistered = !!settings.isRegistered;
|
||||||
|
|||||||
@@ -391,7 +391,8 @@ export default class ProjectsView extends Vue {
|
|||||||
*/
|
*/
|
||||||
private async initializeUserSettings() {
|
private async initializeUserSettings() {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Use new Active Identity façade instead of settings.activeDid
|
||||||
|
this.activeDid = (await this.$getActiveDid()) || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.isRegistered = !!settings.isRegistered;
|
this.isRegistered = !!settings.isRegistered;
|
||||||
this.givenName = settings.firstName || "";
|
this.givenName = settings.firstName || "";
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user