ad2app-lib 1.27.1 → 1.29.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.
package/brand/brand.d.ts CHANGED
@@ -47,9 +47,18 @@ export interface ColorTokens {
47
47
  }
48
48
  export const color: ColorTokens;
49
49
 
50
+ export interface DisplayFontTokens {
51
+ /** Direction-5 display face (spec 099 US5, OFL). Display roles only. */
52
+ family: string;
53
+ weights: readonly number[];
54
+ /** Below this size the face is illegible by design — fall back to `family`. */
55
+ minSizePx: number;
56
+ role: string;
57
+ }
50
58
  export interface FontTokens {
51
59
  family: string;
52
60
  fallback: string;
61
+ display: DisplayFontTokens;
53
62
  }
54
63
  export const font: FontTokens;
55
64
 
package/brand/brand.mjs CHANGED
@@ -47,6 +47,20 @@ export const color = {
47
47
  export const font = {
48
48
  family: 'Funnel Sans',
49
49
  fallback: '-apple-system, Segoe UI, Roboto, Helvetica Neue, sans-serif',
50
+ // The Direction-5 DISPLAY face (spec 099 US5; Jan-picked from a five-font exploration; OFL).
51
+ // It lived only in the landing (SiteShell.tsx wires it through next/font, globals.css exposes it
52
+ // as --font-display) until 2026-08-12, when a rendered story card was judged against the live
53
+ // landing and read as another company's: every headline in the motion lane was being set in the
54
+ // BODY face, because this token had no home here. Display roles ONLY — H1/H2 and big numerals.
55
+ // Below ~24px Unbounded is illegible by design, so body and UI stay Funnel Sans.
56
+ // A non-Next consumer (the Remotion lane) must self-host the same family; there is no CDN at
57
+ // render time.
58
+ display: {
59
+ family: 'Unbounded',
60
+ weights: [500, 700],
61
+ minSizePx: 24,
62
+ role: 'display only: h1, h2, big numerals — never body or UI',
63
+ },
50
64
  };
51
65
 
52
66
  // Social canvas sizes (px), the canonical ad set.
@@ -8,6 +8,7 @@
8
8
  * Naming convention: snake_case, object_verb_past_tense.
9
9
  * Do NOT rename events after they ship — historical data does not migrate.
10
10
  */
11
+ import type { AccessDenialCode } from "../types/I_AccessDenial";
11
12
  /** Canonical PostHog event names. */
12
13
  export declare const EVENTS: {
13
14
  readonly LANDING_CTA_CLICKED: "landing_cta_clicked";
@@ -270,7 +271,12 @@ export interface EventProperties {
270
271
  app_locale: AppLocale;
271
272
  };
272
273
  [EVENTS.ACCESS_DENIED]: {
273
- reason: 'beta-required' | 'subscription-required';
274
+ /**
275
+ * The backend's own machine-readable code, not a client guess — one union
276
+ * shared by the API body and this event (types/I_AccessDenial.ts), so a
277
+ * new wall cannot be counted under a reason that does not exist.
278
+ */
279
+ reason: AccessDenialCode;
274
280
  path?: string;
275
281
  app_locale: AppLocale;
276
282
  };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Why a 403 happened, as a value a client can branch on.
3
+ *
4
+ * The frontend used to recognise these walls by comparing the backend's
5
+ * English message text, and that broke silently the first time a guard's
6
+ * wording changed: expected entitlement 403s were captured as $exception at a
7
+ * rate of ten in two minutes of ordinary tapping, burying real errors in the
8
+ * telemetry that exists to surface them (AD2-1320, 2026-08-19). Prose is copy
9
+ * and copy changes; this code is contract.
10
+ *
11
+ * Every 403 the product EXPECTS carries one of these as `code` in its response
12
+ * body, alongside the human-readable `message`, which is unchanged.
13
+ */
14
+ export declare const ACCESS_DENIAL_CODES: {
15
+ /** No plan and no beta slot — nothing of their own to look at yet. */
16
+ readonly BETA_REQUIRED: "beta-required";
17
+ /** A churned payer attempting a write; they can still read their own data. */
18
+ readonly SUBSCRIPTION_REQUIRED: "subscription-required";
19
+ /**
20
+ * Needs a paid plan AND a connected Late profile. Deliberately distinct from
21
+ * SUBSCRIPTION_REQUIRED, because it names two preconditions at once: a
22
+ * paying user who has simply not connected an account yet must never be sent
23
+ * to buy a plan they already own. Clients show the surface's own empty state
24
+ * for this one and do NOT redirect.
25
+ */
26
+ readonly ENTITLEMENT_PRECONDITION: "entitlement-precondition";
27
+ };
28
+ export type AccessDenialCode = (typeof ACCESS_DENIAL_CODES)[keyof typeof ACCESS_DENIAL_CODES];
29
+ /**
30
+ * The body shape those 403s answer with. `statusCode`/`error` are carried
31
+ * explicitly so the response stays byte-identical to Nest's default envelope
32
+ * and `code` is purely additive — no existing consumer sees a change.
33
+ */
34
+ export interface I_AccessDenialBody {
35
+ statusCode: number;
36
+ error: string;
37
+ message: string;
38
+ code: AccessDenialCode;
39
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ACCESS_DENIAL_CODES = void 0;
4
+ /**
5
+ * Why a 403 happened, as a value a client can branch on.
6
+ *
7
+ * The frontend used to recognise these walls by comparing the backend's
8
+ * English message text, and that broke silently the first time a guard's
9
+ * wording changed: expected entitlement 403s were captured as $exception at a
10
+ * rate of ten in two minutes of ordinary tapping, burying real errors in the
11
+ * telemetry that exists to surface them (AD2-1320, 2026-08-19). Prose is copy
12
+ * and copy changes; this code is contract.
13
+ *
14
+ * Every 403 the product EXPECTS carries one of these as `code` in its response
15
+ * body, alongside the human-readable `message`, which is unchanged.
16
+ */
17
+ exports.ACCESS_DENIAL_CODES = {
18
+ /** No plan and no beta slot — nothing of their own to look at yet. */
19
+ BETA_REQUIRED: "beta-required",
20
+ /** A churned payer attempting a write; they can still read their own data. */
21
+ SUBSCRIPTION_REQUIRED: "subscription-required",
22
+ /**
23
+ * Needs a paid plan AND a connected Late profile. Deliberately distinct from
24
+ * SUBSCRIPTION_REQUIRED, because it names two preconditions at once: a
25
+ * paying user who has simply not connected an account yet must never be sent
26
+ * to buy a plan they already own. Clients show the surface's own empty state
27
+ * for this one and do NOT redirect.
28
+ */
29
+ ENTITLEMENT_PRECONDITION: "entitlement-precondition",
30
+ };
@@ -35,3 +35,4 @@ export * from "./I_Collaboration";
35
35
  export * from "./I_Publish";
36
36
  export * from "./I_SM_Platform";
37
37
  export * from "./scheduling";
38
+ export * from "./I_AccessDenial";
@@ -52,3 +52,5 @@ __exportStar(require("./I_Publish"), exports);
52
52
  __exportStar(require("./I_SM_Platform"), exports);
53
53
  // ── Scheduling domain ─────────────────────────────────────────────────────────
54
54
  __exportStar(require("./scheduling"), exports);
55
+ // ── Access control ────────────────────────────────────────────────────────────
56
+ __exportStar(require("./I_AccessDenial"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ad2app-lib",
3
- "version": "1.27.1",
3
+ "version": "1.29.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "commonjs",
@@ -9,6 +9,8 @@
9
9
  * Do NOT rename events after they ship — historical data does not migrate.
10
10
  */
11
11
 
12
+ import type { AccessDenialCode } from "../types/I_AccessDenial";
13
+
12
14
  /** Canonical PostHog event names. */
13
15
  export const EVENTS = {
14
16
  // Acquisition (landing)
@@ -325,7 +327,12 @@ export interface EventProperties {
325
327
  // different 403s the backend sends. `path` is the request path, never a URL
326
328
  // with query params (no PII in analytics).
327
329
  [EVENTS.ACCESS_DENIED]: {
328
- reason: 'beta-required' | 'subscription-required';
330
+ /**
331
+ * The backend's own machine-readable code, not a client guess — one union
332
+ * shared by the API body and this event (types/I_AccessDenial.ts), so a
333
+ * new wall cannot be counted under a reason that does not exist.
334
+ */
335
+ reason: AccessDenialCode;
329
336
  path?: string;
330
337
  app_locale: AppLocale;
331
338
  };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Why a 403 happened, as a value a client can branch on.
3
+ *
4
+ * The frontend used to recognise these walls by comparing the backend's
5
+ * English message text, and that broke silently the first time a guard's
6
+ * wording changed: expected entitlement 403s were captured as $exception at a
7
+ * rate of ten in two minutes of ordinary tapping, burying real errors in the
8
+ * telemetry that exists to surface them (AD2-1320, 2026-08-19). Prose is copy
9
+ * and copy changes; this code is contract.
10
+ *
11
+ * Every 403 the product EXPECTS carries one of these as `code` in its response
12
+ * body, alongside the human-readable `message`, which is unchanged.
13
+ */
14
+ export const ACCESS_DENIAL_CODES = {
15
+ /** No plan and no beta slot — nothing of their own to look at yet. */
16
+ BETA_REQUIRED: "beta-required",
17
+ /** A churned payer attempting a write; they can still read their own data. */
18
+ SUBSCRIPTION_REQUIRED: "subscription-required",
19
+ /**
20
+ * Needs a paid plan AND a connected Late profile. Deliberately distinct from
21
+ * SUBSCRIPTION_REQUIRED, because it names two preconditions at once: a
22
+ * paying user who has simply not connected an account yet must never be sent
23
+ * to buy a plan they already own. Clients show the surface's own empty state
24
+ * for this one and do NOT redirect.
25
+ */
26
+ ENTITLEMENT_PRECONDITION: "entitlement-precondition",
27
+ } as const;
28
+
29
+ export type AccessDenialCode =
30
+ (typeof ACCESS_DENIAL_CODES)[keyof typeof ACCESS_DENIAL_CODES];
31
+
32
+ /**
33
+ * The body shape those 403s answer with. `statusCode`/`error` are carried
34
+ * explicitly so the response stays byte-identical to Nest's default envelope
35
+ * and `code` is purely additive — no existing consumer sees a change.
36
+ */
37
+ export interface I_AccessDenialBody {
38
+ statusCode: number;
39
+ error: string;
40
+ message: string;
41
+ code: AccessDenialCode;
42
+ }
@@ -36,3 +36,6 @@ export * from "./I_Publish";
36
36
  export * from "./I_SM_Platform";
37
37
  // ── Scheduling domain ─────────────────────────────────────────────────────────
38
38
  export * from "./scheduling";
39
+
40
+ // ── Access control ────────────────────────────────────────────────────────────
41
+ export * from "./I_AccessDenial";