@justai/cuts 0.3.1 → 0.4.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 +37 -13
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justai/cuts",
3
- "version": "0.3.1",
4
- "description": "A persona's named parts \u2014 the page shell that holds them, and the cuts themselves.",
3
+ "version": "0.4.0",
4
+ "description": "A persona's named parts the page shell that holds them, and the cuts themselves.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
7
7
  "type": "git",
package/src/Base.astro CHANGED
@@ -40,8 +40,11 @@ interface Props {
40
40
  /** Defaults are the fleet majority, measured. Override when the persona genuinely differs. */
41
41
  themeDark?: string;
42
42
  themeLight?: string;
43
- /** app.zone's property. justai.pro is its own apex and passes its own. */
44
- gaId?: string;
43
+ /** app.zone's property by default. justai.pro passes its own. Pass `null` for a persona that runs
44
+ * NO analytics at all — the whole GA block and the consent banner are then not rendered. The
45
+ * privacy/terms sub-personas use this: the surface that proves it collects nothing by running
46
+ * nothing. */
47
+ gaId?: string | null;
45
48
  }
46
49
 
47
50
  const {
@@ -61,19 +64,28 @@ const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
61
64
  <meta charset="utf-8" />
62
65
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
63
66
  <link rel="preconnect" href="https://api.justai.pro" crossorigin />
64
- <!-- detail: reveal only after we've positioned at the Kernel no scroll flash.
65
- Gated behind .js so it's always visible if JS is off. -->
66
- <!-- justai auth: capture the SSO token here, synchronously, BEFORE the scroll-spy rewrites the hash
67
- to #kernel — otherwise it clobbers #auth_token before the deferred sdk.js can read it. -->
67
+ {/* justai auth: capture the SSO token here, synchronously, BEFORE anything else can touch the
68
+ hash. Its position in the head is LOAD-BEARING, not stylistic, because two later scripts
69
+ would otherwise take the token: the scroll-spy rewrites the hash to #kernel and CLOBBERS
70
+ #auth_token before the deferred sdk.js can read it; and the language picker copies the
71
+ current hash onto the next locale's URL (LangPicker), which would PROPAGATE the raw token
72
+ into a new page and into history. Capturing first defeats both. Move this and sign-in breaks
73
+ silently — no error, the visitor simply lands back signed out. */}
68
74
  <script is:inline>(function(){try{var h=location.hash||"";var m=h.match(/(?:^#|&)auth_token=([^&]+)/);if(m){localStorage.setItem("justai_token",decodeURIComponent(m[1]))}else if(/(?:^#|&)auth_signout=1\b/.test(h)){localStorage.removeItem("justai_token")}else{return}history.replaceState(null,"",location.pathname+location.search)}catch(e){}})();</script>
69
75
  <script is:inline>document.documentElement.classList.add("js")</script>
76
+ {/* Reveal only after a page marks itself ready — gated behind .js so the page is always visible
77
+ if JS is off, and no scroll flash before we position at the Kernel. This style is a TRAP as
78
+ much as a feature, and the warning must stay beside it: ANY page using this layout without a
79
+ kernel script that sets body.ready stays invisible FOREVER. The 404 is the page that keeps
80
+ falling into this — it carries its own inline body.classList.add("ready") for exactly this
81
+ reason. Nothing errors; the page is simply never shown. */}
70
82
  <style is:inline>html.js body{opacity:0}html.js body.ready{opacity:1}</style>
71
- <!-- The locale list is INJECTED, never typed. It used to be a literal array here — a second
83
+ {/* The locale list is INJECTED, never typed. It used to be a literal array here — a second
72
84
  declaration beside src/i18n/index.ts, kept in agreement by hand, in the one place where
73
85
  being wrong is invisible: a locale missing from this list simply never auto-routes, and
74
86
  nothing anywhere goes red. The same shape in astro.config had already drifted (twelve
75
87
  listed while /it/ was live), which is why this one is wired to the real list instead of
76
- being checked against it. -->
88
+ being checked against it. */}
77
89
  <script is:inline define:vars={{ supported: locales, def: defaultLocale }}>
78
90
  // Auto-route to the visitor's language: a saved choice wins; otherwise the browser
79
91
  // language on first visit. Only on the default (en) root — explicit /xx/ links are
@@ -130,7 +142,7 @@ const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
130
142
  rel="icon"
131
143
  href={favicon}
132
144
  />
133
- <!-- PWA: Workbox service worker (precache + offline) + installable manifest -->
145
+ {/* PWA: Workbox service worker (precache + offline) + installable manifest */}
134
146
  <link rel="manifest" href="/manifest.webmanifest" />
135
147
  <link rel="apple-touch-icon" href="/icon-192.png" />
136
148
  {import.meta.env.DEV ? (
@@ -153,14 +165,21 @@ const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
153
165
  if ("serviceWorker" in navigator) addEventListener("load", () => navigator.serviceWorker.register("/sw.js"));
154
166
  </script>
155
167
  )}
156
- <!-- Google Analytics 4 — anonymous (ad signals + personalization OFF), lazy-loaded after idle.
168
+ {/* Google Analytics 4 — anonymous (ad signals + personalization OFF), lazy-loaded after idle.
157
169
  analytics_storage granted: direct user counting (cookieless modeling needs high traffic to
158
170
  surface, so it shows nothing at low/launch volume).
159
171
 
160
172
  gaId comes through define:vars because this script runs in the BROWSER. An is:inline block
161
173
  has no access to component scope, so a bare `gaId` here would be undefined at runtime and
162
174
  every persona would report to a property that does not exist — silently, with no failed
163
- request and no console error, which is the only kind of analytics bug nobody finds. -->
175
+ request and no console error, which is the only kind of analytics bug nobody finds. */}
176
+ {/* gaId can be null: a persona that measures NOTHING. Pass gaId={null} and the entire analytics
177
+ block below and the consent banner in the body are simply not rendered — no gtag, no cookie,
178
+ no request. The privacy/terms sub-personas run this way, and it is not an exception but their
179
+ character: the one surface that can prove it collects nothing by running nothing (doctrine 15).
180
+ The default stays app.zone's property, so every existing persona is unchanged — this costs the
181
+ ten that measure themselves exactly nothing. */}
182
+ {gaId && (
164
183
  <script is:inline define:vars={{ gaId }}>
165
184
  window.dataLayer = window.dataLayer || [];
166
185
  function gtag() { dataLayer.push(arguments); }
@@ -187,11 +206,15 @@ const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
187
206
  });
188
207
  });
189
208
  </script>
209
+ )}
190
210
  </head>
191
211
  <body>
192
212
  <slot />
193
- <!-- Consent banner — shown only to European-timezone visitors with no stored choice. The
194
- Consent Mode region default already denies analytics for the EEA/UK until "Allow". -->
213
+ {/* Consent banner — shown only to European-timezone visitors with no stored choice. The
214
+ Consent Mode region default already denies analytics for the EEA/UK until "Allow". Gated on
215
+ gaId with the analytics block above: a persona that runs no analytics has nothing to consent
216
+ to, so it renders no banner. */}
217
+ {gaId && (
195
218
  <div id="cc" class="cc" role="dialog" aria-label={t.chromePrivacy}>
196
219
  <div class="cc-card">
197
220
  <p class="cc-text">{t.consentText}</p>
@@ -237,5 +260,6 @@ const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
237
260
  } catch (_) {}
238
261
  })();
239
262
  </script>
263
+ )}
240
264
  </body>
241
265
  </html>