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 +9 -0
- package/brand/brand.mjs +14 -0
- package/dist/analytics/index.d.ts +7 -1
- package/dist/types/I_AccessDenial.d.ts +39 -0
- package/dist/types/I_AccessDenial.js +30 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +2 -0
- package/package.json +1 -1
- package/src/analytics/index.ts +8 -1
- package/src/types/I_AccessDenial.ts +42 -0
- package/src/types/index.ts +3 -0
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
|
-
|
|
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
|
+
};
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
|
@@ -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
package/src/analytics/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -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";
|