@justai/cuts 0.25.1 → 0.26.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justai/cuts",
3
- "version": "0.25.1",
4
- "description": "A persona's named parts the page shell that holds them, and the cuts themselves.",
3
+ "version": "0.26.1",
4
+ "description": "A persona's named parts \u2014 the page shell that holds them, and the cuts themselves.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
7
7
  "type": "git",
@@ -29,6 +29,6 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@justai/core": "^0.3.0"
32
+ "@justai/core": "^0.4.1"
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, analyticsJs, consentCopy } from "@justai/core";
32
+ import { AUTH_CAPTURE_JS, CONSENT_JS, JS_CLASS_JS, REVEAL_CSS, analyticsJs, consentCopy } 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";
@@ -217,73 +217,7 @@ const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
217
217
  <ConfirmSheet id="consent" ask body={consent.consentText}
218
218
  actionLabel={consent.consentAllow} cancelLabel={consent.consentDecline} cancelValue="denied"
219
219
  data-choice-label={consent.consentChoice} />
220
- <script is:inline>
221
- (function () {
222
- try {
223
- // same non-production guard as the analytics block: no measurement → nothing to consent to
224
- var h = location.hostname;
225
- if (h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "[::1]" || h.indexOf("dev-") === 0) return;
226
- var el = document.getElementById("consent"); if (!el || !el.showModal) return;
227
- // returnValue survives a close, so reset before every open or a stale answer replays.
228
- var open = function () { if (!el.open) { el.returnValue = ""; el.showModal(); } };
229
- el.addEventListener("close", function () {
230
- var rv = el.returnValue;
231
- // "confirm" = the ❤️. "denied" = a CHOSEN "Stay invisible" (the sheet's cancelValue).
232
- // Anything else (Escape, a backdrop tap) is NOT an answer, and ONLY A CHOICE MAY BE
233
- // REMEMBERED AS A CHOICE (founder's law, 2026-07-24 — an earlier draft recorded the
234
- // slip-away as a "quiet no", i.e. an answer the visitor never gave). A slip-away
235
- // stores nothing: analytics stays at the regional default and the ask RETURNS on the
236
- // next visit; the session flag below just keeps THIS visit quiet — one dodge buys one
237
- // visit of peace, not an answer.
238
- var v = rv === "confirm" ? "granted" : rv === "denied" ? "denied" : null;
239
- if (v) {
240
- gtag("consent", "update", { analytics_storage: v });
241
- try { localStorage.setItem("consent", v); } catch (_) {}
242
- } else {
243
- try { sessionStorage.setItem("consent-later", "1"); } catch (_) {}
244
- }
245
- });
246
- // THE CHANGE-YOUR-MIND DOOR — and it is universal, not a consent-region courtesy. Trust is
247
- // the choice staying reachable: the Posts cut's footer carries a hidden anchor
248
- // ([data-consent-open]); this script labels it, reveals it, and wires it to reopen the
249
- // sheet for ANY visitor in ANY region, stored choice or not. A non-EEA visitor gets a real
250
- // opt-out they were never offered before; an EEA visitor can reverse either answer. The
251
- // auto-ask below keeps its narrow gates — the DOOR has none.
252
- var doors = document.querySelectorAll("[data-consent-open]");
253
- for (var i = 0; i < doors.length; i++) {
254
- var a = doors[i];
255
- if (!a.textContent) a.textContent = el.getAttribute("data-choice-label") || "privacy choice";
256
- a.hidden = false;
257
- var w = a.closest("[data-consent-door]"); if (w) w.hidden = false;
258
- a.addEventListener("click", function (ev) { ev.preventDefault(); open(); });
259
- }
260
- // auto-ask: no stored CHOICE, not already dodged THIS visit, only where consent is
261
- // legally asked, only on a content page
262
- if (localStorage.getItem("consent")) return;
263
- try { if (sessionStorage.getItem("consent-later")) return; } catch (_) {}
264
- if (!document.getElementById("kernel")) return; // no kernel = not a content page (e.g. 404) — don't ask for consent on a dead end
265
- // WHERE CONSENT IS LEGALLY ASKED — and a timezone is a PROXY for that, never the thing
266
- // itself. `Europe/` carries almost all of the region list in the head, but not all of it:
267
- // an EEA country can sit in another continent's zone entirely, and those visitors were
268
- // DENIED by the regional default while never being offered the dialog — no path to
269
- // consent at all. Twelve zones, measured, listed here with the country that owns them.
270
- // The two lists are siblings: if the region array up there ever changes, this changes too.
271
- var tz = Intl.DateTimeFormat().resolvedOptions().timeZone || "";
272
- var asked = tz.indexOf("Europe/") === 0 || [
273
- "Asia/Nicosia", "Asia/Famagusta", // Cyprus
274
- "Atlantic/Canary", // Spain — Canary Islands
275
- "Atlantic/Azores", "Atlantic/Madeira", // Portugal
276
- "Atlantic/Reykjavik", // Iceland
277
- "Arctic/Longyearbyen", // Norway — Svalbard
278
- "America/Guadeloupe", "America/Martinique", "America/Cayenne", // France — outermost
279
- "Indian/Reunion", "Indian/Mayotte" // regions; EU law applies
280
- ].indexOf(tz) >= 0;
281
- if (!asked) return;
282
- // the first second belongs to the kernel; the question arrives on the second beat
283
- setTimeout(open, 700);
284
- } catch (_) {}
285
- })();
286
- </script>
220
+ <script is:inline set:html={CONSENT_JS} />
287
221
  )}
288
222
  </body>
289
223
  </html>
@@ -71,10 +71,15 @@ const { id, title, body, actionLabel, cancelLabel, destructive = false, ask = fa
71
71
  {title && <strong id={`${id}-t`}>{title}</strong>}
72
72
  <span id={`${id}-b`}>{body}</span>
73
73
  </div>
74
- <button class:list={["confirm-act", { destructive, preferred: ask }]} id={`${id}-act`} value="confirm" autofocus={ask || undefined}>{actionLabel}</button>
74
+ {/* NO autofocus ATTRIBUTE, deliberately: with a fragment in the URL (/me#profile) Chrome
75
+ refuses to process the attribute at parse and prints a console warning on every load —
76
+ on a closed dialog, noise about a focus that was never going to happen. Focus is the
77
+ OPENER's job now, at showModal time: ask → the invitation (CONSENT_JS does it),
78
+ destructive → the cancel (the consumer does it). The genre decides, as before. */}
79
+ <button class:list={["confirm-act", { destructive, preferred: ask }]} id={`${id}-act`} value="confirm">{actionLabel}</button>
75
80
  {ask && <button class="confirm-act quiet" id={`${id}-cancel`} value={cancelValue} formnovalidate>{cancelLabel}</button>}
76
81
  </div>
77
- {!ask && <button class="confirm-act confirm-cancel" id={`${id}-cancel`} value={cancelValue} formnovalidate autofocus>{cancelLabel}</button>}
82
+ {!ask && <button class="confirm-act confirm-cancel" id={`${id}-cancel`} value={cancelValue} formnovalidate>{cancelLabel}</button>}
78
83
  </form>
79
84
  </dialog>
80
85