Extract notification section into reusable component #150
Open
anomalist
wants to merge 9 commits from notification-section
into master
7 changed files with 1344 additions and 201 deletions
@ -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 |
|||
<!-- Parent renders with no props --> |
|||
<NotificationSection /> |
|||
``` |
|||
|
|||
```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<void> { |
|||
await this.hydrateFromSettings(); |
|||
} |
|||
|
|||
private async hydrateFromSettings(): Promise<void> { |
|||
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<void> { |
|||
await this.$saveSettings({ notifyingNewActivityTime: state ? Date.now() : null }); |
|||
this.notifyingNewActivity = state; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### ❌ DON'T (Prop-Driven Coupling) |
|||
|
|||
```vue |
|||
<!-- Parent passes internal state down --> |
|||
<NotificationSection |
|||
:is-registered="isRegistered" |
|||
:notifying-new-activity="notifyingNewActivity" |
|||
/> |
|||
``` |
|||
|
|||
```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<void> { |
|||
await this.hydrateFromSettings(); |
|||
} |
|||
|
|||
private async hydrateFromSettings(): Promise<void> { |
|||
try { |
|||
const s = await this.$accountSettings(); |
|||
this.myState = !!s.mySetting; |
|||
this.myData = s.myData ?? ""; |
|||
} catch (err) { |
|||
// keep defaults |
|||
} |
|||
} |
|||
|
|||
private async updateState(v: boolean): Promise<void> { |
|||
await this.$saveSettings({ mySetting: v }); |
|||
this.myState = v; |
|||
} |
|||
|
|||
async handleUserAction(): Promise<void> { |
|||
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 `<Child :foo="..." :bar="..." @update="..."/>` with `<Child />`. |
|||
- 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 |
@ -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 |
@ -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.** |
@ -0,0 +1,194 @@ |
|||
<template> |
|||
<section |
|||
v-if="isRegistered" |
|||
id="sectionNotifications" |
|||
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8" |
|||
aria-labelledby="notificationsHeading" |
|||
> |
|||
<h2 id="notificationsHeading" class="mb-2 font-bold">Notifications</h2> |
|||
<div class="flex items-center justify-between"> |
|||
<div> |
|||
Reminder Notification |
|||
<button |
|||
class="text-slate-400 fa-fw cursor-pointer" |
|||
aria-label="Learn more about reminder notifications" |
|||
@click.stop="showReminderNotificationInfo" |
|||
> |
|||
<font-awesome-icon icon="question-circle" aria-hidden="true" /> |
|||
</button> |
|||
</div> |
|||
<div |
|||
class="relative ml-2 cursor-pointer" |
|||
role="switch" |
|||
:aria-checked="notifyingReminder" |
|||
aria-label="Toggle reminder notifications" |
|||
tabindex="0" |
|||
@click="showReminderNotificationChoice()" |
|||
> |
|||
<!-- input --> |
|||
<input v-model="notifyingReminder" type="checkbox" class="sr-only" /> |
|||
<!-- line --> |
|||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div> |
|||
<!-- dot --> |
|||
<div |
|||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition" |
|||
></div> |
|||
</div> |
|||
</div> |
|||
<div v-if="notifyingReminder" class="w-full flex justify-between"> |
|||
<span class="ml-8 mr-8">Message: "{{ notifyingReminderMessage }}"</span> |
|||
<span>{{ notifyingReminderTime.replace(" ", " ") }}</span> |
|||
</div> |
|||
<div class="mt-2 flex items-center justify-between"> |
|||
<!-- label --> |
|||
<div> |
|||
New Activity Notification |
|||
<font-awesome-icon |
|||
icon="question-circle" |
|||
class="text-slate-400 fa-fw cursor-pointer" |
|||
@click.stop="showNewActivityNotificationInfo" |
|||
/> |
|||
</div> |
|||
<!-- toggle --> |
|||
<div |
|||
class="relative ml-2 cursor-pointer" |
|||
@click="showNewActivityNotificationChoice()" |
|||
> |
|||
<!-- input --> |
|||
<input v-model="notifyingNewActivity" type="checkbox" class="sr-only" /> |
|||
<!-- line --> |
|||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div> |
|||
<!-- dot --> |
|||
<div |
|||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition" |
|||
></div> |
|||
</div> |
|||
</div> |
|||
<div v-if="notifyingNewActivityTime" class="w-full text-right"> |
|||
{{ notifyingNewActivityTime.replace(" ", " ") }} |
|||
</div> |
|||
<div class="mt-2 text-center"> |
|||
<router-link class="text-sm text-blue-500" to="/help-notifications"> |
|||
Troubleshoot your notifications… |
|||
</router-link> |
|||
</div> |
|||
</section> |
|||
<PushNotificationPermission ref="pushNotificationPermission" /> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Component, Vue } from "vue-facing-decorator"; |
|||
import { Router } from "vue-router"; |
|||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; |
|||
import PushNotificationPermission from "./PushNotificationPermission.vue"; |
|||
import { createNotifyHelpers } from "@/utils/notify"; |
|||
import { NotificationIface } from "@/constants/app"; |
|||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; |
|||
import { |
|||
createNotificationSettingsService, |
|||
type NotificationSettingsService, |
|||
} from "@/composables/useNotificationSettings"; |
|||
|
|||
/** |
|||
* NotificationSection.vue - Notification UI component with service-based logic |
|||
* |
|||
* This component handles the UI presentation of notification functionality: |
|||
* - Reminder notifications with custom messages |
|||
* - New activity notifications |
|||
* - Notification permission management |
|||
* - Help and troubleshooting links |
|||
* |
|||
* Business logic is delegated to NotificationSettingsService for: |
|||
* - Settings hydration and persistence |
|||
* - Registration status checking |
|||
* - Notification permission management |
|||
* |
|||
* The component maintains its lifecycle boundary while keeping logic separate. |
|||
* |
|||
* @author Matthew Raymer |
|||
* @component NotificationSection |
|||
* @vue-facing-decorator |
|||
*/ |
|||
@Component({ |
|||
components: { |
|||
FontAwesomeIcon, |
|||
PushNotificationPermission, |
|||
}, |
|||
mixins: [PlatformServiceMixin], |
|||
}) |
|||
export default class NotificationSection extends Vue { |
|||
$notify!: (notification: NotificationIface, timeout?: number) => void; |
|||
$router!: Router; |
|||
|
|||
// Notification settings service - handles all business logic |
|||
private notificationService!: NotificationSettingsService; |
|||
private notify!: ReturnType<typeof createNotifyHelpers>; |
|||
|
|||
created() { |
|||
this.notify = createNotifyHelpers(this.$notify); |
|||
this.notificationService = createNotificationSettingsService( |
|||
this, |
|||
this.notify, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Computed property to determine if user is registered |
|||
* Reads from the notification service |
|||
*/ |
|||
private get isRegistered(): boolean { |
|||
return this.notificationService.isRegistered; |
|||
} |
|||
|
|||
/** |
|||
* Initialize component state from persisted settings |
|||
* Called when component is mounted |
|||
*/ |
|||
async mounted(): Promise<void> { |
|||
await this.notificationService.hydrateFromSettings(); |
|||
} |
|||
|
|||
// Computed properties for template access |
|||
private get notifyingNewActivity(): boolean { |
|||
return this.notificationService.notifyingNewActivity; |
|||
} |
|||
|
|||
private get notifyingNewActivityTime(): string { |
|||
return this.notificationService.notifyingNewActivityTime; |
|||
} |
|||
|
|||
private get notifyingReminder(): boolean { |
|||
return this.notificationService.notifyingReminder; |
|||
} |
|||
|
|||
private get notifyingReminderMessage(): string { |
|||
return this.notificationService.notifyingReminderMessage; |
|||
} |
|||
|
|||
private get notifyingReminderTime(): string { |
|||
return this.notificationService.notifyingReminderTime; |
|||
} |
|||
|
|||
async showNewActivityNotificationInfo(): Promise<void> { |
|||
await this.notificationService.showNewActivityNotificationInfo( |
|||
this.$router, |
|||
); |
|||
} |
|||
|
|||
async showNewActivityNotificationChoice(): Promise<void> { |
|||
await this.notificationService.showNewActivityNotificationChoice( |
|||
this.$refs.pushNotificationPermission as PushNotificationPermission, |
|||
); |
|||
} |
|||
|
|||
async showReminderNotificationInfo(): Promise<void> { |
|||
await this.notificationService.showReminderNotificationInfo(this.$router); |
|||
} |
|||
|
|||
async showReminderNotificationChoice(): Promise<void> { |
|||
await this.notificationService.showReminderNotificationChoice( |
|||
this.$refs.pushNotificationPermission as PushNotificationPermission, |
|||
); |
|||
} |
|||
} |
|||
</script> |
@ -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<void>; |
|||
updateNewActivityNotification: ( |
|||
enabled: boolean, |
|||
timeText?: string, |
|||
) => Promise<void>; |
|||
updateReminderNotification: ( |
|||
enabled: boolean, |
|||
timeText?: string, |
|||
message?: string, |
|||
) => Promise<void>; |
|||
showNewActivityNotificationInfo: (router: any) => Promise<void>; |
|||
showReminderNotificationInfo: (router: any) => Promise<void>; |
|||
showNewActivityNotificationChoice: ( |
|||
pushNotificationPermissionRef: any, |
|||
) => Promise<void>; |
|||
showReminderNotificationChoice: ( |
|||
pushNotificationPermissionRef: any, |
|||
) => Promise<void>; |
|||
} |
|||
|
|||
/** |
|||
* 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<any>; |
|||
$saveSettings: (changes: any) => Promise<boolean>; |
|||
}, |
|||
private notify: ReturnType<typeof createNotifyHelpers>, |
|||
) {} |
|||
|
|||
/** |
|||
* Load notification settings from database and hydrate internal state |
|||
* Uses the existing settings mechanism for consistency |
|||
*/ |
|||
public async hydrateFromSettings(): Promise<void> { |
|||
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<void> { |
|||
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<void> { |
|||
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<void> { |
|||
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<void> { |
|||
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<void> { |
|||
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<void> { |
|||
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<any>; |
|||
$saveSettings: (changes: any) => Promise<boolean>; |
|||
}, |
|||
notify: ReturnType<typeof createNotifyHelpers>, |
|||
): NotificationSettingsService { |
|||
return new NotificationSettingsService(platformService, notify); |
|||
} |
Loading…
Reference in new issue
This one does make some sense to always include.
This just reminds me that we have others that probably shouldn't be included on every convo:
.cursor/rules/legacy_dexie.mdc:alwaysApply: true
.cursor/rules/documentation.mdc:alwaysApply: true
.cursor/rules/absurd-sql.mdc:alwaysApply: true
.cursor/rules/version_control.mdc:alwaysApply: true
BTW, these ones might be consolidated. (... or if the decision record is just historical then maybe it's not important every time.)
.cursor/rules/timesafari.mdc:alwaysApply: true
.cursor/rules/architectural_decision_record.mdc:alwaysApply: true
I'd support that refactor here because we see it now or logged as a future issue.