Compare commits
41 Commits
electron-b
...
profile_in
| Author | SHA1 | Date | |
|---|---|---|---|
| ff864adbe5 | |||
| 6f9847b524 | |||
| 01279b61f5 | |||
|
|
98f97f2dc9 | ||
|
|
4c7c2d48e9 | ||
| 43e7bc1c12 | |||
|
|
1a77dfb750 | ||
|
|
1365adad92 | ||
|
|
baccb962cf | ||
|
|
0a0a17ef9c | ||
|
|
8dab4ed016 | ||
|
|
4f78bfe744 | ||
|
|
ceceabf7b5 | ||
|
|
9386b2e96f | ||
|
|
128ddff467 | ||
|
|
b834596ba6 | ||
|
|
77a4c60656 | ||
|
|
a11443dc3a | ||
|
|
7f7680f4a6 | ||
|
|
271a45afa3 | ||
|
|
6aac3ca35f | ||
|
|
f0fd8c0f12 | ||
|
|
fd30343ec4 | ||
|
|
e70faff5ce | ||
|
|
9512e8192f | ||
|
|
a6126ecac3 | ||
|
|
d66d8ce1c1 | ||
|
|
277fe49aa8 | ||
|
|
a85b508f44 | ||
|
|
be4ab16b00 | ||
|
|
1305eed9bc | ||
|
|
aa55588cbb | ||
|
|
5f63e05090 | ||
|
|
4391cb2881 | ||
| 0b9c243969 | |||
|
|
74c70c7fa0 | ||
|
|
3be7001d1b | ||
|
|
95a8f5ebe1 | ||
|
|
f31eb5f6c9 | ||
|
|
9f976f011a | ||
|
|
f2026bb921 |
@@ -104,6 +104,161 @@ High-level meta-rules that bundle related sub-rules for specific workflows.
|
||||
- **`meta_bug_diagnosis.mdc`** - Bug investigation workflow bundling
|
||||
- **`meta_bug_fixing.mdc`** - Bug fix 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
|
||||
|
||||
|
||||
192
.cursor/rules/always_on_rules.mdc
Normal file
192
.cursor/rules/always_on_rules.mdc
Normal file
@@ -0,0 +1,192 @@
|
||||
# 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
|
||||
207
.cursor/rules/harbor_pilot_universal.mdc
Normal file
207
.cursor/rules/harbor_pilot_universal.mdc
Normal file
@@ -0,0 +1,207 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
inherits: base_context.mdc
|
||||
---
|
||||
```json
|
||||
{
|
||||
"coaching_level": "standard",
|
||||
"socratic_max_questions": 2,
|
||||
"verbosity": "concise",
|
||||
"timebox_minutes": 10,
|
||||
"format_enforcement": "strict"
|
||||
}
|
||||
```
|
||||
|
||||
# Harbor Pilot — Universal Directive for Human-Facing Technical Guides
|
||||
|
||||
**Author**: System/Shared
|
||||
**Date**: 2025-08-21 (UTC)
|
||||
**Status**: 🚢 ACTIVE — General ruleset extending *Base Context — Human Competence First*
|
||||
|
||||
> **Alignment with Base Context**
|
||||
> - **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.
|
||||
> - **Toggles honored**: Uses the same toggle semantics; defaults above can be overridden by the caller.
|
||||
|
||||
---
|
||||
|
||||
## Objective
|
||||
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
|
||||
- **One Markdown document** as the deliverable.
|
||||
- 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:
|
||||
- `sequenceDiagram` (protocols/flows), `flowchart`, `stateDiagram`, `gantt` (timelines), or `classDiagram` (schemas).
|
||||
- Provide runnable examples where applicable:
|
||||
- **APIs**: `curl` + one client library (e.g., `httpx` for Python).
|
||||
- **CLIs**: literal command blocks and expected output snippets.
|
||||
- **Code**: minimal, self-contained samples (language appropriate).
|
||||
- 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**.
|
||||
|
||||
## Required Sections (extends Base Output Contract)
|
||||
Follow this exact order **after** the Base Contract’s **Objective → Result → Use/Run** headers:
|
||||
|
||||
1. **Context & Scope**
|
||||
- Problem statement, audience, in/out-of-scope bullets.
|
||||
2. **Artifacts & Links**
|
||||
- Repos/PRs, design docs, datasets/HARs/pcaps, scripts/tools, dashboards.
|
||||
3. **Environment & Preconditions**
|
||||
- OS/runtime, versions/build IDs, services/endpoints/URLs, credentials/auth mode (describe acquisition, do not expose secrets).
|
||||
4. **Architecture / Process Overview**
|
||||
- Short prose + **one diagram** selected from the list above.
|
||||
5. **Interfaces & Contracts (choose one)**
|
||||
- **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*).
|
||||
- **Systems/Hardware**: Interfaces table (*Port/Bus, Protocol, Voltage/Timing, Constraints*).
|
||||
6. **Repro: End-to-End Procedure**
|
||||
- Minimal copy-paste steps with code/commands and **expected outputs**.
|
||||
7. **What Works (with Evidence)**
|
||||
- Each item: **Time (UTC)** • **Artifact/Req IDs** • **Status/Result** • **Where to verify**.
|
||||
8. **What Doesn’t (Evidence & Hypotheses)**
|
||||
- Each failure: locus (file/endpoint/module), evidence snippet; short hypothesis and **next probe**.
|
||||
9. **Risks, Limits, Assumptions**
|
||||
- SLOs/limits, rate/size caps, security boundaries (CORS/CSRF/ACLs), retries/backoff/idempotency patterns.
|
||||
10. **Next Steps (Owner • Exit Criteria • Target Date)**
|
||||
- Actionable, assigned, and time-bound.
|
||||
11. **References**
|
||||
- Canonical docs, specs, tickets, prior analyses.
|
||||
|
||||
> **Competence Hooks (per Base Context; keep lightweight):**
|
||||
> - *Why this works* (≤3 bullets) — core invariants or guarantees.
|
||||
> - *Common pitfalls* (≤3 bullets) — the traps we saw in evidence.
|
||||
> - *Next skill unlock* (1 line) — the next capability to implement/learn.
|
||||
> - *Teach-back* (1 line) — prompt the reader to restate the flow/architecture.
|
||||
|
||||
> **Collaboration Hooks (per Base Context):**
|
||||
> - Name reviewers for **Interfaces & Contracts** and the **diagram**.
|
||||
> - Short **sign-off checklist** before merging/publishing the guide.
|
||||
|
||||
## Do / Don’t (Base-aligned)
|
||||
- **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** keep commentary lean; if timeboxed, move depth to **Deferred for depth**.
|
||||
- **Don’t** use marketing language or meta narration (“Perfect!”, “tool called”, “new chat”).
|
||||
- **Don’t** include IDE-specific chatter or internal rules unrelated to the task.
|
||||
|
||||
## Validation Checklist (self-check before returning)
|
||||
- [ ] All Required Sections present and ordered.
|
||||
- [ ] Diagram compiles (basic Mermaid syntax) and fits the problem.
|
||||
- [ ] If API-based, **Auth** and **Key Headers/Params** are listed for each endpoint.
|
||||
- [ ] Repro section includes commands/code **and expected outputs**.
|
||||
- [ ] Every Works/Doesn’t item has **UTC timestamp**, **status/result**, and **verifiable evidence**.
|
||||
- [ ] Next Steps include **Owner**, **Exit Criteria**, **Target Date**.
|
||||
- [ ] Unknowns are `TODO:<missing>` — no fabrication.
|
||||
- [ ] Base **Output Contract** sections satisfied (Objective/Result/Use/Run/Competence/Collaboration/Assumptions/References).
|
||||
|
||||
## Universal Template (fill-in)
|
||||
```markdown
|
||||
# <Title> — Working Notes (As of YYYY-MM-DDTHH:MMZ)
|
||||
|
||||
## Objective
|
||||
<one line>
|
||||
|
||||
## Result
|
||||
<link to the produced guide file or say “this document”>
|
||||
|
||||
## Use/Run
|
||||
<how to apply/test and where to run samples>
|
||||
|
||||
## Context & Scope
|
||||
- Audience: <role(s)>
|
||||
- In scope: <bullets>
|
||||
- Out of scope: <bullets>
|
||||
|
||||
## Artifacts & Links
|
||||
- Repo/PR: <link>
|
||||
- Data/Logs: <paths or links>
|
||||
- Scripts/Tools: <paths>
|
||||
- Dashboards: <links>
|
||||
|
||||
## Environment & Preconditions
|
||||
- OS/Runtime: <details>
|
||||
- Versions/Builds: <list>
|
||||
- Services/Endpoints: <list>
|
||||
- Auth mode: <Bearer/Session/Keys + how acquired>
|
||||
|
||||
## Architecture / Process Overview
|
||||
<short prose>
|
||||
```mermaid
|
||||
<one suitable diagram: sequenceDiagram | flowchart | stateDiagram | gantt | classDiagram>
|
||||
```
|
||||
|
||||
## Interfaces & Contracts
|
||||
### If API-based
|
||||
| Step | Method | Path/URL | Auth | Key Headers/Params | Sample |
|
||||
|---|---|---|---|---|---|
|
||||
| <…> | <…> | <…> | <…> | <…> | below |
|
||||
|
||||
### If Data/Files
|
||||
| Source | Format | Schema/Columns | Size | Validation |
|
||||
|---|---|---|---|---|
|
||||
| <…> | <…> | <…> | <…> | <…> |
|
||||
|
||||
### If Systems/Hardware
|
||||
| Interface | Protocol | Timing/Voltage | Constraints | Notes |
|
||||
|---|---|---|---|---|
|
||||
| <…> | <…> | <…> | <…> | <…> |
|
||||
|
||||
## Repro: End-to-End Procedure
|
||||
```bash
|
||||
# commands / curl examples (redacted where necessary)
|
||||
```
|
||||
```python
|
||||
# minimal client library example (language appropriate)
|
||||
```
|
||||
> Expected output: <snippet/checks>
|
||||
|
||||
## What Works (Evidence)
|
||||
- ✅ <short statement>
|
||||
- **Time**: <YYYY-MM-DDTHH:MMZ>
|
||||
- **Evidence**: file/line/log or request id/status
|
||||
- **Verify at**: <where>
|
||||
|
||||
## What Doesn’t (Evidence & Hypotheses)
|
||||
- ❌ <short failure> at `<component/endpoint/file>`
|
||||
- **Time**: <YYYY-MM-DDTHH:MMZ>
|
||||
- **Evidence**: <snippet/id/status>
|
||||
- **Hypothesis**: <short>
|
||||
- **Next probe**: <short>
|
||||
|
||||
## Risks, Limits, Assumptions
|
||||
<bullets: limits, security boundaries, retries/backoff, idempotency, SLOs>
|
||||
|
||||
## Next Steps
|
||||
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
||||
|---|---|---|---|
|
||||
| <name> | <action> | <measurable outcome> | <YYYY-MM-DD> |
|
||||
|
||||
## References
|
||||
<links/titles>
|
||||
|
||||
## Competence Hooks
|
||||
- *Why this works*: <≤3 bullets>
|
||||
- *Common pitfalls*: <≤3 bullets>
|
||||
- *Next skill unlock*: <1 line>
|
||||
- *Teach-back*: <1 line>
|
||||
|
||||
## Collaboration Hooks
|
||||
- Reviewers: <names/roles>
|
||||
- Sign-off checklist: <≤5 checks>
|
||||
|
||||
## Assumptions & Limits
|
||||
<bullets>
|
||||
|
||||
## Deferred for depth
|
||||
<park deeper material here to respect timeboxing>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Notes for Implementers:**
|
||||
- Respect Base *Do-Not* (no filler, no invented facts, no censorship).
|
||||
- Prefer clarity over completeness when timeboxed; capture unknowns explicitly.
|
||||
- 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 Playwright test investigation rules (see `.cursor/rules/playwright_test_investigation.mdc`)
|
||||
@@ -1,169 +1,285 @@
|
||||
# Meta-Rule: Bug Diagnosis
|
||||
# Meta-Rule: Bug Diagnosis Workflow
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: 2025-08-21
|
||||
**Status**: 🎯 **ACTIVE** - Bug investigation workflow bundling
|
||||
**Date**: August 24, 2025
|
||||
**Status**: 🎯 **ACTIVE** - Core workflow for all bug investigation
|
||||
|
||||
## Purpose
|
||||
|
||||
This meta-rule bundles all the rules needed for systematic bug investigation
|
||||
and root cause analysis. Use this when bugs are reported, performance
|
||||
issues occur, or unexpected behavior happens.
|
||||
This meta-rule defines the systematic approach for investigating and diagnosing
|
||||
bugs, defects, and unexpected behaviors in the TimeSafari application. It ensures
|
||||
consistent, thorough, and efficient problem-solving workflows.
|
||||
|
||||
## 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
|
||||
|
||||
- **Bug Reports**: Investigating reported bugs or issues
|
||||
- **Performance Issues**: Diagnosing slow performance or bottlenecks
|
||||
- **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
|
||||
**ALWAYS** - Apply this workflow to every bug investigation, regardless of
|
||||
severity or complexity. This ensures systematic problem-solving and prevents
|
||||
common investigation pitfalls.
|
||||
|
||||
## Bundled Rules
|
||||
|
||||
### **Investigation Process**
|
||||
### **Investigation Foundation**
|
||||
|
||||
- **`development/research_diagnostic.mdc`** - Systematic investigation
|
||||
workflow with evidence collection and analysis
|
||||
- **`development/investigation_report_example.mdc`** - Investigation
|
||||
documentation templates and examples
|
||||
- **`core/harbor_pilot_universal.mdc`** - Technical guide creation
|
||||
for complex investigations
|
||||
- **`development/research_diagnostic.mdc`** - Research and investigation methodologies
|
||||
- **`development/logging_standards.mdc`** - Logging and debugging best practices
|
||||
- **`development/type_safety_guide.mdc`** - Type safety and error prevention
|
||||
|
||||
### **Evidence Collection**
|
||||
### **Development Workflow**
|
||||
|
||||
- **`development/logging_standards.mdc`** - Logging implementation
|
||||
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
|
||||
- **`workflow/version_control.mdc`** - Version control during investigation
|
||||
- **`development/software_development.mdc`** - Development best practices
|
||||
|
||||
### **Technical Context**
|
||||
## Critical Development Constraints
|
||||
|
||||
- **`app/timesafari.mdc`** - Core application context and
|
||||
architecture for understanding the system
|
||||
- **`app/timesafari_platforms.mdc`** - Platform-specific
|
||||
considerations and constraints
|
||||
### **🚫 NEVER Use Build Commands During Diagnosis**
|
||||
|
||||
## Workflow Sequence
|
||||
**Critical Rule**: Never use `npm run build:web` or similar build commands during bug diagnosis
|
||||
|
||||
### **Phase 1: Initial Investigation (Start Here)**
|
||||
- **Reason**: These commands block the chat and prevent effective troubleshooting
|
||||
- **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
|
||||
|
||||
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 Diagnosis Commands**
|
||||
|
||||
### **Phase 2: Deep Investigation**
|
||||
✅ **Safe to use during diagnosis:**
|
||||
- `npm run lint-fix` - Syntax and style checking
|
||||
- `npm run type-check` - TypeScript validation (if available)
|
||||
- `git status` - Version control status
|
||||
- `ls` / `dir` - File listing
|
||||
- `cat` / `read_file` - File content inspection
|
||||
- `grep_search` - Text pattern searching
|
||||
|
||||
1. **Platform Analysis** - Check `timesafari_platforms.mdc` for
|
||||
platform-specific issues
|
||||
2. **Technical Guide Creation** - Use `harbor_pilot_universal.mdc`
|
||||
for complex investigation documentation
|
||||
3. **Evidence Analysis** - Apply `time_examples.mdc` for proper
|
||||
timestamp handling
|
||||
❌ **Never use during diagnosis:**
|
||||
- `npm run build:web` - Blocks chat
|
||||
- `npm run build:electron` - Blocks chat
|
||||
- `npm run build:capacitor` - Blocks chat
|
||||
- Any long-running build processes
|
||||
|
||||
### **Phase 3: Documentation & Reporting**
|
||||
## Investigation Workflow
|
||||
|
||||
1. **Investigation Report** - Use `investigation_report_example.mdc`
|
||||
for comprehensive documentation
|
||||
2. **Root Cause Analysis** - Synthesize findings into actionable
|
||||
insights
|
||||
### **Phase 1: Problem Definition**
|
||||
|
||||
## Success Criteria
|
||||
1. **Gather Evidence**
|
||||
- Error messages and stack traces
|
||||
- User-reported symptoms
|
||||
- System logs and timestamps
|
||||
- Reproduction steps
|
||||
|
||||
- [ ] **Root cause identified** with supporting evidence
|
||||
- [ ] **Evidence properly collected** with timestamps and context
|
||||
- [ ] **Investigation documented** using appropriate templates
|
||||
- [ ] **Platform factors considered** in diagnosis
|
||||
- [ ] **Reproduction steps documented** for verification
|
||||
- [ ] **Impact assessment completed** with scope defined
|
||||
- [ ] **Next steps identified** for resolution
|
||||
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
|
||||
|
||||
- **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 Mistakes**
|
||||
|
||||
## Integration Points
|
||||
- **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
|
||||
|
||||
### **With Other Meta-Rules**
|
||||
### **Communication Issues**
|
||||
|
||||
- **Feature Planning**: Use complexity assessment for investigation planning
|
||||
- **Bug Fixing**: Investigation results feed directly into fix implementation
|
||||
- **Feature Implementation**: Investigation insights inform future development
|
||||
- **Technical Jargon**: Using unclear terminology
|
||||
- **Missing Context**: Insufficient background information
|
||||
- **Unclear Recommendations**: Vague or ambiguous next steps
|
||||
- **Poor Documentation**: Incomplete or unclear investigation records
|
||||
|
||||
### **With Development Workflow**
|
||||
## Success Criteria
|
||||
|
||||
- Investigation findings inform testing strategy
|
||||
- Root cause analysis drives preventive measures
|
||||
- Evidence collection improves logging standards
|
||||
- [ ] **Problem clearly defined** with sufficient evidence
|
||||
- [ ] **Root cause identified** through systematic investigation
|
||||
- [ ] **Solution approach determined** with clear requirements
|
||||
- [ ] **Documentation complete** for knowledge transfer
|
||||
- [ ] **No chat-blocking commands** used during investigation
|
||||
- [ ] **Platform considerations** properly addressed
|
||||
- [ ] **Timeline and context** properly documented
|
||||
|
||||
## Feedback & Improvement
|
||||
## Integration with Other Meta-Rules
|
||||
|
||||
### **Sub-Rule Ratings (1-5 scale)**
|
||||
### **Bug Fixing**
|
||||
|
||||
- **Research Diagnostic**: ___/5 - Comments: _______________
|
||||
- **Investigation Report**: ___/5 - Comments: _______________
|
||||
- **Technical Guide Creation**: ___/5 - Comments: _______________
|
||||
- **Logging Standards**: ___/5 - Comments: _______________
|
||||
- **Time Standards**: ___/5 - Comments: _______________
|
||||
- **Investigation Results**: Provide foundation for fix implementation
|
||||
- **Solution Requirements**: Define what needs to be built
|
||||
- **Testing Strategy**: Inform validation approach
|
||||
- **Documentation**: Support implementation guidance
|
||||
|
||||
### **Workflow Feedback**
|
||||
### **Feature Planning**
|
||||
|
||||
- **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?
|
||||
- **Root Cause Analysis**: Identify systemic issues
|
||||
- **Prevention Measures**: Plan future issue avoidance
|
||||
- **Architecture Improvements**: Identify structural enhancements
|
||||
- **Process Refinements**: Improve development workflows
|
||||
|
||||
### **Sub-Rule Improvements**
|
||||
### **Research and Documentation**
|
||||
|
||||
- **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
|
||||
- **Knowledge Base**: Contribute to troubleshooting guides
|
||||
- **Pattern Recognition**: Identify common failure modes
|
||||
- **Best Practices**: Develop investigation methodologies
|
||||
- **Team Training**: Improve investigation capabilities
|
||||
|
||||
---
|
||||
|
||||
**See also**:
|
||||
|
||||
- `.cursor/rules/meta_feature_planning.mdc` for planning investigation work
|
||||
- `.cursor/rules/meta_bug_fixing.mdc` for implementing fixes
|
||||
- `.cursor/rules/meta_feature_implementation.mdc` for preventive measures
|
||||
- `.cursor/rules/meta_feature_planning.mdc` for planning improvements
|
||||
- `.cursor/rules/meta_documentation.mdc` for documentation standards
|
||||
|
||||
**Status**: Active meta-rule for bug diagnosis
|
||||
**Priority**: High
|
||||
|
||||
@@ -10,6 +10,45 @@ This meta-rule bundles all the rules needed for implementing bug fixes
|
||||
with proper testing and validation. Use this after diagnosis when
|
||||
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
|
||||
|
||||
- **Post-Diagnosis**: After root cause is identified and fix is planned
|
||||
|
||||
383
.cursor/rules/meta_change_evaluation.mdc
Normal file
383
.cursor/rules/meta_change_evaluation.mdc
Normal file
@@ -0,0 +1,383 @@
|
||||
# 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,6 +14,109 @@ 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.
|
||||
|
||||
## 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
|
||||
|
||||
**ALWAYS** - These rules apply to every single prompt, regardless of the task
|
||||
@@ -165,6 +268,8 @@ or context. They form the foundation for all AI assistant behavior.
|
||||
- [ ] **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
|
||||
- [ ] **Workflow State**: Read current mode constraints from state file
|
||||
- [ ] **Constraint Validation**: Validate user request against current mode
|
||||
|
||||
### During Response Creation
|
||||
|
||||
@@ -172,6 +277,8 @@ or context. They form the foundation for all AI assistant behavior.
|
||||
- [ ] **Competence Hooks**: Include learning and collaboration elements
|
||||
- [ ] **Time Consistency**: Apply UTC standards for all time references
|
||||
- [ ] **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
|
||||
|
||||
@@ -179,6 +286,8 @@ or context. They form the foundation for all AI assistant behavior.
|
||||
- [ ] **Quality Check**: Ensure response meets competence standards
|
||||
- [ ] **Context Review**: Confirm application context was properly considered
|
||||
- [ ] **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
|
||||
|
||||
---
|
||||
|
||||
@@ -194,3 +303,9 @@ or context. They form the foundation for all AI assistant behavior.
|
||||
**Estimated Effort**: Ongoing reference
|
||||
**Dependencies**: All bundled sub-rules
|
||||
**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,6 +10,44 @@ This meta-rule bundles documentation-related rules to create comprehensive,
|
||||
educational documentation that increases human competence rather than just
|
||||
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
|
||||
|
||||
**Use this meta-rule when**:
|
||||
|
||||
@@ -10,6 +10,45 @@ This meta-rule bundles all the rules needed for building features with
|
||||
proper architecture and cross-platform support. Use this when implementing
|
||||
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
|
||||
|
||||
- **Feature Development**: Building new features from planning
|
||||
|
||||
@@ -10,6 +10,44 @@ This meta-rule bundles all the rules needed for comprehensive feature planning
|
||||
across all platforms. Use this when starting any new feature development,
|
||||
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
|
||||
|
||||
- **New Feature Development**: Planning features from concept to implementation
|
||||
|
||||
@@ -11,6 +11,44 @@ systematic investigation, analysis, evidence collection, or research tasks. It p
|
||||
a comprehensive framework for thorough, methodical research workflows that produce
|
||||
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
|
||||
|
||||
**RESEARCH TASKS** - Apply this meta-rule when:
|
||||
|
||||
356
.cursor/rules/playwright-test-investigation.mdc
Normal file
356
.cursor/rules/playwright-test-investigation.mdc
Normal file
@@ -0,0 +1,356 @@
|
||||
---
|
||||
description: when working with playwright tests either generating them or using them to test code
|
||||
alwaysApply: false
|
||||
---
|
||||
# Playwright Test Investigation — Harbor Pilot Directive
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: 2025-08-21T14:22Z
|
||||
**Status**: 🎯 **ACTIVE** - Playwright test debugging guidelines
|
||||
|
||||
## Objective
|
||||
Provide systematic approach for investigating Playwright test failures with focus on UI element conflicts, timing issues, and selector ambiguity.
|
||||
|
||||
## Context & Scope
|
||||
- **Audience**: Developers debugging Playwright test failures
|
||||
- **In scope**: Test failure analysis, selector conflicts, UI state investigation, timing issues
|
||||
- **Out of scope**: Test writing best practices, CI/CD configuration
|
||||
|
||||
## Artifacts & Links
|
||||
- Test results: `test-results/` directory
|
||||
- Error context: `error-context.md` files with page snapshots
|
||||
- Trace files: `trace.zip` files for failed tests
|
||||
- HTML reports: Interactive test reports with screenshots
|
||||
|
||||
## Environment & Preconditions
|
||||
- OS/Runtime: Linux/Windows/macOS with Node.js
|
||||
- Versions: Playwright test framework, browser drivers
|
||||
- Services: Local test server (localhost:8080), test data setup
|
||||
- Auth mode: None required for test investigation
|
||||
|
||||
## Architecture / Process Overview
|
||||
Playwright test investigation follows a systematic diagnostic workflow that leverages built-in debugging tools and error context analysis.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Test Failure] --> B[Check Error Context]
|
||||
B --> C[Analyze Page Snapshot]
|
||||
C --> D[Identify UI Conflicts]
|
||||
D --> E[Check Trace Files]
|
||||
E --> F[Verify Selector Uniqueness]
|
||||
F --> G[Test Selector Fixes]
|
||||
G --> H[Document Root Cause]
|
||||
|
||||
B --> I[Check Test Results Directory]
|
||||
I --> J[Locate Failed Test Results]
|
||||
J --> K[Extract Error Details]
|
||||
|
||||
D --> L[Multiple Alerts?]
|
||||
L --> M[Button Text Conflicts?]
|
||||
M --> N[Timing Issues?]
|
||||
|
||||
E --> O[Use Trace Viewer]
|
||||
O --> P[Analyze Action Sequence]
|
||||
P --> Q[Identify Failure Point]
|
||||
```
|
||||
|
||||
## Interfaces & Contracts
|
||||
|
||||
### Test Results Structure
|
||||
| Component | Format | Content | Validation |
|
||||
|---|---|---|---|
|
||||
| Error Context | Markdown | Page snapshot in YAML | Verify DOM state matches test expectations |
|
||||
| Trace Files | ZIP archive | Detailed execution trace | Use `npx playwright show-trace` |
|
||||
| HTML Reports | Interactive HTML | Screenshots, traces, logs | Check browser for full report |
|
||||
| JSON Results | JSON | Machine-readable results | Parse for automated analysis |
|
||||
|
||||
### Investigation Commands
|
||||
| Step | Command | Expected Output | Notes |
|
||||
|---|---|---|---|
|
||||
| Locate failed tests | `find test-results -name "*test-name*"` | Test result directories | Use exact test name patterns |
|
||||
| Check error context | `cat test-results/*/error-context.md` | Page snapshots | Look for UI state conflicts |
|
||||
| View traces | `npx playwright show-trace trace.zip` | Interactive trace viewer | Analyze exact failure sequence |
|
||||
|
||||
## Repro: End-to-End Investigation Procedure
|
||||
|
||||
### 1. Locate Failed Test Results
|
||||
```bash
|
||||
# Find all results for a specific test
|
||||
find test-results -name "*test-name*" -type d
|
||||
|
||||
# Check for error context files
|
||||
find test-results -name "error-context.md" | head -5
|
||||
```
|
||||
|
||||
### 2. Analyze Error Context
|
||||
```bash
|
||||
# Read error context for specific test
|
||||
cat test-results/test-name-test-description-browser/error-context.md
|
||||
|
||||
# Look for UI conflicts in page snapshot
|
||||
grep -A 10 -B 5 "button.*Yes\|button.*No" test-results/*/error-context.md
|
||||
```
|
||||
|
||||
### 3. Check Trace Files
|
||||
```bash
|
||||
# List available trace files
|
||||
find test-results -name "*.zip" | grep trace
|
||||
|
||||
# View trace in browser
|
||||
npx playwright show-trace test-results/test-name/trace.zip
|
||||
```
|
||||
|
||||
### 4. Investigate Selector Issues
|
||||
```typescript
|
||||
// Check for multiple elements with same text
|
||||
await page.locator('button:has-text("Yes")').count(); // Should be 1
|
||||
|
||||
// Use more specific selectors
|
||||
await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes")').click();
|
||||
```
|
||||
|
||||
## What Works (Evidence)
|
||||
- ✅ **Error context files** provide page snapshots showing exact DOM state at failure
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: `test-results/60-new-activity-New-offers-for-another-user-chromium/error-context.md` shows both alerts visible
|
||||
- **Verify at**: Error context files in test results directory
|
||||
|
||||
- ✅ **Trace files** capture detailed execution sequence for failed tests
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: `trace.zip` files available for all failed tests
|
||||
- **Verify at**: Use `npx playwright show-trace <filename>`
|
||||
|
||||
- ✅ **Page snapshots** reveal UI conflicts like multiple alerts with duplicate button text
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: YAML snapshots show registration + export alerts simultaneously
|
||||
- **Verify at**: Error context markdown files
|
||||
|
||||
## What Doesn't (Evidence & Hypotheses)
|
||||
- ❌ **Generic selectors** fail with multiple similar elements at `test-playwright/testUtils.ts:161`
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: `button:has-text("Yes")` matches both "Yes" and "Yes, Export Data"
|
||||
- **Hypothesis**: Selector ambiguity due to multiple alerts with conflicting button text
|
||||
- **Next probe**: Use more specific selectors or dismiss alerts sequentially
|
||||
|
||||
- ❌ **Timing-dependent tests** fail due to alert stacking at `src/views/ContactsView.vue:860,1283`
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: Both alerts use identical 1000ms delays, ensuring simultaneous display
|
||||
- **Hypothesis**: Race condition between alert displays creates UI conflicts
|
||||
- **Next probe**: Implement alert queuing or prevent overlapping alerts
|
||||
|
||||
## Risks, Limits, Assumptions
|
||||
- **Trace file size**: Large trace files may impact storage and analysis time
|
||||
- **Browser compatibility**: Trace viewer requires specific browser support
|
||||
- **Test isolation**: Shared state between tests may affect investigation results
|
||||
- **Timing sensitivity**: Tests may pass/fail based on system performance
|
||||
|
||||
## Next Steps
|
||||
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
||||
|---|---|---|---|
|
||||
| Development Team | Fix test selectors for multiple alerts | All tests pass consistently | 2025-08-22 |
|
||||
| Development Team | Implement alert queuing system | No overlapping alerts with conflicting buttons | 2025-08-25 |
|
||||
| Development Team | Add test IDs to alert buttons | Unique selectors for all UI elements | 2025-08-28 |
|
||||
|
||||
## References
|
||||
- [Playwright Trace Viewer Documentation](https://playwright.dev/docs/trace-viewer)
|
||||
- [Playwright Test Results](https://playwright.dev/docs/test-reporters)
|
||||
- [Test Investigation Workflow](./research_diagnostic.mdc)
|
||||
|
||||
## Competence Hooks
|
||||
- **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
|
||||
- **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?"
|
||||
|
||||
## Collaboration Hooks
|
||||
- **Reviewers**: QA team, test automation engineers
|
||||
- **Sign-off checklist**: Error context analyzed, trace files reviewed, root cause identified, fix implemented and tested
|
||||
|
||||
## Assumptions & Limits
|
||||
- Test results directory structure follows Playwright conventions
|
||||
- Trace files are enabled in configuration (`trace: "retain-on-failure"`)
|
||||
- Error context files contain valid YAML page snapshots
|
||||
- Browser environment supports trace viewer functionality
|
||||
|
||||
---
|
||||
|
||||
**Status**: Active investigation directive
|
||||
**Priority**: High
|
||||
**Maintainer**: Development team
|
||||
**Next Review**: 2025-09-21
|
||||
# Playwright Test Investigation — Harbor Pilot Directive
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: 2025-08-21T14:22Z
|
||||
**Status**: 🎯 **ACTIVE** - Playwright test debugging guidelines
|
||||
|
||||
## Objective
|
||||
Provide systematic approach for investigating Playwright test failures with focus on UI element conflicts, timing issues, and selector ambiguity.
|
||||
|
||||
## Context & Scope
|
||||
- **Audience**: Developers debugging Playwright test failures
|
||||
- **In scope**: Test failure analysis, selector conflicts, UI state investigation, timing issues
|
||||
- **Out of scope**: Test writing best practices, CI/CD configuration
|
||||
|
||||
## Artifacts & Links
|
||||
- Test results: `test-results/` directory
|
||||
- Error context: `error-context.md` files with page snapshots
|
||||
- Trace files: `trace.zip` files for failed tests
|
||||
- HTML reports: Interactive test reports with screenshots
|
||||
|
||||
## Environment & Preconditions
|
||||
- OS/Runtime: Linux/Windows/macOS with Node.js
|
||||
- Versions: Playwright test framework, browser drivers
|
||||
- Services: Local test server (localhost:8080), test data setup
|
||||
- Auth mode: None required for test investigation
|
||||
|
||||
## Architecture / Process Overview
|
||||
Playwright test investigation follows a systematic diagnostic workflow that leverages built-in debugging tools and error context analysis.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Test Failure] --> B[Check Error Context]
|
||||
B --> C[Analyze Page Snapshot]
|
||||
C --> D[Identify UI Conflicts]
|
||||
D --> E[Check Trace Files]
|
||||
E --> F[Verify Selector Uniqueness]
|
||||
F --> G[Test Selector Fixes]
|
||||
G --> H[Document Root Cause]
|
||||
|
||||
B --> I[Check Test Results Directory]
|
||||
I --> J[Locate Failed Test Results]
|
||||
J --> K[Extract Error Details]
|
||||
|
||||
D --> L[Multiple Alerts?]
|
||||
L --> M[Button Text Conflicts?]
|
||||
M --> N[Timing Issues?]
|
||||
|
||||
E --> O[Use Trace Viewer]
|
||||
O --> P[Analyze Action Sequence]
|
||||
P --> Q[Identify Failure Point]
|
||||
```
|
||||
|
||||
## Interfaces & Contracts
|
||||
|
||||
### Test Results Structure
|
||||
| Component | Format | Content | Validation |
|
||||
|---|---|---|---|
|
||||
| Error Context | Markdown | Page snapshot in YAML | Verify DOM state matches test expectations |
|
||||
| Trace Files | ZIP archive | Detailed execution trace | Use `npx playwright show-trace` |
|
||||
| HTML Reports | Interactive HTML | Screenshots, traces, logs | Check browser for full report |
|
||||
| JSON Results | JSON | Machine-readable results | Parse for automated analysis |
|
||||
|
||||
### Investigation Commands
|
||||
| Step | Command | Expected Output | Notes |
|
||||
|---|---|---|---|
|
||||
| Locate failed tests | `find test-results -name "*test-name*"` | Test result directories | Use exact test name patterns |
|
||||
| Check error context | `cat test-results/*/error-context.md` | Page snapshots | Look for UI state conflicts |
|
||||
| View traces | `npx playwright show-trace trace.zip` | Interactive trace viewer | Analyze exact failure sequence |
|
||||
|
||||
## Repro: End-to-End Investigation Procedure
|
||||
|
||||
### 1. Locate Failed Test Results
|
||||
```bash
|
||||
# Find all results for a specific test
|
||||
find test-results -name "*test-name*" -type d
|
||||
|
||||
# Check for error context files
|
||||
find test-results -name "error-context.md" | head -5
|
||||
```
|
||||
|
||||
### 2. Analyze Error Context
|
||||
```bash
|
||||
# Read error context for specific test
|
||||
cat test-results/test-name-test-description-browser/error-context.md
|
||||
|
||||
# Look for UI conflicts in page snapshot
|
||||
grep -A 10 -B 5 "button.*Yes\|button.*No" test-results/*/error-context.md
|
||||
```
|
||||
|
||||
### 3. Check Trace Files
|
||||
```bash
|
||||
# List available trace files
|
||||
find test-results -name "*.zip" | grep trace
|
||||
|
||||
# View trace in browser
|
||||
npx playwright show-trace test-results/test-name/trace.zip
|
||||
```
|
||||
|
||||
### 4. Investigate Selector Issues
|
||||
```typescript
|
||||
// Check for multiple elements with same text
|
||||
await page.locator('button:has-text("Yes")').count(); // Should be 1
|
||||
|
||||
// Use more specific selectors
|
||||
await page.locator('div[role="alert"]:has-text("Register") button:has-text("Yes")').click();
|
||||
```
|
||||
|
||||
## What Works (Evidence)
|
||||
- ✅ **Error context files** provide page snapshots showing exact DOM state at failure
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: `test-results/60-new-activity-New-offers-for-another-user-chromium/error-context.md` shows both alerts visible
|
||||
- **Verify at**: Error context files in test results directory
|
||||
|
||||
- ✅ **Trace files** capture detailed execution sequence for failed tests
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: `trace.zip` files available for all failed tests
|
||||
- **Verify at**: Use `npx playwright show-trace <filename>`
|
||||
|
||||
- ✅ **Page snapshots** reveal UI conflicts like multiple alerts with duplicate button text
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: YAML snapshots show registration + export alerts simultaneously
|
||||
- **Verify at**: Error context markdown files
|
||||
|
||||
## What Doesn't (Evidence & Hypotheses)
|
||||
- ❌ **Generic selectors** fail with multiple similar elements at `test-playwright/testUtils.ts:161`
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: `button:has-text("Yes")` matches both "Yes" and "Yes, Export Data"
|
||||
- **Hypothesis**: Selector ambiguity due to multiple alerts with conflicting button text
|
||||
- **Next probe**: Use more specific selectors or dismiss alerts sequentially
|
||||
|
||||
- ❌ **Timing-dependent tests** fail due to alert stacking at `src/views/ContactsView.vue:860,1283`
|
||||
- **Time**: 2025-08-21T14:22Z
|
||||
- **Evidence**: Both alerts use identical 1000ms delays, ensuring simultaneous display
|
||||
- **Hypothesis**: Race condition between alert displays creates UI conflicts
|
||||
- **Next probe**: Implement alert queuing or prevent overlapping alerts
|
||||
|
||||
## Risks, Limits, Assumptions
|
||||
- **Trace file size**: Large trace files may impact storage and analysis time
|
||||
- **Browser compatibility**: Trace viewer requires specific browser support
|
||||
- **Test isolation**: Shared state between tests may affect investigation results
|
||||
- **Timing sensitivity**: Tests may pass/fail based on system performance
|
||||
|
||||
## Next Steps
|
||||
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
||||
|---|---|---|---|
|
||||
| Development Team | Fix test selectors for multiple alerts | All tests pass consistently | 2025-08-22 |
|
||||
| Development Team | Implement alert queuing system | No overlapping alerts with conflicting buttons | 2025-08-25 |
|
||||
| Development Team | Add test IDs to alert buttons | Unique selectors for all UI elements | 2025-08-28 |
|
||||
|
||||
## References
|
||||
- [Playwright Trace Viewer Documentation](https://playwright.dev/docs/trace-viewer)
|
||||
- [Playwright Test Results](https://playwright.dev/docs/test-reporters)
|
||||
- [Test Investigation Workflow](./research_diagnostic.mdc)
|
||||
|
||||
## Competence Hooks
|
||||
- **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
|
||||
- **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?"
|
||||
|
||||
## Collaboration Hooks
|
||||
- **Reviewers**: QA team, test automation engineers
|
||||
- **Sign-off checklist**: Error context analyzed, trace files reviewed, root cause identified, fix implemented and tested
|
||||
|
||||
## Assumptions & Limits
|
||||
- Test results directory structure follows Playwright conventions
|
||||
- Trace files are enabled in configuration (`trace: "retain-on-failure"`)
|
||||
- Error context files contain valid YAML page snapshots
|
||||
- Browser environment supports trace viewer functionality
|
||||
|
||||
---
|
||||
|
||||
**Status**: Active investigation directive
|
||||
**Priority**: High
|
||||
**Maintainer**: Development team
|
||||
**Next Review**: 2025-09-21
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -54,6 +54,9 @@ build_logs/
|
||||
# Guard feedback logs (for continuous improvement analysis)
|
||||
.guard-feedback.log
|
||||
|
||||
# Workflow state file (contains dynamic state, not version controlled)
|
||||
.cursor/rules/.workflow_state.json
|
||||
|
||||
# PWA icon files generated by capacitor-assets
|
||||
icons
|
||||
|
||||
|
||||
39
BUILDING.md
39
BUILDING.md
@@ -2768,6 +2768,45 @@ 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
|
||||
most up-to-date information, refer to the actual script files and Vite
|
||||
configuration files in the repository.
|
||||
|
||||
@@ -60,13 +60,49 @@ For complex tasks, you might combine multiple meta-rules:
|
||||
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
|
||||
|
||||
### **Example 1: Bug Investigation**
|
||||
### **Example 1: Bug Investigation (Flexible Flow)**
|
||||
|
||||
**Scenario**: User reports that the contact list isn't loading properly
|
||||
|
||||
**Meta-Rule Selection**:
|
||||
**Initial Meta-Rule Selection**:
|
||||
```
|
||||
meta_core_always_on + meta_research + meta_bug_diagnosis
|
||||
```
|
||||
@@ -76,13 +112,15 @@ meta_core_always_on + meta_research + meta_bug_diagnosis
|
||||
- **Research**: Systematic investigation methodology, evidence collection
|
||||
- **Bug Diagnosis**: Defect analysis framework, root cause identification
|
||||
|
||||
**Workflow**:
|
||||
**Flexible Workflow**:
|
||||
1. Apply core always-on for foundation
|
||||
2. Use research meta-rule for systematic investigation
|
||||
3. Apply bug diagnosis for defect analysis
|
||||
4. Follow the bundled workflow automatically
|
||||
3. Switch to bug diagnosis when you have enough evidence
|
||||
4. **Can go back to research** if diagnosis reveals new questions
|
||||
5. **Can jump to bug fixing** if root cause is obvious
|
||||
6. **Can document findings** at any phase
|
||||
|
||||
### **Example 2: Feature Development**
|
||||
### **Example 2: Feature Development (Iterative Flow)**
|
||||
|
||||
**Scenario**: Building a new contact search feature
|
||||
|
||||
@@ -96,12 +134,15 @@ meta_core_always_on + meta_feature_planning + meta_feature_implementation
|
||||
- **Feature Planning**: Requirements analysis, architecture planning
|
||||
- **Feature Implementation**: Development workflow, testing strategy
|
||||
|
||||
**Workflow**:
|
||||
**Iterative Workflow**:
|
||||
1. Start with core always-on
|
||||
2. Use feature planning for design and requirements
|
||||
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**
|
||||
### **Example 3: Documentation Creation (Parallel Flow)**
|
||||
|
||||
**Scenario**: Writing a migration guide for the new database system
|
||||
|
||||
@@ -114,10 +155,13 @@ meta_core_always_on + meta_documentation
|
||||
- **Core Always-On**: Foundation and context
|
||||
- **Documentation**: Educational focus, templates, quality standards
|
||||
|
||||
**Workflow**:
|
||||
**Parallel Workflow**:
|
||||
1. Apply core always-on for foundation
|
||||
2. Use documentation meta-rule for educational content creation
|
||||
3. Follow educational templates and quality standards
|
||||
3. **Can research** while documenting if you need more information
|
||||
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
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
:weight="2"
|
||||
color="#3b82f6"
|
||||
fill-color="#3b82f6"
|
||||
fill-opacity="0.2"
|
||||
:fill-opacity="0.2"
|
||||
/>
|
||||
</l-map>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
import { AppString, NotificationIface } from "../constants/app";
|
||||
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "../utils/notify";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
@Component({
|
||||
mixins: [PlatformServiceMixin],
|
||||
@@ -44,26 +45,49 @@ export default class TopMessage extends Vue {
|
||||
this.notify = createNotifyHelpers(this.$notify);
|
||||
|
||||
try {
|
||||
// Ultra-concise cached settings loading - replaces 50+ lines of logic!
|
||||
const settings = await this.$accountSettings(undefined, {
|
||||
activeDid: undefined,
|
||||
apiServer: AppString.PROD_ENDORSER_API_SERVER,
|
||||
// Load settings without overriding database values - fixes settings inconsistency
|
||||
logger.debug("[TopMessage] 📥 Loading settings without overrides...");
|
||||
const settings = await this.$accountSettings();
|
||||
|
||||
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 (
|
||||
settings.warnIfTestServer &&
|
||||
settings.apiServer &&
|
||||
settings.apiServer !== AppString.PROD_ENDORSER_API_SERVER
|
||||
) {
|
||||
const didPrefix = settings.activeDid?.slice(11, 15);
|
||||
this.message = "You're not using prod, user " + didPrefix;
|
||||
logger.debug("[TopMessage] ⚠️ Test server warning displayed:", {
|
||||
apiServer: settings.apiServer,
|
||||
didPrefix: didPrefix,
|
||||
});
|
||||
} else if (
|
||||
settings.warnIfProdServer &&
|
||||
settings.apiServer &&
|
||||
settings.apiServer === AppString.PROD_ENDORSER_API_SERVER
|
||||
) {
|
||||
const didPrefix = settings.activeDid?.slice(11, 15);
|
||||
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) {
|
||||
logger.error("[TopMessage] ❌ Error loading settings:", err);
|
||||
this.notify.error(JSON.stringify(err), TIMEOUTS.MODAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +486,15 @@ const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
|
||||
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
|
||||
* @param {string} handleId - Plan handle ID
|
||||
@@ -505,40 +514,140 @@ export async function getPlanFromCache(
|
||||
if (!handleId) {
|
||||
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;
|
||||
|
||||
log(
|
||||
"[EndorserServer] Plan cache is empty for handle",
|
||||
handleId,
|
||||
" Got data:",
|
||||
JSON.stringify(resp.data),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"[EndorserServer] Failed to load plan with handle",
|
||||
handleId,
|
||||
" Got error:",
|
||||
JSON.stringify(error),
|
||||
);
|
||||
}
|
||||
// Check cache first (existing behavior)
|
||||
const cred = planCache.get(handleId);
|
||||
if (cred) {
|
||||
return cred;
|
||||
}
|
||||
|
||||
// Check if request is already in flight (NEW: request deduplication)
|
||||
if (inFlightRequests.has(handleId)) {
|
||||
logger.debug(
|
||||
"[Plan Loading] 🔄 Request already in flight, reusing promise:",
|
||||
{
|
||||
handleId,
|
||||
requesterDid,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1019,19 +1128,82 @@ export async function createAndSubmitClaim(
|
||||
|
||||
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
|
||||
const payload = JSON.stringify({ jwtEncoded: vcJwt });
|
||||
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, {
|
||||
headers: {
|
||||
"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 };
|
||||
} catch (error: unknown) {
|
||||
logger.error("Error submitting claim:", error);
|
||||
// Enhanced error logging with comprehensive context
|
||||
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 =
|
||||
serverMessageForUser(error) ||
|
||||
(error && typeof error === "object" && "message" in error
|
||||
@@ -1494,14 +1666,56 @@ export async function fetchEndorserRateLimits(
|
||||
) {
|
||||
const url = `${apiServer}/api/report/rateLimits`;
|
||||
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 {
|
||||
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;
|
||||
} 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(
|
||||
`[fetchEndorserRateLimits] Error for DID ${issuerDid}:`,
|
||||
errorStringForLog(error),
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1514,8 +1728,53 @@ export async function fetchEndorserRateLimits(
|
||||
* @param {string} issuerDid - The DID for which to check rate limits.
|
||||
* @returns {Promise<AxiosResponse>} The Axios response object.
|
||||
*/
|
||||
export async function fetchImageRateLimits(axios: Axios, issuerDid: string) {
|
||||
const url = DEFAULT_IMAGE_API_SERVER + "/image-limits";
|
||||
export async function fetchImageRateLimits(
|
||||
axios: Axios,
|
||||
issuerDid: string,
|
||||
imageServer?: string,
|
||||
) {
|
||||
const server = imageServer || DEFAULT_IMAGE_API_SERVER;
|
||||
const url = server + "/image-limits";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,13 +974,16 @@ export async function importFromMnemonic(
|
||||
const firstName = settings[0];
|
||||
const isRegistered = settings[1];
|
||||
|
||||
logger.info("[importFromMnemonic] Test User #0 settings verification", {
|
||||
did: newId.did,
|
||||
firstName,
|
||||
isRegistered,
|
||||
expectedFirstName: "User Zero",
|
||||
expectedIsRegistered: true,
|
||||
});
|
||||
logger.debug(
|
||||
"[importFromMnemonic] Test User #0 settings verification",
|
||||
{
|
||||
did: newId.did,
|
||||
firstName,
|
||||
isRegistered,
|
||||
expectedFirstName: "User Zero",
|
||||
expectedIsRegistered: true,
|
||||
},
|
||||
);
|
||||
|
||||
// If settings weren't saved correctly, try individual updates
|
||||
if (firstName !== "User Zero" || isRegistered !== 1) {
|
||||
@@ -1006,7 +1009,7 @@ export async function importFromMnemonic(
|
||||
|
||||
if (retryResult?.values?.length) {
|
||||
const retrySettings = retryResult.values[0];
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"[importFromMnemonic] Test User #0 settings after retry",
|
||||
{
|
||||
firstName: retrySettings[0],
|
||||
|
||||
@@ -13,6 +13,15 @@ const platform = process.env.VITE_PLATFORM || "web";
|
||||
|
||||
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
|
||||
if (platform === "capacitor") {
|
||||
logger.info(`[Main] 📱 Loading Capacitor-specific entry point`);
|
||||
|
||||
@@ -327,7 +327,7 @@ router.onError(errorHandler); // Assign the error handler to the router instance
|
||||
* @param next - Navigation function
|
||||
*/
|
||||
router.beforeEach(async (to, _from, next) => {
|
||||
logger.info(`[Router] 🧭 Navigation guard triggered:`, {
|
||||
logger.debug(`[Router] 🧭 Navigation guard triggered:`, {
|
||||
from: _from?.path || "none",
|
||||
to: to.path,
|
||||
name: to.name,
|
||||
@@ -337,6 +337,22 @@ router.beforeEach(async (to, _from, next) => {
|
||||
});
|
||||
|
||||
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
|
||||
const skipIdentityRoutes = [
|
||||
"/start",
|
||||
@@ -352,11 +368,11 @@ router.beforeEach(async (to, _from, next) => {
|
||||
return next();
|
||||
}
|
||||
|
||||
logger.info(`[Router] 🔍 Checking user identity for route: ${to.path}`);
|
||||
logger.debug(`[Router] 🔍 Checking user identity for route: ${to.path}`);
|
||||
|
||||
// Check if user has any identities
|
||||
const allMyDids = await retrieveAccountDids();
|
||||
logger.info(`[Router] 📋 Found ${allMyDids.length} user identities`);
|
||||
logger.debug(`[Router] 📋 Found ${allMyDids.length} user identities`);
|
||||
|
||||
if (allMyDids.length === 0) {
|
||||
logger.info("[Router] ⚠️ No identities found, creating default identity");
|
||||
@@ -366,7 +382,7 @@ router.beforeEach(async (to, _from, next) => {
|
||||
|
||||
logger.info("[Router] ✅ Default identity created successfully");
|
||||
} else {
|
||||
logger.info(
|
||||
logger.debug(
|
||||
`[Router] ✅ User has ${allMyDids.length} identities, proceeding`,
|
||||
);
|
||||
}
|
||||
@@ -392,7 +408,7 @@ router.beforeEach(async (to, _from, next) => {
|
||||
|
||||
// Add navigation success logging
|
||||
router.afterEach((to, from) => {
|
||||
logger.info(`[Router] ✅ Navigation completed:`, {
|
||||
logger.debug(`[Router] ✅ Navigation completed:`, {
|
||||
from: from?.path || "none",
|
||||
to: to.path,
|
||||
name: to.name,
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -437,17 +437,17 @@ export const PlatformServiceMixin = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility method for retrieving and parsing settings
|
||||
* Utility method for retrieving master settings
|
||||
* Common pattern used across many components
|
||||
*/
|
||||
async $getSettings(
|
||||
key: string,
|
||||
async $getMasterSettings(
|
||||
fallback: Settings | null = null,
|
||||
): Promise<Settings | null> {
|
||||
try {
|
||||
// Master settings: query by id
|
||||
const result = await this.$dbQuery(
|
||||
"SELECT * FROM settings WHERE id = ? OR accountDid = ?",
|
||||
[key, key],
|
||||
"SELECT * FROM settings WHERE id = ?",
|
||||
[MASTER_SETTINGS_KEY],
|
||||
);
|
||||
|
||||
if (!result?.values?.length) {
|
||||
@@ -472,8 +472,7 @@ export const PlatformServiceMixin = {
|
||||
|
||||
return settings;
|
||||
} catch (error) {
|
||||
logger.error(`[Settings Trace] ❌ Failed to get settings:`, {
|
||||
key,
|
||||
logger.error(`[Settings Trace] ❌ Failed to get master settings:`, {
|
||||
error,
|
||||
});
|
||||
return fallback;
|
||||
@@ -491,10 +490,7 @@ export const PlatformServiceMixin = {
|
||||
): Promise<Settings> {
|
||||
try {
|
||||
// Get default settings
|
||||
const defaultSettings = await this.$getSettings(
|
||||
defaultKey,
|
||||
defaultFallback,
|
||||
);
|
||||
const defaultSettings = await this.$getMasterSettings(defaultFallback);
|
||||
|
||||
// If no account DID, return defaults
|
||||
if (!accountDid) {
|
||||
@@ -757,19 +753,20 @@ export const PlatformServiceMixin = {
|
||||
* @returns Fresh settings object from database
|
||||
*/
|
||||
async $settings(defaults: Settings = {}): Promise<Settings> {
|
||||
const settings = await this.$getSettings(MASTER_SETTINGS_KEY, defaults);
|
||||
const settings = await this.$getMasterSettings(defaults);
|
||||
|
||||
if (!settings) {
|
||||
return defaults;
|
||||
}
|
||||
|
||||
// **ELECTRON-SPECIFIC FIX**: Apply platform-specific API server override
|
||||
// This ensures Electron always uses production endpoints regardless of cached settings
|
||||
if (process.env.VITE_PLATFORM === "electron") {
|
||||
// FIXED: Remove forced override - respect user preferences
|
||||
// Only set default if no user preference exists
|
||||
if (!settings.apiServer && process.env.VITE_PLATFORM === "electron") {
|
||||
// Import constants dynamically to get platform-specific values
|
||||
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
||||
"../constants/app"
|
||||
);
|
||||
// Only set if user hasn't specified a preference
|
||||
settings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
||||
}
|
||||
|
||||
@@ -789,10 +786,7 @@ export const PlatformServiceMixin = {
|
||||
): Promise<Settings> {
|
||||
try {
|
||||
// Get default settings first
|
||||
const defaultSettings = await this.$getSettings(
|
||||
MASTER_SETTINGS_KEY,
|
||||
defaults,
|
||||
);
|
||||
const defaultSettings = await this.$getMasterSettings(defaults);
|
||||
|
||||
if (!defaultSettings) {
|
||||
return defaults;
|
||||
@@ -813,14 +807,17 @@ export const PlatformServiceMixin = {
|
||||
defaultSettings,
|
||||
);
|
||||
|
||||
// **ELECTRON-SPECIFIC FIX**: Force production API endpoints for Electron
|
||||
// This ensures Electron doesn't use localhost development servers that might be saved in user settings
|
||||
if (process.env.VITE_PLATFORM === "electron") {
|
||||
// FIXED: Remove forced override - respect user preferences
|
||||
// Only set default if no user preference exists
|
||||
if (
|
||||
!mergedSettings.apiServer &&
|
||||
process.env.VITE_PLATFORM === "electron"
|
||||
) {
|
||||
// Import constants dynamically to get platform-specific values
|
||||
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
||||
"../constants/app"
|
||||
);
|
||||
|
||||
// Only set if user hasn't specified a preference
|
||||
mergedSettings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
||||
}
|
||||
|
||||
@@ -1574,10 +1571,7 @@ export const PlatformServiceMixin = {
|
||||
async $debugMergedSettings(did: string): Promise<void> {
|
||||
try {
|
||||
// Get default settings
|
||||
const defaultSettings = await this.$getSettings(
|
||||
MASTER_SETTINGS_KEY,
|
||||
{},
|
||||
);
|
||||
const defaultSettings = await this.$getMasterSettings({});
|
||||
logger.info(
|
||||
`[PlatformServiceMixin] Default settings:`,
|
||||
defaultSettings,
|
||||
@@ -1624,10 +1618,7 @@ export interface IPlatformServiceMixin {
|
||||
): Promise<QueryExecResult | undefined>;
|
||||
$dbExec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
|
||||
$dbGetOneRow(sql: string, params?: unknown[]): Promise<unknown[] | undefined>;
|
||||
$getSettings(
|
||||
key: string,
|
||||
fallback?: Settings | null,
|
||||
): Promise<Settings | null>;
|
||||
$getMasterSettings(fallback?: Settings | null): Promise<Settings | null>;
|
||||
$getMergedSettings(
|
||||
defaultKey: string,
|
||||
accountDid?: string,
|
||||
@@ -1749,10 +1740,7 @@ declare module "@vue/runtime-core" {
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<unknown[] | undefined>;
|
||||
$getSettings(
|
||||
key: string,
|
||||
defaults?: Settings | null,
|
||||
): Promise<Settings | null>;
|
||||
$getMasterSettings(defaults?: Settings | null): Promise<Settings | null>;
|
||||
$getMergedSettings(
|
||||
key: string,
|
||||
did?: string,
|
||||
|
||||
298
src/utils/errorHandler.ts
Normal file
298
src/utils/errorHandler.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
482
src/utils/performanceOptimizer.ts
Normal file
482
src/utils/performanceOptimizer.ts
Normal file
@@ -0,0 +1,482 @@
|
||||
/**
|
||||
* 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();
|
||||
};
|
||||
@@ -58,8 +58,10 @@
|
||||
v-if="!isRegistered"
|
||||
:passkeys-enabled="PASSKEYS_ENABLED"
|
||||
:given-name="givenName"
|
||||
message="Before you can publicly announce a new project or time commitment,
|
||||
a friend needs to register you."
|
||||
:message="
|
||||
`Before you can publicly announce a new project or time commitment, ` +
|
||||
`a friend needs to register you.`
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- Notifications -->
|
||||
@@ -752,6 +754,7 @@ import "leaflet/dist/leaflet.css";
|
||||
|
||||
import { Buffer } from "buffer/";
|
||||
import "dexie-export-import";
|
||||
|
||||
// @ts-expect-error - they aren't exporting it but it's there
|
||||
import { ImportProgress } from "dexie-export-import";
|
||||
import { LeafletMouseEvent } from "leaflet";
|
||||
@@ -813,11 +816,13 @@ import {
|
||||
isApiError,
|
||||
ImportContent,
|
||||
} from "@/interfaces/accountView";
|
||||
import {
|
||||
ProfileService,
|
||||
createProfileService,
|
||||
ProfileData,
|
||||
} from "@/services/ProfileService";
|
||||
// Profile data interface (inlined from ProfileService)
|
||||
interface ProfileData {
|
||||
description: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
includeLocation: boolean;
|
||||
}
|
||||
|
||||
const inputImportFileNameRef = ref<Blob>();
|
||||
|
||||
@@ -916,7 +921,6 @@ export default class AccountViewView extends Vue {
|
||||
imageLimits: ImageRateLimits | null = null;
|
||||
limitsMessage: string = "";
|
||||
|
||||
private profileService!: ProfileService;
|
||||
private notify!: ReturnType<typeof createNotifyHelpers>;
|
||||
|
||||
created() {
|
||||
@@ -926,7 +930,10 @@ export default class AccountViewView extends Vue {
|
||||
// This prevents the "Cannot read properties of undefined (reading 'Default')" error
|
||||
if (L.Icon.Default) {
|
||||
// Type-safe way to handle Leaflet icon prototype
|
||||
const iconDefault = L.Icon.Default.prototype as Record<string, unknown>;
|
||||
const iconDefault = L.Icon.Default.prototype as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
if ("_getIconUrl" in iconDefault) {
|
||||
delete iconDefault._getIconUrl;
|
||||
}
|
||||
@@ -948,17 +955,21 @@ export default class AccountViewView extends Vue {
|
||||
* @throws Will display specific messages to the user based on different errors.
|
||||
*/
|
||||
async mounted(): Promise<void> {
|
||||
this.profileService = createProfileService(
|
||||
this.axios,
|
||||
this.partnerApiServer,
|
||||
);
|
||||
try {
|
||||
await this.initializeState();
|
||||
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) {
|
||||
try {
|
||||
const profile = await this.profileService.loadProfile(this.activeDid);
|
||||
const profile = await this.loadProfile(this.activeDid);
|
||||
if (profile) {
|
||||
this.userProfileDesc = profile.description;
|
||||
this.userProfileLatitude = profile.latitude;
|
||||
@@ -1411,21 +1422,24 @@ export default class AccountViewView extends Vue {
|
||||
|
||||
async checkLimits(): Promise<void> {
|
||||
this.loadingLimits = true;
|
||||
const did = this.activeDid;
|
||||
if (!did) {
|
||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IDENTIFIER;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const did = this.activeDid;
|
||||
|
||||
if (!did) {
|
||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IDENTIFIER;
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$saveUserSettings(did, {
|
||||
apiServer: this.apiServer,
|
||||
partnerApiServer: this.partnerApiServer,
|
||||
webPushServer: this.webPushServer,
|
||||
});
|
||||
|
||||
const imageResp = await fetchImageRateLimits(this.axios, did);
|
||||
const imageResp = await fetchImageRateLimits(
|
||||
this.axios,
|
||||
did,
|
||||
this.DEFAULT_IMAGE_API_SERVER,
|
||||
);
|
||||
|
||||
if (imageResp.status === 200) {
|
||||
this.imageLimits = imageResp.data;
|
||||
@@ -1451,7 +1465,26 @@ export default class AccountViewView extends Vue {
|
||||
} catch (error) {
|
||||
this.limitsMessage =
|
||||
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);
|
||||
} finally {
|
||||
this.loadingLimits = false;
|
||||
@@ -1459,24 +1492,70 @@ export default class AccountViewView extends Vue {
|
||||
}
|
||||
|
||||
async onClickSaveApiServer(): Promise<void> {
|
||||
await this.$saveSettings({
|
||||
apiServer: this.apiServerInput,
|
||||
// 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(),
|
||||
});
|
||||
this.apiServer = this.apiServerInput;
|
||||
|
||||
await this.$saveSettings({
|
||||
apiServer: newApiServer,
|
||||
});
|
||||
this.apiServer = newApiServer;
|
||||
|
||||
// Add this line to save to user-specific settings
|
||||
await this.$saveUserSettings(this.activeDid, {
|
||||
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> {
|
||||
await this.$saveSettings({
|
||||
partnerApiServer: this.partnerApiServerInput,
|
||||
// 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(),
|
||||
});
|
||||
this.partnerApiServer = this.partnerApiServerInput;
|
||||
|
||||
await this.$saveSettings({
|
||||
partnerApiServer: newPartnerServer,
|
||||
});
|
||||
this.partnerApiServer = newPartnerServer;
|
||||
|
||||
await this.$saveUserSettings(this.activeDid, {
|
||||
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> {
|
||||
@@ -1550,7 +1629,6 @@ export default class AccountViewView extends Vue {
|
||||
|
||||
onMapReady(map: L.Map): void {
|
||||
try {
|
||||
logger.debug("Map ready event fired, map object:", map);
|
||||
// doing this here instead of on the l-map element avoids a recentering after a drag then zoom at startup
|
||||
const zoom =
|
||||
this.userProfileLatitude && this.userProfileLongitude ? 12 : 2;
|
||||
@@ -1579,19 +1657,15 @@ export default class AccountViewView extends Vue {
|
||||
// Try to set map ready after component is mounted
|
||||
setTimeout(() => {
|
||||
this.isMapReady = true;
|
||||
logger.debug("Map ready set to true after mounted");
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Fallback method to handle map initialization failures
|
||||
private handleMapInitFailure(): void {
|
||||
logger.debug("Starting map initialization timeout (5 seconds)");
|
||||
setTimeout(() => {
|
||||
if (!this.isMapReady) {
|
||||
logger.warn("Map failed to initialize, forcing ready state");
|
||||
this.isMapReady = true;
|
||||
} else {
|
||||
logger.debug("Map initialized successfully, timeout not needed");
|
||||
}
|
||||
}, 5000); // 5 second timeout
|
||||
}
|
||||
@@ -1615,7 +1689,7 @@ export default class AccountViewView extends Vue {
|
||||
|
||||
logger.debug("Saving profile data:", profileData);
|
||||
|
||||
const success = await this.profileService.saveProfile(
|
||||
const success = await this.saveProfileToServer(
|
||||
this.activeDid,
|
||||
profileData,
|
||||
);
|
||||
@@ -1634,7 +1708,7 @@ export default class AccountViewView extends Vue {
|
||||
|
||||
toggleUserProfileLocation(): void {
|
||||
try {
|
||||
const updated = this.profileService.toggleProfileLocation({
|
||||
const updated = this.toggleProfileLocation({
|
||||
description: this.userProfileDesc,
|
||||
latitude: this.userProfileLatitude,
|
||||
longitude: this.userProfileLongitude,
|
||||
@@ -1679,8 +1753,7 @@ export default class AccountViewView extends Vue {
|
||||
|
||||
async deleteProfile(): Promise<void> {
|
||||
try {
|
||||
logger.debug("Attempting to delete profile for DID:", this.activeDid);
|
||||
const success = await this.profileService.deleteProfile(this.activeDid);
|
||||
const success = await this.deleteProfileFromServer(this.activeDid);
|
||||
if (success) {
|
||||
this.notify.success(ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_DELETED);
|
||||
this.userProfileDesc = "";
|
||||
@@ -1688,7 +1761,6 @@ export default class AccountViewView extends Vue {
|
||||
this.userProfileLongitude = 0;
|
||||
this.includeUserProfileLocation = false;
|
||||
this.isMapReady = false; // Reset map state
|
||||
logger.debug("Profile deleted successfully, UI state reset");
|
||||
} else {
|
||||
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
|
||||
}
|
||||
@@ -1734,7 +1806,6 @@ export default class AccountViewView extends Vue {
|
||||
this.isMapReady = false;
|
||||
this.userProfileLatitude = 0;
|
||||
this.userProfileLongitude = 0;
|
||||
logger.debug("Location unchecked, map state reset");
|
||||
} else {
|
||||
// Location checkbox was checked, start map initialization timeout
|
||||
this.isMapReady = false;
|
||||
@@ -1743,7 +1814,6 @@ export default class AccountViewView extends Vue {
|
||||
// Try to set map ready after a short delay to allow Vue to render
|
||||
setTimeout(() => {
|
||||
if (!this.isMapReady) {
|
||||
logger.debug("Setting map ready after timeout");
|
||||
this.isMapReady = true;
|
||||
}
|
||||
}, 1000); // 1 second delay
|
||||
@@ -1796,5 +1866,338 @@ export default class AccountViewView extends Vue {
|
||||
onRecheckLimits() {
|
||||
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>
|
||||
|
||||
@@ -124,7 +124,7 @@ import {
|
||||
NOTIFY_CONFIRMATION_RESTRICTION,
|
||||
} from "../constants/notifications";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
||||
|
||||
import { GiveSummaryRecord, GiveActionClaim } from "../interfaces";
|
||||
import { AgreeActionClaim } from "../interfaces/claims";
|
||||
import {
|
||||
@@ -223,7 +223,7 @@ export default class ContactAmountssView extends Vue {
|
||||
const contact = await this.$getContact(contactDid);
|
||||
this.contact = contact;
|
||||
|
||||
const settings = await this.$getSettings(MASTER_SETTINGS_KEY);
|
||||
const settings = await this.$getMasterSettings();
|
||||
this.activeDid = settings?.activeDid || "";
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
|
||||
|
||||
@@ -273,6 +273,7 @@ import {
|
||||
didInfoForContact,
|
||||
displayAmount,
|
||||
getHeaders,
|
||||
isDid,
|
||||
register,
|
||||
setVisibilityUtil,
|
||||
} from "../libs/endorserServer";
|
||||
@@ -289,6 +290,7 @@ import {
|
||||
NOTIFY_REGISTRATION_ERROR,
|
||||
NOTIFY_SERVER_ACCESS_ERROR,
|
||||
NOTIFY_NO_IDENTITY_ERROR,
|
||||
NOTIFY_CONTACT_INVALID_DID,
|
||||
} from "@/constants/notifications";
|
||||
import { THAT_UNNAMED_PERSON } from "@/constants/entities";
|
||||
|
||||
@@ -380,22 +382,29 @@ export default class DIDView extends Vue {
|
||||
|
||||
/**
|
||||
* Determines which DID to display based on URL parameters
|
||||
* Falls back to active DID if no parameter provided
|
||||
* Validates DID format and shows error for invalid DIDs
|
||||
*/
|
||||
private async determineDIDToDisplay() {
|
||||
const pathParam = window.location.pathname.substring("/did/".length);
|
||||
let showDid = pathParam;
|
||||
|
||||
if (!showDid) {
|
||||
// No DID provided in URL, use active DID
|
||||
showDid = this.activeDid;
|
||||
if (showDid) {
|
||||
this.notifyDefaultToActiveDID();
|
||||
this.notifyDefaultToActiveDID();
|
||||
} else {
|
||||
// DID provided in URL, validate it
|
||||
const decodedDid = decodeURIComponent(showDid);
|
||||
if (!isDid(decodedDid)) {
|
||||
// Invalid DID format - show error and redirect
|
||||
this.notify.error(NOTIFY_CONTACT_INVALID_DID.message, TIMEOUTS.LONG);
|
||||
this.$router.push({ name: "home" });
|
||||
return;
|
||||
}
|
||||
showDid = decodedDid;
|
||||
}
|
||||
|
||||
if (showDid) {
|
||||
this.viewingDid = decodeURIComponent(showDid);
|
||||
}
|
||||
this.viewingDid = showDid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -568,10 +568,27 @@ export default class HomeView extends Vue {
|
||||
this.isRegistered = true;
|
||||
}
|
||||
} 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(
|
||||
"[HomeView Settings Trace] ⚠️ Registration check failed",
|
||||
{
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: errorMessage,
|
||||
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(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -585,8 +602,7 @@ export default class HomeView extends Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures API server is correctly set for the current platform
|
||||
* For Electron, always use production endpoint regardless of saved settings
|
||||
* Ensures correct API server configuration
|
||||
*
|
||||
* @internal
|
||||
* Called after loading settings to ensure correct API endpoint
|
||||
@@ -594,12 +610,9 @@ export default class HomeView extends Vue {
|
||||
private async ensureCorrectApiServer() {
|
||||
const { DEFAULT_ENDORSER_API_SERVER } = await import("../constants/app");
|
||||
|
||||
if (process.env.VITE_PLATFORM === "electron") {
|
||||
// **CRITICAL FIX**: Always use production API server for Electron
|
||||
// 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
|
||||
// Only set default if no user preference exists
|
||||
if (!this.apiServer) {
|
||||
// Set default API server for any platform if not already set
|
||||
this.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
||||
}
|
||||
}
|
||||
@@ -1161,9 +1174,13 @@ export default class HomeView extends Vue {
|
||||
location: fulfillsPlan
|
||||
? { lat: fulfillsPlan.locLat, lon: fulfillsPlan.locLon }
|
||||
: null,
|
||||
inSearchBox: fulfillsPlan
|
||||
? this.latLongInAnySearchBox(fulfillsPlan.locLat, fulfillsPlan.locLon)
|
||||
: null,
|
||||
inSearchBox:
|
||||
fulfillsPlan?.locLat && fulfillsPlan?.locLon
|
||||
? this.latLongInAnySearchBox(
|
||||
fulfillsPlan.locLat,
|
||||
fulfillsPlan.locLon,
|
||||
)
|
||||
: null,
|
||||
finalResult: anyMatch,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ export default class IdentitySwitcherView extends Vue {
|
||||
if (did) {
|
||||
try {
|
||||
const newSettings = await this.$accountSettings(did);
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"[IdentitySwitcher Settings Trace] ✅ New account settings loaded",
|
||||
{
|
||||
did,
|
||||
@@ -252,7 +252,7 @@ export default class IdentitySwitcherView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"[IdentitySwitcher Settings Trace] 🔄 Navigating to home to trigger watcher",
|
||||
{
|
||||
newDid: did,
|
||||
|
||||
@@ -226,7 +226,7 @@
|
||||
<div class="grid items-start grid-cols-1 sm:grid-cols-3 gap-4 mt-4">
|
||||
<!-- First, offers on the left-->
|
||||
<div class="bg-slate-100 px-4 py-3 rounded-md">
|
||||
<div v-if="activeDid && isRegistered">
|
||||
<div v-if="activeDid && isRegistered" class="mb-4">
|
||||
<div class="text-center">
|
||||
<button
|
||||
data-testId="offerButton"
|
||||
@@ -243,13 +243,13 @@
|
||||
:project-name="name"
|
||||
/>
|
||||
|
||||
<h3 class="text-lg font-bold mb-3 mt-4">Offered To This Idea</h3>
|
||||
<h3 class="text-lg font-bold leading-tight mb-3">Offered To This Idea</h3>
|
||||
|
||||
<div v-if="offersToThis.length === 0">
|
||||
(None yet. Wanna
|
||||
<span class="cursor-pointer text-blue-500" @click="openOfferDialog()"
|
||||
>offer something... especially if others join you</span
|
||||
>?)
|
||||
<div v-if="offersToThis.length === 0" class="text-sm">
|
||||
(None yet.<span v-if="activeDid && isRegistered"> Wanna
|
||||
<span class="cursor-pointer text-blue-500" @click="openOfferDialog()"
|
||||
>offer something… especially if others join you</span
|
||||
>?</span>)
|
||||
</div>
|
||||
|
||||
<ul v-else class="text-sm border-t border-slate-300">
|
||||
@@ -314,7 +314,7 @@
|
||||
<!-- Now, gives TO this project in the middle -->
|
||||
<!-- (similar to "FROM" gift display below) -->
|
||||
<div class="bg-slate-100 px-4 py-3 rounded-md" data-testId="gives-to">
|
||||
<div v-if="activeDid && isRegistered">
|
||||
<div v-if="activeDid && isRegistered" class="mb-4">
|
||||
<div class="text-center">
|
||||
<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"
|
||||
@@ -325,7 +325,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-bold mt-4">Given To This Project</h3>
|
||||
<h3 class="text-lg font-bold leading-tight mb-3">Given To This Project</h3>
|
||||
|
||||
<div v-if="givesToThis.length === 0" class="text-sm">
|
||||
(None yet. If you've seen something, say something by clicking a
|
||||
@@ -476,7 +476,7 @@
|
||||
<!-- Finally, gives FROM this project on the right -->
|
||||
<!-- (similar to "TO" gift display above) -->
|
||||
<div class="bg-slate-100 px-4 py-3 rounded-md" data-testId="gives-from">
|
||||
<div v-if="activeDid && isRegistered">
|
||||
<div v-if="activeDid && isRegistered" class="mb-4">
|
||||
<div class="text-center">
|
||||
<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"
|
||||
@@ -494,11 +494,11 @@
|
||||
:is-from-project-view="true"
|
||||
/>
|
||||
|
||||
<h3 class="text-lg font-bold mb-3 mt-4">
|
||||
<h3 class="text-lg font-bold leading-tight mb-3">
|
||||
Benefitted From This Project
|
||||
</h3>
|
||||
|
||||
<div v-if="givesProvidedByThis.length === 0">(None yet.)</div>
|
||||
<div v-if="givesProvidedByThis.length === 0" class="text-sm">(None yet.)</div>
|
||||
|
||||
<ul v-else class="text-sm border-t border-slate-300">
|
||||
<li
|
||||
|
||||
@@ -203,7 +203,7 @@ export default class StartView extends Vue {
|
||||
// Load account count for display logic
|
||||
this.numAccounts = await retrieveAccountCount();
|
||||
|
||||
logger.info("[StartView] Component mounted", {
|
||||
logger.debug("[StartView] Component mounted", {
|
||||
hasGivenName: !!this.givenName,
|
||||
accountCount: this.numAccounts,
|
||||
passkeysEnabled: this.PASSKEYS_ENABLED,
|
||||
@@ -221,7 +221,7 @@ export default class StartView extends Vue {
|
||||
* Routes user to new identifier creation flow with seed-based approach
|
||||
*/
|
||||
public onClickNewSeed() {
|
||||
logger.info("[StartView] User selected new seed generation");
|
||||
logger.debug("[StartView] User selected new seed generation");
|
||||
this.$router.push({ name: "new-identifier" });
|
||||
}
|
||||
|
||||
@@ -235,14 +235,14 @@ export default class StartView extends Vue {
|
||||
const keyName =
|
||||
AppString.APP_NAME + (this.givenName ? " - " + this.givenName : "");
|
||||
|
||||
logger.info("[StartView] Initiating passkey registration", {
|
||||
logger.debug("[StartView] Initiating passkey registration", {
|
||||
keyName,
|
||||
hasGivenName: !!this.givenName,
|
||||
});
|
||||
|
||||
await registerSaveAndActivatePasskey(keyName);
|
||||
|
||||
logger.info("[StartView] Passkey registration successful");
|
||||
logger.debug("[StartView] Passkey registration successful");
|
||||
this.$router.push({ name: "account" });
|
||||
} catch (error) {
|
||||
logger.error("[StartView] Passkey registration failed", error);
|
||||
@@ -255,7 +255,7 @@ export default class StartView extends Vue {
|
||||
* Routes user to account import flow for existing seed phrase
|
||||
*/
|
||||
public onClickNo() {
|
||||
logger.info("[StartView] User selected existing seed import");
|
||||
logger.debug("[StartView] User selected existing seed import");
|
||||
this.$router.push({ name: "import-account" });
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ export default class StartView extends Vue {
|
||||
* Routes user to address derivation flow for existing seed
|
||||
*/
|
||||
public onClickDerive() {
|
||||
logger.info("[StartView] User selected address derivation");
|
||||
logger.debug("[StartView] User selected address derivation");
|
||||
this.$router.push({ name: "import-derive" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,13 +91,95 @@
|
||||
name: 'shared-photo',
|
||||
query: { fileName },
|
||||
}"
|
||||
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2"
|
||||
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2"
|
||||
data-testId="fileUploadButton"
|
||||
>
|
||||
Go to Shared Page
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- URL Flow Testing Section -->
|
||||
<div class="mt-8">
|
||||
<h2 class="text-xl font-bold mb-4">URL Flow Testing</h2>
|
||||
<p class="text-sm text-gray-600 mb-3">
|
||||
Test claim and partner server URL flow from initialization to change
|
||||
propagation.
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="p-4 border border-gray-300 rounded-md bg-gray-50">
|
||||
<h3 class="font-semibold mb-2">Current URL State</h3>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div>
|
||||
<strong>API Server:</strong>
|
||||
<span class="font-mono">{{ apiServer || "Not Set" }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Partner API Server:</strong>
|
||||
<span class="font-mono">{{ partnerApiServer || "Not Set" }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Active DID:</strong>
|
||||
<span class="font-mono">{{ activeDid || "Not Set" }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Platform:</strong>
|
||||
<span class="font-mono">{{ getCurrentPlatform() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<button
|
||||
:class="primaryButtonClasses"
|
||||
:disabled="isUrlTestRunning"
|
||||
@click="testUrlFlow()"
|
||||
>
|
||||
{{ isUrlTestRunning ? "Testing..." : "Test URL Flow" }}
|
||||
</button>
|
||||
|
||||
<button :class="secondaryButtonClasses" @click="changeApiServer()">
|
||||
Change API Server (Test → Prod)
|
||||
</button>
|
||||
|
||||
<button
|
||||
:class="secondaryButtonClasses"
|
||||
@click="changePartnerApiServer()"
|
||||
>
|
||||
Change Partner API Server (Test → Prod)
|
||||
</button>
|
||||
|
||||
<button :class="warningButtonClasses" @click="resetToDefaults()">
|
||||
Reset to Defaults
|
||||
</button>
|
||||
|
||||
<button :class="secondaryButtonClasses" @click="refreshSettings()">
|
||||
Refresh Settings
|
||||
</button>
|
||||
|
||||
<button
|
||||
:class="secondaryButtonClasses"
|
||||
@click="logEnvironmentState()"
|
||||
>
|
||||
Log Environment State
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border border-gray-300 rounded-md bg-gray-50">
|
||||
<h3 class="font-semibold mb-2">URL Flow Test Results</h3>
|
||||
<div class="max-h-64 overflow-y-auto space-y-2">
|
||||
<div
|
||||
v-for="(result, index) in urlTestResults"
|
||||
:key="index"
|
||||
class="p-2 border border-gray-200 rounded text-xs font-mono bg-white"
|
||||
>
|
||||
{{ result }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<h2 class="text-xl font-bold mb-4">Passkeys</h2>
|
||||
See console for results.
|
||||
@@ -326,6 +408,11 @@ export default class Help extends Vue {
|
||||
showEntityGridTest = false;
|
||||
showPlatformServiceTest = false;
|
||||
|
||||
// for URL flow testing
|
||||
isUrlTestRunning = false;
|
||||
urlTestResults: string[] = [];
|
||||
partnerApiServer: string | undefined;
|
||||
|
||||
/**
|
||||
* Computed properties for template streamlining
|
||||
* Eliminates repeated classes and logic in template
|
||||
@@ -534,24 +621,93 @@ export default class Help extends Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Component initialization
|
||||
*
|
||||
* Loads user settings and account information for testing interface
|
||||
* Uses PlatformServiceMixin for database access
|
||||
*/
|
||||
async mounted() {
|
||||
const settings = await this.$accountSettings();
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.userName = settings.firstName;
|
||||
logger.info(
|
||||
"[TestView] 🚀 Component mounting - starting URL flow tracking",
|
||||
);
|
||||
|
||||
const account = await retrieveAccountMetadata(this.activeDid);
|
||||
if (this.activeDid) {
|
||||
if (account) {
|
||||
this.credIdHex = account.passkeyCredIdHex as string;
|
||||
} else {
|
||||
alert("No account found for DID " + this.activeDid);
|
||||
// Boot-time logging for initial configuration
|
||||
logger.info("[TestView] 🌍 Boot-time configuration detected:", {
|
||||
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(),
|
||||
});
|
||||
|
||||
try {
|
||||
// Track settings loading
|
||||
logger.info("[TestView] 📥 Loading account settings...");
|
||||
const settings = await this.$accountSettings();
|
||||
|
||||
logger.info("[TestView] 📊 Settings loaded:", {
|
||||
activeDid: settings.activeDid,
|
||||
apiServer: settings.apiServer,
|
||||
partnerApiServer: settings.partnerApiServer,
|
||||
isRegistered: settings.isRegistered,
|
||||
firstName: settings.firstName,
|
||||
});
|
||||
|
||||
// Update component state
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.partnerApiServer = settings.partnerApiServer || "";
|
||||
this.userName = settings.firstName;
|
||||
|
||||
logger.info("[TestView] ✅ Component state updated:", {
|
||||
activeDid: this.activeDid,
|
||||
apiServer: this.apiServer,
|
||||
partnerApiServer: this.partnerApiServer,
|
||||
});
|
||||
|
||||
// Load account metadata
|
||||
if (this.activeDid) {
|
||||
logger.info(
|
||||
"[TestView] 🔍 Loading account metadata for DID:",
|
||||
this.activeDid,
|
||||
);
|
||||
const account = await retrieveAccountMetadata(this.activeDid);
|
||||
|
||||
if (account) {
|
||||
this.credIdHex = account.passkeyCredIdHex as string;
|
||||
logger.info("[TestView] ✅ Account metadata loaded:", {
|
||||
did: account.did,
|
||||
hasPasskey: !!account.passkeyCredIdHex,
|
||||
passkeyId: account.passkeyCredIdHex,
|
||||
});
|
||||
} else {
|
||||
logger.warn(
|
||||
"[TestView] ⚠️ No account found for DID:",
|
||||
this.activeDid,
|
||||
);
|
||||
alert("No account found for DID " + this.activeDid);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("[TestView] 🎯 Component initialization complete:", {
|
||||
activeDid: this.activeDid,
|
||||
apiServer: this.apiServer,
|
||||
partnerApiServer: this.partnerApiServer,
|
||||
hasPasskey: !!this.credIdHex,
|
||||
platform: this.getCurrentPlatform(),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"[TestView] ❌ Error during component initialization:",
|
||||
error,
|
||||
);
|
||||
this.$notify(
|
||||
{
|
||||
group: "error",
|
||||
type: "error",
|
||||
title: "Initialization Error",
|
||||
text: `Failed to initialize component: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
5000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -824,5 +980,276 @@ export default class Help extends Vue {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the URL flow from initialization to change propagation.
|
||||
* This simulates the flow where a user's DID is set, and then the
|
||||
* claim and partner server URLs are updated.
|
||||
*/
|
||||
public async testUrlFlow() {
|
||||
this.isUrlTestRunning = true;
|
||||
this.urlTestResults = [];
|
||||
|
||||
try {
|
||||
logger.info("[TestView] 🔬 Starting comprehensive URL flow test");
|
||||
this.addUrlTestResult("🚀 Starting URL flow test...");
|
||||
|
||||
// Test 1: Current state
|
||||
this.addUrlTestResult(`📊 Current State:`);
|
||||
this.addUrlTestResult(` - API Server: ${this.apiServer || "Not Set"}`);
|
||||
this.addUrlTestResult(
|
||||
` - Partner API Server: ${this.partnerApiServer || "Not Set"}`,
|
||||
);
|
||||
this.addUrlTestResult(` - Active DID: ${this.activeDid || "Not Set"}`);
|
||||
this.addUrlTestResult(` - Platform: ${this.getCurrentPlatform()}`);
|
||||
|
||||
// Test 2: Load fresh settings
|
||||
this.addUrlTestResult(`\n📥 Testing Settings Loading:`);
|
||||
const startTime = Date.now();
|
||||
const settings = await this.$accountSettings();
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
this.addUrlTestResult(` - Settings loaded in ${loadTime}ms`);
|
||||
this.addUrlTestResult(
|
||||
` - API Server from settings: ${settings.apiServer || "Not Set"}`,
|
||||
);
|
||||
this.addUrlTestResult(
|
||||
` - Partner API Server from settings: ${settings.partnerApiServer || "Not Set"}`,
|
||||
);
|
||||
|
||||
// Test 3: Database query
|
||||
this.addUrlTestResult(`\n💾 Testing Database Query:`);
|
||||
const dbStartTime = Date.now();
|
||||
const dbResult = await this.$dbQuery(
|
||||
"SELECT apiServer, partnerApiServer, activeDid FROM settings WHERE id = ? OR accountDid = ?",
|
||||
[1, this.activeDid || ""],
|
||||
);
|
||||
const dbTime = Date.now() - dbStartTime;
|
||||
|
||||
if (dbResult?.values) {
|
||||
this.addUrlTestResult(` - Database query completed in ${dbTime}ms`);
|
||||
this.addUrlTestResult(
|
||||
` - Raw DB values: ${JSON.stringify(dbResult.values)}`,
|
||||
);
|
||||
} else {
|
||||
this.addUrlTestResult(
|
||||
` - Database query failed or returned no results`,
|
||||
);
|
||||
}
|
||||
|
||||
// Test 4: Environment variables
|
||||
this.addUrlTestResult(`\n🌍 Testing Environment Variables:`);
|
||||
this.addUrlTestResult(
|
||||
` - VITE_PLATFORM: ${import.meta.env.VITE_PLATFORM || "Not Set"}`,
|
||||
);
|
||||
this.addUrlTestResult(
|
||||
` - VITE_DEFAULT_ENDORSER_API_SERVER: ${import.meta.env.VITE_DEFAULT_ENDORSER_API_SERVER || "Not Set"}`,
|
||||
);
|
||||
this.addUrlTestResult(
|
||||
` - VITE_DEFAULT_PARTNER_API_SERVER: ${import.meta.env.VITE_DEFAULT_PARTNER_API_SERVER || "Not Set"}`,
|
||||
);
|
||||
|
||||
// Test 5: Constants
|
||||
this.addUrlTestResult(`\n📋 Testing App Constants:`);
|
||||
this.addUrlTestResult(
|
||||
` - PROD_ENDORSER_API_SERVER: ${AppString.PROD_ENDORSER_API_SERVER}`,
|
||||
);
|
||||
this.addUrlTestResult(
|
||||
` - PROD_PARTNER_API_SERVER: ${AppString.PROD_PARTNER_API_SERVER}`,
|
||||
);
|
||||
|
||||
// Test 6: Change detection
|
||||
this.addUrlTestResult(`\n🔄 Testing Change Detection:`);
|
||||
const originalApiServer = this.apiServer;
|
||||
const originalPartnerServer = this.partnerApiServer;
|
||||
|
||||
// Simulate a change
|
||||
this.addUrlTestResult(` - Original API Server: ${originalApiServer}`);
|
||||
this.addUrlTestResult(
|
||||
` - Original Partner Server: ${originalPartnerServer}`,
|
||||
);
|
||||
|
||||
// Test 7: Settings update
|
||||
this.addUrlTestResult(`\n💾 Testing Settings Update:`);
|
||||
const testChanges = {
|
||||
apiServer:
|
||||
originalApiServer === "https://api.endorser.ch"
|
||||
? "https://test-api.endorser.ch"
|
||||
: "https://api.endorser.ch",
|
||||
};
|
||||
|
||||
this.addUrlTestResult(
|
||||
` - Attempting to change API Server to: ${testChanges.apiServer}`,
|
||||
);
|
||||
const updateResult = await this.$saveSettings(testChanges);
|
||||
this.addUrlTestResult(
|
||||
` - Update result: ${updateResult ? "Success" : "Failed"}`,
|
||||
);
|
||||
|
||||
// Test 8: Verify change propagation
|
||||
this.addUrlTestResult(`\n✅ Testing Change Propagation:`);
|
||||
const newSettings = await this.$accountSettings();
|
||||
this.addUrlTestResult(
|
||||
` - New API Server from settings: ${newSettings.apiServer || "Not Set"}`,
|
||||
);
|
||||
this.addUrlTestResult(
|
||||
` - Component state API Server: ${this.apiServer || "Not Set"}`,
|
||||
);
|
||||
this.addUrlTestResult(
|
||||
` - Change propagated: ${newSettings.apiServer === this.apiServer ? "Yes" : "No"}`,
|
||||
);
|
||||
|
||||
// Test 9: Revert changes
|
||||
this.addUrlTestResult(`\n🔄 Reverting Changes:`);
|
||||
const revertResult = await this.$saveSettings({
|
||||
apiServer: originalApiServer,
|
||||
});
|
||||
this.addUrlTestResult(
|
||||
` - Revert result: ${revertResult ? "Success" : "Failed"}`,
|
||||
);
|
||||
|
||||
// Test 10: Final verification
|
||||
this.addUrlTestResult(`\n🎯 Final Verification:`);
|
||||
const finalSettings = await this.$accountSettings();
|
||||
this.addUrlTestResult(
|
||||
` - Final API Server: ${finalSettings.apiServer || "Not Set"}`,
|
||||
);
|
||||
this.addUrlTestResult(
|
||||
` - Matches original: ${finalSettings.apiServer === originalApiServer ? "Yes" : "No"}`,
|
||||
);
|
||||
|
||||
this.addUrlTestResult(`\n✅ URL flow test completed successfully!`);
|
||||
logger.info("[TestView] ✅ URL flow test completed successfully");
|
||||
} catch (error) {
|
||||
const errorMsg = `❌ URL flow test failed: ${error instanceof Error ? error.message : String(error)}`;
|
||||
this.addUrlTestResult(errorMsg);
|
||||
logger.error("[TestView] ❌ URL flow test failed:", error);
|
||||
} finally {
|
||||
this.isUrlTestRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a result to the URL test results array.
|
||||
*/
|
||||
private addUrlTestResult(message: string) {
|
||||
this.urlTestResults.push(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the API server to the production URL.
|
||||
*/
|
||||
public changeApiServer() {
|
||||
const currentServer = this.apiServer;
|
||||
const newServer =
|
||||
currentServer === "https://api.endorser.ch"
|
||||
? "https://test-api.endorser.ch"
|
||||
: "https://api.endorser.ch";
|
||||
|
||||
logger.info("[TestView] 🔄 Changing API server:", {
|
||||
from: currentServer,
|
||||
to: newServer,
|
||||
});
|
||||
|
||||
this.apiServer = newServer;
|
||||
this.addUrlTestResult(
|
||||
`API Server changed from ${currentServer} to ${newServer}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the partner API server to the production URL.
|
||||
*/
|
||||
public changePartnerApiServer() {
|
||||
const currentServer = this.partnerApiServer;
|
||||
const newServer =
|
||||
currentServer === "https://partner-api.endorser.ch"
|
||||
? "https://test-partner-api.endorser.ch"
|
||||
: "https://partner-api.endorser.ch";
|
||||
|
||||
logger.info("[TestView] 🔄 Changing partner API server:", {
|
||||
from: currentServer,
|
||||
to: newServer,
|
||||
});
|
||||
|
||||
this.partnerApiServer = newServer;
|
||||
this.addUrlTestResult(
|
||||
`Partner API Server changed from ${currentServer} to ${newServer}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all URL-related settings to their initial values.
|
||||
*/
|
||||
public resetToDefaults() {
|
||||
this.apiServer = AppString.TEST_ENDORSER_API_SERVER;
|
||||
this.partnerApiServer = AppString.TEST_PARTNER_API_SERVER;
|
||||
this.activeDid = "";
|
||||
this.addUrlTestResult("URL Flow Test Results Reset to Defaults.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes settings from the database to verify changes.
|
||||
*/
|
||||
public async refreshSettings() {
|
||||
try {
|
||||
logger.info("[TestView] 🔄 Refreshing settings from database");
|
||||
const settings = await this.$accountSettings();
|
||||
|
||||
// Update component state
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.partnerApiServer = settings.partnerApiServer || "";
|
||||
|
||||
logger.info("[TestView] ✅ Settings refreshed:", {
|
||||
apiServer: this.apiServer,
|
||||
partnerApiServer: this.partnerApiServer,
|
||||
});
|
||||
|
||||
this.addUrlTestResult(
|
||||
`Settings refreshed - API Server: ${this.apiServer}, Partner API Server: ${this.partnerApiServer}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("[TestView] ❌ Error refreshing settings:", error);
|
||||
this.addUrlTestResult(
|
||||
`Error refreshing settings: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the current environment state to the console.
|
||||
*/
|
||||
public logEnvironmentState() {
|
||||
logger.info("[TestView] 🌐 Current Environment State:", {
|
||||
VITE_PLATFORM: import.meta.env.VITE_PLATFORM,
|
||||
VITE_DEFAULT_ENDORSER_API_SERVER: import.meta.env
|
||||
.VITE_DEFAULT_ENDORSER_API_SERVER,
|
||||
VITE_DEFAULT_PARTNER_API_SERVER: import.meta.env
|
||||
.VITE_DEFAULT_PARTNER_API_SERVER,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
activeDid: this.activeDid,
|
||||
apiServer: this.apiServer,
|
||||
partnerApiServer: this.partnerApiServer,
|
||||
});
|
||||
this.$notify({
|
||||
group: "info",
|
||||
type: "info",
|
||||
title: "Environment State Logged",
|
||||
text: "Current environment state logged to console.",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current platform based on the API server.
|
||||
*/
|
||||
public getCurrentPlatform(): string {
|
||||
if (this.apiServer?.includes(AppString.PROD_ENDORSER_API_SERVER)) {
|
||||
return "Production";
|
||||
} else if (this.apiServer?.includes(AppString.TEST_ENDORSER_API_SERVER)) {
|
||||
return "Test";
|
||||
} else {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { UNNAMED_ENTITY_NAME } from '../src/constants/entities';
|
||||
import { deleteContact, generateAndRegisterEthrUser, importUser } from './testUtils';
|
||||
import { NOTIFY_CONTACT_INVALID_DID } from '../src/constants/notifications';
|
||||
|
||||
test('Check activity feed - check that server is running', async ({ page }) => {
|
||||
// Load app homepage
|
||||
@@ -170,6 +171,19 @@ test('Confirm test API setting (may fail if you are running your own Time Safari
|
||||
await expect(page.locator('#apiServerInput')).toHaveValue(endorserServer);
|
||||
});
|
||||
|
||||
test('Check invalid DID shows error and redirects', async ({ page }) => {
|
||||
await importUser(page, '00');
|
||||
|
||||
// Navigate to an invalid DID URL
|
||||
await page.goto('./did/0');
|
||||
|
||||
// Should show error message about invalid DID format
|
||||
await expect(page.getByText(NOTIFY_CONTACT_INVALID_DID.message)).toBeVisible();
|
||||
|
||||
// Should redirect to homepage
|
||||
await expect(page).toHaveURL(/.*\/$/);
|
||||
});
|
||||
|
||||
test('Check User 0 can register a random person', async ({ page }) => {
|
||||
await importUser(page, '00');
|
||||
const newDid = await generateAndRegisterEthrUser(page);
|
||||
|
||||
@@ -23,10 +23,11 @@ test('New offers for another user', async ({ page }) => {
|
||||
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(autoCreatedDid + ', A Friend');
|
||||
await expect(page.locator('button > svg.fa-plus')).toBeVisible();
|
||||
await page.locator('button > svg.fa-plus').click();
|
||||
await page.locator('div[role="alert"] button:has-text("No")').click(); // don't register
|
||||
await expect(page.locator('div[role="alert"] h4:has-text("Success")')).toBeVisible();
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
|
||||
await expect(page.locator('div[role="alert"] h4:has-text("Success")')).toBeVisible(); // wait for info alert to be visible…
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // …and dismiss it
|
||||
await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone
|
||||
await page.locator('div[role="alert"] button:text-is("No")').click(); // Dismiss register prompt
|
||||
await page.locator('div[role="alert"] button:text-is("No, Not Now")').click(); // Dismiss export data prompt
|
||||
|
||||
// show buttons to make offers directly to people
|
||||
await page.getByRole('button').filter({ hasText: /See Actions/i }).click();
|
||||
|
||||
@@ -158,10 +158,10 @@ export async function generateAndRegisterEthrUser(page: Page): Promise<string> {
|
||||
.fill(`${newDid}, ${contactName}`);
|
||||
await page.locator("button > svg.fa-plus").click();
|
||||
// register them
|
||||
await page.locator('div[role="alert"] button:has-text("Yes")').click();
|
||||
await page.locator('div[role="alert"] button:text-is("Yes")').click();
|
||||
// wait for it to disappear because the next steps may depend on alerts being gone
|
||||
await expect(
|
||||
page.locator('div[role="alert"] button:has-text("Yes")')
|
||||
page.locator('div[role="alert"] button:text-is("Yes")')
|
||||
).toBeHidden();
|
||||
await expect(page.locator("li", { hasText: contactName })).toBeVisible();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user