Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors breadcrumb handling by introducing a shared BreadcrumbStore contract in @hawk.so/core and updating the JavaScript SDK to use a browser-specific implementation (BrowserBreadcrumbStore) instead of the previous BreadcrumbManager.
Changes:
- Added
BreadcrumbStore(plus related types) to@hawk.so/coreand re-exported them from the core entrypoint. - Refactored the JavaScript SDK breadcrumbs implementation to
BrowserBreadcrumbStoreand updatedCatcherto use the new store API (add/get/clear). - Updated JavaScript tests and types to align with the new names and API surface (and removed the local
breadcrumbs-api.tstype).
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/javascript/tests/catcher.user.test.ts | Updates singleton reset/import to BrowserBreadcrumbStore. |
| packages/javascript/tests/catcher.transport.test.ts | Updates singleton reset/import to BrowserBreadcrumbStore. |
| packages/javascript/tests/catcher.test.ts | Updates imports/reset and helper ordering. |
| packages/javascript/tests/catcher.global-handlers.test.ts | Updates singleton reset/import to BrowserBreadcrumbStore. |
| packages/javascript/tests/catcher.context.test.ts | Updates singleton reset/import to BrowserBreadcrumbStore. |
| packages/javascript/tests/catcher.breadcrumbs.test.ts | Updates singleton reset/import to BrowserBreadcrumbStore. |
| packages/javascript/tests/catcher.addons.test.ts | Updates singleton reset/import to BrowserBreadcrumbStore. |
| packages/javascript/tests/breadcrumbs.test.ts | Renames the suite and updates API calls to add/get/clear. |
| packages/javascript/src/types/index.ts | Re-exports breadcrumb store types from @hawk.so/core. |
| packages/javascript/src/types/breadcrumbs-api.ts | Removes the local BreadcrumbsAPI definition in favor of core types. |
| packages/javascript/src/catcher.ts | Switches from BreadcrumbManager to BrowserBreadcrumbStore and updates the public breadcrumbs getter typing. |
| packages/javascript/src/addons/breadcrumbs.ts | Introduces BrowserBreadcrumbStore implementing the core BreadcrumbStore contract and renames browser hint type. |
| packages/core/src/index.ts | Re-exports breadcrumb store types from the core package entrypoint. |
| packages/core/src/breadcrumbs/breadcrumb-store.ts | Adds the new shared breadcrumb store contract and associated types. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -10,9 +11,10 @@ import { buildElementSelector, isValidBreadcrumb, log, Sanitizer } from '@hawk.s | |||
| const DEFAULT_MAX_BREADCRUMBS = 15; | |||
|
|
|||
| /** | |||
| * Hint object passed to beforeBreadcrumb callback | |||
| * Hint object passed to beforeBreadcrumb callback. | |||
| * Extends generic {@link BreadcrumbHint} with browser-specific data. | |||
| */ | |||
| export interface BreadcrumbHint { | |||
| export interface BrowserBreadcrumbHint extends BreadcrumbHint { | |||
| /** | |||
There was a problem hiding this comment.
This module previously exported BreadcrumbHint, BreadcrumbInput, and BreadcrumbManager, but the refactor renames/removes them (BrowserBreadcrumbHint, core BreadcrumbInput, BrowserBreadcrumbStore). If any consumers deep-import these symbols, this becomes a breaking change. Consider keeping deprecated compatibility exports (e.g., aliasing the old names to the new ones / re-exporting the core types) to make migration non-breaking.
There was a problem hiding this comment.
BreadcrumbHint, BreadcrumbInput, and BreadcrumbManager were never in public index.ts.
All we had exported is BreadcrumbsAPI which persisted, but marked as deprecated, since it's same thing as BreadcrumbStore, which has more convenient naming.
packages/javascript/src/catcher.ts
Outdated
| @@ -123,7 +124,7 @@ export default class Catcher { | |||
| /** | |||
| * Breadcrumb manager instance | |||
There was a problem hiding this comment.
The JSDoc says "Breadcrumb manager instance" but the field is now a breadcrumb store (breadcrumbStore). Please update the comment to match the new naming to avoid confusion during maintenance.
| * Breadcrumb manager instance | |
| * Breadcrumb store instance |
| function resetManager(): void { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| (BreadcrumbManager as any).instance = null; | ||
| (BrowserBreadcrumbStore as any).instance = null; | ||
| } |
There was a problem hiding this comment.
Tests reset the singleton by mutating a private static field via (BrowserBreadcrumbStore as any).instance = null. This bypasses destroy() and can leak monkeypatched globals/event listeners between tests (fetch/XHR/history/click handlers). Prefer a public reset helper (or calling destroy() on the existing instance) so globals are reliably restored and tests don't depend on private internals.
There was a problem hiding this comment.
Now calling destroy() on instance instead of assigning null to it.
| localStorage.clear(); | ||
| mockParse.mockResolvedValue([]); | ||
| (BreadcrumbManager as any).instance = null; | ||
| (BrowserBreadcrumbStore as any).instance = null; |
There was a problem hiding this comment.
Tests reset the singleton by mutating a private static field via (BrowserBreadcrumbStore as any).instance = null. This bypasses destroy() and can leak monkeypatched globals/event listeners between tests. Prefer a public reset helper (or calling destroy() on the existing instance) so globals are reliably restored and tests don't depend on private internals.
| (BrowserBreadcrumbStore as any).instance = null; | |
| const instance = (BrowserBreadcrumbStore as any).instance as { destroy?: () => void } | null | undefined; | |
| if (instance && typeof instance.destroy === 'function') { | |
| instance.destroy(); | |
| } |
There was a problem hiding this comment.
Just calling destroy on instance before tests.
Fixed.
| localStorage.clear(); | ||
| mockParse.mockResolvedValue([]); | ||
| (BreadcrumbManager as any).instance = null; | ||
| (BrowserBreadcrumbStore as any).instance = null; |
There was a problem hiding this comment.
Tests reset the singleton by mutating a private static field via (BrowserBreadcrumbStore as any).instance = null. This bypasses destroy() and can leak monkeypatched globals/event listeners between tests. Prefer a public reset helper (or calling destroy() on the existing instance) so globals are reliably restored and tests don't depend on private internals.
| (BrowserBreadcrumbStore as any).instance = null; | |
| const instance = (BrowserBreadcrumbStore as any).instance as { destroy?: () => void } | null | undefined; | |
| if (instance && typeof instance.destroy === 'function') { | |
| instance.destroy(); | |
| } |
| localStorage.clear(); | ||
| mockParse.mockResolvedValue([]); | ||
| (BreadcrumbManager as any).instance = null; | ||
| (BrowserBreadcrumbStore as any).instance = null; |
There was a problem hiding this comment.
Tests reset the singleton by mutating a private static field via (BrowserBreadcrumbStore as any).instance = null. This bypasses destroy() and can leak monkeypatched globals/event listeners between tests. Prefer a public reset helper (or calling destroy() on the existing instance) so globals are reliably restored and tests don't depend on private internals.
| (BrowserBreadcrumbStore as any).instance = null; | |
| const currentInstance = (BrowserBreadcrumbStore as any).instance; | |
| if (currentInstance && typeof currentInstance.destroy === 'function') { | |
| currentInstance.destroy(); | |
| } |
| localStorage.clear(); | ||
| mockParse.mockResolvedValue([]); | ||
| (BreadcrumbManager as any).instance = null; | ||
| (BrowserBreadcrumbStore as any).instance = null; |
There was a problem hiding this comment.
Tests reset the singleton by mutating a private static field via (BrowserBreadcrumbStore as any).instance = null. This bypasses destroy() and can leak monkeypatched globals/event listeners between tests. Prefer a public reset helper (or calling destroy() on the existing instance) so globals are reliably restored and tests don't depend on private internals.
| (BrowserBreadcrumbStore as any).instance = null; | |
| const instance = (BrowserBreadcrumbStore as any).instance; | |
| if (instance && typeof instance.destroy === 'function') { | |
| instance.destroy(); | |
| } |
| localStorage.clear(); | ||
| mockParse.mockResolvedValue([]); | ||
| (BreadcrumbManager as any).instance = null; | ||
| (BrowserBreadcrumbStore as any).instance = null; |
There was a problem hiding this comment.
Tests reset the singleton by mutating a private static field via (BrowserBreadcrumbStore as any).instance = null. This bypasses destroy() and can leak monkeypatched globals/event listeners between tests. Prefer a public reset helper (or calling destroy() on the existing instance) so globals are reliably restored and tests don't depend on private internals.
| (BrowserBreadcrumbStore as any).instance = null; | |
| const anyStore = BrowserBreadcrumbStore as any; | |
| if (typeof anyStore.getInstance === 'function') { | |
| const instance = anyStore.getInstance(); | |
| if (instance && typeof instance.destroy === 'function') { | |
| instance.destroy(); | |
| } | |
| } |
| localStorage.clear(); | ||
| mockParse.mockResolvedValue([]); | ||
| (BreadcrumbManager as any).instance = null; | ||
| (BrowserBreadcrumbStore as any).instance = null; |
There was a problem hiding this comment.
Tests reset the singleton by mutating a private static field via (BrowserBreadcrumbStore as any).instance = null. This bypasses destroy() and can leak monkeypatched globals/event listeners between tests. Prefer a public reset helper (or calling destroy() on the existing instance) so globals are reliably restored and tests don't depend on private internals.
| (BrowserBreadcrumbStore as any).instance = null; | |
| const instance = (BrowserBreadcrumbStore as any).instance; | |
| if (instance && typeof instance.destroy === 'function') { | |
| instance.destroy(); | |
| } |
| localStorage.clear(); | ||
| mockParse.mockResolvedValue([]); | ||
| (BreadcrumbManager as any).instance = null; | ||
| (BrowserBreadcrumbStore as any).instance = null; |
There was a problem hiding this comment.
Tests reset the singleton by mutating a private static field via (BrowserBreadcrumbStore as any).instance = null. This bypasses destroy() and can leak monkeypatched globals/event listeners between tests. Prefer a public reset helper (or calling destroy() on the existing instance) so globals are reliably restored and tests don't depend on private internals.
| (BrowserBreadcrumbStore as any).instance = null; | |
| // Reset BrowserBreadcrumbStore via its public API if available, rather than mutating private internals. | |
| (BrowserBreadcrumbStore as any).destroy?.(); |
|
don't forget to bump version |
- fix breadcrumbStore field TSDoc in catcher - destroy BrowserBreadcrumbStore instance before each tests
Thx for reminder. |
BreadcrumbStoreaddedBreadcrumbManageris nowBrowserBreadcrumbStore