@aglyn/aglyn 1.0.0-beta.243 → 1.0.0-beta.246

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/aglyn",
3
- "version": "1.0.0-beta.243",
3
+ "version": "1.0.0-beta.246",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -37,16 +37,16 @@
37
37
  "./package.json": "./package.json"
38
38
  },
39
39
  "dependencies": {
40
- "@aglyn/shared-data-enums": "1.0.0-beta.243",
41
- "@aglyn/shared-data-mdi": "1.0.0-beta.243",
42
- "@aglyn/shared-data-types": "1.0.0-beta.243",
43
- "@aglyn/shared-util-email": "1.0.0-beta.243",
44
- "@aglyn/shared-util-first-touch": "1.0.0-beta.243",
45
- "@aglyn/shared-util-http": "1.0.0-beta.243",
46
- "@aglyn/shared-util-logger": "1.0.0-beta.243",
47
- "@aglyn/shared-util-timestamp": "1.0.0-beta.243",
48
- "@aglyn/shared-util-tools": "1.0.0-beta.243",
49
- "@aglyn/shared-util-vendor": "1.0.0-beta.243",
40
+ "@aglyn/shared-data-enums": "1.0.0-beta.246",
41
+ "@aglyn/shared-data-mdi": "1.0.0-beta.246",
42
+ "@aglyn/shared-data-types": "1.0.0-beta.246",
43
+ "@aglyn/shared-util-email": "1.0.0-beta.246",
44
+ "@aglyn/shared-util-first-touch": "1.0.0-beta.246",
45
+ "@aglyn/shared-util-http": "1.0.0-beta.246",
46
+ "@aglyn/shared-util-logger": "1.0.0-beta.246",
47
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.246",
48
+ "@aglyn/shared-util-tools": "1.0.0-beta.246",
49
+ "@aglyn/shared-util-vendor": "1.0.0-beta.246",
50
50
  "@data-driven-forms/react-form-renderer": "^4.2.0",
51
51
  "@msgpack/msgpack": "^3.1.3",
52
52
  "@types/unist": "^3.0.3",
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The coarse browser FAMILY an error report carries (AGL-3732).
19
+ *
20
+ * React #418 kept arriving from en-GB visitors on aglyn.com with no
21
+ * translator on the page and no way to reproduce it, and the report could not
22
+ * say what browser it came from. This answers that one question and nothing
23
+ * finer: one family out of seven, never the user-agent string and never a
24
+ * version, so it narrows a cluster without identifying a visitor.
25
+ *
26
+ * Order matters, and every test reads a token the browser itself puts there:
27
+ *
28
+ * - **webview first.** An Android WebView marks itself `; wv)`. On iOS every
29
+ * browser is WebKit; Safari and the real third-party browsers say who they
30
+ * are (`Safari/`, `CriOS`, `FxiOS`, `EdgiOS`), and an app's WKWebView —
31
+ * Instagram, Facebook, a mail client — carries no `Safari/` token, or its
32
+ * own `FBAN`/`Instagram` marker. An in-app browser injects into and
33
+ * rewrites the DOM, which is exactly what a hydration mismatch looks like,
34
+ * so it is its own family.
35
+ * - **edge and samsung before chrome**, because both also say `Chrome/`.
36
+ * - **firefox** on `Firefox/` and `FxiOS`.
37
+ * - **chrome** on `Chrome/`/`CriOS`/`Chromium`; Chromium brands that rename
38
+ * themselves (Opera's `OPR/`, Yandex's `YaBrowser/`) are `other`.
39
+ * - **safari** last: `Safari/` with no Chromium token.
40
+ *
41
+ * Pure and total: anything it cannot place, including no string at all, is
42
+ * `other`. No `'use client'` — see the note atop `error-beacon.ts`; the
43
+ * ingest route imports {@link isBrowserFamily} on the server.
44
+ */
45
+ export declare const BROWSER_FAMILIES: readonly ["safari", "chrome", "firefox", "edge", "samsung", "webview", "other"];
46
+ export type BrowserFamily = (typeof BROWSER_FAMILIES)[number];
47
+ /** True only for one of the seven values the beacon writes. */
48
+ export declare function isBrowserFamily(value: unknown): value is BrowserFamily;
49
+ export declare function browserFamily(userAgent: unknown): BrowserFamily;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * The coarse browser FAMILY an error report carries (AGL-3732).
18
+ *
19
+ * React #418 kept arriving from en-GB visitors on aglyn.com with no
20
+ * translator on the page and no way to reproduce it, and the report could not
21
+ * say what browser it came from. This answers that one question and nothing
22
+ * finer: one family out of seven, never the user-agent string and never a
23
+ * version, so it narrows a cluster without identifying a visitor.
24
+ *
25
+ * Order matters, and every test reads a token the browser itself puts there:
26
+ *
27
+ * - **webview first.** An Android WebView marks itself `; wv)`. On iOS every
28
+ * browser is WebKit; Safari and the real third-party browsers say who they
29
+ * are (`Safari/`, `CriOS`, `FxiOS`, `EdgiOS`), and an app's WKWebView —
30
+ * Instagram, Facebook, a mail client — carries no `Safari/` token, or its
31
+ * own `FBAN`/`Instagram` marker. An in-app browser injects into and
32
+ * rewrites the DOM, which is exactly what a hydration mismatch looks like,
33
+ * so it is its own family.
34
+ * - **edge and samsung before chrome**, because both also say `Chrome/`.
35
+ * - **firefox** on `Firefox/` and `FxiOS`.
36
+ * - **chrome** on `Chrome/`/`CriOS`/`Chromium`; Chromium brands that rename
37
+ * themselves (Opera's `OPR/`, Yandex's `YaBrowser/`) are `other`.
38
+ * - **safari** last: `Safari/` with no Chromium token.
39
+ *
40
+ * Pure and total: anything it cannot place, including no string at all, is
41
+ * `other`. No `'use client'` — see the note atop `error-beacon.ts`; the
42
+ * ingest route imports {@link isBrowserFamily} on the server.
43
+ */ export const BROWSER_FAMILIES = [
44
+ 'safari',
45
+ 'chrome',
46
+ 'firefox',
47
+ 'edge',
48
+ 'samsung',
49
+ 'webview',
50
+ 'other'
51
+ ];
52
+ const FAMILY_SET = new Set(BROWSER_FAMILIES);
53
+ /** True only for one of the seven values the beacon writes. */ export function isBrowserFamily(value) {
54
+ return typeof value === 'string' && FAMILY_SET.has(value);
55
+ }
56
+ const IOS_DEVICE = /\b(?:iPhone|iPad|iPod)\b/;
57
+ const IN_APP_MARKER = /\b(?:FBAN|FBAV|FB_IAB|Instagram|Line)[/\s]/;
58
+ export function browserFamily(userAgent) {
59
+ if (typeof userAgent !== 'string' || !userAgent) return 'other';
60
+ const ua = userAgent;
61
+ if (/;\s*wv\)/.test(ua)) return 'webview';
62
+ if (IOS_DEVICE.test(ua)) {
63
+ if (IN_APP_MARKER.test(ua)) return 'webview';
64
+ if (/\bEdgiOS\//.test(ua)) return 'edge';
65
+ if (/\bFxiOS\//.test(ua)) return 'firefox';
66
+ if (/\bCriOS\//.test(ua)) return 'chrome';
67
+ return /\bSafari\//.test(ua) ? 'safari' : 'webview';
68
+ }
69
+ if (/\bEdg(?:e|A)?\//.test(ua)) return 'edge';
70
+ if (/\bSamsungBrowser\//.test(ua)) return 'samsung';
71
+ if (/\bFirefox\//.test(ua)) return 'firefox';
72
+ if (/\b(?:OPR|Opera|YaBrowser)\//.test(ua)) return 'other';
73
+ if (/\b(?:Chrome|Chromium)\//.test(ua)) return 'chrome';
74
+ if (/\bSafari\//.test(ua)) return 'safari';
75
+ return 'other';
76
+ }
77
+
78
+ //# sourceMappingURL=browser-family.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/browser-family.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The coarse browser FAMILY an error report carries (AGL-3732).\n *\n * React #418 kept arriving from en-GB visitors on aglyn.com with no\n * translator on the page and no way to reproduce it, and the report could not\n * say what browser it came from. This answers that one question and nothing\n * finer: one family out of seven, never the user-agent string and never a\n * version, so it narrows a cluster without identifying a visitor.\n *\n * Order matters, and every test reads a token the browser itself puts there:\n *\n * - **webview first.** An Android WebView marks itself `; wv)`. On iOS every\n * browser is WebKit; Safari and the real third-party browsers say who they\n * are (`Safari/`, `CriOS`, `FxiOS`, `EdgiOS`), and an app's WKWebView —\n * Instagram, Facebook, a mail client — carries no `Safari/` token, or its\n * own `FBAN`/`Instagram` marker. An in-app browser injects into and\n * rewrites the DOM, which is exactly what a hydration mismatch looks like,\n * so it is its own family.\n * - **edge and samsung before chrome**, because both also say `Chrome/`.\n * - **firefox** on `Firefox/` and `FxiOS`.\n * - **chrome** on `Chrome/`/`CriOS`/`Chromium`; Chromium brands that rename\n * themselves (Opera's `OPR/`, Yandex's `YaBrowser/`) are `other`.\n * - **safari** last: `Safari/` with no Chromium token.\n *\n * Pure and total: anything it cannot place, including no string at all, is\n * `other`. No `'use client'` — see the note atop `error-beacon.ts`; the\n * ingest route imports {@link isBrowserFamily} on the server.\n */\n\nexport const BROWSER_FAMILIES = [\n 'safari',\n 'chrome',\n 'firefox',\n 'edge',\n 'samsung',\n 'webview',\n 'other',\n] as const\n\nexport type BrowserFamily = (typeof BROWSER_FAMILIES)[number]\n\nconst FAMILY_SET: ReadonlySet<string> = new Set(BROWSER_FAMILIES)\n\n/** True only for one of the seven values the beacon writes. */\nexport function isBrowserFamily(value: unknown): value is BrowserFamily {\n return typeof value === 'string' && FAMILY_SET.has(value)\n}\n\nconst IOS_DEVICE = /\\b(?:iPhone|iPad|iPod)\\b/\nconst IN_APP_MARKER = /\\b(?:FBAN|FBAV|FB_IAB|Instagram|Line)[/\\s]/\n\nexport function browserFamily(userAgent: unknown): BrowserFamily {\n if (typeof userAgent !== 'string' || !userAgent) return 'other'\n const ua = userAgent\n if (/;\\s*wv\\)/.test(ua)) return 'webview'\n if (IOS_DEVICE.test(ua)) {\n if (IN_APP_MARKER.test(ua)) return 'webview'\n if (/\\bEdgiOS\\//.test(ua)) return 'edge'\n if (/\\bFxiOS\\//.test(ua)) return 'firefox'\n if (/\\bCriOS\\//.test(ua)) return 'chrome'\n return /\\bSafari\\//.test(ua) ? 'safari' : 'webview'\n }\n if (/\\bEdg(?:e|A)?\\//.test(ua)) return 'edge'\n if (/\\bSamsungBrowser\\//.test(ua)) return 'samsung'\n if (/\\bFirefox\\//.test(ua)) return 'firefox'\n if (/\\b(?:OPR|Opera|YaBrowser)\\//.test(ua)) return 'other'\n if (/\\b(?:Chrome|Chromium)\\//.test(ua)) return 'chrome'\n if (/\\bSafari\\//.test(ua)) return 'safari'\n return 'other'\n}\n"],"names":["BROWSER_FAMILIES","FAMILY_SET","Set","isBrowserFamily","value","has","IOS_DEVICE","IN_APP_MARKER","browserFamily","userAgent","ua","test"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BC,GAED,OAAO,MAAMA,mBAAmB;IAC9B;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAS;AAIV,MAAMC,aAAkC,IAAIC,IAAIF;AAEhD,6DAA6D,GAC7D,OAAO,SAASG,gBAAgBC,KAAc;IAC5C,OAAO,OAAOA,UAAU,YAAYH,WAAWI,GAAG,CAACD;AACrD;AAEA,MAAME,aAAa;AACnB,MAAMC,gBAAgB;AAEtB,OAAO,SAASC,cAAcC,SAAkB;IAC9C,IAAI,OAAOA,cAAc,YAAY,CAACA,WAAW,OAAO;IACxD,MAAMC,KAAKD;IACX,IAAI,WAAWE,IAAI,CAACD,KAAK,OAAO;IAChC,IAAIJ,WAAWK,IAAI,CAACD,KAAK;QACvB,IAAIH,cAAcI,IAAI,CAACD,KAAK,OAAO;QACnC,IAAI,aAAaC,IAAI,CAACD,KAAK,OAAO;QAClC,IAAI,YAAYC,IAAI,CAACD,KAAK,OAAO;QACjC,IAAI,YAAYC,IAAI,CAACD,KAAK,OAAO;QACjC,OAAO,aAAaC,IAAI,CAACD,MAAM,WAAW;IAC5C;IACA,IAAI,kBAAkBC,IAAI,CAACD,KAAK,OAAO;IACvC,IAAI,qBAAqBC,IAAI,CAACD,KAAK,OAAO;IAC1C,IAAI,cAAcC,IAAI,CAACD,KAAK,OAAO;IACnC,IAAI,8BAA8BC,IAAI,CAACD,KAAK,OAAO;IACnD,IAAI,0BAA0BC,IAAI,CAACD,KAAK,OAAO;IAC/C,IAAI,aAAaC,IAAI,CAACD,KAAK,OAAO;IAClC,OAAO;AACT"}
@@ -73,7 +73,7 @@
73
73
  aiCrm: {
74
74
  '#draft-an-email': 'New timeline entry, a new or finished task, or an edited note makes the next summary a new one.',
75
75
  '#match-columns': 'R. What you type into the request is kept with the AI request, as every AI request is, and members of your workspace can see it in the list of AI jobs, so describe the email rather than pasting private details into…',
76
- '#summarize-a-record': 'T, company, deal or lead, with a suggested next step.'
76
+ '#summarize-a-record': 'He columns of your file and the fields they fill. It can also set up the CRM from the Assist chat: your deal pipelines and their stages, and your custom fields.'
77
77
  },
78
78
  aiEmail: {
79
79
  '#where-to-start-it': 'Eaders to choose between. Ask for a campaign instead and it writes the same design plus the draft campaign that would send it.',
@@ -121,7 +121,7 @@
121
121
  },
122
122
  aiProducts: {
123
123
  '#propose-a-first-catalog': 'Criptions, search listings and tags with AI as they land. Once the import has created your products, their copy is written as above and waits in the review table on the products page.',
124
- '#write-a-products-copy': 'Th AI Aglyn AI can write the copy for your products and help you set up a store.'
124
+ '#write-a-products-copy': 'Ur products and help you set up a store. Everything it does is a proposal: you review it, and nothing is saved to your catalog until you apply it.'
125
125
  },
126
126
  aiSeo: {
127
127
  '#fix-what-the-seo-check-finds': 'Sting section has the same Write with AI control. It proposes an SEO title and an SEO description from the product\'s name and description.',
@@ -200,8 +200,9 @@
200
200
  '#4-save-and-test': 'Ces](actions-builder.md#sequences) for the rest, including what a wait means for the emails sent after it.'
201
201
  },
202
202
  catalog: {
203
- '#categories-and-tags': 'Eir prices left empty, and proposed discounts are created switched off.',
204
- '#product-reviews': ', so that collection\'s grid is in price order — high to low if that is the sort chosen, low to high otherwise.'
203
+ '#categories-and-tags': 'Heir suggested prices, and proposed discounts are created switched off.',
204
+ '#review-moderation': 'Ff or change the delay on the Reviews card, and change its wording under Emails → Review request.',
205
+ '#review-settings': '1 to 5 and write a review in the Product reviews block on your product page template.'
205
206
  },
206
207
  commerce: {
207
208
  '#commerce-analytics': 'Hat was refunded, and leaves out test-mode orders. The card lists the five latest orders, and Open store takes you to the products hub.',
@@ -390,7 +391,7 @@
390
391
  '#form-crm-routing': 'Since the same signup form is often placed by more than one push.',
391
392
  '#form-details': 'Something in that card differs from what\'s saved, and saving one card never saves the other.',
392
393
  '#form-submissions': 'R.',
393
- '#form-versions': 'Scribe* or marketing consent — counts even before you pick it as the consent field.',
394
+ '#form-versions': 'Ll walkthrough — the form, a live audience of everyone who submits it, and a reusable band that places it on any page — see Build a newsletter sign-up band.',
394
395
  '#inbox-card': 'Ad no email field cannot be replied to, and says so instead of offering a Send that would fail.',
395
396
  '#one-forms-own-page': 'Goes straight to that site\'s view, with no filter to set. Collaborators invited to particular sites don\'t see the organization\'s Inbox — they keep each site\'s own, which is unchanged.',
396
397
  '#replying-to-a-submission': 'Saved to Inbox on a form you bound to a dataset is the signal to go and check the dataset.',
@@ -563,7 +564,7 @@
563
564
  shipping: {
564
565
  '#carrier-accounts': 'Tment or suite, city, state or region, postal code, the two-letter country code and a phone number, saved beside the location\'s name.',
565
566
  '#shipping-labels': 'Account and not kept by Aglyn. Labels bought on your own account are billed to you by the carrier, so Aglyn charges nothing for them.',
566
- '#where-parcels-ship-from': 'Te. The destination coverage rules explain what your zones make checkout ask and refuse.',
567
+ '#where-parcels-ship-from': 'Redits.',
567
568
  '#zones-and-rates': 'Ced from the zones and rates you save there, at the storefront\'s cart, on a product\'s Buy button, and on a draft order\'s payment link.'
568
569
  },
569
570
  shippingEasy: {