@gscdump/nuxt 0.22.1 → 1.5.1

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.
Files changed (65) hide show
  1. package/README.md +10 -125
  2. package/dist/module.d.mts +22 -0
  3. package/dist/module.mjs +30 -0
  4. package/dist/runtime-config.d.mts +12 -0
  5. package/dist/runtime-config.mjs +27 -0
  6. package/package.json +25 -44
  7. package/app/assets/css/main.css +0 -2
  8. package/app/components/GscAnalyzerPanel.vue +0 -94
  9. package/app/components/GscAnonymizationBanner.vue +0 -46
  10. package/app/components/GscBootProgress.vue +0 -297
  11. package/app/components/GscCommandPalette.vue +0 -77
  12. package/app/components/GscDashboardPage.vue +0 -26
  13. package/app/components/GscEngineBadge.vue +0 -28
  14. package/app/components/GscHero.vue +0 -215
  15. package/app/components/GscLazyTopResult.vue +0 -45
  16. package/app/components/GscPerformanceChart.vue +0 -532
  17. package/app/components/GscPositionDistributionChart.vue +0 -63
  18. package/app/components/GscQueryLabel.vue +0 -63
  19. package/app/components/GscSitePageHeader.vue +0 -40
  20. package/app/components/GscSourceDebugPanel.vue +0 -195
  21. package/app/components/GscSyncStatusCell.vue +0 -54
  22. package/app/components/GscTopRollupCard.vue +0 -90
  23. package/app/composables/_useGscBackfill.ts +0 -111
  24. package/app/composables/_useGscQueryDispatcher.ts +0 -123
  25. package/app/composables/_useGscResource.ts +0 -114
  26. package/app/composables/_useGscSharedSiteResource.ts +0 -136
  27. package/app/composables/useGscAnalytics.ts +0 -205
  28. package/app/composables/useGscAnalyticsClient.ts +0 -9
  29. package/app/composables/useGscAnalyticsConfig.ts +0 -8
  30. package/app/composables/useGscAnalyticsSourceInfo.ts +0 -159
  31. package/app/composables/useGscAnalyzer.ts +0 -452
  32. package/app/composables/useGscAnalyzerBatch.ts +0 -106
  33. package/app/composables/useGscAnalyzerDefs.ts +0 -118
  34. package/app/composables/useGscAuth.ts +0 -115
  35. package/app/composables/useGscConsoleUrl.ts +0 -45
  36. package/app/composables/useGscCountries.ts +0 -47
  37. package/app/composables/useGscCurrentSite.ts +0 -15
  38. package/app/composables/useGscInspectionHistory.ts +0 -42
  39. package/app/composables/useGscInspections.ts +0 -52
  40. package/app/composables/useGscPanelContext.ts +0 -31
  41. package/app/composables/useGscParquetTable.ts +0 -198
  42. package/app/composables/useGscPeriod.ts +0 -243
  43. package/app/composables/useGscQuery.ts +0 -303
  44. package/app/composables/useGscQueryDispatcher.ts +0 -14
  45. package/app/composables/useGscRequestIndexingInspect.ts +0 -20
  46. package/app/composables/useGscResource.ts +0 -11
  47. package/app/composables/useGscRollup.ts +0 -252
  48. package/app/composables/useGscSearchAppearance.ts +0 -47
  49. package/app/composables/useGscSiteAnalyzer.ts +0 -500
  50. package/app/composables/useGscSitemapHistory.ts +0 -41
  51. package/app/composables/useGscSitemaps.ts +0 -45
  52. package/app/composables/useGscTableState.ts +0 -119
  53. package/app/plugins/analytics.ts +0 -57
  54. package/app/utils/anonymization.ts +0 -24
  55. package/app/utils/country-names.ts +0 -56
  56. package/app/utils/duckdb-wasm.ts +0 -166
  57. package/app/utils/gsc-constants.ts +0 -10
  58. package/app/utils/gsc-error.ts +0 -58
  59. package/app/utils/gsc-fetch.ts +0 -94
  60. package/app/utils/gsc-filters.ts +0 -32
  61. package/app/utils/gsc-rows.ts +0 -72
  62. package/app/utils/position.ts +0 -7
  63. package/module.ts +0 -81
  64. package/nuxt.config.ts +0 -41
  65. package/types.ts +0 -119
package/README.md CHANGED
@@ -1,136 +1,21 @@
1
1
  # @gscdump/nuxt
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@gscdump/nuxt?color=yellow)](https://npmjs.com/package/@gscdump/nuxt)
4
- [![npm downloads](https://img.shields.io/npm/dm/@gscdump/nuxt?color=yellow)](https://npm.chart.dev/@gscdump/nuxt)
5
- [![license](https://img.shields.io/github/license/harlan-zw/gscdump?color=yellow)](https://github.com/harlan-zw/gscdump/blob/main/LICENSE)
3
+ Minimal Nuxt integration for gscdump applications.
6
4
 
7
- > Nuxt layer: GSC analytics UI, Nuxt auth/runtime wiring, and DuckDB-WASM integration. It uses `@gscdump/sdk` internally for hosted analytics HTTP reads.
5
+ It owns two stable Nuxt seams:
8
6
 
9
- This package assumes a site is already linked. It does not own partner
10
- onboarding, registration, lifecycle decisions, token exchange, or webhook
11
- subscription management. Use `@gscdump/sdk` from the host app for those
12
- flows, then pass the linked `gscdumpSiteId` into the analytics composables
13
- here.
7
+ - typed `runtimeConfig.public.analytics` defaults
8
+ - build-time analyzer registry injection as `$gscAnalyzers`
14
9
 
15
- Three consumption modes:
16
-
17
- | Mode | Who | What it does |
18
- | --- | --- | --- |
19
- | `origin` | gscdump.com | Reads R2 data directly (binding) + DuckDB service binding. Owns the data. |
20
- | `consumer` | nuxtseo.com | Proxies all reads to an origin over HTTP + an API key. No data locally. |
21
- | `local` | `examples/nuxt-dashboard` | Reads a `gscdump sync` dump from `GSCDUMP_DATA_DIR`. Offline dev. |
22
-
23
- ## Install
24
-
25
- ```bash
26
- pnpm add @gscdump/nuxt
27
- ```
10
+ Views, authentication, data fetching, server routes, and product fallback
11
+ policy stay in the host application. Browser runtime primitives live in
12
+ `@gscdump/engine-duckdb-wasm`; hosted API access lives in `@gscdump/sdk`.
28
13
 
29
14
  ```ts
30
- // nuxt.config.ts
31
15
  export default defineNuxtConfig({
32
- extends: ['@gscdump/nuxt'],
33
- runtimeConfig: {
34
- analytics: {
35
- mode: 'origin', // or 'consumer' / 'local'
36
- },
37
- },
38
- })
39
- ```
40
-
41
- ## Register an auth provider
42
-
43
- ```ts
44
- // server/plugins/register-auth.ts
45
- export default defineNitroPlugin((nitro) => {
46
- nitro.hooks.hook('analytics:register-auth-provider', (register) => {
47
- register({
48
- resolve: async (event) => {
49
- const session = await getUserSession(event)
50
- if (!session)
51
- return null
52
- return { userId: session.user.id, siteIds: session.accessibleSiteIds }
53
- },
54
- })
55
- })
56
- })
57
- ```
58
-
59
- ## Data access: which endpoint?
60
-
61
- | Need | Endpoint | Client composable |
62
- | --- | --- | --- |
63
- | Pre-aggregated widgets (top pages, daily totals) | `GET /api/sites/:siteId/rollup/:id` | `useGscRollup` / `useGscRollups` / `useGscRollupFanout` |
64
- | Raw rows filtered by dimension/range (`where(eq(page, ...))`) | `POST /api/sites/:siteId/rows` | `useGscRowQuery({ site, state })` |
65
- | Analyzer result (striking-distance, CTR anomaly, …) | `POST /api/sites/:siteId/analyze` | `useGscAnalyzer(siteId).analyze(...)` |
66
-
67
- Rule of thumb: reach for `/rows` + `useGscRowQuery` for detail pages that
68
- need a freeform slice of the data. Only fall back to `/analyze` when the
69
- server runs an actual analyzer (anything in the registry); hitting it for
70
- plain row lookups routes through analyzer-gating logic you don't need.
71
-
72
- ## Consumer mode (cross-origin)
73
-
74
- Used when the Nuxt app authenticating the viewer is not the same origin as
75
- the data — e.g. nuxtseo.com pro consuming gscdump.com. Two pieces:
76
-
77
- 1. **Point fetch at the origin**: set `runtimeConfig.public.analytics.apiBase`
78
- in `nuxt.config.ts` (or via `NUXT_PUBLIC_ANALYTICS_API_BASE`). Empty =
79
- same-origin (the default origin-mode shape).
80
-
81
- 2. **Hand the layer a per-viewer api key**: register a client plugin that
82
- trades the viewer's host session for an api key, then call
83
- `setGscFetchHeaders({ 'x-api-key': key })`. The layer's `useGscFetch`
84
- attaches the header to every `/api/__gsc/*` call automatically. Browser
85
- parquet reads use exact-object URLs minted by the source endpoint; runtime
86
- preflights can include this header, while DuckDB-WASM range reads are
87
- authorized by the token embedded in each URL.
88
-
89
- Wire it from a `'pre'`-enforced client plugin so downstream layer plugins see
90
- the auth on first read. The layer's `setGscAuth` accepts a value, a `Ref`,
91
- or a getter — use a getter when credentials may refresh during the session:
92
-
93
- ```ts
94
- // plugins/00.gscdump-auth.client.ts
95
- export default defineNuxtPlugin({
96
- name: 'gscdump-auth',
97
- enforce: 'pre',
98
- async setup() {
99
- const creds = ref<{ apiKey: string, browserAnalyzerEnabled?: boolean } | null>(null)
100
- creds.value = await $fetch('/api/me/gscdump-credentials').catch(() => null)
101
- setGscAuth(() => ({
102
- apiKey: creds.value?.apiKey ?? null,
103
- browserAnalyzerEnabled: !!creds.value?.browserAnalyzerEnabled,
104
- }))
16
+ modules: ['@gscdump/nuxt'],
17
+ gscdumpAnalytics: {
18
+ analyzers: '~/gscAnalyzers',
105
19
  },
106
20
  })
107
21
  ```
108
-
109
- `useGscQuery` mounts that race the credential fetch resolve once the
110
- `'pre'` plugin's `await` settles — Nuxt blocks subsequent plugins on it.
111
-
112
- Onboarding and lifecycle state stay outside this layer. A consumer app should
113
- use `@gscdump/sdk` from its host backend to register users/sites, read
114
- `GET /api/partner/users/:userId/lifecycle`, handle webhook or realtime
115
- invalidation, and decide when a linked site is ready to render. Once it has a
116
- linked site id, this layer handles query reads and analytics UI only.
117
-
118
- ### CORS
119
-
120
- The origin app (`gscdump.com` or your own) must allow the consumer origin
121
- on `/api/__gsc/*`, `/api/r2-data/*`, and any host endpoints the helper
122
- calls. Allow-headers must include `x-api-key`, `Cache-Control`, and `Range`
123
- (DuckDB-WASM uses partial reads); expose `Content-Range`, `Accept-Ranges`,
124
- `Content-Length`. Preflight needs to cover `PATCH` for any settings calls.
125
-
126
- ## Related
127
-
128
- - [`@gscdump/sdk`](../sdk) — framework-agnostic hosted API client used by host onboarding code and by this Nuxt layer internally.
129
- - [`gscdump`](../gscdump) — local package and query builder.
130
- - [`@gscdump/engine`](../engine) — Storage engine consumed in origin mode.
131
- - [`@gscdump/engine-duckdb-wasm`](../engine-duckdb-wasm) — DuckDB-WASM browser runtime used by client-side widgets.
132
- - [`@gscdump/analysis`](../analysis) — Analyzers powering `/analyze` endpoints.
133
-
134
- ## License
135
-
136
- [MIT](../../LICENSE)
@@ -0,0 +1,22 @@
1
+ import { GscdumpAnalyticsRuntimeConfig, GscdumpDefaultEngine, GscdumpRuntimeEnvironment, resolveGscdumpAnalyticsRuntimeConfig } from "./runtime-config.mjs";
2
+ import { NuxtModule } from "@nuxt/schema";
3
+ interface GscdumpNuxtModuleOptions {
4
+ /**
5
+ * Nuxt-resolvable module exporting an analyzer array as `ANALYZERS` or
6
+ * `default`.
7
+ */
8
+ analyzers?: string;
9
+ }
10
+ declare module '@nuxt/schema' {
11
+ interface NuxtConfig {
12
+ gscdumpAnalytics?: GscdumpNuxtModuleOptions;
13
+ }
14
+ interface NuxtOptions {
15
+ gscdumpAnalytics: GscdumpNuxtModuleOptions;
16
+ }
17
+ interface PublicRuntimeConfig {
18
+ analytics: GscdumpAnalyticsRuntimeConfig;
19
+ }
20
+ }
21
+ declare const gscdumpNuxtModule: NuxtModule<GscdumpNuxtModuleOptions>;
22
+ export { type GscdumpAnalyticsRuntimeConfig, type GscdumpDefaultEngine, GscdumpNuxtModuleOptions, type GscdumpRuntimeEnvironment, gscdumpNuxtModule as default, resolveGscdumpAnalyticsRuntimeConfig };
@@ -0,0 +1,30 @@
1
+ import { resolveGscdumpAnalyticsRuntimeConfig } from "./runtime-config.mjs";
2
+ import process from "node:process";
3
+ import { addPluginTemplate, defineNuxtModule } from "@nuxt/kit";
4
+ const gscdumpNuxtModule = defineNuxtModule({
5
+ meta: {
6
+ name: "@gscdump/nuxt",
7
+ configKey: "gscdumpAnalytics",
8
+ compatibility: { nuxt: ">=4.0.0" }
9
+ },
10
+ defaults: {},
11
+ setup(options, nuxt) {
12
+ nuxt.options.runtimeConfig.public.analytics = resolveGscdumpAnalyticsRuntimeConfig(nuxt.options.runtimeConfig.public.analytics, process.env);
13
+ if (!options.analyzers) return;
14
+ addPluginTemplate({
15
+ filename: "gscdump-analyzers.plugin.mjs",
16
+ getContents: () => [
17
+ `import { defineNuxtPlugin } from '#app'`,
18
+ `import * as registry from ${JSON.stringify(options.analyzers)}`,
19
+ `const analyzers = registry.ANALYZERS ?? registry.default`,
20
+ `if (!Array.isArray(analyzers))`,
21
+ ` throw new TypeError('gscdump analyzer registry must export an array')`,
22
+ `export default defineNuxtPlugin(() => ({`,
23
+ ` provide: { gscAnalyzers: analyzers },`,
24
+ `}))`,
25
+ ``
26
+ ].join("\n")
27
+ });
28
+ }
29
+ });
30
+ export { gscdumpNuxtModule as default, resolveGscdumpAnalyticsRuntimeConfig };
@@ -0,0 +1,12 @@
1
+ type GscdumpDefaultEngine = 'auto' | 'client' | 'server';
2
+ interface GscdumpAnalyticsRuntimeConfig {
3
+ apiBase: string;
4
+ duckdbBundleBase: string;
5
+ duckdbThreadBundleBase: string;
6
+ timezone: string;
7
+ toastErrors: boolean;
8
+ defaultEngine: GscdumpDefaultEngine;
9
+ }
10
+ type GscdumpRuntimeEnvironment = Readonly<Record<string, string | undefined>>;
11
+ declare function resolveGscdumpAnalyticsRuntimeConfig(configured: Partial<GscdumpAnalyticsRuntimeConfig> | undefined, environment: GscdumpRuntimeEnvironment): GscdumpAnalyticsRuntimeConfig;
12
+ export { GscdumpAnalyticsRuntimeConfig, GscdumpDefaultEngine, GscdumpRuntimeEnvironment, resolveGscdumpAnalyticsRuntimeConfig };
@@ -0,0 +1,27 @@
1
+ const DEFAULT_ENGINES = /* @__PURE__ */ new Set([
2
+ "auto",
3
+ "client",
4
+ "server"
5
+ ]);
6
+ function defaultEngine(value) {
7
+ return value && DEFAULT_ENGINES.has(value) ? value : "auto";
8
+ }
9
+ function resolveGscdumpAnalyticsRuntimeConfig(configured, environment) {
10
+ const defaults = {
11
+ apiBase: environment.GSCDUMP_ANALYTICS_API_BASE ?? "",
12
+ duckdbBundleBase: environment.GSCDUMP_DUCKDB_BUNDLE_BASE ?? "",
13
+ duckdbThreadBundleBase: environment.GSCDUMP_DUCKDB_THREAD_BUNDLE_BASE ?? "",
14
+ timezone: environment.GSCDUMP_ANALYTICS_TIMEZONE ?? "",
15
+ toastErrors: environment.GSCDUMP_ANALYTICS_TOAST_ERRORS === "true",
16
+ defaultEngine: defaultEngine(environment.GSCDUMP_ANALYTICS_DEFAULT_ENGINE)
17
+ };
18
+ return {
19
+ apiBase: configured?.apiBase ?? defaults.apiBase,
20
+ duckdbBundleBase: configured?.duckdbBundleBase ?? defaults.duckdbBundleBase,
21
+ duckdbThreadBundleBase: configured?.duckdbThreadBundleBase ?? defaults.duckdbThreadBundleBase,
22
+ timezone: configured?.timezone ?? defaults.timezone,
23
+ toastErrors: configured?.toastErrors ?? defaults.toastErrors,
24
+ defaultEngine: configured?.defaultEngine ?? defaults.defaultEngine
25
+ };
26
+ }
27
+ export { resolveGscdumpAnalyticsRuntimeConfig };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@gscdump/nuxt",
3
3
  "type": "module",
4
- "version": "0.22.1",
5
- "description": "Nuxt layer: GSC analytics UI + DuckDB-WASM integration. Frontend-only; server primitives live in @gscdump/analysis, @gscdump/engine-sqlite, @gscdump/cloudflare, and host apps.",
4
+ "version": "1.5.1",
5
+ "description": "Minimal Nuxt integration for gscdump analytics runtime config and analyzer registries.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
8
8
  "email": "harlan@harlanzw.com",
@@ -17,60 +17,41 @@
17
17
  "sideEffects": false,
18
18
  "exports": {
19
19
  ".": {
20
- "types": "./types.ts",
21
- "import": "./nuxt.config.ts",
22
- "default": "./nuxt.config.ts"
20
+ "types": "./dist/module.d.mts",
21
+ "import": "./dist/module.mjs",
22
+ "default": "./dist/module.mjs"
23
23
  },
24
- "./types": {
25
- "types": "./types.ts",
26
- "import": "./types.ts",
27
- "default": "./types.ts"
28
- },
29
- "./module": {
30
- "types": "./module.ts",
31
- "import": "./module.ts",
32
- "default": "./module.ts"
24
+ "./runtime-config": {
25
+ "types": "./dist/runtime-config.d.mts",
26
+ "import": "./dist/runtime-config.mjs",
27
+ "default": "./dist/runtime-config.mjs"
33
28
  }
34
29
  },
35
- "main": "./nuxt.config.ts",
36
- "types": "./types.ts",
30
+ "main": "./dist/module.mjs",
31
+ "types": "./dist/module.d.mts",
37
32
  "files": [
38
33
  "README.md",
39
- "app",
40
- "module.ts",
41
- "nuxt.config.ts",
42
- "types.ts"
34
+ "dist"
43
35
  ],
36
+ "engines": {
37
+ "node": ">=22"
38
+ },
44
39
  "peerDependencies": {
45
- "nuxt": "^4.4.6",
46
- "vue": "^3.5.34"
40
+ "nuxt": "^4.5.0"
47
41
  },
48
42
  "dependencies": {
49
- "@duckdb/duckdb-wasm": "^1.32.0",
50
- "@nuxt/ui": "^4.8.0",
51
- "@unovis/ts": "^1.6.5",
52
- "@unovis/vue": "^1.6.5",
53
- "@vueuse/core": "^14.3.0",
54
- "@vueuse/nuxt": "^14.3.0",
55
- "defu": "^6.1.7",
56
- "nuxt-use-query": "^0.1.0",
57
- "ofetch": "^1.5.1",
58
- "tailwindcss": "^4.3.0",
59
- "@gscdump/analysis": "0.22.1",
60
- "@gscdump/contracts": "0.22.1",
61
- "@gscdump/engine": "0.22.1",
62
- "@gscdump/engine-duckdb-wasm": "0.22.1",
63
- "@gscdump/sdk": "0.22.1",
64
- "gscdump": "0.22.1"
43
+ "@nuxt/kit": "4.5.0",
44
+ "@nuxt/schema": "4.5.0"
65
45
  },
66
46
  "devDependencies": {
67
- "@nuxt/kit": "^4.4.6",
68
- "nuxt": "^4.4.6",
69
- "typescript": "^6.0.3",
70
- "vue": "^3.5.34"
47
+ "nuxt": "^4.5.0",
48
+ "obuild": "^0.4.38",
49
+ "typescript": "^7.0.2",
50
+ "vitest": "^4.1.10"
71
51
  },
72
52
  "scripts": {
73
- "build": "nuxt prepare",
74
- "typecheck": "nuxt typecheck"
53
+ "build": "obuild",
54
+ "typecheck": "tsc --noEmit",
55
+ "test": "vitest"
75
56
  }
76
57
  }
@@ -1,2 +0,0 @@
1
- @import "tailwindcss";
2
- @import "@nuxt/ui";
@@ -1,94 +0,0 @@
1
- <script setup lang="ts">
2
- // Unified shell for `/analyze` analyzer tabs. Drives the chrome that 11
3
- // per-analyzer sections used to repeat: stat tiles, body, caption. The body
4
- // component + tile projection + caption come from `def.capabilities.panel`;
5
- // the shell owns the loading/error/empty gating unless `panel.ownsLifecycle`
6
- // is set (pipeline panels manage their own phase state internally).
7
-
8
- import type {
9
- GscAnalyzerDefinition,
10
- GscAnalyzerPanelResult,
11
- } from '../composables/useGscAnalyzerDefs'
12
-
13
- interface Props {
14
- def: GscAnalyzerDefinition
15
- rows: unknown[]
16
- meta: Record<string, unknown> | null
17
- queryMs?: number | null
18
- loading?: boolean
19
- error?: string | null
20
- /** Forwarded to the body component as `range`. Optional. */
21
- range?: { start: string, end: string } | null
22
- }
23
-
24
- const props = defineProps<Props>()
25
-
26
- const panel = computed(() => props.def.capabilities?.panel)
27
-
28
- const tiles = computed(() => {
29
- const p = panel.value
30
- if (!p?.summarize)
31
- return []
32
- const result: GscAnalyzerPanelResult = {
33
- results: props.rows,
34
- meta: props.meta ?? {},
35
- queryMs: props.queryMs ?? null,
36
- }
37
- return p.summarize(result)
38
- })
39
-
40
- const showBody = computed(() => {
41
- if (!panel.value)
42
- return false
43
- if (panel.value.ownsLifecycle)
44
- return true
45
- return !props.loading && !props.error && props.rows.length > 0
46
- })
47
-
48
- const bodyProps = computed(() => ({
49
- rows: props.rows,
50
- meta: props.meta ?? {},
51
- range: props.range ?? null,
52
- }))
53
- </script>
54
-
55
- <template>
56
- <section v-if="panel" class="gsc-analyzer-panel">
57
- <div v-if="tiles.length > 0" class="gsc-analyzer-panel__tiles">
58
- <div v-for="(t, i) in tiles" :key="`${t.label}-${i}`" class="gsc-analyzer-panel__tile">
59
- <span class="gsc-analyzer-panel__tile-label">{{ t.label }}</span>
60
- <span class="gsc-analyzer-panel__tile-value" :style="t.valueColor ? { color: t.valueColor } : undefined">{{ t.value }}</span>
61
- </div>
62
- </div>
63
-
64
- <template v-if="!panel.ownsLifecycle">
65
- <div v-if="error" class="gsc-analyzer-panel__error">
66
- {{ error }}
67
- </div>
68
- <div v-else-if="loading" class="gsc-analyzer-panel__loading">
69
- Running analyzer…
70
- </div>
71
- <div v-else-if="rows.length === 0" class="gsc-analyzer-panel__empty">
72
- No rows.
73
- </div>
74
- </template>
75
-
76
- <component :is="panel.component" v-if="showBody" v-bind="bodyProps" />
77
-
78
- <p v-if="panel.caption" class="gsc-analyzer-panel__caption">
79
- <slot name="caption">{{ panel.caption }}</slot>
80
- </p>
81
- </section>
82
- </template>
83
-
84
- <style scoped>
85
- .gsc-analyzer-panel { background: #fff; border: 1px solid #ececef; border-top: 0; border-radius: 0 0 6px 6px; padding: 1rem 1rem 0.9rem; }
86
- .gsc-analyzer-panel__tiles { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 0.85rem; padding: 0 0.15rem; }
87
- .gsc-analyzer-panel__tile { display: flex; flex-direction: column; gap: 0.1rem; }
88
- .gsc-analyzer-panel__tile-label { font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase; color: #888; }
89
- .gsc-analyzer-panel__tile-value { font-size: 1.3rem; font-weight: 600; color: #1d1d1f; font-variant-numeric: tabular-nums; }
90
- .gsc-analyzer-panel__caption { margin: 0.8rem 0 0; font-size: 0.78rem; color: #666; font-style: italic; text-align: center; }
91
- .gsc-analyzer-panel__caption :deep(code) { background: #f0f0f3; padding: 0.05rem 0.3rem; border-radius: 3px; font-size: 0.74rem; color: #4c3ca0; }
92
- .gsc-analyzer-panel__error { padding: 1rem 1.25rem; color: #c00; background: #fff5f5; font-family: ui-monospace, monospace; font-size: 0.82rem; white-space: pre-wrap; }
93
- .gsc-analyzer-panel__loading, .gsc-analyzer-panel__empty { padding: 2.5rem; text-align: center; color: var(--ui-text-dimmed); font-size: 0.9rem; }
94
- </style>
@@ -1,46 +0,0 @@
1
- <script setup lang="ts">
2
- // Surfaces the trailing-28d impression-weighted anonymization % from the
3
- // daily_totals rollup. GSC anonymises queries for low-volume or sensitive
4
- // terms, so query-grained breakdowns (keywords, pages-by-query) undercount
5
- // impressions by this amount. Hidden when data isn't ready or the gap is
6
- // negligible (<1%).
7
-
8
- import { useGscRollup } from '../composables/useGscRollup'
9
- import { weightedAnonPct } from '../utils/anonymization'
10
-
11
- interface DailyTotal {
12
- impressions: number
13
- anonymizedImpressionsPct: number
14
- }
15
-
16
- const { siteIdentifier } = defineProps<{
17
- siteIdentifier: string | null | undefined
18
- }>()
19
-
20
- const { data: daily } = useGscRollup<DailyTotal[]>(() => siteIdentifier ?? null, 'daily_totals')
21
-
22
- const formatted = computed(() => {
23
- if (!siteIdentifier)
24
- return null
25
- const pct = weightedAnonPct(daily.value)
26
- if (pct == null)
27
- return null
28
- const percent = pct * 100
29
- if (percent < 1)
30
- return null
31
- return percent.toFixed(percent < 10 ? 1 : 0)
32
- })
33
- </script>
34
-
35
- <template>
36
- <div
37
- v-if="formatted"
38
- class="flex items-start gap-2 border border-amber-500/30 bg-amber-500/5 px-3 py-2 text-xs text-amber-200/90"
39
- >
40
- <UIcon name="i-ph-info" class="mt-0.5 flex-shrink-0 text-amber-400" />
41
- <div>
42
- <span class="font-semibold">~{{ formatted }}% of impressions are anonymised by Google.</span>
43
- Query-grained breakdowns below exclude these, so the sum won't match site-level totals.
44
- </div>
45
- </div>
46
- </template>