@happyvertical/smrt-analytics 0.57.21 → 0.57.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-analytics",
5
- "packageVersion": "0.57.21",
5
+ "packageVersion": "0.57.22",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-analytics:AnalyticsDataStreamCollection": {
8
8
  "name": "analyticsdatastreamcollection",
@@ -3,16 +3,18 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-analytics",
6
- "packageVersion": "0.57.21",
6
+ "packageVersion": "0.57.22",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "749a3d1d6f86f63b5d996422fe5cdc10a9d3ca6e6934f443f2bba1f2989e47f5",
11
- "packageJson": "1d3e4901ee0ef1adfefb0fe4cb7d414a7b1c06e6638d7c0d95ef3d46e94f5c68",
12
- "agents": "52d16fea29774e6ac50038329c1c644f2f25d2b02df4fc75c96effe7ce477713"
10
+ "manifest": "0743eb708220bddbbbfa85946823aef7055ecd0a78b44ef21dc5fdec879c7e25",
11
+ "packageJson": "598b5c7b7bf71ea5829ac21345074db1c64c80ad351e6eff4f14a97375e100e9",
12
+ "agents": "0738e00081ba0afec63eead2120be90659465f3daf4fbd15948594d062448677",
13
+ "moduleDoc:agents/browser.md": "af729304f8ffe8106261665b23f7bbeedcf64fdb08a46f22df1f0bea100ab21b"
13
14
  },
14
15
  "exports": [
15
16
  ".",
17
+ "./browser",
16
18
  "./manifest",
17
19
  "./manifest.json",
18
20
  "./playground",
@@ -3636,7 +3638,14 @@
3636
3638
  "polymorphicAssociations": 0,
3637
3639
  "uuidColumns": 22
3638
3640
  },
3639
- "agentDoc": "# @happyvertical/smrt-analytics\n\nGA4/Plausible/Matomo analytics integration with server-side event tracking and AI-powered reporting.\n\n## Models\n\n- **AnalyticsProperty**: GA4 property, Plausible site, or Matomo site. Provider metadata, status, sync timestamps.\n- **AnalyticsDataStream**: web/iOS/Android data streams. Measurement IDs (G-XXXXXX for GA4, Firebase app ID for mobile).\n- **AnalyticsEvent**: tracked events with params (JSON), retry queue, conversion classification.\n- **AnalyticsReport**: saved report configs with dimensions/metrics (JSON), scheduling (daily/weekly/monthly), result caching.\n\nAll models use STI (`tableStrategy: 'sti'`).\n\nAll four persisted models are `@TenantScoped({ mode: 'optional' })` with a nullable `tenantId` (#1410), matching the framework convention used by sibling domain packages (ads/jobs/commerce). **When a tenant context is active**, the interceptor auto-filters `list`/`get`/raw reads and binds `create`/`save` writes to that tenant, keeping one tenant's properties, data streams, events (end-user PII), and cached report rows from reaching another. Because the mode is `optional`, the interceptor intentionally passes **through unfiltered when no tenant context is established** — so a surface is only safe insofar as it establishes tenant context first. Generated SvelteKit routes do this (#1540); the CLI/MCP code paths do not yet, which is a known framework-level residual (fail-closed tenant context for CLI/MCP) tracked outside this package, not an analytics-specific gap.\n\n## Key Collection Methods\n\n`findByExternalId()`, `findByProvider()`, `findNeedingSync()`, `findGA4Properties()`, `findPlausibleSites()`, `findMatomoSites()`\n\n## AI Operations\n\n`analyzePerformance()`, `analyzeResults()`, `hasPositiveTrends()` route through registered prompts (see Prompt Registry below) so tenant overrides apply via `resolvePrompt()`. `isPerformingWell()` still uses the inline `is()` shortcut.\n\n## Prompt Registry\n\nThree prompts are registered at module-load time via `definePrompt()` from `@happyvertical/smrt-prompts`. Re-exported from the package root for tenant override targeting.\n\n| Key | Method | Variables (PII-conscious) |\n|-----|--------|---------------------------|\n| `smrtAnalytics.property.analyzePerformance` | `AnalyticsProperty.analyzePerformance()` | `period`, `propertyDisplayName`, `propertyProvider` |\n| `smrtAnalytics.report.analyzeResults` | `AnalyticsReport.analyzeResults()` | `reportName`, `reportDimensions`, `reportMetrics`, `dateRangeStart`, `dateRangeEnd`, `rowCount`, `reportData` |\n| `smrtAnalytics.report.hasPositiveTrends` | `AnalyticsReport.hasPositiveTrends()` | `reportMetrics`, `reportData` |\n\n**Excluded from variables (never reach the AI provider):**\n- `id`, `propertyId`, `tenantId`, `externalId` — internal foreign keys / UUIDs that link to identifying records.\n- `apiSecret`, `measurementId`, `siteDomain` — provider credentials and platform-specific identifiers (GA4 API secrets, Matomo `idSite`, custom `G-XXXX` measurement IDs).\n- `providerMetadata` — extensible JSON blob that may contain credentials, account IDs, or other configuration secrets.\n- `lastError`, raw `dimensionFilter` / `metricFilter` JSON — internal error strings (may contain auth tokens) and filter expressions that may reference cookie IDs, user-pseudo-IDs, or IP-derived geos.\n\n**`resultData` is forwarded verbatim.** This package does not — and cannot — strip PII from result rows, because the row schema is determined by the dimensions/metrics the caller asked the analytics provider to return. If the caller persists rows containing a `userPseudoId`, `clientId`, IP-derived geolocation, or any other identifier, those fields WILL reach the AI provider. Callers must:\n\n- exclude PII-bearing dimensions before persisting `resultData` (e.g. don't request `userPseudoId` as a GA4 dimension), or\n- apply a column allowlist at the call site before invoking `analyzeResults()`, or\n- override the prompt template via `PromptOverride` to redact rows.\n\nThe forwarding contract is pinned by a regression test in `src/__tests__/analytics-report-prompt.test.ts`.\n\nSee `src/prompts.ts` for the full rationale block.\n\n## UI Registry\n\nSvelte components live in `src/svelte/` and auto-register with `ModuleUIRegistry` from `@happyvertical/smrt-ui/registry` when `@happyvertical/smrt-analytics/svelte` is imported.\n\nSlot declarations live in `src/ui.ts`, exported via the `./ui` package subpath. The slots are: `analytics-summary`, `events-table`, `property-info`, `property-status-badge`, `stat-card`, `trend-badge` (see `ANALYTICS_UI_SLOTS` for icons / categories / display order).\n\n```typescript\nimport '@happyvertical/smrt-analytics/svelte'; // side-effect: registers slots\nimport { ModuleUIRegistry } from '@happyvertical/smrt-ui/registry';\nconst StatCard = ModuleUIRegistry.get('@happyvertical/smrt-analytics', 'stat-card');\n```\n\n## Recipes (#3719)\n\n`src/recipes.ts` declares `analytics.reports` (`AnalyticsReportsRecipe`:\n`AnalyticsProperty` and `AnalyticsReport`), emitted into the built\n`manifest.json` and `smrt-knowledge.json` `recipes`. `AnalyticsEvent` and\n`AnalyticsDataStream` are left out: event tracking and stream setup are separate\nconcerns. User-facing help is `src/reports.recipe.md` (#3591); keep it and the\n`@field({ description })` text on the two models in plain language, and never\nreference a `sensitive` field (`apiSecret`, `providerMetadata`) in help.\n\n- **Runtime is `server`.** Reports and their rows live in the app database, the\n AI summary needs a server-held key, and provider credentials sit on the\n property row (so no `providers` are declared: there are no env secrets to name).\n- **One route, deliberately.** `/analytics/summary` mounts\n `@happyvertical/smrt-analytics/svelte#AnalyticsSummary`, which renders an empty\n state without data. `PropertyInfo` and `EventsTable` take required row props\n and throw with none, so they are not routes; a host places them in its own\n pages. Add a route only for a component that survives being mounted bare.\n- **No demo seed.** The playground sample data is module-local, not an export.\n- **What the assistant can do.** Generated MCP exposes `list` and `get` on\n `AnalyticsReport` only; `analyzeResults()` is a model method the app calls,\n not a tool. The help says so and points at `reports.query` (declared reports,\n `@happyvertical/smrt-reports`) and `reports.runtime.*` (runtime reports,\n `@happyvertical/smrt-chat`) for aggregates over the app's own data.\n\n`src/__tests__/recipes.test.ts` runs the real scan, manifest and generation\npasses in memory and renders the help. See\n[core recipes](../core/agents/recipes.md).\n\n## Gotchas\n\n- **`apiSecret` / `providerMetadata` stripped from API/MCP**: marked `@field({ sensitive: true })` (#1540) so they never appear in generated responses or `where` filters. They are still stored at rest **unencrypted** — envelope encryption via `@happyvertical/smrt-secrets` is a separate, breaking follow-up (tracked, not done here).\n- **Event retry at model level**: no background job integration\n- **JSON fields**: params, dimensions, metrics use string storage with getter/setter helpers\n- **Measurement IDs differ per platform**: G-XXXXXX (GA4 web) vs Firebase app ID (mobile)\n- **`analyzeResults` is now a single AI call**: previously issued a second freeform `do()` to summarize \"top 3 insights\"; folded into the registered template, and `insights` mirrors `analysis` in the response shape.\n",
3641
+ "agentDoc": "# @happyvertical/smrt-analytics\n\nGA4/Plausible/Matomo analytics integration with server-side event tracking and AI-powered reporting.\n\n## Models\n\n- **AnalyticsProperty**: GA4 property, Plausible site, or Matomo site. Provider metadata, status, sync timestamps.\n- **AnalyticsDataStream**: web/iOS/Android data streams. Measurement IDs (G-XXXXXX for GA4, Firebase app ID for mobile).\n- **AnalyticsEvent**: tracked events with params (JSON), retry queue, conversion classification.\n- **AnalyticsReport**: saved report configs with dimensions/metrics (JSON), scheduling (daily/weekly/monthly), result caching.\n\nAll models use STI (`tableStrategy: 'sti'`).\n\nAll four persisted models are `@TenantScoped({ mode: 'optional' })` with a nullable `tenantId` (#1410), matching the framework convention used by sibling domain packages (ads/jobs/commerce). **When a tenant context is active**, the interceptor auto-filters `list`/`get`/raw reads and binds `create`/`save` writes to that tenant, keeping one tenant's properties, data streams, events (end-user PII), and cached report rows from reaching another. Because the mode is `optional`, the interceptor intentionally passes **through unfiltered when no tenant context is established** — so a surface is only safe insofar as it establishes tenant context first. Generated SvelteKit routes do this (#1540); the CLI/MCP code paths do not yet, which is a known framework-level residual (fail-closed tenant context for CLI/MCP) tracked outside this package, not an analytics-specific gap.\n\n## Key Collection Methods\n\n`findByExternalId()`, `findByProvider()`, `findNeedingSync()`, `findGA4Properties()`, `findPlausibleSites()`, `findMatomoSites()`\n\n## AI Operations\n\n`analyzePerformance()`, `analyzeResults()`, `hasPositiveTrends()` route through registered prompts (see Prompt Registry below) so tenant overrides apply via `resolvePrompt()`. `isPerformingWell()` still uses the inline `is()` shortcut.\n\n## Prompt Registry\n\nThree prompts are registered at module-load time via `definePrompt()` from `@happyvertical/smrt-prompts`. Re-exported from the package root for tenant override targeting.\n\n| Key | Method | Variables (PII-conscious) |\n|-----|--------|---------------------------|\n| `smrtAnalytics.property.analyzePerformance` | `AnalyticsProperty.analyzePerformance()` | `period`, `propertyDisplayName`, `propertyProvider` |\n| `smrtAnalytics.report.analyzeResults` | `AnalyticsReport.analyzeResults()` | `reportName`, `reportDimensions`, `reportMetrics`, `dateRangeStart`, `dateRangeEnd`, `rowCount`, `reportData` |\n| `smrtAnalytics.report.hasPositiveTrends` | `AnalyticsReport.hasPositiveTrends()` | `reportMetrics`, `reportData` |\n\n**Excluded from variables (never reach the AI provider):**\n- `id`, `propertyId`, `tenantId`, `externalId` — internal foreign keys / UUIDs that link to identifying records.\n- `apiSecret`, `measurementId`, `siteDomain` — provider credentials and platform-specific identifiers (GA4 API secrets, Matomo `idSite`, custom `G-XXXX` measurement IDs).\n- `providerMetadata` — extensible JSON blob that may contain credentials, account IDs, or other configuration secrets.\n- `lastError`, raw `dimensionFilter` / `metricFilter` JSON — internal error strings (may contain auth tokens) and filter expressions that may reference cookie IDs, user-pseudo-IDs, or IP-derived geos.\n\n**`resultData` is forwarded verbatim.** This package does not — and cannot — strip PII from result rows, because the row schema is determined by the dimensions/metrics the caller asked the analytics provider to return. If the caller persists rows containing a `userPseudoId`, `clientId`, IP-derived geolocation, or any other identifier, those fields WILL reach the AI provider. Callers must:\n\n- exclude PII-bearing dimensions before persisting `resultData` (e.g. don't request `userPseudoId` as a GA4 dimension), or\n- apply a column allowlist at the call site before invoking `analyzeResults()`, or\n- override the prompt template via `PromptOverride` to redact rows.\n\nThe forwarding contract is pinned by a regression test in `src/__tests__/analytics-report-prompt.test.ts`.\n\nSee `src/prompts.ts` for the full rationale block.\n\n## UI Registry\n\nSvelte components live in `src/svelte/` and auto-register with `ModuleUIRegistry` from `@happyvertical/smrt-ui/registry` when `@happyvertical/smrt-analytics/svelte` is imported.\n\nSlot declarations live in `src/ui.ts`, exported via the `./ui` package subpath. The slots are: `analytics-summary`, `events-table`, `property-info`, `property-status-badge`, `stat-card`, `trend-badge` (see `ANALYTICS_UI_SLOTS` for icons / categories / display order).\n\n```typescript\nimport '@happyvertical/smrt-analytics/svelte'; // side-effect: registers slots\nimport { ModuleUIRegistry } from '@happyvertical/smrt-ui/registry';\nconst StatCard = ModuleUIRegistry.get('@happyvertical/smrt-analytics', 'stat-card');\n```\n\n## Recipes (#3719)\n\n`src/recipes.ts` declares `analytics.reports` (`AnalyticsReportsRecipe`:\n`AnalyticsProperty` and `AnalyticsReport`), emitted into the built\n`manifest.json` and `smrt-knowledge.json` `recipes`. `AnalyticsEvent` and\n`AnalyticsDataStream` are left out: event tracking and stream setup are separate\nconcerns. User-facing help is `src/reports.recipe.md` (#3591); keep it and the\n`@field({ description })` text on the two models in plain language, and never\nreference a `sensitive` field (`apiSecret`, `providerMetadata`) in help.\n\n- **Runtime is `server`.** Reports and their rows live in the app database, the\n AI summary needs a server-held key, and provider credentials sit on the\n property row (so no `providers` are declared: there are no env secrets to name).\n- **One route, deliberately.** `/analytics/summary` mounts\n `@happyvertical/smrt-analytics/svelte#AnalyticsSummary`, which renders an empty\n state without data. `PropertyInfo` and `EventsTable` take required row props\n and throw with none, so they are not routes; a host places them in its own\n pages. Add a route only for a component that survives being mounted bare.\n- **No demo seed.** The playground sample data is module-local, not an export.\n- **What the assistant can do.** Generated MCP exposes `list` and `get` on\n `AnalyticsReport` only; `analyzeResults()` is a model method the app calls,\n not a tool. The help says so and points at `reports.query` (declared reports,\n `@happyvertical/smrt-reports`) and `reports.runtime.*` (runtime reports,\n `@happyvertical/smrt-chat`) for aggregates over the app's own data.\n\n`src/__tests__/recipes.test.ts` runs the real scan, manifest and generation\npasses in memory and renders the help. See\n[core recipes](../core/agents/recipes.md).\n\n## Gotchas\n\n- **`apiSecret` / `providerMetadata` stripped from API/MCP**: marked `@field({ sensitive: true })` (#1540) so they never appear in generated responses or `where` filters. They are still stored at rest **unencrypted** — envelope encryption via `@happyvertical/smrt-secrets` is a separate, breaking follow-up (tracked, not done here).\n- **Event retry at model level**: no background job integration\n- **JSON fields**: params, dimensions, metrics use string storage with getter/setter helpers\n- **Measurement IDs differ per platform**: G-XXXXXX (GA4 web) vs Firebase app ID (mobile)\n- **`analyzeResults` is now a single AI call**: previously issued a second freeform `do()` to summarize \"top 3 insights\"; folded into the registered template, and `insights` mirrors `analysis` in the response shape.\n\n## Browser tracking (#4251)\n\nThe isolated `./browser` subpath owns the optional browser tracker and Matomo\nHTTP adapter; it must never import model registration or the server SDK.\nThe optional `smrt-ui/analytics` context connects shared component standard\nevents to the configured provider. See [browser contract](agents/browser.md).\n",
3642
+ "moduleDocs": [
3643
+ {
3644
+ "path": "agents/browser.md",
3645
+ "module": "browser",
3646
+ "content": "# Browser analytics (#4251)\n\n`@happyvertical/smrt-analytics/browser` is an isolated browser entry, without\nmodel registration or server SDK imports. SDK analytics already owns server\nMatomo reporting/tracking/admin; its Node transport is not browser-compatible.\nThis adapter uses Matomo's free Tracking HTTP API:\nhttps://developer.matomo.org/api-reference/tracking-api\n\nHosts install `setAnalyticsContext(name => tracker?.track(name))` from\n`@happyvertical/smrt-ui/analytics` during root component initialization, then\ncreate/dispose a tracker on browser mount/config changes. Standard events are\nallowlisted by default; apps add only business-specific static definitions.\n`STANDARD_ANALYTICS_EVENTS` is also exported for inspection.\nComponents have no Matomo dependency and absent context is a noop. Configure\n`PUBLIC_ANALYTICS_ENABLED=true`, `PUBLIC_MATOMO_URL`, `PUBLIC_MATOMO_SITE_ID`.\nDevelopment additionally requires `PUBLIC_ANALYTICS_ALLOW_DEVELOPMENT=true`.\nOptional `PUBLIC_ANALYTICS_HEARTBEAT_SECONDS` (minimum 5) sends a Matomo ping\nonly while the document is visible/focused. No background/unload listeners.\n\nOn SvelteKit afterNavigate, use `safeRoutePath(page.route.id)` (null excludes\nunknown routes), combine with origin and a static title, then `pageview`.\nDo not pass location.pathname for dynamic routes. Queries/fragments are always\nremoved. Virtual pages and iframe event-only `context({url,title})` are supported.\nNo browser location URL, referrer or document title is read implicitly.\n\nShared events: Planner opened and successful explicit cookbook application;\nproject question open→answered and decision open→decided; preview link open\n(engagement), pending→approved/rejected (persisted props). Initial historical\nrows and unchanged/error/cancelled results never count as conversions. Planner\nhydration does not count as applying a cookbook. Apps must update authoritative\nprops after their saves and must not emit these events again. No shared\ninvitation acceptance UI exists, so this slice cannot claim that server outcome.\nNo server-side conversion retry/attribution or exact-once persistence is promised.\n\nViews dedupe consecutive sanitized URLs; events dedupe per static key and page\ncontext. Counts therefore measure a successful outcome per view, not total\ntransactions. Business outcomes can pass `track(key, {dedupeKey: receiptId})`\nfor distinct confirmed submissions; this key stays in memory and never enters\nthe provider payload. Failed transport is best effort without retries/logging, and\nno-cors responses cannot prove Matomo acceptance. Actual collector receipt is a\nseparate integration check. Network requests carry no cookie, HTTP referrer,\nvisitor ID, private data, dimensions, query, token, chat, email or freeform input.\nThe server naturally receives IP/User-Agent headers; configure Matomo retention\nand IP anonymization at the deployment boundary. No unique visitor accuracy is\npromised without an identifier. CSP connect-src must allow the configured host.\n\n## Behavior-to-test matrix\n\nAll rows: actor browser visitor; executor N/A (no persistence/transactions);\nSQL dialect N/A (no database); runtime browser and inert SSR.\n\n| Invariant | Trigger / positive | Negative | Edge / level / command |\n|---|---|---|---|\n| Config gating | explicit production enable | missing, malformed, dev, SSR | env public strings; unit; analytics browser.test.ts |\n| Privacy/navigation | template page + static allowlist | query/hash, unknown key/cross-origin | provider seam; unit; analytics browser.test.ts |\n| Lifecycle/dedupe | new SPA route + success | repeated route/key, disposed | timer/provider errors; unit; analytics browser.test.ts |\n| Engagement | visible focused interval | hidden/unfocused/disposed | timer; unit; analytics browser.test.ts |\n| Matomo protocol | view/event/ping | invalid endpoint/site | fetch minimal payload/failure; unit; analytics browser.test.ts |\n| Auto shared outcomes | observed persisted transition | historical, unchanged/failure, rerender | context + component; ui analytics and projects UI tests |\n| Planner success | explicit applied signal | hydration, failed commands, unmounted | state + lifecycle; planner tests |\n\nFocused validation uses package `exec vitest run` with the named tests; build\nsmrt-ui first, then analytics/projects/planner; verify package exports afterward.\nThis is a new feature; base bug reproduction N/A.\n"
3647
+ }
3648
+ ],
3640
3649
  "recipes": [
3641
3650
  {
3642
3651
  "id": "analytics.reports",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-analytics",
3
- "version": "0.57.21",
3
+ "version": "0.57.22",
4
4
  "smrtJsdoc": "strict",
5
5
  "description": "Analytics integration models for SMRT framework",
6
6
  "type": "module",
@@ -9,7 +9,8 @@
9
9
  "files": [
10
10
  "dist",
11
11
  "CLAUDE.md",
12
- "AGENTS.md"
12
+ "AGENTS.md",
13
+ "agents"
13
14
  ],
14
15
  "exports": {
15
16
  ".": {
@@ -30,14 +31,18 @@
30
31
  "types": "./dist/svelte/index.d.ts",
31
32
  "svelte": "./dist/svelte/index.js",
32
33
  "import": "./dist/svelte/index.js"
34
+ },
35
+ "./browser": {
36
+ "types": "./dist/browser.d.ts",
37
+ "import": "./dist/browser.js"
33
38
  }
34
39
  },
35
40
  "dependencies": {
36
- "@happyvertical/smrt-core": "0.57.21",
37
- "@happyvertical/smrt-prompts": "0.57.21",
38
- "@happyvertical/smrt-tenancy": "0.57.21",
39
- "@happyvertical/smrt-types": "0.57.21",
40
- "@happyvertical/smrt-ui": "0.57.21"
41
+ "@happyvertical/smrt-core": "0.57.22",
42
+ "@happyvertical/smrt-prompts": "0.57.22",
43
+ "@happyvertical/smrt-tenancy": "0.57.22",
44
+ "@happyvertical/smrt-types": "0.57.22",
45
+ "@happyvertical/smrt-ui": "0.57.22"
41
46
  },
42
47
  "peerDependencies": {
43
48
  "svelte": "^5.57.0"
@@ -48,8 +53,8 @@
48
53
  }
49
54
  },
50
55
  "devDependencies": {
51
- "@happyvertical/smrt-scanner": "0.57.21",
52
- "@happyvertical/smrt-vitest": "0.57.21",
56
+ "@happyvertical/smrt-scanner": "0.57.22",
57
+ "@happyvertical/smrt-vitest": "0.57.22",
53
58
  "@happyvertical/sql": "^0.106.0",
54
59
  "@sveltejs/package": "^2.5.8",
55
60
  "@types/node": "24.13.6",