@justai/cuts 0.22.0 → 0.24.0

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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/Base.astro +24 -48
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justai/cuts",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "A persona's named parts — the page shell that holds them, and the cuts themselves.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -29,6 +29,6 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@justai/core": "^0.1.0"
32
+ "@justai/core": "^0.2.0"
33
33
  }
34
34
  }
package/src/Base.astro CHANGED
@@ -29,7 +29,7 @@
29
29
  import { dir as dirOf } from "@justai/ui/locale";
30
30
  // The load-bearing behaviour is framework-free and lives in @justai/core, so `feed` (Next) injects the
31
31
  // SAME strings instead of hand-copying them. This binding just decides where they go in an Astro head.
32
- import { AUTH_CAPTURE_JS, JS_CLASS_JS, REVEAL_CSS } from "@justai/core";
32
+ import { AUTH_CAPTURE_JS, JS_CLASS_JS, REVEAL_CSS, analyticsJs } from "@justai/core";
33
33
  // The consent ask IS a confirmation — a moment of trust — so it rides the fleet's ONE dialog
34
34
  // rather than keeping a private dialect. One anatomy for "are you sure?" and "may I count you?".
35
35
  import ConfirmSheet from "./ConfirmSheet.astro";
@@ -186,52 +186,12 @@ const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
186
186
  character: the one surface that can prove it collects nothing by running nothing (doctrine 15).
187
187
  The default stays app.zone's property, so every existing persona is unchanged — this costs the
188
188
  ten that measure themselves exactly nothing. */}
189
- {gaId && (
190
- <script is:inline define:vars={{ gaId }}>
191
- // A non-production page measures NOTHING. Found by the first read through the analytics MCP:
192
- // 127.0.0.1 + localhost were the property's TOP "persona" (562 fake users in 28 days — local
193
- // dev, plus every headless verification sweep loading the built HTML). The guard also covers
194
- // the edge's dev- preview hosts: previews are rehearsals, not visits. gtag is still defined
195
- // (as the same queue-stub) even on a silenced host, so the consent script can never hit
196
- // "gtag is not defined" — the exact trap documented below. The whole block sits in an if
197
- // rather than behind an early return: Astro 7's compiler rejects a top-level `return` in an
198
- // inline script at BUILD time (the define:vars IIFE only exists later, at runtime).
199
- window.gtag = window.gtag || function () { (window.dataLayer = window.dataLayer || []).push(arguments); };
200
- var __h = location.hostname;
201
- var __nonprod = __h === "localhost" || __h === "127.0.0.1" || __h === "::1" || __h === "[::1]" || __h.indexOf("dev-") === 0;
202
- if (!__nonprod) {
203
- window.dataLayer = window.dataLayer || [];
204
- // gtag is put on WINDOW, not declared as `function gtag`, and this is load-bearing: `define:vars`
205
- // wraps this inline script in an IIFE to scope the injected `gaId`, so a `function gtag` here
206
- // would be trapped in that IIFE — and the consent banner's own inline script (in the body) calls
207
- // gtag, giving "gtag is not defined" the moment a European visitor clicks Allow/Decline (the
208
- // choice is never applied, the banner never closes). Global on window, both scripts see it.
209
- window.gtag = window.gtag || function () { window.dataLayer.push(arguments); };
210
- gtag("js", new Date());
211
- gtag("consent", "default", { ad_storage: "denied", ad_user_data: "denied", ad_personalization: "denied", analytics_storage: "granted" });
212
- // EEA + UK + CH: deny analytics until the visitor consents (the banner below grants it).
213
- gtag("consent", "default", { region: ["AT","BE","BG","HR","CY","CZ","DK","EE","FI","FR","DE","GR","HU","IE","IT","LV","LT","LU","MT","NL","PL","PT","RO","SK","SI","ES","SE","IS","LI","NO","GB","CH"], ad_storage: "denied", ad_user_data: "denied", ad_personalization: "denied", analytics_storage: "denied" });
214
- try { var cc0 = localStorage.getItem("consent"); if (cc0) gtag("consent", "update", { analytics_storage: cc0 }); } catch (e) {}
215
- // page_title is PINNED to the title as it is at load, before anything can touch it. Only one
216
- // persona mutates document.title today — time shows a live clock in the tab — and without this
217
- // its analytics reports "13:32 · Amsterdam" as the page name instead of the page. For every
218
- // other persona it captures the same string the title already has, so it costs nothing and
219
- // removes a trap the next persona that animates its tab would otherwise walk into.
220
- //
221
- // Found by migration: it existed only in time, and the extraction dropped it silently. Nothing
222
- // errored — the analytics would simply have been wrong from that day on.
223
- gtag("config", gaId, { cookie_expires: 60 * 60 * 24 * 60, page_title: document.title, /* two months — matches the GA data retention; the cookie must not outlive the data it labels (foundations/15) */ allow_google_signals: false, allow_ad_personalization_signals: false });
224
- addEventListener("load", function () {
225
- (window.requestIdleCallback || function (f) { setTimeout(f, 1200); })(function () {
226
- var s = document.createElement("script");
227
- s.async = true;
228
- s.src = "https://www.googletagmanager.com/gtag/js?id=" + gaId;
229
- document.head.appendChild(s);
230
- });
231
- });
232
- }
233
- </script>
234
- )}
189
+ {/* THE BLOCK ITSELF LIVES IN @justai/core (analyticsJs) — because feed and auth need the byte-
190
+ identical thing and cannot import an .astro file. Everything that made it load-bearing (the
191
+ non-production guard, why gtag goes on window, the two consent defaults and their sibling
192
+ timezone list, the pinned page_title, the idle load) is documented there, beside the code.
193
+ What stays here is only the decision of WHICH id this persona reports into. */}
194
+ {gaId && <script is:inline set:html={analyticsJs(gaId)} />}
235
195
  </head>
236
196
  <body>
237
197
  <slot />
@@ -296,7 +256,23 @@ const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
296
256
  if (localStorage.getItem("consent")) return;
297
257
  try { if (sessionStorage.getItem("consent-later")) return; } catch (_) {}
298
258
  if (!document.getElementById("kernel")) return; // no kernel = not a content page (e.g. 404) — don't ask for consent on a dead end
299
- if ((Intl.DateTimeFormat().resolvedOptions().timeZone || "").indexOf("Europe/") !== 0) return;
259
+ // WHERE CONSENT IS LEGALLY ASKED — and a timezone is a PROXY for that, never the thing
260
+ // itself. `Europe/` carries almost all of the region list in the head, but not all of it:
261
+ // an EEA country can sit in another continent's zone entirely, and those visitors were
262
+ // DENIED by the regional default while never being offered the dialog — no path to
263
+ // consent at all. Twelve zones, measured, listed here with the country that owns them.
264
+ // The two lists are siblings: if the region array up there ever changes, this changes too.
265
+ var tz = Intl.DateTimeFormat().resolvedOptions().timeZone || "";
266
+ var asked = tz.indexOf("Europe/") === 0 || [
267
+ "Asia/Nicosia", "Asia/Famagusta", // Cyprus
268
+ "Atlantic/Canary", // Spain — Canary Islands
269
+ "Atlantic/Azores", "Atlantic/Madeira", // Portugal
270
+ "Atlantic/Reykjavik", // Iceland
271
+ "Arctic/Longyearbyen", // Norway — Svalbard
272
+ "America/Guadeloupe", "America/Martinique", "America/Cayenne", // France — outermost
273
+ "Indian/Reunion", "Indian/Mayotte" // regions; EU law applies
274
+ ].indexOf(tz) >= 0;
275
+ if (!asked) return;
300
276
  // the first second belongs to the kernel; the question arrives on the second beat
301
277
  setTimeout(open, 700);
302
278
  } catch (_) {}