@vectoriox/iox-ui 5.59.1 → 5.61.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/fesm2022/vectoriox-iox-ui-generation-core.mjs +651 -266
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/generation-core/index.d.ts +165 -70
- package/package.json +1 -1
- package/src/generation-core/plan-edits.ts +156 -38
- package/src/generation-core/plan-wire.ts +458 -0
- package/src/generation-core/public-api.ts +2 -1
- package/src/generation-core/stock-photos.ts +53 -13
- package/src/generation-core/strict-schema.ts +96 -0
- package/src/generation-core/site-plan-schema.ts +0 -252
|
@@ -5897,57 +5897,129 @@ declare enum AiCallStatus {
|
|
|
5897
5897
|
}
|
|
5898
5898
|
|
|
5899
5899
|
/**
|
|
5900
|
-
* The
|
|
5901
|
-
*
|
|
5902
|
-
*
|
|
5900
|
+
* The plan WIRE (AI plan M1-3, vectoriox/iox-cms#45): what a model writes when it plans a site — a compact format inside
|
|
5901
|
+
* the Claude API's structured-output limits (≤ 24 optional parameters and ≤ 16 unions per request; `strict-schema.ts`).
|
|
5902
|
+
* The Site Plan itself (`SPEC_SITE_PLAN_SHAPE`) has 213 optional keys once every pattern is a branch, so the model never
|
|
5903
|
+
* writes it: it writes this, and ONE pure `expandPlanWire` turns it into the Site Plan that `validateSitePlan` and the
|
|
5904
|
+
* stage's gate judge exactly as before.
|
|
5903
5905
|
*
|
|
5904
|
-
*
|
|
5905
|
-
*
|
|
5906
|
-
*
|
|
5907
|
-
*
|
|
5908
|
-
*
|
|
5909
|
-
*
|
|
5906
|
+
* - **Every key required.** A plan key that may be absent is written with its stated "none" — `''`, `0`, `false`, `[]`.
|
|
5907
|
+
* - **Closed lists as enums** (patterns, page types, needs, slots, parts, forms, entrances, header/footer), from the
|
|
5908
|
+
* contract — never per-pattern branches: the gate refuses a slot or a part the chosen pattern does not have, with its path.
|
|
5909
|
+
* - **Maps as lists:** slots `[{ slot, text }]`, links `[{ slot, route }]`, seed values `[{ field, value }]`, roles fixed.
|
|
5910
|
+
* - **One data source object** for both kinds; `type` says which keys are read (the other kind's hold their none).
|
|
5911
|
+
* - **What the model does not decide is not on the wire** (`PLAN_WIRE_DERIVED`): the look (the owner's pick), the run,
|
|
5912
|
+
* the language (the reading's), the facts (the owner's own, verbatim), the version, the home page (the page at `/`).
|
|
5913
|
+
* So the format is the same for every studio and every run — one compiled grammar, cached by the provider.
|
|
5914
|
+
*
|
|
5915
|
+
* ONE source: every wire key names the plan keys it carries (`refs`) and is described in the plan table's own words, and
|
|
5916
|
+
* every plan key is either carried or derived (`planWireGaps()`); a key the plan shape gains breaks the builder until it
|
|
5917
|
+
* is placed. The expanders translate, never repair: a value the model got wrong stays wrong, for the gate to refuse.
|
|
5910
5918
|
*/
|
|
5911
|
-
declare const
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5919
|
+
declare const PLAN_WIRE_SCHEMA_NAME = "site_plan";
|
|
5920
|
+
declare enum PlanWireEntry {
|
|
5921
|
+
Plan = "plan",
|
|
5922
|
+
Business = "business",
|
|
5923
|
+
ContentType = "contentType",
|
|
5924
|
+
Roles = "roles",
|
|
5925
|
+
Field = "field",
|
|
5926
|
+
Seed = "seed",
|
|
5927
|
+
SeedValue = "seedValue",
|
|
5928
|
+
DataSource = "dataSource",
|
|
5929
|
+
Page = "page",
|
|
5930
|
+
PageSeo = "pageSeo",
|
|
5931
|
+
Section = "section",
|
|
5932
|
+
SlotText = "slotText",
|
|
5933
|
+
SlotLink = "slotLink",
|
|
5934
|
+
Globals = "globals"
|
|
5935
|
+
}
|
|
5936
|
+
/** The plan keys the model does not write, and where the expander takes each from. */
|
|
5937
|
+
declare const PLAN_WIRE_DERIVED: Readonly<Record<string, string>>;
|
|
5938
|
+
/** The wire's shape table, in the plan table's words — what the model reads, and what the format is built from. */
|
|
5939
|
+
declare const PLAN_WIRE_SHAPE: Readonly<Record<`${PlanWireEntry}`, ShapeEntry>>;
|
|
5940
|
+
/** Plan keys neither carried by a wire key nor derived — always empty (a spec and the builder hold it). */
|
|
5941
|
+
declare function planWireGaps(): string[];
|
|
5942
|
+
declare class PlanWireShapeError extends Error {
|
|
5925
5943
|
}
|
|
5926
|
-
/** A content type's field. */
|
|
5927
|
-
declare function fieldJsonSchema(): JsonSchema;
|
|
5928
|
-
/** A content type, its seed's field values as a list (`values: [{ field, value }]`). */
|
|
5929
|
-
declare function contentTypeJsonSchema(): JsonSchema;
|
|
5930
|
-
/** A data source: a cms list / single item, or a relative one. */
|
|
5931
|
-
declare function dataSourceJsonSchema(): JsonSchema;
|
|
5932
5944
|
/** The patterns a page section may use (every non-global one), in library order. */
|
|
5933
5945
|
declare function sectionPatternIds(): string[];
|
|
5934
|
-
|
|
5935
|
-
declare function
|
|
5936
|
-
|
|
5937
|
-
declare function
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5946
|
+
declare function wireFieldJsonSchema(): JsonSchema;
|
|
5947
|
+
declare function wireContentTypeJsonSchema(): JsonSchema;
|
|
5948
|
+
declare function wireDataSourceJsonSchema(): JsonSchema;
|
|
5949
|
+
declare function wireSectionJsonSchema(): JsonSchema;
|
|
5950
|
+
declare function wirePageJsonSchema(): JsonSchema;
|
|
5951
|
+
/** The format a model writes a plan in. The same for every studio and run: nothing in it is pinned. */
|
|
5952
|
+
declare function planWireJsonSchema(): JsonSchema;
|
|
5953
|
+
/** What the plan takes from the generation, not from the model. */
|
|
5954
|
+
interface PlanWireContext {
|
|
5955
|
+
design: DesignChoices;
|
|
5956
|
+
source: SitePlanSource;
|
|
5957
|
+
/** The owner's own facts (the analysis's, as corrected), verbatim. */
|
|
5958
|
+
facts?: Readonly<Partial<Record<FactKey, string>>>;
|
|
5959
|
+
locale?: `${SiteLocale}`;
|
|
5960
|
+
}
|
|
5961
|
+
declare function expandWireField(wire: unknown): ContentFieldPlan | unknown;
|
|
5962
|
+
declare function expandWireContentType(wire: unknown): ContentTypePlan | unknown;
|
|
5963
|
+
declare function expandWireDataSource(wire: unknown): DataSourcePlan | unknown;
|
|
5964
|
+
declare function expandWireSection(wire: unknown): PageSectionPlan | unknown;
|
|
5965
|
+
declare function expandWirePage(wire: unknown): PagePlan | unknown;
|
|
5944
5966
|
/**
|
|
5945
|
-
* A model's
|
|
5946
|
-
* answer got wrong is still wrong, for `validateSitePlan
|
|
5967
|
+
* A model's plan (the wire) + what the generation decides (the look, the run, the language, the owner's facts) → the
|
|
5968
|
+
* Site Plan. Pure. Translates, never repairs: whatever the answer got wrong is still wrong, for `validateSitePlan`.
|
|
5947
5969
|
*/
|
|
5948
|
-
declare function
|
|
5949
|
-
|
|
5950
|
-
declare function
|
|
5970
|
+
declare function expandPlanWire(wire: unknown, context: PlanWireContext): SitePlan | unknown;
|
|
5971
|
+
declare function fieldToWire(field: ContentFieldPlan): PlainObject;
|
|
5972
|
+
declare function contentTypeToWire(type: ContentTypePlan): PlainObject;
|
|
5973
|
+
declare function dataSourceToWire(source: DataSourcePlan): PlainObject;
|
|
5974
|
+
declare function sectionToWire(section: PageSectionPlan): PlainObject;
|
|
5975
|
+
declare function pageToWire(page: PagePlan): PlainObject;
|
|
5976
|
+
/** The plan → the wire: what a model would have written (the plan edits' prompt shows it; fakes and evals write it). */
|
|
5977
|
+
declare function planToWire(plan: SitePlan): PlainObject;
|
|
5978
|
+
|
|
5979
|
+
/**
|
|
5980
|
+
* What a structured-output request costs the provider's grammar compiler, counted the way the Claude API counts it
|
|
5981
|
+
* (platform.claude.com/docs/en/build-with-claude/structured-outputs, "Schema complexity limits", read 2026-10-27):
|
|
5982
|
+
* "Total optional parameters across all strict tool schemas and JSON output schemas. Each parameter not listed in
|
|
5983
|
+
* `required` counts toward this limit" (24); "Total parameters that use `anyOf` or type arrays (for example,
|
|
5984
|
+
* `"type": ["string", "null"]`) across all strict schemas" (16); "Maximum number of tools with `strict: true`" (20).
|
|
5985
|
+
* The limits are per REQUEST — every strict schema in it together. The ONE counter: the specs hold every schema iox-cms
|
|
5986
|
+
* sends to it, and the fake Claude API refuses with it exactly as Claude does — the plan stage once sent 213 optional
|
|
5987
|
+
* parameters and every suite stayed green because the fake accepted anything (vectoriox/iox-cms#45).
|
|
5988
|
+
*/
|
|
5989
|
+
declare const STRICT_SCHEMA_LIMITS: {
|
|
5990
|
+
readonly strictTools: 20;
|
|
5991
|
+
readonly optional: 24;
|
|
5992
|
+
readonly unions: 16;
|
|
5993
|
+
};
|
|
5994
|
+
/** What a schema iox-cms sends may use — half of each limit, so a step can gain a field (or a strict tool) without a 400. */
|
|
5995
|
+
declare const STRICT_SCHEMA_BUDGET: {
|
|
5996
|
+
readonly optional: 12;
|
|
5997
|
+
readonly unions: 8;
|
|
5998
|
+
};
|
|
5999
|
+
interface StrictSchemaComplexity {
|
|
6000
|
+
/** Properties not listed in their object's `required`, every object of every schema. */
|
|
6001
|
+
optional: number;
|
|
6002
|
+
/** Nodes using `anyOf` or a type array. */
|
|
6003
|
+
unions: number;
|
|
6004
|
+
/** Tools with `strict: true` (a JSON output format is not a tool). */
|
|
6005
|
+
strictTools: number;
|
|
6006
|
+
/** What the strict mode does not support, each with its path (`pages.items.properties.route: minLength`). */
|
|
6007
|
+
unsupported: string[];
|
|
6008
|
+
}
|
|
6009
|
+
/** The cost of ONE request's strict schemas together: its JSON output format and its strict tools' input schemas. */
|
|
6010
|
+
declare function strictSchemaComplexity(schemas: ReadonlyArray<JsonSchema>, strictTools?: number): StrictSchemaComplexity;
|
|
6011
|
+
/** A refusal shaped like the Claude API's (HTTP 400, `invalid_request_error`). */
|
|
6012
|
+
interface StrictSchemaRefusal {
|
|
6013
|
+
status: 400;
|
|
6014
|
+
type: 'invalid_request_error';
|
|
6015
|
+
message: string;
|
|
6016
|
+
}
|
|
6017
|
+
/**
|
|
6018
|
+
* The 400 the Claude API answers a request over its limits with, or null. The optional-parameter message is the one
|
|
6019
|
+
* Claude answered on 2026-10-27, verbatim; the union, strict-tool and unsupported messages follow its shape (not yet
|
|
6020
|
+
* captured).
|
|
6021
|
+
*/
|
|
6022
|
+
declare function strictSchemaRefusal(complexity: StrictSchemaComplexity): StrictSchemaRefusal | null;
|
|
5951
6023
|
|
|
5952
6024
|
/**
|
|
5953
6025
|
* Changing a plan by sentence (AI plan §3d "The Plan prompt box", M1-5; reused by M2 after a build): the CLOSED list
|
|
@@ -6106,9 +6178,21 @@ interface PlanEditsAnswer {
|
|
|
6106
6178
|
}
|
|
6107
6179
|
/** The answer's own keys, explained. */
|
|
6108
6180
|
declare const PLAN_EDITS_ANSWER_SHAPE: ShapeEntry;
|
|
6109
|
-
/**
|
|
6181
|
+
/** Each op's keys (`PLAN_EDIT_SHAPE`) → the wire key that carries it. */
|
|
6182
|
+
declare const PLAN_EDIT_WIRE_KEYS: Readonly<Record<`${PlanEditOp}`, Readonly<Record<string, string>>>>;
|
|
6183
|
+
/** The flat edit's shape table: every key required, each with its none. */
|
|
6184
|
+
declare const PLAN_EDIT_WIRE_SHAPE: ShapeEntry;
|
|
6185
|
+
/** Per op, the wire keys it reads, in `PLAN_EDIT_SHAPE`'s words (`?` = may hold its none) — what the model reads. */
|
|
6186
|
+
declare function planEditWireOps(): Record<string, Record<string, string>>;
|
|
6187
|
+
/** Op keys no wire key carries, or wire keys that do not exist — always empty (a spec and the builder hold it). */
|
|
6188
|
+
declare function planEditWireGaps(): string[];
|
|
6189
|
+
/** The format a sentence's answer is written in: the outcome, the flat edits, the reason. */
|
|
6110
6190
|
declare function planEditsJsonSchema(): JsonSchema;
|
|
6111
|
-
/**
|
|
6191
|
+
/** One flat edit → the op's edit: the keys it reads, a none left out where the op may go without it. Never repaired. */
|
|
6192
|
+
declare function expandWireEdit(wire: unknown): PlanEdit | unknown;
|
|
6193
|
+
/** An edit → the flat wire edit (what a model would write — fakes and specs write it). */
|
|
6194
|
+
declare function planEditToWire(edit: PlanEdit): PlainObject;
|
|
6195
|
+
/** A model's answer → edits (each flat edit expanded); never repaired. */
|
|
6112
6196
|
declare function planEditsFromWire(answer: unknown): PlanEditsAnswer | unknown;
|
|
6113
6197
|
|
|
6114
6198
|
/**
|
|
@@ -6296,29 +6380,47 @@ declare function publishVerdict(audit: SiteAudit, acknowledged?: ReadonlyArray<s
|
|
|
6296
6380
|
* into the studio's own storage (never hot-linked), placed only in an EMPTY picture slot of a section that may show
|
|
6297
6381
|
* one — never the owner's work (a collection, a detail page, a gallery: what the nine portfolio types sell is theirs),
|
|
6298
6382
|
* never a person (testimonials, the team), never anywhere when the business type says `stock: false`. Its alt text is
|
|
6299
|
-
* the provider's description of what it shows (never a claim), and the credit line its licence requires
|
|
6300
|
-
* the footer. Pure: the build step does the copying.
|
|
6383
|
+
* the provider's description of what it shows (never a claim), and the credit line its licence requires — when it
|
|
6384
|
+
* requires one — is placed in the footer. Pure: the build step does the copying.
|
|
6301
6385
|
*/
|
|
6302
6386
|
/** Where a stock photo comes from (the library's provider). */
|
|
6303
6387
|
declare enum StockProviderId {
|
|
6304
|
-
Magnific = "magnific"
|
|
6388
|
+
Magnific = "magnific",
|
|
6389
|
+
Pixabay = "pixabay"
|
|
6390
|
+
}
|
|
6391
|
+
/** Words and the address they link to. */
|
|
6392
|
+
interface StockLink {
|
|
6393
|
+
line: string;
|
|
6394
|
+
url: string;
|
|
6305
6395
|
}
|
|
6306
|
-
/** What a
|
|
6396
|
+
/** What a provider's terms ask of IOX, per provider — the ONE place a credit line is decided. */
|
|
6397
|
+
interface StockProviderTerms {
|
|
6398
|
+
name: string;
|
|
6399
|
+
/** The line a site showing one of its pictures must carry (in the footer), or null when its licence asks none. */
|
|
6400
|
+
credit: StockLink | null;
|
|
6401
|
+
/** What to show wherever its SEARCH RESULTS are displayed — the operator's review of the library, never a site. */
|
|
6402
|
+
sourceNotice: StockLink;
|
|
6403
|
+
}
|
|
6404
|
+
/**
|
|
6405
|
+
* The providers' terms (AI plan §3g, I2; read 2026-10-25 / 2026-10-27). Magnific's free licence requires "Designed by
|
|
6406
|
+
* Magnific", linked, next to the resource or in the site's footer. Pixabay's Content License: "You do not need to credit
|
|
6407
|
+
* Pixabay or the contributor" — but its API asks to "show your users where the images and videos are from, whenever
|
|
6408
|
+
* search results are displayed", which only the operator's review does.
|
|
6409
|
+
*/
|
|
6410
|
+
declare const STOCK_PROVIDER_TERMS: Readonly<Record<StockProviderId, StockProviderTerms>>;
|
|
6411
|
+
/** Where a picture came from and under which licence — recorded with every library photo and every studio copy. */
|
|
6307
6412
|
interface StockCredit {
|
|
6308
6413
|
provider: `${StockProviderId}`;
|
|
6309
6414
|
/** The provider's id of the picture. */
|
|
6310
6415
|
resourceId: string;
|
|
6311
6416
|
/** The provider's page for it. */
|
|
6312
6417
|
sourceUrl: string;
|
|
6313
|
-
/** The provider's own
|
|
6418
|
+
/** The provider's own words for what the picture shows (Magnific's title, Pixabay's tags). */
|
|
6314
6419
|
title: string;
|
|
6315
6420
|
author: string;
|
|
6316
|
-
/**
|
|
6421
|
+
/** The licence it is used under, as the provider names it (Magnific `freemium`, Pixabay `content-license`). */
|
|
6317
6422
|
licence: string;
|
|
6318
6423
|
licenceUrl: string;
|
|
6319
|
-
/** The words the licence requires, and where they link. */
|
|
6320
|
-
line: string;
|
|
6321
|
-
lineUrl: string;
|
|
6322
6424
|
}
|
|
6323
6425
|
/** One picture of the platform library (`iox-stock/<type>/`), as its manifest lists it. */
|
|
6324
6426
|
interface StockPhoto {
|
|
@@ -6356,26 +6458,19 @@ declare function applyStockPhotos(trees: ReadonlyArray<SerializedNode>, placed:
|
|
|
6356
6458
|
url: string;
|
|
6357
6459
|
alt: string;
|
|
6358
6460
|
}>): number;
|
|
6359
|
-
/** The
|
|
6360
|
-
declare function
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
}>;
|
|
6461
|
+
/** The line a picture's licence requires on the site, or null when it requires none (`STOCK_PROVIDER_TERMS`). */
|
|
6462
|
+
declare function stockCreditLine(credit: Pick<StockCredit, 'provider'>): StockLink | null;
|
|
6463
|
+
/** The credit lines a set of photos needs — one per distinct line, none for a provider whose licence asks none. */
|
|
6464
|
+
declare function stockCreditLines(photos: ReadonlyArray<Pick<StockPhoto, 'credit'>>): StockLink[];
|
|
6364
6465
|
/** The footer node id of a credit line (stable — a build that runs twice finds it). */
|
|
6365
6466
|
declare const stockCreditNodeId: (index: number) => string;
|
|
6366
6467
|
/**
|
|
6367
6468
|
* The credit line, as the builder would create a Link (a plain node the owner may move or restyle): the licence's words,
|
|
6368
6469
|
* linking where it asks, opening in a new tab. Placed at the end of the footer — the place the licence names for a site.
|
|
6369
6470
|
*/
|
|
6370
|
-
declare function stockCreditNode(index: number, credit:
|
|
6371
|
-
line: string;
|
|
6372
|
-
url: string;
|
|
6373
|
-
}): SerializedNode;
|
|
6471
|
+
declare function stockCreditNode(index: number, credit: StockLink): SerializedNode;
|
|
6374
6472
|
/** Append the credit lines to the footer root (once each). Returns how many it added. */
|
|
6375
|
-
declare function addStockCredits(footer: SerializedNode, credits: ReadonlyArray<
|
|
6376
|
-
line: string;
|
|
6377
|
-
url: string;
|
|
6378
|
-
}>): number;
|
|
6473
|
+
declare function addStockCredits(footer: SerializedNode, credits: ReadonlyArray<StockLink>): number;
|
|
6379
6474
|
|
|
6380
|
-
export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES, PLAN_EDITS_ANSWER_SHAPE, PLAN_EDITS_SCHEMA_NAME, PLAN_EDIT_SHAPE, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanChangeAction, PlanChangeKind, PlanDataSourceMode, PlanDataSourceType, PlanEditIssueCode, PlanEditOp, PlanEditsOutcome, PlanWordIssueCode, PreflightRefusalCode, PublishRefusalCode, REQUIRED_CONTENT_ROLES, RelativeOp, SAMPLE_IMAGE_REF, SITE_CHECKS, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_LIBRARY, SITE_LOCALES, SITE_NEED_IDS, SITE_PATTERNS,
|
|
6381
|
-
export type { AddBarItemResult, AiEntitlementsView, AiMeter, AiModelEntry, AiRefusal, AiTokenCounts, AiTokenPrice, AuditPage, AuditPlaceholder, BarItemsEditResult, BuildNotice, BuildStepError, BuildStepFacts, BuildStepRecord, CmsDataSourcePlan, ComposeContext, ComposeIssue, ComposedDataSource, ComposedItem, ComposedNavHeader, ComposedPage, ComposedPlaceholder, ComposedRoute, ComposedSection, ComposedSite, ContentFieldPlan, ContentRoles, ContentSeedPlan, ContentTypePlan, CreateNodeOptions, CreatedRecord, DataSourcePlan, DynamicKeysAsList, EntranceAction, EntranceActionOptions, EntranceInteraction, EntranceMark, EntranceSpec, EntranceStamp, FactKey, JsonSchema, NavHeaderInput, NavHeaderOptions, NavPage, NavShapeOptions, NavShapeResult, NodeBaseline, PageArchetype, PageArchetypeId, PageArchetypeSlot, PageData, PageDataLine, PagePlan, PageSectionPlan, PageSeoPlan, PathIssue, PatternManifestEntry, PatternNode, PatternPath, PatternRoleNeed, PlainObject, PlanChange, PlanEdit, PlanEditIssue, PlanEditResult, PlanEditsAnswer, PlanEntrancesInput, PlanWordContext, PlanWordIssue, PreflightRefusal, PublishRefusal, RelativeDataSourcePlan, SectionEntrances, SectionInput, SectionOptions, SerializedBinding, SerializedNode, SerializedResponsive, ShapeEntry, ShapeIssueCodes, ShowcaseInput, SiteAudit, SiteAuditOptions, SiteAuditSnapshot, SiteBuildRun, SiteBusiness, SiteCheck, SiteFinding, SiteNeedId, SitePageRef, SitePattern, SitePlan, SitePlanIssue,
|
|
6475
|
+
export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES, PLAN_EDITS_ANSWER_SHAPE, PLAN_EDITS_SCHEMA_NAME, PLAN_EDIT_SHAPE, PLAN_EDIT_WIRE_KEYS, PLAN_EDIT_WIRE_SHAPE, PLAN_WIRE_DERIVED, PLAN_WIRE_SCHEMA_NAME, PLAN_WIRE_SHAPE, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanChangeAction, PlanChangeKind, PlanDataSourceMode, PlanDataSourceType, PlanEditIssueCode, PlanEditOp, PlanEditsOutcome, PlanWireEntry, PlanWireShapeError, PlanWordIssueCode, PreflightRefusalCode, PublishRefusalCode, REQUIRED_CONTENT_ROLES, RelativeOp, SAMPLE_IMAGE_REF, SITE_CHECKS, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_LIBRARY, SITE_LOCALES, SITE_NEED_IDS, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_ARCHETYPES, SPEC_SITE_COPY_LENGTHS, SPEC_SITE_DENSITY_COPY, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_LOCALES, SPEC_SITE_NEED_IDS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_REQUIRED_ROLES, SPEC_SITE_ROLES, SPEC_SITE_TEMPLATES, STOCK_FAMILIES, STOCK_PROVIDER_TERMS, STRICT_SCHEMA_BUDGET, STRICT_SCHEMA_LIMITS, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SiteCheckId, SiteFindingCode, SiteLocale, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, StepState, StockProviderId, addBarItem, addStockCredits, applyPlanEdits, applyStockPhotos, archetypeIssues, assertStyleSlots, assertValidSitePlan, auditSite, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, contentTypePreviewFields, contentTypeToWire, copyLengthOf, costUsd, createSerializedNode, creditsOfUsd, dataSourceToWire, describePlanData, describeShapeKey, entranceAction, entranceInteraction, estimateStepCredits, expandPlanWire, expandWireContentType, expandWireDataSource, expandWireEdit, expandWireField, expandWirePage, expandWireSection, failedBuildStep, fieldToWire, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, isRtlLocale, jsonAnyOf, jsonBoolean, jsonClosedObject, jsonInteger, jsonListOf, jsonOneOf, jsonSchemaKeywords, jsonSchemaObjects, jsonShapedObject, jsonText, modelCanDo, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, pageToWire, parsePatternPath, parseSampleImageRef, pathAt, patternById, patternHasForm, patternLinkSlots, patternManifest, patternNeedsBinding, patternNodes, patternOptionalParts, patternRoleNeeds, patternScopes, patternSlotBudgets, patternSlots, patternTakesBinding, phoneMenuPages, planEditToWire, planEditWireGaps, planEditWireOps, planEditsFromWire, planEditsJsonSchema, planEntrances, planFromTemplate, planNavShape, planStockPhotos, planToWire, planWireGaps, planWireJsonSchema, planWordIssues, publishRefusal, publishVerdict, releasedLayoutDefaults, removeBarItem, requiredAcknowledgements, requiredShapeKeys, roleField, routeMatches, routeParams, sampleImageRef, sectionEntrances, sectionPatternIds, sectionToWire, setStyleProps, shapeKeys, showcaseGlobals, showcasePages, showcasePlans, siteNavPages, slotBudget, stockCopyKey, stockCreditLine, stockCreditLines, stockCreditNode, stockCreditNodeId, stockSlots, strictSchemaComplexity, strictSchemaRefusal, validatePlanEdits, validateSitePlan, walkSerialized, wireContentTypeJsonSchema, wireDataSourceJsonSchema, wireFieldJsonSchema, wirePageJsonSchema, wireSectionJsonSchema, wordPattern, writtenTexts };
|
|
6476
|
+
export type { AddBarItemResult, AiEntitlementsView, AiMeter, AiModelEntry, AiRefusal, AiTokenCounts, AiTokenPrice, AuditPage, AuditPlaceholder, BarItemsEditResult, BuildNotice, BuildStepError, BuildStepFacts, BuildStepRecord, CmsDataSourcePlan, ComposeContext, ComposeIssue, ComposedDataSource, ComposedItem, ComposedNavHeader, ComposedPage, ComposedPlaceholder, ComposedRoute, ComposedSection, ComposedSite, ContentFieldPlan, ContentRoles, ContentSeedPlan, ContentTypePlan, CreateNodeOptions, CreatedRecord, DataSourcePlan, DynamicKeysAsList, EntranceAction, EntranceActionOptions, EntranceInteraction, EntranceMark, EntranceSpec, EntranceStamp, FactKey, JsonSchema, NavHeaderInput, NavHeaderOptions, NavPage, NavShapeOptions, NavShapeResult, NodeBaseline, PageArchetype, PageArchetypeId, PageArchetypeSlot, PageData, PageDataLine, PagePlan, PageSectionPlan, PageSeoPlan, PathIssue, PatternManifestEntry, PatternNode, PatternPath, PatternRoleNeed, PlainObject, PlanChange, PlanEdit, PlanEditIssue, PlanEditResult, PlanEditsAnswer, PlanEntrancesInput, PlanWireContext, PlanWordContext, PlanWordIssue, PreflightRefusal, PublishRefusal, RelativeDataSourcePlan, SectionEntrances, SectionInput, SectionOptions, SerializedBinding, SerializedNode, SerializedResponsive, ShapeEntry, ShapeIssueCodes, ShowcaseInput, SiteAudit, SiteAuditOptions, SiteAuditSnapshot, SiteBuildRun, SiteBusiness, SiteCheck, SiteFinding, SiteNeedId, SitePageRef, SitePattern, SitePlan, SitePlanIssue, SitePlanSource, SitePlanValidation, SiteTemplate, StockCredit, StockLink, StockPhoto, StockPlacement, StockProviderTerms, StockSlot, StrictSchemaComplexity, StrictSchemaRefusal, TemplatePlanInput, UndoKept, UndoPreview, UndoRecord, WrittenText };
|
package/package.json
CHANGED
|
@@ -3,9 +3,10 @@ import {
|
|
|
3
3
|
} from './json-schema';
|
|
4
4
|
import { patternById, patternOptionalParts, patternSlots, patternTakesBinding } from './patterns';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
sectionPatternIds,
|
|
8
|
-
|
|
6
|
+
contentTypeToWire, dataSourceToWire, expandWireContentType, expandWireDataSource, expandWireField, expandWirePage, expandWireSection, fieldToWire,
|
|
7
|
+
pageToWire, PlanWireShapeError, sectionPatternIds, sectionToWire, wireContentTypeJsonSchema, wireDataSourceJsonSchema, wireFieldJsonSchema,
|
|
8
|
+
wirePageJsonSchema, wireSectionJsonSchema,
|
|
9
|
+
} from './plan-wire';
|
|
9
10
|
import {
|
|
10
11
|
ContentFieldPlan, ContentTypePlan, DataSourcePlan, PagePlan, PageSectionPlan, PlanDataSourceMode, PlanDataSourceType, SitePlan,
|
|
11
12
|
} from './site-plan.model';
|
|
@@ -209,7 +210,7 @@ const setSections = (d: Draft, at: number, sections: PageSectionPlan[]) => { d.p
|
|
|
209
210
|
/** One handler per op — a data table, never an if-chain. */
|
|
210
211
|
const APPLY: Readonly<Record<`${PlanEditOp}`, Apply>> = {
|
|
211
212
|
[PlanEditOp.AddPage]: (d, e, c, path, changed) => {
|
|
212
|
-
const page =
|
|
213
|
+
const page = e.page as PagePlan;
|
|
213
214
|
if (pageIndex(d, page.key) >= 0) return c.add(PlanEditIssueCode.Duplicate, `${path}.page.key`, `page "${page.key}" is already in the plan`);
|
|
214
215
|
const after = e.after === undefined ? d.pages.length - 1 : findPage(d, e.after, c, `${path}.after`);
|
|
215
216
|
if (after < 0 && e.after !== undefined) return;
|
|
@@ -257,7 +258,7 @@ const APPLY: Readonly<Record<`${PlanEditOp}`, Apply>> = {
|
|
|
257
258
|
if (at < 0) return;
|
|
258
259
|
const sections = [...d.pages[at].sections];
|
|
259
260
|
const index = e.at === undefined ? sections.length : clamp(e.at, sections.length);
|
|
260
|
-
sections.splice(index, 0,
|
|
261
|
+
sections.splice(index, 0, e.section as PageSectionPlan);
|
|
261
262
|
setSections(d, at, sections);
|
|
262
263
|
changed.push({ kind: PlanChangeKind.Section, action: PlanChangeAction.Added, page: d.pages[at].key, index });
|
|
263
264
|
},
|
|
@@ -304,7 +305,7 @@ const APPLY: Readonly<Record<`${PlanEditOp}`, Apply>> = {
|
|
|
304
305
|
changed.push({ kind: PlanChangeKind.Section, action: PlanChangeAction.Changed, page: d.pages[at].key, index: e.index });
|
|
305
306
|
},
|
|
306
307
|
[PlanEditOp.AddContentType]: (d, e, c, path, changed) => {
|
|
307
|
-
const type =
|
|
308
|
+
const type = e.contentType as ContentTypePlan;
|
|
308
309
|
if (d.contentTypes.some(t => t.name === type.name)) return c.add(PlanEditIssueCode.Duplicate, `${path}.contentType.name`, `content type "${type.name}" is already in the plan`);
|
|
309
310
|
d.contentTypes.push(type);
|
|
310
311
|
changed.push({ kind: PlanChangeKind.ContentType, action: PlanChangeAction.Added, name: type.name });
|
|
@@ -382,47 +383,164 @@ export const PLAN_EDITS_ANSWER_SHAPE: ShapeEntry = {
|
|
|
382
383
|
reason: "string — when cannot, ONE short line for the owner in their language (what to do instead); else ''",
|
|
383
384
|
};
|
|
384
385
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
386
|
+
/**
|
|
387
|
+
* ONE edit on the wire (vectoriox/iox-cms#45): a flat object whose keys are ALL required — the 13 ops as `anyOf`
|
|
388
|
+
* branches, each embedding whole pages, sections and content types, came to 395 optional parameters against the Claude
|
|
389
|
+
* API's 24. `op` says which keys are read (`PLAN_EDIT_WIRE_KEYS`, from `PLAN_EDIT_SHAPE`); every other key holds its
|
|
390
|
+
* none. A whole page, section, content type or field travels as a list of exactly one, in the plan wire's format.
|
|
391
|
+
*/
|
|
392
|
+
interface EditWireKey {
|
|
393
|
+
text: string;
|
|
394
|
+
none: unknown;
|
|
395
|
+
schema(): JsonSchema;
|
|
396
|
+
/** The wire value → the edit's (default: as written). */
|
|
397
|
+
expand?(value: unknown): unknown;
|
|
398
|
+
/** The edit's value → the wire's (default: as is). */
|
|
399
|
+
toWire?(value: any): unknown;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const NO_INDEX = -1;
|
|
403
|
+
|
|
404
|
+
/** A list holding exactly one object: none → absent, one → it (expanded), more → the list (the gate refuses it). */
|
|
405
|
+
const oneOf = (schema: () => JsonSchema, expand: (v: unknown) => unknown, toWire: (v: any) => unknown, what: string): EditWireKey => ({
|
|
406
|
+
text: `${what}[] — exactly one when the op takes it`,
|
|
407
|
+
none: [],
|
|
408
|
+
schema: () => jsonListOf(schema()),
|
|
409
|
+
expand: v => (Array.isArray(v) ? (v.length === 0 ? undefined : v.length === 1 ? expand(v[0]) : v) : v),
|
|
410
|
+
toWire: v => [toWire(v)],
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const textKey = (text: string): EditWireKey => ({ text: `string — ${text}`, none: '', schema: jsonText });
|
|
414
|
+
const indexKey = (text: string): EditWireKey => ({ text: `integer — ${text}`, none: NO_INDEX, schema: jsonInteger });
|
|
415
|
+
|
|
416
|
+
const EDIT_WIRE: Readonly<Record<string, EditWireKey>> = {
|
|
417
|
+
page: textKey('a page key'),
|
|
418
|
+
name: textKey('renamePage: its new name (the menu label)'),
|
|
419
|
+
route: textKey('renamePage: its new route'),
|
|
420
|
+
after: textKey('addPage: the key of the page it follows in the menu'),
|
|
421
|
+
into: textKey('mergePages: the page that stays'),
|
|
422
|
+
from: textKey('mergePages: the page whose sections move into it, then removed'),
|
|
423
|
+
index: indexKey('the section, 0-based on its page'),
|
|
424
|
+
to: indexKey('movePage / moveSection: the new 0-based place'),
|
|
425
|
+
at: indexKey('addSection: its 0-based place on the page'),
|
|
426
|
+
pattern: { text: 'string — swapPattern: the section pattern it becomes', none: '', schema: () => jsonOneOf(['', ...sectionPatternIds()]) },
|
|
427
|
+
slot: textKey("setSlot: one of the pattern's slots"),
|
|
428
|
+
text: textKey("setSlot: the words, in the owner's language and voice"),
|
|
429
|
+
binding: textKey('setSectionData: the list source the section repeats'),
|
|
430
|
+
contentType: textKey('addField: a content type of the plan'),
|
|
431
|
+
newPage: oneOf(wirePageJsonSchema, expandWirePage, pageToWire, 'page'),
|
|
432
|
+
newSection: oneOf(wireSectionJsonSchema, expandWireSection, sectionToWire, 'section'),
|
|
433
|
+
newContentType: oneOf(wireContentTypeJsonSchema, expandWireContentType, contentTypeToWire, 'contentType'),
|
|
434
|
+
dataSources: {
|
|
435
|
+
text: 'dataSource[] — addContentType: the list / single sources its pages read', none: [],
|
|
436
|
+
schema: () => jsonListOf(wireDataSourceJsonSchema()),
|
|
437
|
+
expand: v => (Array.isArray(v) ? v.map(expandWireDataSource) : v),
|
|
438
|
+
toWire: (v: DataSourcePlan[]) => v.map(dataSourceToWire),
|
|
439
|
+
},
|
|
440
|
+
newField: oneOf(wireFieldJsonSchema, expandWireField, fieldToWire, 'field'),
|
|
441
|
+
sample: { text: "string | string[] — addField: the samples' value for it", none: '', schema: () => jsonAnyOf([jsonText(), jsonListOf(jsonText())]) },
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
/** Each op's keys (`PLAN_EDIT_SHAPE`) → the wire key that carries it. */
|
|
445
|
+
export const PLAN_EDIT_WIRE_KEYS: Readonly<Record<`${PlanEditOp}`, Readonly<Record<string, string>>>> = {
|
|
446
|
+
[PlanEditOp.AddPage]: { page: 'newPage', after: 'after' },
|
|
447
|
+
[PlanEditOp.RemovePage]: { page: 'page' },
|
|
448
|
+
[PlanEditOp.RenamePage]: { page: 'page', name: 'name', route: 'route' },
|
|
449
|
+
[PlanEditOp.MergePages]: { into: 'into', from: 'from' },
|
|
450
|
+
[PlanEditOp.MovePage]: { page: 'page', to: 'to' },
|
|
451
|
+
[PlanEditOp.AddSection]: { page: 'page', section: 'newSection', at: 'at' },
|
|
452
|
+
[PlanEditOp.RemoveSection]: { page: 'page', index: 'index' },
|
|
453
|
+
[PlanEditOp.MoveSection]: { page: 'page', from: 'index', to: 'to' },
|
|
454
|
+
[PlanEditOp.SwapPattern]: { page: 'page', index: 'index', pattern: 'pattern' },
|
|
455
|
+
[PlanEditOp.SetSlot]: { page: 'page', index: 'index', slot: 'slot', text: 'text' },
|
|
456
|
+
[PlanEditOp.AddContentType]: { contentType: 'newContentType', dataSources: 'dataSources' },
|
|
457
|
+
[PlanEditOp.AddField]: { contentType: 'contentType', field: 'newField', sample: 'sample' },
|
|
458
|
+
[PlanEditOp.SetSectionData]: { page: 'page', index: 'index', binding: 'binding' },
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const isEditWireNone = (wireKey: string, v: unknown) => {
|
|
462
|
+
const none = EDIT_WIRE[wireKey].none;
|
|
463
|
+
return v === undefined || (Array.isArray(none) ? Array.isArray(v) && v.length === 0 : v === none) || (wireKey === 'sample' && Array.isArray(v) && v.length === 0);
|
|
464
|
+
};
|
|
465
|
+
const noneText = (none: unknown) => (Array.isArray(none) ? '[]' : typeof none === 'string' ? "''" : String(none));
|
|
466
|
+
|
|
467
|
+
/** The flat edit's shape table: every key required, each with its none. */
|
|
468
|
+
export const PLAN_EDIT_WIRE_SHAPE: ShapeEntry = {
|
|
469
|
+
op: `${OPS.map(op => `'${op}'`).join(' | ')} — which keys below are read`,
|
|
470
|
+
...Object.fromEntries(Object.entries(EDIT_WIRE).map(([k, spec]) => [k, `${spec.text} — ${noneText(spec.none)} when the op does not read it`])),
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
/** Per op, the wire keys it reads, in `PLAN_EDIT_SHAPE`'s words (`?` = may hold its none) — what the model reads. */
|
|
474
|
+
export function planEditWireOps(): Record<string, Record<string, string>> {
|
|
475
|
+
return Object.fromEntries(OPS.map(op => {
|
|
476
|
+
const entry = PLAN_EDIT_SHAPE[op as PlanEditOp];
|
|
477
|
+
const keys = PLAN_EDIT_WIRE_KEYS[op as PlanEditOp];
|
|
478
|
+
return [op, Object.fromEntries(Object.entries(keys).map(([planKey, wireKey]) => {
|
|
479
|
+
const optionalKey = `${planKey}?` in entry;
|
|
480
|
+
return [`${wireKey}${optionalKey ? '?' : ''}`, entry[optionalKey ? `${planKey}?` : planKey]];
|
|
481
|
+
}))];
|
|
482
|
+
}));
|
|
403
483
|
}
|
|
404
484
|
|
|
405
|
-
/**
|
|
485
|
+
/** Op keys no wire key carries, or wire keys that do not exist — always empty (a spec and the builder hold it). */
|
|
486
|
+
export function planEditWireGaps(): string[] {
|
|
487
|
+
const gaps: string[] = [];
|
|
488
|
+
for (const op of OPS) {
|
|
489
|
+
const keys = PLAN_EDIT_WIRE_KEYS[op as PlanEditOp] ?? {};
|
|
490
|
+
for (const planKey of shapeKeys(PLAN_EDIT_SHAPE[op as PlanEditOp])) {
|
|
491
|
+
if (planKey === 'op') continue;
|
|
492
|
+
if (!keys[planKey]) gaps.push(`${op}.${planKey}`);
|
|
493
|
+
else if (!EDIT_WIRE[keys[planKey]]) gaps.push(`${op}.${planKey} → ${keys[planKey]}`);
|
|
494
|
+
}
|
|
495
|
+
for (const planKey of Object.keys(keys)) if (!shapeKeys(PLAN_EDIT_SHAPE[op as PlanEditOp]).includes(planKey)) gaps.push(`${op}.${planKey} (not an op key)`);
|
|
496
|
+
}
|
|
497
|
+
return gaps;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** The format a sentence's answer is written in: the outcome, the flat edits, the reason. */
|
|
406
501
|
export function planEditsJsonSchema(): JsonSchema {
|
|
502
|
+
const gaps = planEditWireGaps();
|
|
503
|
+
if (gaps.length) throw new PlanWireShapeError(`the plan edits wire does not carry ${gaps.join(', ')}`);
|
|
504
|
+
const edit = jsonShapedObject(PLAN_EDIT_WIRE_SHAPE, {
|
|
505
|
+
op: jsonOneOf(OPS),
|
|
506
|
+
...Object.fromEntries(Object.entries(EDIT_WIRE).map(([k, spec]) => [k, spec.schema()])),
|
|
507
|
+
}, 'edit');
|
|
407
508
|
return jsonShapedObject(PLAN_EDITS_ANSWER_SHAPE, {
|
|
408
509
|
outcome: jsonOneOf(Object.values(PlanEditsOutcome)),
|
|
409
|
-
edits: jsonListOf(
|
|
510
|
+
edits: jsonListOf(edit),
|
|
410
511
|
reason: jsonText(),
|
|
411
512
|
}, 'planEdits');
|
|
412
513
|
}
|
|
413
514
|
|
|
414
|
-
/**
|
|
515
|
+
/** One flat edit → the op's edit: the keys it reads, a none left out where the op may go without it. Never repaired. */
|
|
516
|
+
export function expandWireEdit(wire: unknown): PlanEdit | unknown {
|
|
517
|
+
if (!isPlainObject(wire)) return wire;
|
|
518
|
+
const keys = PLAN_EDIT_WIRE_KEYS[wire['op'] as PlanEditOp];
|
|
519
|
+
if (!keys) return wire;
|
|
520
|
+
const entry = PLAN_EDIT_SHAPE[wire['op'] as PlanEditOp];
|
|
521
|
+
const out: PlainObject = Object.fromEntries(Object.entries(wire).filter(([k]) => k === 'op' || !(k in EDIT_WIRE)));
|
|
522
|
+
for (const [planKey, wireKey] of Object.entries(keys)) {
|
|
523
|
+
const raw = wire[wireKey];
|
|
524
|
+
if (`${planKey}?` in entry && isEditWireNone(wireKey, raw)) continue;
|
|
525
|
+
const value = EDIT_WIRE[wireKey].expand ? EDIT_WIRE[wireKey].expand!(raw) : raw;
|
|
526
|
+
if (value !== undefined) out[planKey] = value;
|
|
527
|
+
}
|
|
528
|
+
return out;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** An edit → the flat wire edit (what a model would write — fakes and specs write it). */
|
|
532
|
+
export function planEditToWire(edit: PlanEdit): PlainObject {
|
|
533
|
+
const out: PlainObject = { op: edit.op, ...Object.fromEntries(Object.entries(EDIT_WIRE).map(([k, spec]) => [k, Array.isArray(spec.none) ? [] : spec.none])) };
|
|
534
|
+
for (const [planKey, wireKey] of Object.entries(PLAN_EDIT_WIRE_KEYS[edit.op])) {
|
|
535
|
+
const value = (edit as unknown as PlainObject)[planKey];
|
|
536
|
+
if (value === undefined) continue;
|
|
537
|
+
out[wireKey] = EDIT_WIRE[wireKey].toWire ? EDIT_WIRE[wireKey].toWire!(value) : value;
|
|
538
|
+
}
|
|
539
|
+
return out;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** A model's answer → edits (each flat edit expanded); never repaired. */
|
|
415
543
|
export function planEditsFromWire(answer: unknown): PlanEditsAnswer | unknown {
|
|
416
544
|
if (!isPlainObject(answer) || !Array.isArray(answer['edits'])) return answer;
|
|
417
|
-
|
|
418
|
-
if (!isPlainObject(raw)) return raw;
|
|
419
|
-
const e: PlainObject = { ...raw };
|
|
420
|
-
if (isPlainObject(e['page']) && Array.isArray((e['page'] as PlainObject)['sections'])) {
|
|
421
|
-
e['page'] = { ...(e['page'] as PlainObject), sections: ((e['page'] as PlainObject)['sections'] as unknown[]).map(sectionFromWire) };
|
|
422
|
-
}
|
|
423
|
-
if (isPlainObject(e['section'])) e['section'] = sectionFromWire(e['section']);
|
|
424
|
-
if (isPlainObject(e['contentType'])) e['contentType'] = contentTypeFromWire(e['contentType']);
|
|
425
|
-
return e;
|
|
426
|
-
});
|
|
427
|
-
return { ...answer, edits };
|
|
545
|
+
return { ...answer, edits: (answer['edits'] as unknown[]).map(expandWireEdit) };
|
|
428
546
|
}
|