@vectoriox/iox-ui 5.46.0 → 5.48.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.
Files changed (32) hide show
  1. package/design-core/index.d.ts +5 -3
  2. package/fesm2022/vectoriox-iox-ui-design-core.mjs +7 -4
  3. package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
  4. package/fesm2022/vectoriox-iox-ui-generation-core.mjs +351 -78
  5. package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
  6. package/fesm2022/vectoriox-iox-ui-harness-core.mjs +4179 -0
  7. package/fesm2022/vectoriox-iox-ui-harness-core.mjs.map +1 -0
  8. package/fesm2022/vectoriox-iox-ui.mjs +29 -1
  9. package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
  10. package/generation-core/index.d.ts +252 -18
  11. package/harness-core/index.d.ts +2498 -0
  12. package/harness-core/package.json +3 -0
  13. package/index.d.ts +22 -3
  14. package/package.json +5 -1
  15. package/src/design-core/design-apply.ts +8 -4
  16. package/src/generation-core/entrances.ts +110 -0
  17. package/src/generation-core/plan-data.ts +71 -0
  18. package/src/generation-core/public-api.ts +3 -0
  19. package/src/generation-core/shape-check.ts +92 -0
  20. package/src/generation-core/site-spec.generated.ts +131 -2
  21. package/src/generation-core/validate-site-plan.ts +9 -62
  22. package/src/harness-core/business-types.ts +141 -0
  23. package/src/harness-core/harness-spec.generated.ts +3528 -0
  24. package/src/harness-core/intake.ts +85 -0
  25. package/src/harness-core/lint.ts +94 -0
  26. package/src/harness-core/look.ts +32 -0
  27. package/src/harness-core/motion.ts +67 -0
  28. package/src/harness-core/needs.ts +42 -0
  29. package/src/harness-core/prompt-context.ts +91 -0
  30. package/src/harness-core/public-api.ts +19 -0
  31. package/src/harness-core/rank.ts +226 -0
  32. package/src/harness-core/site-analysis.ts +180 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "module": "../fesm2022/vectoriox-iox-ui-harness-core.mjs"
3
+ }
package/index.d.ts CHANGED
@@ -113,13 +113,15 @@ interface PageScrollStart {
113
113
  * Scrolling choice (`planScroll`); `scrollTo` actions and the dialog lock go through it whether or not
114
114
  * Lenis runs. Never on the server: there is no window to scroll.
115
115
  */
116
- declare class PageScrollProvider {
116
+ declare class PageScrollProvider implements OnDestroy {
117
117
  private zone;
118
118
  private lenis;
119
119
  private rafId?;
120
120
  private wrapper;
121
121
  private lockedOverflow;
122
122
  init(start: PageScrollStart): void;
123
+ /** Provided by a component (a page, the Design step's specimen): its Lenis and loop go with it. */
124
+ ngOnDestroy(): void;
123
125
  /** Stop Lenis and its loop; the page falls back to the browser's own scrolling. */
124
126
  destroy(): void;
125
127
  /** Hard-stop page scroll (dialog open, A1b): wheel/touch frozen until start(). */
@@ -3034,6 +3036,23 @@ declare function hasOwnPageTransition(anim: RouteAnimationLike | null | undefine
3034
3036
  */
3035
3037
  declare function pageTransitionOf(anim: RouteAnimationLike | null | undefined, ctx?: PageTransitionContext): PlannedPageTransition;
3036
3038
 
3039
+ /** The two layers a builder preview of a page change plays on, and the stage that holds them. */
3040
+ interface TransitionPreviewLayers {
3041
+ /** Holds both layers; it carries the perspective the 3D presets need. */
3042
+ stage: HTMLElement;
3043
+ /** The page that leaves. */
3044
+ out: HTMLElement | null;
3045
+ /** The page that arrives. */
3046
+ in: HTMLElement | null;
3047
+ }
3048
+ /**
3049
+ * Play a resolved page change (`resolvePageTransition`) on two stacked layers — the ONE dual-layer preview the
3050
+ * builder's Page panel and the Design step's specimen both play (rev 3.1 §3o). The live site plays the same
3051
+ * frames through its own transition service. Resolves when the arriving layer has finished (or at once when
3052
+ * there is no arriving layer). The layers are left at their last frame: the caller decides what to show after.
3053
+ */
3054
+ declare function playDualLayerTransition(resolved: ResolvedPageTransition, layers: TransitionPreviewLayers): Promise<void>;
3055
+
3037
3056
  /**
3038
3057
  * Gallery effect registry — the SHARED, effect-agnostic description of how each gallery slide looks
3039
3058
  * and animates in a given crossfade state. Modeled on `binding-pipes.ts` / `PAGE_TRANSITION_PRESETS`:
@@ -3156,5 +3175,5 @@ declare class GalleryLoop {
3156
3175
  private emit;
3157
3176
  }
3158
3177
 
3159
- export { A11yAttribute, A11yTrait, ACCESSIBLE_NAME_TYPES, AOSService, ARIA_CURRENT_PAGE, BACKDROP_TWIN_KEY, BACKDROP_WHOLE_KEY, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, COMPONENT_STATE_SELECTOR, CONTAINER_HTML_TAGS, CONTEXT_ACTION_TYPES, CURRENT_LINK_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ComponentState, ContainerComponent, ContextStore, DECORATIVE_TYPES, DEFAULT_GALLERY_SCHEDULER, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DIALOG_CLOSE_ABOVE_ATTRIBUTE, DIALOG_NODE_TYPE, DeclarationContext, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, HOVER_DEFAULT_TRANSITION, HOVER_STATES, INTERACTION_HIDDEN_ATTRIBUTE, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxDialogComponent, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, IoxVideoComponent, LAST_EMITTED_STATES, LEGACY_DIRECTION_KEY, LEGACY_HOVER_TRANSITION, LEGACY_SWIPER_CONTROLS, LinkedContainerComponent, MANAGED_A11Y_ATTRIBUTES, MAX_LINES_KEY, NAMED_GROUP_ROLES, NO_PAGE_TRANSITION, ON_STATE_ROLES, PAGE_STYLE_TOKENS, PAGE_TOKEN_FALLBACKS, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PART_PAGINATION_MODIFIER, PHONE_TRANSITION_QUERY, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, PageStyleField, PageStyleSource, REDUCED_MOTION_QUERY, SCROLL_EASINGS, SLIDER_INPUTS_ATTRIBUTE, SLIDER_INPUT_NAMES, STATE_PSEUDO_CLASS, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_A11Y_OPTIONS, SWIPER_RESPONSIVE_TRAITS, SWIPER_TRIGGER_EVENTS, ScrollEasing, SectionComponent, SwiperModuleName, TARGETED_STATE_SUFFIX, TEXT_ALIGN_KEY, TEXT_DIRECTION_KEY, TRANSITION_FEELS, TextAlign, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, VisibilityActionType, activeActions, applyBindingPipes, applyFeel, bootSlider, breakpointDef, breakpointForWidth, breakpointQueryFor, buildInteractionKeyframes, buildPartSelector, buildSwiperBreakpoints, buildTargetedStateSelector, buildTargetedSuffixSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, closeAboveMediaQuery, collectContextSlotKeys, compileDeclarations, compilePageStyle, componentStatesFor, composeVirtualTraits, containerHtmlTag, contextActionEffect, contextSlotKeys, countOverrides, declaredInputNames, decomposeBackdropFilter, defaultHoverTransition, defaultMessageResolver, deriveSeoFromContent, describePageStyle, describeSeoFields, dialogCloseAboveMinWidth, domHasSliderParts, effectiveActionType, effectiveTransitionId, entrancePreStateStyle, feelFor, finalKeyframe, getSeoValue, hasHoverState, hasOwnPageTransition, hasOwnTransition, initialSlotValues, isActionEnabled, isContextAction, isCurrentLink, isExternalOrFragment, isGradientPaint, isInputOverriddenAt, isLastEmittedStateKey, isOverriddenAt, isValidSlotKey, isWholeBackdropSet, keyframePropertyName, keyframeStyleEntries, legacyEnterToTransition, mediaQueryFor, mergeSwiperBreakpoints, migrateLegacyDirection, migrateLegacyTextAlign, motionAllowed, nearestCommonAncestorId, needsAccessibleName, needsGroupName, nodeA11yAttributes, nodeCssId, nodeTraitValue, nodeUsesContext, normalizeAppPath, normalizeGalleryImage, normalizeIconClass, orderStatesForEmit, pageTransitionOf, parentBreakpoint, parseUrlList, partClassName, planResponsiveRules, planScroll, planSwiperOptions, prefersReducedMotion, renderDefaultDeclarations, resetPageStyleField, resetSeoField, resolveBoundValue, resolveContextValue, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveSeo, resolveStateStyles, resolveStyleProps, resolveToggleVisibility, resolveValueChain, rewriteViewportUnits, safeSetInput, serializeA11yAttributes, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, sliderControlElements, sliderInputsFromProps, sliderInputsOf, stateNamesIn, statePseudoClass, stateSelectorSuffix, stripSuppressedProps, swiperAutoplayAllowed, swiperLoopOptions, swiperModuleNames, syncNodeA11yAttributes, targetedStateSelector, viewportTransitionContext, withContextSlots, withDefaultHoverTransition, wrapInBreakpointQuery, wrapInMediaQuery };
3160
- export type { A11yNodeSource, BoundValueSpec, BreakpointDef, ContextActionEffect, ContextActionLike, ContextItemRef, ContextSlotDefault, ContextValueSpec, ContextWriteScope, CurrentLinkOptions, FormMessageResolver, GalleryBoundSource, GalleryDurations, GalleryEffect, GalleryEffectDescriptor, GalleryImage, GalleryLoopOptions, GalleryLoopScheduler, GalleryLoopState, GallerySlideState, IBuilderEventEmitter, InputMap, InteractionKeyframe, IoxContextSlot, IoxPipe, MatchMediaHost, NodeA11yContext, OverrideBreakpoint, PageScrollStart, PageStyleFieldState, PageStyleOptions, PageStyleSettings, PageTransitionCategory, PageTransitionCategoryMeta, PageTransitionContext, PageTransitionOptions, PageTransitionPreset, PipeArgSpec, PipeContext, PipeDescriptor, PlannedPageTransition, ResolvedPageTransition, ResponsiveLayer, ResponsiveOverrides, ResponsiveRuleSet, RouteAnimationLike, ScrollPlanContext, SeoDerivedContent, SeoFieldSource, SeoFieldState, SeoResolveContext, SetContextParams, SliderBootOptions, SliderControlElements, SliderInputs, StateStyleMap, StyleMap, StyleTreeNode, SwiperBreakpointMap, SwiperModuleFlags, SwiperPlan, SwiperPlanContext, ToggleableAction, TransitionFeel, TransitionPhase, TransitionTimelineInput, TransitionViewport, ValueSource, VirtualTraitGroup };
3178
+ export { A11yAttribute, A11yTrait, ACCESSIBLE_NAME_TYPES, AOSService, ARIA_CURRENT_PAGE, BACKDROP_TWIN_KEY, BACKDROP_WHOLE_KEY, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, COMPONENT_STATE_SELECTOR, CONTAINER_HTML_TAGS, CONTEXT_ACTION_TYPES, CURRENT_LINK_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ComponentState, ContainerComponent, ContextStore, DECORATIVE_TYPES, DEFAULT_GALLERY_SCHEDULER, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DIALOG_CLOSE_ABOVE_ATTRIBUTE, DIALOG_NODE_TYPE, DeclarationContext, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, HOVER_DEFAULT_TRANSITION, HOVER_STATES, INTERACTION_HIDDEN_ATTRIBUTE, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxDialogComponent, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, IoxVideoComponent, LAST_EMITTED_STATES, LEGACY_DIRECTION_KEY, LEGACY_HOVER_TRANSITION, LEGACY_SWIPER_CONTROLS, LinkedContainerComponent, MANAGED_A11Y_ATTRIBUTES, MAX_LINES_KEY, NAMED_GROUP_ROLES, NO_PAGE_TRANSITION, ON_STATE_ROLES, PAGE_STYLE_TOKENS, PAGE_TOKEN_FALLBACKS, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PART_PAGINATION_MODIFIER, PHONE_TRANSITION_QUERY, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, PageStyleField, PageStyleSource, REDUCED_MOTION_QUERY, SCROLL_EASINGS, SLIDER_INPUTS_ATTRIBUTE, SLIDER_INPUT_NAMES, STATE_PSEUDO_CLASS, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_A11Y_OPTIONS, SWIPER_RESPONSIVE_TRAITS, SWIPER_TRIGGER_EVENTS, ScrollEasing, SectionComponent, SwiperModuleName, TARGETED_STATE_SUFFIX, TEXT_ALIGN_KEY, TEXT_DIRECTION_KEY, TRANSITION_FEELS, TextAlign, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, VisibilityActionType, activeActions, applyBindingPipes, applyFeel, bootSlider, breakpointDef, breakpointForWidth, breakpointQueryFor, buildInteractionKeyframes, buildPartSelector, buildSwiperBreakpoints, buildTargetedStateSelector, buildTargetedSuffixSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, closeAboveMediaQuery, collectContextSlotKeys, compileDeclarations, compilePageStyle, componentStatesFor, composeVirtualTraits, containerHtmlTag, contextActionEffect, contextSlotKeys, countOverrides, declaredInputNames, decomposeBackdropFilter, defaultHoverTransition, defaultMessageResolver, deriveSeoFromContent, describePageStyle, describeSeoFields, dialogCloseAboveMinWidth, domHasSliderParts, effectiveActionType, effectiveTransitionId, entrancePreStateStyle, feelFor, finalKeyframe, getSeoValue, hasHoverState, hasOwnPageTransition, hasOwnTransition, initialSlotValues, isActionEnabled, isContextAction, isCurrentLink, isExternalOrFragment, isGradientPaint, isInputOverriddenAt, isLastEmittedStateKey, isOverriddenAt, isValidSlotKey, isWholeBackdropSet, keyframePropertyName, keyframeStyleEntries, legacyEnterToTransition, mediaQueryFor, mergeSwiperBreakpoints, migrateLegacyDirection, migrateLegacyTextAlign, motionAllowed, nearestCommonAncestorId, needsAccessibleName, needsGroupName, nodeA11yAttributes, nodeCssId, nodeTraitValue, nodeUsesContext, normalizeAppPath, normalizeGalleryImage, normalizeIconClass, orderStatesForEmit, pageTransitionOf, parentBreakpoint, parseUrlList, partClassName, planResponsiveRules, planScroll, planSwiperOptions, playDualLayerTransition, prefersReducedMotion, renderDefaultDeclarations, resetPageStyleField, resetSeoField, resolveBoundValue, resolveContextValue, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveSeo, resolveStateStyles, resolveStyleProps, resolveToggleVisibility, resolveValueChain, rewriteViewportUnits, safeSetInput, serializeA11yAttributes, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, sliderControlElements, sliderInputsFromProps, sliderInputsOf, stateNamesIn, statePseudoClass, stateSelectorSuffix, stripSuppressedProps, swiperAutoplayAllowed, swiperLoopOptions, swiperModuleNames, syncNodeA11yAttributes, targetedStateSelector, viewportTransitionContext, withContextSlots, withDefaultHoverTransition, wrapInBreakpointQuery, wrapInMediaQuery };
3179
+ export type { A11yNodeSource, BoundValueSpec, BreakpointDef, ContextActionEffect, ContextActionLike, ContextItemRef, ContextSlotDefault, ContextValueSpec, ContextWriteScope, CurrentLinkOptions, FormMessageResolver, GalleryBoundSource, GalleryDurations, GalleryEffect, GalleryEffectDescriptor, GalleryImage, GalleryLoopOptions, GalleryLoopScheduler, GalleryLoopState, GallerySlideState, IBuilderEventEmitter, InputMap, InteractionKeyframe, IoxContextSlot, IoxPipe, MatchMediaHost, NodeA11yContext, OverrideBreakpoint, PageScrollStart, PageStyleFieldState, PageStyleOptions, PageStyleSettings, PageTransitionCategory, PageTransitionCategoryMeta, PageTransitionContext, PageTransitionOptions, PageTransitionPreset, PipeArgSpec, PipeContext, PipeDescriptor, PlannedPageTransition, ResolvedPageTransition, ResponsiveLayer, ResponsiveOverrides, ResponsiveRuleSet, RouteAnimationLike, ScrollPlanContext, SeoDerivedContent, SeoFieldSource, SeoFieldState, SeoResolveContext, SetContextParams, SliderBootOptions, SliderControlElements, SliderInputs, StateStyleMap, StyleMap, StyleTreeNode, SwiperBreakpointMap, SwiperModuleFlags, SwiperPlan, SwiperPlanContext, ToggleableAction, TransitionFeel, TransitionPhase, TransitionPreviewLayers, TransitionTimelineInput, TransitionViewport, ValueSource, VirtualTraitGroup };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectoriox/iox-ui",
3
- "version": "5.46.0",
3
+ "version": "5.48.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=20.0.0",
6
6
  "@angular/core": ">=20.0.0",
@@ -47,6 +47,10 @@
47
47
  "types": "./generation-core/index.d.ts",
48
48
  "default": "./fesm2022/vectoriox-iox-ui-generation-core.mjs"
49
49
  },
50
+ "./harness-core": {
51
+ "types": "./harness-core/index.d.ts",
52
+ "default": "./fesm2022/vectoriox-iox-ui-harness-core.mjs"
53
+ },
50
54
  "./media-core": {
51
55
  "types": "./media-core/index.d.ts",
52
56
  "default": "./fesm2022/vectoriox-iox-ui-media-core.mjs"
@@ -8,7 +8,7 @@ import {
8
8
  } from './flavor-seed';
9
9
  import { DesignChoices, DesignTokenRow, flavorSourceStamp, StoredDesignTokenRow } from './design-token.model';
10
10
  import { FONT_FAMILY_TOKENS, fontFamilyKey, FontFamilyToken, fontRoleReferences, parseFontFamilies, withoutScriptPartners } from './fonts';
11
- import { DesignConfigEntry, DesignConfigKey, designConfigEntries, flavorPackById, isRetiredFlavorId, packDesignChoices, StoredDesign } from './design-settings';
11
+ import { DesignConfigEntry, DesignConfigKey, designConfigEntries, flavorPackById, isRetiredFlavorId, packDesignChoices, sameDesignChoices, StoredDesign } from './design-settings';
12
12
 
13
13
  /**
14
14
  * Applying a look (design-system DS4): the COMPLETE `buildFlavorTokens` set for the chosen
@@ -147,8 +147,10 @@ export interface DesignRevertSnapshot extends FlavorRevertSnapshot {
147
147
  * What an Apply of `plan` stores as its revert snapshot — the ONE rule the builder's Apply and the
148
148
  * server-side site build (W2) share: the whole pre-apply token set, what the apply writes into each row,
149
149
  * and where a revert goes back to — never a null flavor (a studio with no design row wore its design's
150
- * defaults, so that is what it returns to). An apply that writes nothing keeps the previous snapshot:
151
- * clobbering a useful one with "nothing changed" would be a lie.
150
+ * defaults, so that is what it returns to). An apply that changes NOTHING keeps the previous snapshot:
151
+ * clobbering a useful one with "nothing changed" would be a lie. A choice that lives in the config only
152
+ * (Scrolling · How animated · Between pages) IS a change — pass `next` so a motion-only Apply gets its own
153
+ * snapshot and Revert goes back to the motion worn just before it (vectoriox/iox-ui#28).
152
154
  */
153
155
  export function designApplySnapshot(
154
156
  previous: StoredDesign,
@@ -156,8 +158,10 @@ export function designApplySnapshot(
156
158
  existing: ReadonlyArray<StoredDesignTokenRow>,
157
159
  plan: FlavorSeedPlan,
158
160
  appliedAt: number,
161
+ next?: DesignChoices,
159
162
  ): DesignRevertSnapshot | null {
160
- if (!plan.create.length && !plan.update.length) return previous.revert;
163
+ const wroteTokens = plan.create.length > 0 || plan.update.length > 0;
164
+ if (!wroteTokens && (!next || sameDesignChoices(previous.choices, next))) return previous.revert;
161
165
  const origin: Pick<DesignRevertSnapshot, 'from' | 'fromConfig'> = previous.storedFlavorId
162
166
  ? {
163
167
  from: { flavorId: previous.storedFlavorId, flavorVersion: previous.flavorVersion, brandSeed: previous.storedSeed },
@@ -0,0 +1,110 @@
1
+ import { AnimateId, SPEC_AXES } from '@vectoriox/iox-ui/design-core';
2
+
3
+ /**
4
+ * How animated (AI plan rev 3.1 §3p) — the ONE reading of the contract's `axes.animations` (what each level
5
+ * moves, how) and `axes.animationFamilies` (which parts a section family lets move). The pieces every stamper
6
+ * shares: the Design step's business specimen (H4-4) today, `planEntrances` in the composer (M1-6b) next. Pure.
7
+ *
8
+ * A level with `content` moves a section's content together, once; otherwise it names parts (heading, items,
9
+ * media…) and each part enters on its own, items one after another. Navigation, footers and globals are a family
10
+ * with no parts, so nothing there ever moves.
11
+ */
12
+
13
+ export enum EntrancePart {
14
+ Content = 'content',
15
+ Heading = 'heading',
16
+ Items = 'items',
17
+ Media = 'media',
18
+ RowMedia = 'rowMedia',
19
+ HeroText = 'heroText',
20
+ }
21
+
22
+ /** What an entrance stamp is marked with — a later restamp touches only what the look made (H4-5). */
23
+ export const ENTRANCE_ORIGIN_LOOK = 'look';
24
+ /** The trigger every entrance plays on. */
25
+ export const ENTRANCE_TRIGGER = 'viewportEnter';
26
+ /** The easing every entrance plays with (only `opacity` and `transform` move). */
27
+ export const ENTRANCE_EASING = 'ease-out';
28
+
29
+ /** One entrance as the contract states it. */
30
+ export interface EntranceSpec {
31
+ readonly type: string;
32
+ readonly duration: number;
33
+ readonly distance?: number;
34
+ /** Items: ms added per item, up to `cap` items. */
35
+ readonly stagger?: number;
36
+ readonly cap?: number;
37
+ /** The type a right-to-left page plays instead (a side-by-side row slides in from ITS side). */
38
+ readonly mirror?: string;
39
+ }
40
+
41
+ /** What one section family moves at one level. `content` set = the whole content together, and no parts. */
42
+ export interface SectionEntrances {
43
+ readonly content: EntranceSpec | null;
44
+ readonly parts: Readonly<Partial<Record<EntrancePart, EntranceSpec>>>;
45
+ }
46
+
47
+ /** The interaction action a stamp carries — the shape both interaction runtimes play. */
48
+ export interface EntranceAction {
49
+ type: string;
50
+ target: 'self';
51
+ duration: number;
52
+ delay: number;
53
+ easing: typeof ENTRANCE_EASING;
54
+ once: true;
55
+ params?: { distance: number };
56
+ }
57
+
58
+ export interface EntranceInteraction {
59
+ id: string;
60
+ trigger: typeof ENTRANCE_TRIGGER;
61
+ actions: [EntranceAction];
62
+ origin: typeof ENTRANCE_ORIGIN_LOOK;
63
+ }
64
+
65
+ type Level = Readonly<{ parts: Readonly<Partial<Record<EntrancePart, EntranceSpec>>> }>;
66
+ const LEVELS = SPEC_AXES.animations as unknown as Readonly<Record<string, Level>>;
67
+ const FAMILIES = SPEC_AXES.animationFamilies as unknown as Readonly<Record<string, ReadonlyArray<EntrancePart>>>;
68
+
69
+ const NOTHING: SectionEntrances = { content: null, parts: {} };
70
+
71
+ /** What a section of `family` moves at `level` — nothing for an unknown level or family. */
72
+ export function sectionEntrances(level: AnimateId | string | null | undefined, family: string): SectionEntrances {
73
+ const lv = level ? LEVELS[level] : undefined;
74
+ const allow = FAMILIES[family] ?? [];
75
+ if (!lv || !allow.length) return NOTHING;
76
+ if (lv.parts.content && allow.includes(EntrancePart.Content)) return { content: lv.parts.content, parts: {} };
77
+ const parts: Partial<Record<EntrancePart, EntranceSpec>> = {};
78
+ for (const part of allow) {
79
+ if (part === EntrancePart.Content) continue;
80
+ // A level without its own side-by-side entrance gives a row's picture the plain picture one.
81
+ const spec = lv.parts[part] ?? (part === EntrancePart.RowMedia ? lv.parts.media : undefined);
82
+ if (spec) parts[part] = spec;
83
+ }
84
+ return { content: null, parts };
85
+ }
86
+
87
+ /** The literal delay of the `index`-th item: `stagger` ms each, the last `cap` waiting no longer than the one before. */
88
+ export function staggerDelay(spec: EntranceSpec, index = 0): number {
89
+ if (!spec.stagger) return 0;
90
+ const cap = Math.max(1, spec.cap ?? 1);
91
+ return Math.min(Math.max(0, index), cap - 1) * spec.stagger;
92
+ }
93
+
94
+ /** The action a node plays: the spec's type (mirrored on a right-to-left page), its distance and its delay. */
95
+ export function entranceAction(spec: EntranceSpec, opts: { index?: number; rtl?: boolean } = {}): EntranceAction {
96
+ return {
97
+ type: opts.rtl && spec.mirror ? spec.mirror : spec.type,
98
+ target: 'self',
99
+ duration: spec.duration,
100
+ delay: staggerDelay(spec, opts.index),
101
+ easing: ENTRANCE_EASING,
102
+ once: true,
103
+ ...(spec.distance ? { params: { distance: spec.distance } } : {}),
104
+ };
105
+ }
106
+
107
+ /** The stamp on a node: `viewportEnter`, once, on itself (a duplicated node needs no remap), from the look. */
108
+ export function entranceInteraction(nodeId: string, action: EntranceAction): EntranceInteraction {
109
+ return { id: `${nodeId}-enter`, trigger: ENTRANCE_TRIGGER, actions: [action], origin: ENTRANCE_ORIGIN_LOOK };
110
+ }
@@ -0,0 +1,71 @@
1
+ import { BindScope, patternById, patternNodes, patternScopes } from './patterns';
2
+ import { CmsDataSourcePlan, DataSourcePlan, PagePlan, PlanDataSourceMode, PlanDataSourceType, SitePlan } from './site-plan.model';
3
+
4
+ /**
5
+ * What each page of a plan shows and where its data comes from (AI plan rev 3 §3d, `describePlanData`) — the
6
+ * Plan step's data lines, in the vocabulary the builder already uses. Pure and STRUCTURED: the screen words
7
+ * each kind in its own language ("Lists every Project", "One Project, found by its address /work/:slug").
8
+ */
9
+
10
+ export enum PageDataKind {
11
+ /** A section repeats every item of a content type. */
12
+ List = 'list',
13
+ /** A section repeats the newest `count` items. */
14
+ Latest = 'latest',
15
+ /** A template page: one item, found by its address — one page per item. */
16
+ One = 'one',
17
+ /** The next (or previous) item beside the page's own. */
18
+ Siblings = 'siblings',
19
+ /** A form: what it sends, to the studio's inbox (+ an email when Email is connected). */
20
+ Form = 'form',
21
+ /** Words and pictures, no data. */
22
+ Static = 'static',
23
+ }
24
+
25
+ export interface PageDataLine {
26
+ kind: PageDataKind;
27
+ /** The content type's label ("Projects"), for every kind that reads one. */
28
+ contentType?: string;
29
+ /** Latest: how many. */
30
+ count?: number;
31
+ /** One: the page's own address, with its parameter. */
32
+ route?: string;
33
+ /** Form: the form preset (`contact`). */
34
+ form?: string;
35
+ }
36
+
37
+ export interface PageData {
38
+ pageKey: string;
39
+ lines: PageDataLine[];
40
+ }
41
+
42
+ const sameLine = (a: PageDataLine, b: PageDataLine) => JSON.stringify(a) === JSON.stringify(b);
43
+
44
+ function sourceLine(source: DataSourcePlan | undefined, label: (name: string) => string): PageDataLine | null {
45
+ if (!source || source.type !== PlanDataSourceType.Cms) return null;
46
+ const cms = source as CmsDataSourcePlan;
47
+ if (cms.mode !== PlanDataSourceMode.Query) return null;
48
+ return cms.limit
49
+ ? { kind: PageDataKind.Latest, contentType: label(cms.contentType), count: cms.limit }
50
+ : { kind: PageDataKind.List, contentType: label(cms.contentType) };
51
+ }
52
+
53
+ function pageLines(page: PagePlan, plan: SitePlan, label: (name: string) => string): PageDataLine[] {
54
+ const lines: PageDataLine[] = [];
55
+ const add = (line: PageDataLine | null) => { if (line && !lines.some(l => sameLine(l, line))) lines.push(line); };
56
+ if (page.template) add({ kind: PageDataKind.One, contentType: label(page.template), route: page.route });
57
+ for (const section of page.sections) {
58
+ const pattern = patternById(section.pattern);
59
+ if (!pattern) continue;
60
+ if (section.binding) add(sourceLine(plan.dataSources.find(d => d.alias === section.binding), label));
61
+ if (page.template && patternScopes(pattern).has(BindScope.Next)) add({ kind: PageDataKind.Siblings, contentType: label(page.template) });
62
+ for (const node of patternNodes(pattern)) if (node.preset) add({ kind: PageDataKind.Form, form: node.preset });
63
+ }
64
+ return lines.length ? lines : [{ kind: PageDataKind.Static }];
65
+ }
66
+
67
+ /** One entry per page of the plan, in plan order. */
68
+ export function describePlanData(plan: SitePlan): PageData[] {
69
+ const label = (name: string) => plan.contentTypes.find(t => t.name === name)?.label ?? name;
70
+ return plan.pages.map(page => ({ pageKey: page.key, lines: pageLines(page, plan, label) }));
71
+ }
@@ -17,6 +17,9 @@ export * from './site-plan.model';
17
17
  export * from './patterns';
18
18
  export * from './templates';
19
19
  export * from './serialized-node';
20
+ export * from './shape-check';
20
21
  export * from './validate-site-plan';
21
22
  export * from './compose-site';
22
23
  export * from './nav-shape';
24
+ export * from './entrances';
25
+ export * from './plan-data';
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The ONE way a framework-free gate refuses a document "with a path, never repairs" (W1's site-plan gate, the
3
+ * harness's analysis gate): a documented shape table (key → type text; `key?` optional, `[name]` the object's
4
+ * own field names) whose key lists the gate READS — so the shape a model is told about is the shape refused —
5
+ * and a collector that records each refusal at its dotted path. Generic over each gate's issue-code enum.
6
+ */
7
+
8
+ export type ShapeEntry = Readonly<Record<string, string>>;
9
+ export type PlainObject = Record<string, unknown>;
10
+
11
+ const OPTIONAL_MARK = '?';
12
+ const DYNAMIC_KEY = /^\[[^\]]+\]\??$/;
13
+ const isDynamic = (key: string) => DYNAMIC_KEY.test(key);
14
+
15
+ /** Every fixed key an object of the shape may carry. */
16
+ export function shapeKeys(entry: ShapeEntry): string[] {
17
+ return Object.keys(entry).filter(k => !isDynamic(k)).map(k => (k.endsWith(OPTIONAL_MARK) ? k.slice(0, -1) : k));
18
+ }
19
+
20
+ /** The keys an object of the shape must carry. */
21
+ export function requiredShapeKeys(entry: ShapeEntry): string[] {
22
+ return Object.keys(entry).filter(k => !isDynamic(k) && !k.endsWith(OPTIONAL_MARK));
23
+ }
24
+
25
+ export const isPlainObject = (v: unknown): v is PlainObject => !!v && typeof v === 'object' && !Array.isArray(v);
26
+ export const isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.trim() !== '';
27
+ /** `path.key`, or `key` at the root. */
28
+ export const pathAt = (path: string, key: string) => (path ? `${path}.${key}` : key);
29
+
30
+ export interface PathIssue<C extends string> {
31
+ code: C;
32
+ /** Where, as a dotted path into the document (`pages[2].sections[0].binding`). */
33
+ path: string;
34
+ message: string;
35
+ }
36
+
37
+ /** The three refusals every shape gate shares, named in that gate's own code enum. */
38
+ export interface ShapeIssueCodes<C extends string> {
39
+ /** A value of the wrong type, or a required value missing. */
40
+ shape: C;
41
+ /** A key the shape does not have. */
42
+ unknownKey: C;
43
+ /** A name used twice where it must be unique. */
44
+ duplicate: C;
45
+ }
46
+
47
+ export class PathIssueCollector<C extends string> {
48
+ readonly issues: PathIssue<C>[] = [];
49
+
50
+ /** `subject` names the document in a refusal ("… is not part of the plan shape"). */
51
+ constructor(private readonly subject: string, private readonly codes: ShapeIssueCodes<C>) {}
52
+
53
+ add(code: C, path: string, message: string): void {
54
+ this.issues.push({ code, path, message });
55
+ }
56
+
57
+ keys(obj: PlainObject, allowed: ReadonlyArray<string>, path: string): void {
58
+ for (const key of Object.keys(obj)) if (!allowed.includes(key)) this.add(this.codes.unknownKey, pathAt(path, key), `"${key}" is not part of the ${this.subject} shape`);
59
+ }
60
+
61
+ string(obj: PlainObject, key: string, path: string, required = true): string | null {
62
+ const v = obj[key];
63
+ if (v === undefined && !required) return null;
64
+ if (!isNonEmptyString(v)) {
65
+ this.add(this.codes.shape, pathAt(path, key), 'must be a non-empty string');
66
+ return null;
67
+ }
68
+ return v;
69
+ }
70
+
71
+ boolean(obj: PlainObject, key: string, path: string): void {
72
+ if (obj[key] !== undefined && typeof obj[key] !== 'boolean') this.add(this.codes.shape, pathAt(path, key), 'must be a boolean');
73
+ }
74
+
75
+ array(obj: PlainObject, key: string, path: string): unknown[] {
76
+ const v = obj[key];
77
+ if (!Array.isArray(v)) {
78
+ this.add(this.codes.shape, pathAt(path, key), 'must be an array');
79
+ return [];
80
+ }
81
+ return v;
82
+ }
83
+
84
+ unique(values: Array<string | null>, path: (i: number) => string, what: string): void {
85
+ const seen = new Set<string>();
86
+ values.forEach((v, i) => {
87
+ if (v === null) return;
88
+ if (seen.has(v)) this.add(this.codes.duplicate, path(i), `${what} "${v}" is used twice`);
89
+ seen.add(v);
90
+ });
91
+ }
92
+ }
@@ -25,7 +25,7 @@ export const SPEC_SITE_PLAN_SHAPE = {
25
25
  "business": {
26
26
  "name": "string — the brand: the header, the hero, the copyright line",
27
27
  "tagline?": "string — the hero lead and the footer line",
28
- "facts?": "{ [fact]: string } — facts (email, phone, city): only the customer's own words; a blank fact is a publish-blocking placeholder, never invented"
28
+ "facts?": "{ [fact]: string } — facts (email, phone, city, address, hours): only the customer's own words; a blank fact is a publish-blocking placeholder, never invented"
29
29
  },
30
30
  "design": {
31
31
  "flavorId": "string — a v2 flavor pack id",
@@ -104,7 +104,9 @@ export const SPEC_SITE_PLAN_SHAPE = {
104
104
  export const SPEC_SITE_FACTS = {
105
105
  "email": "Email address",
106
106
  "phone": "Phone number",
107
- "city": "City or area"
107
+ "city": "City or area",
108
+ "address": "Street address",
109
+ "hours": "Opening hours"
108
110
  } as const;
109
111
 
110
112
  export const SPEC_SITE_PATTERNS = {
@@ -2052,3 +2054,130 @@ export const SPEC_SITE_TEMPLATES = {
2052
2054
  }
2053
2055
  }
2054
2056
  } as const;
2057
+
2058
+ export const SPEC_SITE_LIBRARY = [
2059
+ {
2060
+ "id": "nav.inline-cta",
2061
+ "family": "nav"
2062
+ },
2063
+ {
2064
+ "id": "nav.centered-logo",
2065
+ "family": "nav"
2066
+ },
2067
+ {
2068
+ "id": "nav.overlay-menu",
2069
+ "family": "nav"
2070
+ },
2071
+ {
2072
+ "id": "hero.split",
2073
+ "family": "hero"
2074
+ },
2075
+ {
2076
+ "id": "hero.centered",
2077
+ "family": "hero"
2078
+ },
2079
+ {
2080
+ "id": "hero.full-bleed-media",
2081
+ "family": "hero"
2082
+ },
2083
+ {
2084
+ "id": "hero.stacked-editorial",
2085
+ "family": "hero"
2086
+ },
2087
+ {
2088
+ "id": "proof.logo-strip",
2089
+ "family": "proof"
2090
+ },
2091
+ {
2092
+ "id": "proof.stat-row",
2093
+ "family": "proof"
2094
+ },
2095
+ {
2096
+ "id": "proof.testimonial-single",
2097
+ "family": "proof"
2098
+ },
2099
+ {
2100
+ "id": "proof.testimonial-carousel",
2101
+ "family": "proof"
2102
+ },
2103
+ {
2104
+ "id": "feature.triad",
2105
+ "family": "feature"
2106
+ },
2107
+ {
2108
+ "id": "feature.alternating-rows",
2109
+ "family": "feature"
2110
+ },
2111
+ {
2112
+ "id": "feature.icon-list",
2113
+ "family": "feature"
2114
+ },
2115
+ {
2116
+ "id": "feature.tabbed",
2117
+ "family": "feature"
2118
+ },
2119
+ {
2120
+ "id": "collection.card-grid",
2121
+ "family": "collection"
2122
+ },
2123
+ {
2124
+ "id": "collection.masonry-gallery",
2125
+ "family": "collection"
2126
+ },
2127
+ {
2128
+ "id": "collection.slider",
2129
+ "family": "collection"
2130
+ },
2131
+ {
2132
+ "id": "collection.list-detail",
2133
+ "family": "collection"
2134
+ },
2135
+ {
2136
+ "id": "story.long-form",
2137
+ "family": "story"
2138
+ },
2139
+ {
2140
+ "id": "story.timeline",
2141
+ "family": "story"
2142
+ },
2143
+ {
2144
+ "id": "story.quote-break",
2145
+ "family": "story"
2146
+ },
2147
+ {
2148
+ "id": "cta.banner",
2149
+ "family": "cta"
2150
+ },
2151
+ {
2152
+ "id": "cta.split-form",
2153
+ "family": "cta"
2154
+ },
2155
+ {
2156
+ "id": "contact.split-map",
2157
+ "family": "contact"
2158
+ },
2159
+ {
2160
+ "id": "contact.simple",
2161
+ "family": "contact"
2162
+ },
2163
+ {
2164
+ "id": "faq.accordion",
2165
+ "family": "faq"
2166
+ },
2167
+ {
2168
+ "id": "pricing.tiers",
2169
+ "family": "pricing"
2170
+ },
2171
+ {
2172
+ "id": "footer.three-column",
2173
+ "family": "footer"
2174
+ },
2175
+ {
2176
+ "id": "footer.mega",
2177
+ "family": "footer"
2178
+ },
2179
+ {
2180
+ "id": "footer.minimal",
2181
+ "family": "footer"
2182
+ }
2183
+ ] as const;