diff --git a/.cursor/rules/component-creation-ideals.mdc b/.cursor/rules/component-creation-ideals.mdc new file mode 100644 index 00000000..661da418 --- /dev/null +++ b/.cursor/rules/component-creation-ideals.mdc @@ -0,0 +1,321 @@ +--- +alwaysApply: true +version: "2.0.0" +lastUpdated: "2025-08-15" +priority: "critical" +--- +# Component Creation Ideals β€” TimeSafari Architecture (Directive MDC, v2) + +> **Agent role**: Apply these rules when creating, refactoring, or reviewing Vue components (Vue 3 with **vue-facing-decorator**). Prioritize **self-contained** components that hydrate from and persist to **PlatformServiceMixin** settings. Minimize parent–child coupling and prop drilling. Prefer **concision** where a separate component would add more API surface than value. + +## πŸ“š Cross-References + +- **Migration Example**: See `src/components/NotificationSection.vue` for successful refactor implementation +- **Parent Component**: See `src/views/AccountViewView.vue` for before/after comparison +- **Settings Infrastructure**: See `src/utils/PlatformServiceMixin.ts` for available methods +- **Related Rules**: See `.cursor/rules/` for other architectural guidelines + +## Golden Rules (Enforce) + +### Priority Levels +- πŸ”΄ **CRITICAL**: Must be followed - breaking these creates architectural problems +- 🟑 **HIGH**: Strongly recommended - important for maintainability +- 🟒 **MEDIUM**: Good practice - improves code quality + +1. **Self-Contained Components** πŸ”΄ **CRITICAL** + - Do **not** require parent props for internal state or behavior. + - Hydrate on `mounted()` via `this.$accountSettings()`; persist with `this.$saveSettings()`. + - Encapsulate business logic inside the component; avoid delegating core logic to the parent. + - Prefer **computed** getters for derived values; avoid stored duplicates of derived state. + +2. **Settings-First Architecture** πŸ”΄ **CRITICAL** + - Use `PlatformServiceMixin` for reading/writing settings. Do **not** introduce new state managers (Pinia, custom stores) unless the state is *truly global*. + - Prefer **fetch-on-mount** over passing values via props. + +3. **Single Responsibility** 🟑 **HIGH** + - Each component owns **one clear purpose** (UI + related logic + settings persistence). Avoid splitting UI/logic across multiple components unless reusability clearly benefits. + +4. **Internal State Lifecycle** 🟑 **HIGH** + - Pattern: **defaults β†’ hydrate on mount β†’ computed for derived β†’ persist on change**. + - Handle hydration errors gracefully (keep safe defaults; surface actionable UI states as needed). + +5. **Minimal Props** πŸ”΄ **CRITICAL** + - Props are for **pure configuration** (labels, limits, feature flags). Do not pass data that can be loaded internally. + - **Never** pass props that mirror settings values (e.g., `isRegistered`, `notifying*`). Load those from settings. + +6. **Communication & Events** 🟑 **HIGH** + - Children may emit events for *user interactions* (e.g., `submitted`, `closed`) but **not** to offload core logic to the parent. + - Do not emit events solely to persist settings; the child handles persistence. + +7. **Testing** 🟒 **MEDIUM** + - Unit tests mount components in isolation; mock `this.$accountSettings`/`this.$saveSettings`. + - Verify hydration on mount, persistence on mutation, and graceful failure on settings errors. + +--- + +## Concision-First Decision Framework + +> **Goal:** Avoid unnecessary components when a concise script, composable, or helper suffices. + +**Prefer NOT making a component when:** +- **One-off UI**: used in exactly one view, unlikely to repeat. +- **Small scope**: ~≀100–150 LOC, ≀3 reactive fields, ≀2 handlers. +- **Purely presentational** with trivial logic. +- **Local invariants**: behavior depends entirely on the view’s context. +- **Abstraction cost > benefit**: would create an anemic component with props mirroring parent state. +- **Better fit as code reuse**: logic works as a **composable/service/helper** without introducing new UI. + +**Concise alternatives:** +- **Composable**: `useFeature()` encapsulates settings I/O and state. +- **Service/Module**: plain TS helpers for formatting/validation. +- **Directive**: tiny DOM behaviors that don’t need a lifecycle boundary. + +**When to make a component (even without reuse yet):** +- **Isolation boundary**: async side effects, permission prompts, or recoverable error states. +- **Stateful widget**: internal settings persistence, media controls, complex a11y. +- **Slots/composition**: needs flexible children or layout. +- **Different change rate**: sub-tree churns independently of the parent. +- **Testability/ownership**: clear, ownable surface that’s easier to unit-test in isolation. + +**Rule of Three (guardrail):** +- 1st time: inline or composable. +- 2nd time: consider shared abstraction. +- 3rd time: extract a component. + +**PR language (use when choosing concision):** +- β€œOne-off, ~80 LOC, no expected reuse. A component would add an API surface with no consumer. Keeping it local reduces cognitive load. If we see a second usage, promote to a composable; third usage, a component.” +- β€œLogic lives in `useX()` to keep the view concise without prop plumbing; settings stay via `PlatformServiceMixin`.” + +--- + +## Anti-Patterns (Reject) + +- Props that duplicate internal/derivable state: `:is-registered`, `:notifying-*`, etc. +- Child components that are **UI-only** while parents hold the business logic for that feature. +- Introducing Pinia/custom stores for per-component state. +- Emitting `update:*` events to push settings responsibilities to parents. +- Scattering a feature across multiple micro-components without a clear reuse reason. +- Using props for computed/derived values (e.g., `showAdvancedFeatures` as a prop). + +--- + +## Quick Examples + +### βœ… DO (Self-Contained, Settings-First) + +```vue + + +``` + +```ts +// NotificationSection.vue (vue-facing-decorator + PlatformServiceMixin) +import { Component, Vue } from "vue-facing-decorator"; +import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; + +@Component({ + mixins: [PlatformServiceMixin], +}) +export default class NotificationSection extends Vue { + private notifyingNewActivity: boolean = false; + + async mounted(): Promise { + await this.hydrateFromSettings(); + } + + private async hydrateFromSettings(): Promise { + try { + const s = await this.$accountSettings(); + this.notifyingNewActivity = !!s.notifyingNewActivityTime; + } catch (err) { + // Keep defaults; optionally surface a non-blocking UI notice + } + } + + private async updateNotifying(state: boolean): Promise { + await this.$saveSettings({ notifyingNewActivityTime: state ? Date.now() : null }); + this.notifyingNewActivity = state; + } +} +``` + +### ❌ DON'T (Prop-Driven Coupling) + +```vue + + +``` + +```ts +// Child receives props (anti-pattern) +export default class NotificationSection extends Vue { + isRegistered: boolean = false; + notifyingNewActivity: boolean = false; +} +``` + +--- + +## Component Structure Template (Drop-In) + +```ts +/** + * ComponentName.vue β€” Purpose + * Owns: UI + logic + settings persistence (PlatformServiceMixin). + */ +import { Component, Vue } from "vue-facing-decorator"; +import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; +import type { Router } from "vue-router"; + +@Component({ + components: { + // child components here + }, + mixins: [PlatformServiceMixin], // Settings access +}) +export default class ComponentName extends Vue { + // Internal state + private myState: boolean = false; + private myData: string = ""; + + // Derived + private get isEnabled(): boolean { + return this.myState && this.hasPermissions; + } + + async mounted(): Promise { + await this.hydrateFromSettings(); + } + + private async hydrateFromSettings(): Promise { + try { + const s = await this.$accountSettings(); + this.myState = !!s.mySetting; + this.myData = s.myData ?? ""; + } catch (err) { + // keep defaults + } + } + + private async updateState(v: boolean): Promise { + await this.$saveSettings({ mySetting: v }); + this.myState = v; + } + + async handleUserAction(): Promise { + await this.updateState(true); + } +} +``` + +--- + +## Migration Playbook (Props β†’ Self-Contained) + +> **Agent, when you see a component receiving `@Prop()` values that mirror settings or internal state, apply this playbook.** + +1. **Detect Anti-Props** + - Flag props matching: `is*`, `has*`, `notifying*`, or mirroring settings keys. + - Search for parent usage of these props and events. + +2. **Inline State** + - Remove anti-props and `@Prop()` declarations. + - Add private fields for state inside the child. + - Add `mounted()` hydration and persistence helpers via `PlatformServiceMixin`. + +3. **Parent Simplification** + - Replace `` with ``. + - Delete now-unused local state, watchers, and computed values that existed only to feed the child. + +4. **Events** + - Keep only user-interaction events (e.g., `submitted`). Remove persistence/logic events that the child now owns. + +5. **Tests** + - Update unit tests to mount child in isolation; mock settings I/O. + - Verify: hydrate on mount, persist on change, safe fallback on error. + +--- + +## When Exceptions Are Acceptable + +- **Truly Global State** across distant components β†’ use Pinia/service *sparingly*. +- **Reusable Form Inputs** β†’ accept `value` prop and emit `input`/`update:modelValue`; keep validation/business logic internal. +- **Configuration-Only Props** for labels, visual variants, or limits β€” not for state that can be fetched. + +--- + +## Definition of Done (Checklist) + +- [ ] No props required for internal state or settings-backed values. +- [ ] Uses `PlatformServiceMixin` for all settings I/O. +- [ ] Hydrates on `mounted()`, persists on mutations. +- [ ] Single-responsibility: UI + logic + persistence together. +- [ ] Computed getters for derived state. +- [ ] Unit tests mock settings and cover hydrate/persist/failure paths. +- [ ] Parent components contain no leftover `notifying-*` or similar prop wiring. + +--- + +## Testing Snippets + +```ts +// Hydration on mount +test("hydrates from settings on mount", async () => { + const wrap = mount(MyComponent); + await wrap.vm.$nextTick(); + expect((wrap.vm as any).myState).toBe(true); +}); +``` + +```ts +// Mock settings methods +jest.spyOn(wrapper.vm as any, "$accountSettings").mockResolvedValue({ mySetting: true }); +jest.spyOn(wrapper.vm as any, "$saveSettings").mockResolvedValue(void 0); +``` + +```ts +// Graceful failure +test("handles settings load failure", async () => { + jest.spyOn(wrapper.vm as any, "$accountSettings").mockRejectedValue(new Error("DB Error")); + const wrap = mount(MyComponent); + await wrap.vm.$nextTick(); + expect((wrap.vm as any).myState).toBe(false); // default +}); +``` + +--- + +## Rationale (Short) + +- **Concision first where appropriate** β†’ avoid unnecessary components and API surfaces. +- **Reduced coupling** β†’ portability and reuse when boundaries are justified. +- **Maintainability** β†’ changes localized to the owning component. +- **Consistency** β†’ one canonical path for settings-backed features. + +**Rule of thumb:** _Can this feature operate independently, and does a component materially improve isolation or testability?_ If not, keep it concise (inline or composable). + +--- + +## πŸ“ Version History + +### v2.0.0 (2025-08-15) +- **Major Enhancement**: Added Concision-First Decision Framework +- **New**: Rule of Three guardrail for component extraction +- **New**: PR language guidance for code reviews +- **Enhanced**: Agent role includes concision preference +- **Refined**: Anti-patterns and examples updated + +### v1.0.0 (2025-08-15) +- Initial creation based on successful NotificationSection refactor +- Established core architectural principles for self-contained components +- Added comprehensive migration playbook and testing guidelines +- Included practical examples and anti-pattern detection + +### Future Enhancements +- Additional migration patterns for complex components +- Integration with automated refactoring tools +- Performance benchmarking guidelines +- Advanced testing strategies for complex state management diff --git a/.cursor/rules/harbor_pilot_universal.mdc b/.cursor/rules/harbor_pilot_universal.mdc index 8f3c1ef8..f2965394 100644 --- a/.cursor/rules/harbor_pilot_universal.mdc +++ b/.cursor/rules/harbor_pilot_universal.mdc @@ -202,3 +202,5 @@ Follow this exact order **after** the Base Contract’s **Objective β†’ Result **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`) diff --git a/.cursor/rules/historical-comment-management.mdc b/.cursor/rules/historical-comment-management.mdc new file mode 100644 index 00000000..4f78f38a --- /dev/null +++ b/.cursor/rules/historical-comment-management.mdc @@ -0,0 +1,236 @@ +--- +description: when comments are generated by the model +alwaysApply: false +--- +# Historical Comment Management β€” Harbor Pilot Directive + +> **Agent role**: When encountering historical comments about removed methods, deprecated patterns, or architectural changes, apply these guidelines to maintain code clarity and developer guidance. + +## 🎯 Purpose + +Historical comments should either be **removed entirely** or **transformed into actionable guidance** for future developers. Avoid keeping comments that merely state what was removed without explaining why or what to do instead. + +## πŸ“‹ Decision Framework + +### Remove Historical Comments When: +- **Obsolete Information**: Comment describes functionality that no longer exists +- **No Action Required**: Comment doesn't help future developers make decisions +- **Outdated Context**: Comment refers to old patterns that are no longer relevant +- **Self-Evident**: The current code clearly shows the current approach + +### Transform Historical Comments When: +- **Architectural Context**: The change represents a significant pattern shift +- **Migration Guidance**: Future developers might need to understand the evolution +- **Decision Rationale**: The "why" behind the change is still relevant +- **Alternative Approaches**: The comment can guide future implementation choices + +## πŸ”„ Transformation Patterns + +### 1. From Removal Notice to Migration Note +```typescript +// ❌ REMOVE THIS +// turnOffNotifyingFlags method removed - notification state is now managed by NotificationSection component + +// βœ… TRANSFORM TO THIS +// Note: Notification state management has been migrated to NotificationSection component +// which handles its own lifecycle and persistence via PlatformServiceMixin +``` + +### 2. From Deprecation Notice to Implementation Guide +```typescript +// ❌ REMOVE THIS +// This will be handled by the NewComponent now +// No need to call oldMethod() as it's no longer needed + +// βœ… TRANSFORM TO THIS +// Note: This functionality has been migrated to NewComponent +// which provides better separation of concerns and testability +``` + +### 3. From Historical Note to Architectural Context +```typescript +// ❌ REMOVE THIS +// Old approach: used direct database calls +// New approach: uses service layer + +// βœ… TRANSFORM TO THIS +// Note: Database access has been abstracted through service layer +// for better testability and platform independence +``` + +## 🚫 Anti-Patterns to Remove + +- Comments that only state what was removed +- Comments that don't explain the current approach +- Comments that reference non-existent methods +- Comments that are self-evident from the code +- Comments that don't help future decision-making + +## βœ… Best Practices + +### When Keeping Historical Context: +1. **Explain the "Why"**: Why was the change made? +2. **Describe the "What"**: What is the current approach? +3. **Provide Context**: When might this information be useful? +4. **Use Actionable Language**: Guide future decisions, not just document history + +### When Removing Historical Context: +1. **Verify Obsoleteness**: Ensure the information is truly outdated +2. **Check for Dependencies**: Ensure no other code references the old approach +3. **Update Related Docs**: If removing from code, consider adding to documentation +4. **Preserve in Git History**: The change is preserved in version control + +## πŸ” Implementation Checklist + +- [ ] Identify historical comments about removed/deprecated functionality +- [ ] Determine if comment provides actionable guidance +- [ ] Transform useful comments into migration notes or architectural context +- [ ] Remove comments that are purely historical without guidance value +- [ ] Ensure remaining comments explain current approach and rationale +- [ ] Update related documentation if significant context is removed + +## πŸ“š Examples + +### Good Historical Comment (Keep & Transform) +```typescript +// Note: Database access has been migrated from direct IndexedDB calls to PlatformServiceMixin +// This provides better platform abstraction and consistent error handling across web/mobile/desktop +// When adding new database operations, use this.$getContact(), this.$saveSettings(), etc. +``` + +### Bad Historical Comment (Remove) +```typescript +// Old method getContactFromDB() removed - now handled by PlatformServiceMixin +// No need to call the old method anymore +``` + +## 🎯 Integration with Harbor Pilot + +This rule works in conjunction with: +- **Component Creation Ideals**: Maintains architectural consistency +- **Migration Patterns**: Documents evolution of patterns +- **Code Review Guidelines**: Ensures comments provide value + +## πŸ“ Version History + +### v1.0.0 (2025-08-21) +- Initial creation based on notification system cleanup +- Established decision framework for historical comment management +- Added transformation patterns and anti-patterns +- Integrated with existing Harbor Pilot architecture rules +# Historical Comment Management β€” Harbor Pilot Directive + +> **Agent role**: When encountering historical comments about removed methods, deprecated patterns, or architectural changes, apply these guidelines to maintain code clarity and developer guidance. + +## 🎯 Purpose + +Historical comments should either be **removed entirely** or **transformed into actionable guidance** for future developers. Avoid keeping comments that merely state what was removed without explaining why or what to do instead. + +## πŸ“‹ Decision Framework + +### Remove Historical Comments When: +- **Obsolete Information**: Comment describes functionality that no longer exists +- **No Action Required**: Comment doesn't help future developers make decisions +- **Outdated Context**: Comment refers to old patterns that are no longer relevant +- **Self-Evident**: The current code clearly shows the current approach + +### Transform Historical Comments When: +- **Architectural Context**: The change represents a significant pattern shift +- **Migration Guidance**: Future developers might need to understand the evolution +- **Decision Rationale**: The "why" behind the change is still relevant +- **Alternative Approaches**: The comment can guide future implementation choices + +## πŸ”„ Transformation Patterns + +### 1. From Removal Notice to Migration Note +```typescript +// ❌ REMOVE THIS +// turnOffNotifyingFlags method removed - notification state is now managed by NotificationSection component + +// βœ… TRANSFORM TO THIS +// Note: Notification state management has been migrated to NotificationSection component +// which handles its own lifecycle and persistence via PlatformServiceMixin +``` + +### 2. From Deprecation Notice to Implementation Guide +```typescript +// ❌ REMOVE THIS +// This will be handled by the NewComponent now +// No need to call oldMethod() as it's no longer needed + +// βœ… TRANSFORM TO THIS +// Note: This functionality has been migrated to NewComponent +// which provides better separation of concerns and testability +``` + +### 3. From Historical Note to Architectural Context +```typescript +// ❌ REMOVE THIS +// Old approach: used direct database calls +// New approach: uses service layer + +// βœ… TRANSFORM TO THIS +// Note: Database access has been abstracted through service layer +// for better testability and platform independence +``` + +## 🚫 Anti-Patterns to Remove + +- Comments that only state what was removed +- Comments that don't explain the current approach +- Comments that reference non-existent methods +- Comments that are self-evident from the code +- Comments that don't help future decision-making + +## βœ… Best Practices + +### When Keeping Historical Context: +1. **Explain the "Why"**: Why was the change made? +2. **Describe the "What"**: What is the current approach? +3. **Provide Context**: When might this information be useful? +4. **Use Actionable Language**: Guide future decisions, not just document history + +### When Removing Historical Context: +1. **Verify Obsoleteness**: Ensure the information is truly outdated +2. **Check for Dependencies**: Ensure no other code references the old approach +3. **Update Related Docs**: If removing from code, consider adding to documentation +4. **Preserve in Git History**: The change is preserved in version control + +## πŸ” Implementation Checklist + +- [ ] Identify historical comments about removed/deprecated functionality +- [ ] Determine if comment provides actionable guidance +- [ ] Transform useful comments into migration notes or architectural context +- [ ] Remove comments that are purely historical without guidance value +- [ ] Ensure remaining comments explain current approach and rationale +- [ ] Update related documentation if significant context is removed + +## πŸ“š Examples + +### Good Historical Comment (Keep & Transform) +```typescript +// Note: Database access has been migrated from direct IndexedDB calls to PlatformServiceMixin +// This provides better platform abstraction and consistent error handling across web/mobile/desktop +// When adding new database operations, use this.$getContact(), this.$saveSettings(), etc. +``` + +### Bad Historical Comment (Remove) +```typescript +// Old method getContactFromDB() removed - now handled by PlatformServiceMixin +// No need to call the old method anymore +``` + +## 🎯 Integration with Harbor Pilot + +This rule works in conjunction with: +- **Component Creation Ideals**: Maintains architectural consistency +- **Migration Patterns**: Documents evolution of patterns +- **Code Review Guidelines**: Ensures comments provide value + +## πŸ“ Version History + +### v1.0.0 (2025-08-21) +- Initial creation based on notification system cleanup +- Established decision framework for historical comment management +- Added transformation patterns and anti-patterns +- Integrated with existing Harbor Pilot architecture rules diff --git a/.cursor/rules/realistic_time_estimation.mdc b/.cursor/rules/realistic_time_estimation.mdc new file mode 100644 index 00000000..aa48577a --- /dev/null +++ b/.cursor/rules/realistic_time_estimation.mdc @@ -0,0 +1,348 @@ +--- +description: when generating text that has project task work estimates +alwaysApply: false +--- +# No Time Estimates β€” Harbor Pilot Directive + +> **Agent role**: **DO NOT MAKE TIME ESTIMATES**. Instead, use phases, milestones, and complexity levels. Time estimates are consistently wrong and create unrealistic expectations. + +## 🎯 Purpose + +Development time estimates are consistently wrong and create unrealistic expectations. This rule ensures we focus on phases, milestones, and complexity rather than trying to predict specific timeframes. + +## 🚨 Critical Rule + +**DO NOT MAKE TIME ESTIMATES** +- **Never provide specific time estimates** - they are always wrong +- **Use phases and milestones** instead of days/weeks +- **Focus on complexity and dependencies** rather than time +- **Set expectations based on progress, not deadlines** + +## πŸ“Š Planning Framework (Not Time Estimates) + +### **Complexity Categories** +- **Simple**: Text changes, styling updates, minor bug fixes +- **Medium**: New features, refactoring, component updates +- **Complex**: Architecture changes, integrations, cross-platform work +- **Unknown**: New technologies, APIs, or approaches + +### **Platform Complexity** +- **Single platform**: Web-only or mobile-only changes +- **Two platforms**: Web + mobile or web + desktop +- **Three platforms**: Web + mobile + desktop +- **Cross-platform consistency**: Ensuring behavior matches across all platforms + +### **Testing Complexity** +- **Basic**: Unit tests for new functionality +- **Comprehensive**: Integration tests, cross-platform testing +- **User acceptance**: User testing, feedback integration + +## πŸ” Planning Process (No Time Estimates) + +### **Step 1: Break Down the Work** +- Identify all subtasks and dependencies +- Group related work into logical phases +- Identify critical path and blockers + +### **Step 2: Define Phases and Milestones** +- **Phase 1**: Foundation work (basic fixes, core functionality) +- **Phase 2**: Enhancement work (new features, integrations) +- **Phase 3**: Polish work (testing, user experience, edge cases) + +### **Step 3: Identify Dependencies** +- **Technical dependencies**: What must be built first +- **Platform dependencies**: What works on which platforms +- **Testing dependencies**: What can be tested when + +### **Step 4: Set Progress Milestones** +- **Milestone 1**: Basic functionality working +- **Milestone 2**: All platforms supported +- **Milestone 3**: Fully tested and polished + +## πŸ“‹ Planning Checklist (No Time Estimates) + +- [ ] Work broken down into logical phases +- [ ] Dependencies identified and mapped +- [ ] Milestones defined with clear criteria +- [ ] Complexity levels assigned to each phase +- [ ] Platform requirements identified +- [ ] Testing strategy planned +- [ ] Risk factors identified +- [ ] Success criteria defined + +## 🎯 Example Planning (No Time Estimates) + +### **Example 1: Simple Feature** +``` +Phase 1: Core implementation +- Basic functionality +- Single platform support +- Unit tests + +Phase 2: Platform expansion +- Multi-platform support +- Integration tests + +Phase 3: Polish +- User testing +- Edge case handling +``` + +### **Example 2: Complex Cross-Platform Feature** +``` +Phase 1: Foundation +- Architecture design +- Core service implementation +- Basic web platform support + +Phase 2: Platform Integration +- Mobile platform support +- Desktop platform support +- Cross-platform consistency + +Phase 3: Testing & Polish +- Comprehensive testing +- Error handling +- User experience refinement +``` + +## 🚫 Anti-Patterns to Avoid + +- **"This should take X days"** - Red flag for time estimation +- **"Just a few hours"** - Ignores complexity and testing +- **"Similar to X"** - Without considering differences +- **"Quick fix"** - Nothing is ever quick in software +- **"No testing needed"** - Testing always takes effort + +## βœ… Best Practices + +### **When Planning:** +1. **Break down everything** - no work is too small to plan +2. **Consider all platforms** - web, mobile, desktop differences +3. **Include testing strategy** - unit, integration, and user testing +4. **Account for unknowns** - there are always surprises +5. **Focus on dependencies** - what blocks what + +### **When Presenting Plans:** +1. **Show the phases** - explain the logical progression +2. **Highlight dependencies** - what could block progress +3. **Define milestones** - clear success criteria +4. **Identify risks** - what could go wrong +5. **Suggest alternatives** - ways to reduce scope or complexity + +## πŸ”„ Continuous Improvement + +### **Track Progress** +- Record planned vs. actual phases completed +- Identify what took longer than expected +- Learn from complexity misjudgments +- Adjust planning process based on experience + +### **Learn from Experience** +- **Underestimated complexity**: Increase complexity categories +- **Missed dependencies**: Improve dependency mapping +- **Platform surprises**: Better platform research upfront + +## 🎯 Integration with Harbor Pilot + +This rule works in conjunction with: +- **Project Planning**: Focuses on phases and milestones +- **Resource Allocation**: Based on complexity, not time +- **Risk Management**: Identifies blockers and dependencies +- **Stakeholder Communication**: Sets progress-based expectations + +## πŸ“ Version History + +### v2.0.0 (2025-08-21) +- **Major Change**: Completely removed time estimation approach +- **New Focus**: Phases, milestones, and complexity-based planning +- **Eliminated**: All time multipliers, estimates, and calculations +- **Added**: Dependency mapping and progress milestone framework + +### v1.0.0 (2025-08-21) +- Initial creation based on user feedback about estimation accuracy +- ~~Established realistic estimation multipliers and process~~ +- ~~Added comprehensive estimation checklist and examples~~ +- Integrated with Harbor Pilot planning and risk management + +--- + +## 🚨 Remember + +**DO NOT MAKE TIME ESTIMATES. Use phases, milestones, and complexity instead. Focus on progress, not deadlines.** + +## 🚨 Remember + +**Your first estimate is wrong. Your second estimate is probably still wrong. Focus on progress, not deadlines.** +# No Time Estimates β€” Harbor Pilot Directive + +> **Agent role**: **DO NOT MAKE TIME ESTIMATES**. Instead, use phases, milestones, and complexity levels. Time estimates are consistently wrong and create unrealistic expectations. + +## 🎯 Purpose + +Development time estimates are consistently wrong and create unrealistic expectations. This rule ensures we focus on phases, milestones, and complexity rather than trying to predict specific timeframes. + +## 🚨 Critical Rule + +**DO NOT MAKE TIME ESTIMATES** +- **Never provide specific time estimates** - they are always wrong +- **Use phases and milestones** instead of days/weeks +- **Focus on complexity and dependencies** rather than time +- **Set expectations based on progress, not deadlines** + +## πŸ“Š Planning Framework (Not Time Estimates) + +### **Complexity Categories** +- **Simple**: Text changes, styling updates, minor bug fixes +- **Medium**: New features, refactoring, component updates +- **Complex**: Architecture changes, integrations, cross-platform work +- **Unknown**: New technologies, APIs, or approaches + +### **Platform Complexity** +- **Single platform**: Web-only or mobile-only changes +- **Two platforms**: Web + mobile or web + desktop +- **Three platforms**: Web + mobile + desktop +- **Cross-platform consistency**: Ensuring behavior matches across all platforms + +### **Testing Complexity** +- **Basic**: Unit tests for new functionality +- **Comprehensive**: Integration tests, cross-platform testing +- **User acceptance**: User testing, feedback integration + +## πŸ” Planning Process (No Time Estimates) + +### **Step 1: Break Down the Work** +- Identify all subtasks and dependencies +- Group related work into logical phases +- Identify critical path and blockers + +### **Step 2: Define Phases and Milestones** +- **Phase 1**: Foundation work (basic fixes, core functionality) +- **Phase 2**: Enhancement work (new features, integrations) +- **Phase 3**: Polish work (testing, user experience, edge cases) + +### **Step 3: Identify Dependencies** +- **Technical dependencies**: What must be built first +- **Platform dependencies**: What works on which platforms +- **Testing dependencies**: What can be tested when + +### **Step 4: Set Progress Milestones** +- **Milestone 1**: Basic functionality working +- **Milestone 2**: All platforms supported +- **Milestone 3**: Fully tested and polished + +## πŸ“‹ Planning Checklist (No Time Estimates) + +- [ ] Work broken down into logical phases +- [ ] Dependencies identified and mapped +- [ ] Milestones defined with clear criteria +- [ ] Complexity levels assigned to each phase +- [ ] Platform requirements identified +- [ ] Testing strategy planned +- [ ] Risk factors identified +- [ ] Success criteria defined + +## 🎯 Example Planning (No Time Estimates) + +### **Example 1: Simple Feature** +``` +Phase 1: Core implementation +- Basic functionality +- Single platform support +- Unit tests + +Phase 2: Platform expansion +- Multi-platform support +- Integration tests + +Phase 3: Polish +- User testing +- Edge case handling +``` + +### **Example 2: Complex Cross-Platform Feature** +``` +Phase 1: Foundation +- Architecture design +- Core service implementation +- Basic web platform support + +Phase 2: Platform Integration +- Mobile platform support +- Desktop platform support +- Cross-platform consistency + +Phase 3: Testing & Polish +- Comprehensive testing +- Error handling +- User experience refinement +``` + +## 🚫 Anti-Patterns to Avoid + +- **"This should take X days"** - Red flag for time estimation +- **"Just a few hours"** - Ignores complexity and testing +- **"Similar to X"** - Without considering differences +- **"Quick fix"** - Nothing is ever quick in software +- **"No testing needed"** - Testing always takes effort + +## βœ… Best Practices + +### **When Planning:** +1. **Break down everything** - no work is too small to plan +2. **Consider all platforms** - web, mobile, desktop differences +3. **Include testing strategy** - unit, integration, and user testing +4. **Account for unknowns** - there are always surprises +5. **Focus on dependencies** - what blocks what + +### **When Presenting Plans:** +1. **Show the phases** - explain the logical progression +2. **Highlight dependencies** - what could block progress +3. **Define milestones** - clear success criteria +4. **Identify risks** - what could go wrong +5. **Suggest alternatives** - ways to reduce scope or complexity + +## πŸ”„ Continuous Improvement + +### **Track Progress** +- Record planned vs. actual phases completed +- Identify what took longer than expected +- Learn from complexity misjudgments +- Adjust planning process based on experience + +### **Learn from Experience** +- **Underestimated complexity**: Increase complexity categories +- **Missed dependencies**: Improve dependency mapping +- **Platform surprises**: Better platform research upfront + +## 🎯 Integration with Harbor Pilot + +This rule works in conjunction with: +- **Project Planning**: Focuses on phases and milestones +- **Resource Allocation**: Based on complexity, not time +- **Risk Management**: Identifies blockers and dependencies +- **Stakeholder Communication**: Sets progress-based expectations + +## πŸ“ Version History + +### v2.0.0 (2025-08-21) +- **Major Change**: Completely removed time estimation approach +- **New Focus**: Phases, milestones, and complexity-based planning +- **Eliminated**: All time multipliers, estimates, and calculations +- **Added**: Dependency mapping and progress milestone framework + +### v1.0.0 (2025-08-21) +- Initial creation based on user feedback about estimation accuracy +- ~~Established realistic estimation multipliers and process~~ +- ~~Added comprehensive estimation checklist and examples~~ +- Integrated with Harbor Pilot planning and risk management + +--- + +## 🚨 Remember + +**DO NOT MAKE TIME ESTIMATES. Use phases, milestones, and complexity instead. Focus on progress, not deadlines.** + +## 🚨 Remember + +**Your first estimate is wrong. Your second estimate is probably still wrong. Focus on progress, not deadlines.** diff --git a/src/components/NotificationSection.vue b/src/components/NotificationSection.vue new file mode 100644 index 00000000..08beb72a --- /dev/null +++ b/src/components/NotificationSection.vue @@ -0,0 +1,194 @@ + + + diff --git a/src/composables/useNotificationSettings.ts b/src/composables/useNotificationSettings.ts new file mode 100644 index 00000000..646af7f5 --- /dev/null +++ b/src/composables/useNotificationSettings.ts @@ -0,0 +1,233 @@ +/** + * useNotificationSettings.ts - Notification settings and permissions service + * + * This service handles all notification-related business logic including: + * - Settings hydration and persistence + * - Registration status checking + * - Notification permission management + * - Settings state management + * + * Separates business logic from UI components while maintaining + * the lifecycle boundary and settings access patterns. + * + * @author Matthew Raymer + * @service NotificationSettingsService + */ + +import { createNotifyHelpers } from "@/utils/notify"; +import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView"; +import { DAILY_CHECK_TITLE, DIRECT_PUSH_TITLE } from "@/libs/util"; +import type { ComponentPublicInstance } from "vue"; + +/** + * Interface for notification settings state + */ +export interface NotificationSettingsState { + isRegistered: boolean; + notifyingNewActivity: boolean; + notifyingNewActivityTime: string; + notifyingReminder: boolean; + notifyingReminderMessage: string; + notifyingReminderTime: string; +} + +/** + * Interface for notification settings actions + */ +export interface NotificationSettingsActions { + hydrateFromSettings: () => Promise; + updateNewActivityNotification: ( + enabled: boolean, + timeText?: string, + ) => Promise; + updateReminderNotification: ( + enabled: boolean, + timeText?: string, + message?: string, + ) => Promise; + showNewActivityNotificationInfo: (router: any) => Promise; + showReminderNotificationInfo: (router: any) => Promise; + showNewActivityNotificationChoice: ( + pushNotificationPermissionRef: any, + ) => Promise; + showReminderNotificationChoice: ( + pushNotificationPermissionRef: any, + ) => Promise; +} + +/** + * Service class for managing notification settings and permissions + * + * @param platformService - PlatformServiceMixin instance for settings access + * @param notify - Notification helper functions + */ +export class NotificationSettingsService + implements NotificationSettingsState, NotificationSettingsActions +{ + // State properties + public isRegistered: boolean = false; + public notifyingNewActivity: boolean = false; + public notifyingNewActivityTime: string = ""; + public notifyingReminder: boolean = false; + public notifyingReminderMessage: string = ""; + public notifyingReminderTime: string = ""; + + constructor( + private platformService: ComponentPublicInstance & { + $accountSettings: () => Promise; + $saveSettings: (changes: any) => Promise; + }, + private notify: ReturnType, + ) {} + + /** + * Load notification settings from database and hydrate internal state + * Uses the existing settings mechanism for consistency + */ + public async hydrateFromSettings(): Promise { + try { + const settings = await this.platformService.$accountSettings(); + + // Hydrate registration status + this.isRegistered = !!settings?.isRegistered; + + // Hydrate boolean flags from time presence + this.notifyingNewActivity = !!settings.notifyingNewActivityTime; + this.notifyingNewActivityTime = settings.notifyingNewActivityTime || ""; + this.notifyingReminder = !!settings.notifyingReminderTime; + this.notifyingReminderMessage = settings.notifyingReminderMessage || ""; + this.notifyingReminderTime = settings.notifyingReminderTime || ""; + } catch (error) { + console.error("Failed to hydrate notification settings:", error); + // Keep default values on error + } + } + + /** + * Update new activity notification settings + */ + public async updateNewActivityNotification( + enabled: boolean, + timeText: string = "", + ): Promise { + await this.platformService.$saveSettings({ + notifyingNewActivityTime: timeText, + }); + this.notifyingNewActivity = enabled; + this.notifyingNewActivityTime = timeText; + } + + /** + * Update reminder notification settings + */ + public async updateReminderNotification( + enabled: boolean, + timeText: string = "", + message: string = "", + ): Promise { + await this.platformService.$saveSettings({ + notifyingReminderMessage: message, + notifyingReminderTime: timeText, + }); + this.notifyingReminder = enabled; + this.notifyingReminderMessage = message; + this.notifyingReminderTime = timeText; + } + + /** + * Show new activity notification info dialog + */ + public async showNewActivityNotificationInfo(router: any): Promise { + this.notify.confirm( + ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.NEW_ACTIVITY_INFO, + async () => { + await router.push({ + name: "help-notification-types", + }); + }, + ); + } + + /** + * Show reminder notification info dialog + */ + public async showReminderNotificationInfo(router: any): Promise { + this.notify.confirm( + ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.REMINDER_INFO, + async () => { + await router.push({ + name: "help-notification-types", + }); + }, + ); + } + + /** + * Handle new activity notification choice (enable/disable) + */ + public async showNewActivityNotificationChoice( + pushNotificationPermissionRef: any, + ): Promise { + if (!this.notifyingNewActivity) { + // Enable notification + pushNotificationPermissionRef.open( + DAILY_CHECK_TITLE, + async (success: boolean, timeText: string) => { + if (success) { + await this.updateNewActivityNotification(true, timeText); + } + }, + ); + } else { + // Disable notification + this.notify.notificationOff(DAILY_CHECK_TITLE, async (success) => { + if (success) { + await this.updateNewActivityNotification(false); + } + }); + } + } + + /** + * Handle reminder notification choice (enable/disable) + */ + public async showReminderNotificationChoice( + pushNotificationPermissionRef: any, + ): Promise { + if (!this.notifyingReminder) { + // Enable notification + pushNotificationPermissionRef.open( + DIRECT_PUSH_TITLE, + async (success: boolean, timeText: string, message?: string) => { + if (success) { + await this.updateReminderNotification(true, timeText, message); + } + }, + ); + } else { + // Disable notification + this.notify.notificationOff(DIRECT_PUSH_TITLE, async (success) => { + if (success) { + await this.updateReminderNotification(false); + } + }); + } + } +} + +/** + * Factory function to create a NotificationSettingsService instance + * + * @param platformService - PlatformServiceMixin instance + * @param notify - Notification helper functions + * @returns NotificationSettingsService instance + */ +export function createNotificationSettingsService( + platformService: ComponentPublicInstance & { + $accountSettings: () => Promise; + $saveSettings: (changes: any) => Promise; + }, + notify: ReturnType, +): NotificationSettingsService { + return new NotificationSettingsService(platformService, notify); +} diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index eb99665c..c578ffbc 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -60,91 +60,7 @@ @share-info="onShareInfo" /> - - -
-

Notifications

-
-
- Reminder Notification - -
-
- - - -
- -
-
-
-
- Message: "{{ notifyingReminderMessage }}" - {{ notifyingReminderTime.replace(" ", " ") }} -
-
- -
- New Activity Notification - -
- -
- - - -
- -
-
-
-
- {{ notifyingNewActivityTime.replace(" ", " ") }} -
-
- - Troubleshoot your notifications… - -
-
- + @@ -765,12 +681,13 @@ import { Capacitor } from "@capacitor/core"; import EntityIcon from "../components/EntityIcon.vue"; import ImageMethodDialog from "../components/ImageMethodDialog.vue"; -import PushNotificationPermission from "../components/PushNotificationPermission.vue"; + import QuickNav from "../components/QuickNav.vue"; import TopMessage from "../components/TopMessage.vue"; import UserNameDialog from "../components/UserNameDialog.vue"; import DataExportSection from "../components/DataExportSection.vue"; import IdentitySection from "@/components/IdentitySection.vue"; +import NotificationSection from "@/components/NotificationSection.vue"; import RegistrationNotice from "@/components/RegistrationNotice.vue"; import LocationSearchSection from "@/components/LocationSearchSection.vue"; import UsageLimitsSection from "@/components/UsageLimitsSection.vue"; @@ -796,11 +713,7 @@ import { getHeaders, tokenExpiryTimeDescription, } from "../libs/endorserServer"; -import { - DAILY_CHECK_TITLE, - DIRECT_PUSH_TITLE, - retrieveAccountMetadata, -} from "../libs/util"; +import { retrieveAccountMetadata } from "../libs/util"; import { logger } from "../utils/logger"; import { PlatformServiceMixin } from "../utils/PlatformServiceMixin"; import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify"; @@ -829,7 +742,7 @@ interface UserNameDialogRef { LMap, LMarker, LTileLayer, - PushNotificationPermission, + NotificationSection, QuickNav, TopMessage, UserNameDialog, @@ -880,12 +793,7 @@ export default class AccountViewView extends Vue { includeUserProfileLocation: boolean = false; savingProfile: boolean = false; - // Notification properties - notifyingNewActivity: boolean = false; - notifyingNewActivityTime: string = ""; - notifyingReminder: boolean = false; - notifyingReminderMessage: string = ""; - notifyingReminderTime: string = ""; + // Push notification subscription (kept for service worker checks) subscription: PushSubscription | null = null; // UI state properties @@ -1003,10 +911,8 @@ export default class AccountViewView extends Vue { const registration = await navigator.serviceWorker?.ready; this.subscription = await registration.pushManager.getSubscription(); if (!this.subscription) { - if (this.notifyingNewActivity || this.notifyingReminder) { - // the app thought there was a subscription but there isn't, so fix the settings - this.turnOffNotifyingFlags(); - } + // Notification settings cleanup is now handled by the NotificationSection component + // which manages its own state lifecycle and persistence } } catch (error) { this.notify.warning( @@ -1046,11 +952,6 @@ export default class AccountViewView extends Vue { this.isRegistered = !!settings?.isRegistered; this.isSearchAreasSet = !!settings.searchBoxes; this.searchBox = settings.searchBoxes?.[0] || null; - this.notifyingNewActivity = !!settings.notifyingNewActivityTime; - this.notifyingNewActivityTime = settings.notifyingNewActivityTime || ""; - this.notifyingReminder = !!settings.notifyingReminderTime; - this.notifyingReminderMessage = settings.notifyingReminderMessage || ""; - this.notifyingReminderTime = settings.notifyingReminderTime || ""; this.partnerApiServer = settings.partnerApiServer || this.partnerApiServer; this.partnerApiServerInput = settings.partnerApiServer || this.partnerApiServerInput; @@ -1132,87 +1033,6 @@ export default class AccountViewView extends Vue { } } - async showNewActivityNotificationInfo(): Promise { - this.notify.confirm( - ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.NEW_ACTIVITY_INFO, - async () => { - await (this.$router as Router).push({ - name: "help-notification-types", - }); - }, - ); - } - - async showNewActivityNotificationChoice(): Promise { - if (!this.notifyingNewActivity) { - ( - this.$refs.pushNotificationPermission as PushNotificationPermission - ).open(DAILY_CHECK_TITLE, async (success: boolean, timeText: string) => { - if (success) { - await this.$saveSettings({ - notifyingNewActivityTime: timeText, - }); - this.notifyingNewActivity = true; - this.notifyingNewActivityTime = timeText; - } - }); - } else { - this.notify.notificationOff(DAILY_CHECK_TITLE, async (success) => { - if (success) { - await this.$saveSettings({ - notifyingNewActivityTime: "", - }); - this.notifyingNewActivity = false; - this.notifyingNewActivityTime = ""; - } - }); - } - } - - async showReminderNotificationInfo(): Promise { - this.notify.confirm( - ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.REMINDER_INFO, - async () => { - await (this.$router as Router).push({ - name: "help-notification-types", - }); - }, - ); - } - - async showReminderNotificationChoice(): Promise { - if (!this.notifyingReminder) { - ( - this.$refs.pushNotificationPermission as PushNotificationPermission - ).open( - DIRECT_PUSH_TITLE, - async (success: boolean, timeText: string, message?: string) => { - if (success) { - await this.$saveSettings({ - notifyingReminderMessage: message, - notifyingReminderTime: timeText, - }); - this.notifyingReminder = true; - this.notifyingReminderMessage = message || ""; - this.notifyingReminderTime = timeText; - } - }, - ); - } else { - this.notify.notificationOff(DIRECT_PUSH_TITLE, async (success) => { - if (success) { - await this.$saveSettings({ - notifyingReminderMessage: "", - notifyingReminderTime: "", - }); - this.notifyingReminder = false; - this.notifyingReminderMessage = ""; - this.notifyingReminderTime = ""; - } - }); - } - } - public async toggleHideRegisterPromptOnNewContact(): Promise { const newSetting = !this.hideRegisterPromptOnNewContact; await this.$saveSettings({ @@ -1229,19 +1049,8 @@ export default class AccountViewView extends Vue { this.passkeyExpirationDescription = tokenExpiryTimeDescription(); } - public async turnOffNotifyingFlags(): Promise { - // should tell the push server as well - await this.$saveSettings({ - notifyingNewActivityTime: "", - notifyingReminderMessage: "", - notifyingReminderTime: "", - }); - this.notifyingNewActivity = false; - this.notifyingNewActivityTime = ""; - this.notifyingReminder = false; - this.notifyingReminderMessage = ""; - this.notifyingReminderTime = ""; - } + // Note: Notification state management has been migrated to NotificationSection component + // which handles its own lifecycle and persistence via PlatformServiceMixin /** * Asynchronously exports the database into a downloadable JSON file.