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.