@pygmalionjs/pygmalion 0.5.25 → 0.5.28
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/dist-lib/{FrozenRoutePreview-Df_DH4UK.js → FrozenRoutePreview-fEKTwThA.js} +1648 -1585
- package/dist-lib/pygmalion.js +2291 -2194
- package/dist-lib/testing.js +1 -1
- package/dist-lib/types/editor/designImport.d.ts +10 -0
- package/dist-lib/types/editor/screenConformance.d.ts +166 -0
- package/dist-lib/types/editor/sessionPresets.d.ts +18 -0
- package/dist-lib/types/editor/store.d.ts +15 -0
- package/dist-lib/types/lib.d.ts +2 -0
- package/package.json +1 -1
package/dist-lib/testing.js
CHANGED
|
@@ -183,6 +183,11 @@ export interface DesignImportPage {
|
|
|
183
183
|
stateKind?: string;
|
|
184
184
|
/** Host-declared situation group the canvas arranges the frame into. */
|
|
185
185
|
lane?: string;
|
|
186
|
+
/**
|
|
187
|
+
* Runtimes this screen exists in, by session preset id. Absent means every
|
|
188
|
+
* runtime — only the screens a runtime cannot reach at all need to say so.
|
|
189
|
+
*/
|
|
190
|
+
runtimes?: readonly string[];
|
|
186
191
|
scenarioId?: string;
|
|
187
192
|
/**
|
|
188
193
|
* Every scenario this screen demonstrates. Coverage is the whole set; the
|
|
@@ -301,6 +306,11 @@ export interface DesignImportInitialPage {
|
|
|
301
306
|
state?: string;
|
|
302
307
|
stateKind?: string;
|
|
303
308
|
lane?: string;
|
|
309
|
+
/**
|
|
310
|
+
* Runtimes this screen exists in, by session preset id. Absent means every
|
|
311
|
+
* runtime — only the screens a runtime cannot reach at all need to say so.
|
|
312
|
+
*/
|
|
313
|
+
runtimes?: readonly string[];
|
|
304
314
|
scenarioId?: string;
|
|
305
315
|
interactions?: DesignScreenInteraction[];
|
|
306
316
|
environment?: StoryboardEnvironment;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a screen declaration has to carry to be worth trusting.
|
|
3
|
+
*
|
|
4
|
+
* A declared screen is a claim: replay these steps and you arrive at this
|
|
5
|
+
* screen. Nothing checked the claim. A step whose selector had moved failed
|
|
6
|
+
* quietly, the capture went ahead from wherever it stood, and the frame shipped
|
|
7
|
+
* under the declared name — a screen nobody had ever seen, filed as the one they
|
|
8
|
+
* meant. Ten of seventy screens in the first host were in that state, including
|
|
9
|
+
* two the designer had been looking at for weeks.
|
|
10
|
+
*
|
|
11
|
+
* The floor here is that a declaration must be able to fail. Reviewing the
|
|
12
|
+
* declarations by eye does not scale and does not survive a refactor; a screen
|
|
13
|
+
* that cannot fail will eventually be wrong without saying so.
|
|
14
|
+
*
|
|
15
|
+
* The second rule is subtler and matters more over time. A screen may declare
|
|
16
|
+
* an assertion that proves only that the application booted — every screen in
|
|
17
|
+
* the catalog satisfies it — which reads as evidence in review and is none. That
|
|
18
|
+
* shape is recognizable without knowing what a host named its shared constant:
|
|
19
|
+
* an assertion that nearly every other screen also declares distinguishes
|
|
20
|
+
* nothing. So the check compares the declarations against each other rather
|
|
21
|
+
* than against a list of names, and stays useful in a codebase it has never
|
|
22
|
+
* seen.
|
|
23
|
+
*/
|
|
24
|
+
/** Reasons must say something; the surface ledger uses the same floor. */
|
|
25
|
+
export declare const MIN_UNVERIFIED_REASON_LENGTH = 10;
|
|
26
|
+
/**
|
|
27
|
+
* How many screens may share an assertion before it stops counting as evidence
|
|
28
|
+
* that you reached one of them.
|
|
29
|
+
*
|
|
30
|
+
* Expressed as a share of the catalog rather than a count, so it means the same
|
|
31
|
+
* thing for a catalog of twelve screens and one of three hundred.
|
|
32
|
+
*/
|
|
33
|
+
export declare const GENERIC_ASSERTION_SHARE = 0.5;
|
|
34
|
+
export type ScreenConformanceKind =
|
|
35
|
+
/** Steps replay with nothing that could fail if they went elsewhere. */
|
|
36
|
+
'no-arrival-evidence'
|
|
37
|
+
/** Everything it asserts is asserted by most of the catalog. */
|
|
38
|
+
| 'generic-arrival-only'
|
|
39
|
+
/** The host admitted the gap and said why. Informational. */
|
|
40
|
+
| 'unverified-declared';
|
|
41
|
+
export interface ScreenConformanceFinding {
|
|
42
|
+
severity: 'error' | 'warning' | 'info';
|
|
43
|
+
kind: ScreenConformanceKind;
|
|
44
|
+
screenId: string;
|
|
45
|
+
message: string;
|
|
46
|
+
/** Where to declare the fix, named so the reader does not have to guess. */
|
|
47
|
+
fix: string;
|
|
48
|
+
}
|
|
49
|
+
interface ConformanceAssertion {
|
|
50
|
+
selector?: string;
|
|
51
|
+
text?: string;
|
|
52
|
+
attribute?: {
|
|
53
|
+
name?: string;
|
|
54
|
+
value?: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export interface ConformanceScreen {
|
|
58
|
+
id: string;
|
|
59
|
+
name?: string;
|
|
60
|
+
/** Steps replayed before the capture. */
|
|
61
|
+
interactions?: readonly unknown[];
|
|
62
|
+
assertions?: readonly ConformanceAssertion[];
|
|
63
|
+
/**
|
|
64
|
+
* An admission that this screen's arrival is not checked, and why. Held to
|
|
65
|
+
* the same floor as a surface verdict's reason: "later" is not a reason.
|
|
66
|
+
*/
|
|
67
|
+
arrivalUnverified?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Reads the catalog against itself.
|
|
71
|
+
*
|
|
72
|
+
* The host owns which screens exist and what proves each one; this only refuses
|
|
73
|
+
* to let a screen claim something nothing could contradict.
|
|
74
|
+
*/
|
|
75
|
+
export declare function validateScreenConformance(screens: readonly ConformanceScreen[], options?: {
|
|
76
|
+
genericShare?: number;
|
|
77
|
+
}): ScreenConformanceFinding[];
|
|
78
|
+
/**
|
|
79
|
+
* A test hook exists so something can aim at it. One that nothing aims at is
|
|
80
|
+
* not a hook, it is a comment that looks like contract — and the failure is
|
|
81
|
+
* silent in both directions. A hook nobody targets can be deleted by a refactor
|
|
82
|
+
* with no test going red; a target naming a hook that no longer exists fails
|
|
83
|
+
* only when that screen is next captured, which may be never.
|
|
84
|
+
*
|
|
85
|
+
* The host owns the source and hands over the hooks it found; this side owns the
|
|
86
|
+
* declarations and knows what they aim at. Neither could answer alone.
|
|
87
|
+
*/
|
|
88
|
+
export interface HookTargetingInput {
|
|
89
|
+
/** Hooks the product declares, as the host's own scan found them. */
|
|
90
|
+
hooks: readonly string[];
|
|
91
|
+
/** Selectors the declarations aim at — assertions, steps, axis requirements. */
|
|
92
|
+
targets: readonly string[];
|
|
93
|
+
/**
|
|
94
|
+
* Hooks answered some other way: named in a scenario test, or recorded as
|
|
95
|
+
* out of scope. Absent means nothing is excused.
|
|
96
|
+
*/
|
|
97
|
+
answered?: readonly string[];
|
|
98
|
+
}
|
|
99
|
+
export type HookTargetingKind = 'hook-untargeted' | 'target-missing';
|
|
100
|
+
export interface HookTargetingFinding {
|
|
101
|
+
severity: 'error';
|
|
102
|
+
kind: HookTargetingKind;
|
|
103
|
+
hook: string;
|
|
104
|
+
message: string;
|
|
105
|
+
fix: string;
|
|
106
|
+
}
|
|
107
|
+
/** Pulls hook names out of a selector, so `[data-testid="x"][y]` reads as `x`. */
|
|
108
|
+
export declare function hooksInSelector(selector: string): string[];
|
|
109
|
+
export declare function validateHookTargeting(input: HookTargetingInput): HookTargetingFinding[];
|
|
110
|
+
/**
|
|
111
|
+
* Frames that should have been an axis.
|
|
112
|
+
*
|
|
113
|
+
* A frame is for a different screen in a flow; an axis is for the same screen
|
|
114
|
+
* held a different way. Hosts reach for a frame either way, because a frame is
|
|
115
|
+
* the thing they already know how to declare — and then the storyboard grows
|
|
116
|
+
* nodes that are not places, and the reader cannot tell a step from a variation.
|
|
117
|
+
*
|
|
118
|
+
* The signal is a group of screens that agree on route, flow, and scenario and
|
|
119
|
+
* differ only in the state they name, none of which says it is a branch of the
|
|
120
|
+
* others. That is one screen wearing several states, which is what the axis and
|
|
121
|
+
* the screen-states control exist for.
|
|
122
|
+
*/
|
|
123
|
+
export interface StateGroupScreen {
|
|
124
|
+
id: string;
|
|
125
|
+
name?: string;
|
|
126
|
+
route?: string;
|
|
127
|
+
flow?: string;
|
|
128
|
+
scenario?: string;
|
|
129
|
+
state?: string;
|
|
130
|
+
/** Set when the host already declares this as a branch of its group. */
|
|
131
|
+
stateKind?: string;
|
|
132
|
+
}
|
|
133
|
+
export interface StateGroupFinding {
|
|
134
|
+
severity: 'warning';
|
|
135
|
+
kind: 'axis-candidate';
|
|
136
|
+
screenIds: readonly string[];
|
|
137
|
+
message: string;
|
|
138
|
+
fix: string;
|
|
139
|
+
}
|
|
140
|
+
export declare function detectStateGroupCandidates(screens: readonly StateGroupScreen[]): StateGroupFinding[];
|
|
141
|
+
/**
|
|
142
|
+
* Routes the catalog only ever shows working.
|
|
143
|
+
*
|
|
144
|
+
* Empty, slow, and failed are states a product spends real time in and a
|
|
145
|
+
* catalog almost never holds, because reaching one is not something a designer
|
|
146
|
+
* can click — so they go undrawn until a customer finds them. A route whose
|
|
147
|
+
* every screen boots on the default path is not proof that nothing else exists
|
|
148
|
+
* there; it is the absence of the question.
|
|
149
|
+
*
|
|
150
|
+
* This counts rather than judges: the host decides which routes deserve the
|
|
151
|
+
* other states, and only the host knows which requests a route even makes.
|
|
152
|
+
*/
|
|
153
|
+
export interface ConditionCoverageScreen {
|
|
154
|
+
id: string;
|
|
155
|
+
route?: string;
|
|
156
|
+
/** Boot conditions this screen declares (network outcomes, seeded storage). */
|
|
157
|
+
conditions?: number;
|
|
158
|
+
}
|
|
159
|
+
export interface ConditionCoverageRoute {
|
|
160
|
+
route: string;
|
|
161
|
+
screens: number;
|
|
162
|
+
/** Screens declaring at least one boot condition. */
|
|
163
|
+
conditioned: number;
|
|
164
|
+
}
|
|
165
|
+
export declare function summarizeConditionCoverage(screens: readonly ConditionCoverageScreen[]): ConditionCoverageRoute[];
|
|
166
|
+
export {};
|
|
@@ -16,6 +16,11 @@ import type { StoryboardEnvironment } from './designImport';
|
|
|
16
16
|
* frames captured under the old one.
|
|
17
17
|
*/
|
|
18
18
|
export interface SessionPresetDef {
|
|
19
|
+
/**
|
|
20
|
+
* Stable key a screen names to say it exists in this runtime. Labels are for
|
|
21
|
+
* reading and change; this does not.
|
|
22
|
+
*/
|
|
23
|
+
id: string;
|
|
19
24
|
/** Button text. The panel column is narrow, so keep it short. */
|
|
20
25
|
label: string;
|
|
21
26
|
/**
|
|
@@ -44,3 +49,16 @@ export declare function getSelectedSessionPreset(): number | null;
|
|
|
44
49
|
*/
|
|
45
50
|
export declare function selectSessionPreset(index: number | null): void;
|
|
46
51
|
export declare function subscribeSessionPresets(listener: () => void): () => void;
|
|
52
|
+
/**
|
|
53
|
+
* Whether a screen belongs in the runtime currently selected.
|
|
54
|
+
*
|
|
55
|
+
* A screen that names no runtime exists in all of them — most do, and a product
|
|
56
|
+
* that ships one build should not have to say so 75 times. Naming runtimes is
|
|
57
|
+
* for the screens that only exist in some: an EMR-linked build has surfaces the
|
|
58
|
+
* standalone one cannot reach at all, and showing them side by side reads as a
|
|
59
|
+
* catalog of screens that do not exist.
|
|
60
|
+
*
|
|
61
|
+
* With no preset selected everything shows, because "the product's own default"
|
|
62
|
+
* is not a claim about which runtime is running.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isInSelectedSessionPreset(runtimes: readonly string[] | undefined): boolean;
|
|
@@ -187,6 +187,12 @@ export interface PageModel {
|
|
|
187
187
|
stateKind?: string;
|
|
188
188
|
/** Host-declared situation group the frame is arranged into (see frameLanes). */
|
|
189
189
|
lane?: string;
|
|
190
|
+
/**
|
|
191
|
+
* Runtimes this screen exists in, by session preset id. Absent means every
|
|
192
|
+
* runtime — most screens are the same product either way, and only the ones
|
|
193
|
+
* a runtime cannot reach at all need to say so.
|
|
194
|
+
*/
|
|
195
|
+
runtimes?: readonly string[];
|
|
190
196
|
scenarioId?: string;
|
|
191
197
|
/** Every scenario the screen demonstrates; coverage counts this set. */
|
|
192
198
|
scenarioIds?: readonly string[];
|
|
@@ -341,6 +347,14 @@ export declare class EditorStore {
|
|
|
341
347
|
/** The group member holding the group's canvas slot, or null for non-groups. */
|
|
342
348
|
shownScreenStateId(scenario: string, canvas: string): string | null;
|
|
343
349
|
/** False only for state variants folded into the Screen states control. */
|
|
350
|
+
/**
|
|
351
|
+
* Whether the frame belongs in the runtime the panel is set to.
|
|
352
|
+
*
|
|
353
|
+
* An EMR-linked build has surfaces the standalone one cannot reach at all, so
|
|
354
|
+
* showing every frame under both runtimes reads as a catalog of screens that
|
|
355
|
+
* do not exist. A frame that names no runtime exists in all of them.
|
|
356
|
+
*/
|
|
357
|
+
isInSelectedRuntime(page: PageModel): boolean;
|
|
344
358
|
isScreenStateShown(page: PageModel): boolean;
|
|
345
359
|
/** Swaps a folded variant into its group's canvas slot; the camera stays. */
|
|
346
360
|
private revealScreenState;
|
|
@@ -476,6 +490,7 @@ export declare class EditorStore {
|
|
|
476
490
|
state?: string;
|
|
477
491
|
stateKind?: string;
|
|
478
492
|
lane?: string;
|
|
493
|
+
runtimes?: readonly string[];
|
|
479
494
|
scenarioId?: string;
|
|
480
495
|
scenarioIds?: readonly string[];
|
|
481
496
|
interactions?: DesignScreenInteraction[];
|
package/dist-lib/types/lib.d.ts
CHANGED
|
@@ -82,6 +82,8 @@ import { type SessionPresetDef } from './editor/sessionPresets';
|
|
|
82
82
|
export type { ViewportPresetDef } from './editor/viewportPresets';
|
|
83
83
|
export { getSelectedSessionPreset, getSessionPresets, selectSessionPreset, setSessionPresets, subscribeSessionPresets, } from './editor/sessionPresets';
|
|
84
84
|
export type { SessionPresetDef } from './editor/sessionPresets';
|
|
85
|
+
export { GENERIC_ASSERTION_SHARE, MIN_UNVERIFIED_REASON_LENGTH, detectStateGroupCandidates, hooksInSelector, summarizeConditionCoverage, validateHookTargeting, validateScreenConformance, } from './editor/screenConformance';
|
|
86
|
+
export type { ConditionCoverageRoute, ConditionCoverageScreen, ConformanceScreen, HookTargetingFinding, HookTargetingInput, HookTargetingKind, ScreenConformanceFinding, ScreenConformanceKind, StateGroupFinding, StateGroupScreen, } from './editor/screenConformance';
|
|
85
87
|
export { clearPreviewEnvironmentControlValues, getPreviewEnvironmentControlValue, getPreviewEnvironmentControls, getPreviewEnvironmentOverride, setPreviewEnvironmentControlValue, setPreviewEnvironmentControls, subscribePreviewEnvironmentControls, } from './editor/previewEnvironmentControls';
|
|
86
88
|
export type { PreviewEnvironmentControlDef, PreviewEnvironmentControlOptionDef, } from './editor/previewEnvironmentControls';
|
|
87
89
|
export { createDesignImportController, createDesignScreenCollection, DESIGN_IMPORT_KINDS, expandDesignScreenCasesViewports, expandDesignScreenCollectionViewports, mergeScreenPresets, } from './editor/designImport';
|