@ogygie/generate 0.22.0 → 0.24.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.
@@ -0,0 +1,9 @@
1
+ import type { ExtensionContribution } from "../emit/screen/extensions.js";
2
+ import type { CommandOutput } from "./output.js";
3
+ import { type GenerateProject } from "./project.js";
4
+ /** Every screen of the map that mounts on a point, with what the registry needs of it. */
5
+ export declare function mountedScreensOf(project: GenerateProject): Array<ExtensionContribution & {
6
+ point: string;
7
+ }>;
8
+ /** Writes the registry of the given points (every declared point when `pointIds` is undefined); says what moved. Code 1 when a point names no front app. */
9
+ export declare function writeExtensionRegistries(project: GenerateProject, out: CommandOutput, pointIds?: readonly string[]): number;
package/dist/cli/run.js CHANGED
@@ -35,8 +35,9 @@ import {
35
35
  tableNameOf,
36
36
  targetKindOf,
37
37
  throwOgygieError,
38
- verifyGeneration
39
- } from "../chunk-ASXSTGY5.js";
38
+ verifyGeneration,
39
+ writeExtensionRegistries
40
+ } from "../chunk-ZPRW6QDW.js";
40
41
 
41
42
  // src/cli/check-command.ts
42
43
  import { existsSync, readFileSync, readdirSync } from "node:fs";
@@ -256,26 +257,29 @@ function generatedFeatureIdsOf(project) {
256
257
  async function runGenerateAllCommand(project, out) {
257
258
  const ids = generatedFeatureIdsOf(project);
258
259
  if (ids.length === 0) {
260
+ const registries2 = writeExtensionRegistries(project, out);
259
261
  out.log("ogygie generate --all \u2014 aucune spec de la carte ne porte de bloc # G\xE9n\xE9ration, rien \xE0 r\xE9g\xE9n\xE9rer.");
260
- return 0;
262
+ return registries2;
261
263
  }
262
264
  const refused = [];
263
265
  let current = project;
264
266
  for (const [index, id] of ids.entries()) {
265
267
  if (index > 0) {
266
- const reloaded = loadGenerateProject(project.root);
267
- if (!reloaded.ok) {
268
- for (const problem of reloaded.problems) out.error(formatProblem(problem));
268
+ const reloaded2 = loadGenerateProject(project.root);
269
+ if (!reloaded2.ok) {
270
+ for (const problem of reloaded2.problems) out.error(formatProblem(problem));
269
271
  out.error(`ogygie generate --all \u2014 le projet ne se relit plus apr\xE8s ${ids[index - 1]}, arr\xEAt.`);
270
272
  return 1;
271
273
  }
272
- current = reloaded.project;
274
+ current = reloaded2.project;
273
275
  }
274
276
  const code = await runGenerateFeatureCommand(current, id, out);
275
277
  if (code !== 0) refused.push(id);
276
278
  }
279
+ const reloaded = loadGenerateProject(project.root);
280
+ const registries = reloaded.ok ? writeExtensionRegistries(reloaded.project, out) : 1;
277
281
  out.log(`ogygie generate --all \u2014 ${ids.length} spec(s) g\xE9n\xE9r\xE9e(s) de la carte : ${ids.length - refused.length} r\xE9g\xE9n\xE9r\xE9e(s)${refused.length === 0 ? "" : `, ${refused.length} refus\xE9e(s) (${refused.join(", ")})`}.`);
278
- return refused.length === 0 ? 0 : 1;
282
+ return refused.length === 0 && registries === 0 ? 0 : 1;
279
283
  }
280
284
  async function runGenerateFeatureCommand(project, featureId, out) {
281
285
  if (featureId === void 0 || featureId === "") {
@@ -953,7 +957,8 @@ async function runVerifyCommand(project, out) {
953
957
  ...project.apiApp === void 0 ? {} : { apiAppPath: project.apiApp.path },
954
958
  ...project.manifest.tenancy === void 0 ? {} : { tenantColumn: project.manifest.tenancy.column },
955
959
  library: loadHolesLibrary(),
956
- loadConsumed: (block) => loadConsumedFeatures(project, block)
960
+ loadConsumed: (block) => loadConsumedFeatures(project, block),
961
+ frontApps: project.manifest.apps.filter((app) => app.type === "front").map((app) => ({ name: app.name, path: app.path }))
957
962
  });
958
963
  const elapsedMs = Math.round(performance2.now() - started);
959
964
  if (!report.ok) {
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @intent Emits `content/<id>.gen.ts` of a screen (D56): one content list of
3
+ * the block — the MATERIAL of a rubric, written in the spec in the product's
4
+ * source language — as a typed constant the content view renders. Plain text
5
+ * and one optional link per entry, exactly as the spec wrote them: a change of
6
+ * wording is a change of the spec, regenerated, never an edit here.
7
+ */
8
+ import type { ContentListPlan, ScreenPlan } from "../screen-plan.js";
9
+ export declare function contentFilePath(list: ContentListPlan): string;
10
+ export declare function emitContentList(plan: ScreenPlan, list: ContentListPlan): string;
@@ -0,0 +1,18 @@
1
+ import type { ExtensionPoint } from "../../manifest-generation.schema.js";
2
+ export declare const EXTENSION_REGISTRY_FILE = "extensions.gen.ts";
3
+ export interface ExtensionContribution {
4
+ /** The mounted screen's feature id, its folder relative to the repo root, and the id of its contribution on the point. */
5
+ featureId: string;
6
+ folder: string;
7
+ mountId: string;
8
+ }
9
+ export interface ExtensionRegistryInput {
10
+ point: ExtensionPoint;
11
+ /** The host screen's folder, relative to the repo root (`<front app>/<point.folder>`). */
12
+ hostFolder: string;
13
+ contributions: readonly ExtensionContribution[];
14
+ generatorVersion: string;
15
+ }
16
+ /** Repo-relative path of a point's registry. */
17
+ export declare function extensionRegistryPath(appPath: string, point: ExtensionPoint): string;
18
+ export declare function emitExtensionRegistry(input: ExtensionRegistryInput): string;
@@ -1,6 +1,8 @@
1
- import type { CustomViewPlan, FormViewPlan, ListViewPlan, ScreenPlan } from "../screen-plan.js";
1
+ import type { ContentViewPlan, CustomViewPlan, FormViewPlan, ListViewPlan, ScreenPlan } from "../screen-plan.js";
2
2
  export declare function emitListView(plan: ScreenPlan, view: ListViewPlan): string;
3
3
  export declare function emitFormView(plan: ScreenPlan, view: FormViewPlan): string;
4
+ /** D56: a content view — a DisclosureList over the constant of its list; the link label is the one text of the view, translated here; no operation, no fetch. */
5
+ export declare function emitContentView(plan: ScreenPlan, view: ContentViewPlan): string;
4
6
  /** The props of a custom view: the received fields of the output, and one MutationLike per action. */
5
7
  export declare function customViewPropsLines(view: CustomViewPlan): string[];
6
8
  /** The signature line of a custom view — what the markers protect and compare. */
@@ -1,8 +1,8 @@
1
1
  import { type ConsumedRoute } from "../consumed-feature.js";
2
- import type { GuardDenial, GuardRegistry, FrontTarget } from "../manifest-generation.schema.js";
2
+ import type { GuardDenial, GuardRegistry, FrontTarget, ExtensionPoint } from "../manifest-generation.schema.js";
3
3
  import { type GenerationBlock, type HttpOperation } from "../generation-block.schema.js";
4
4
  import type { HttpOperationKind } from "../motif-catalog.js";
5
- import { type CustomView, type FormView, type ListView, type ScreenBlock, type ScreenHole } from "../screen-block.schema.js";
5
+ import { type CustomView, type FormView, type ListView, type ScreenBlock, type ScreenHole, type ContentEntry, type ContentView, isOperationView } from "../screen-block.schema.js";
6
6
  import type { ParsedScreenBehavior } from "../screen-grammar.js";
7
7
  import { type FieldModel, type SchemaModels } from "../zod-introspection.js";
8
8
  export type { ConsumedRoute };
@@ -128,7 +128,20 @@ export interface CustomViewPlan {
128
128
  /** The output overlaid by the hole's example — what its test renders. */
129
129
  exampleInput: unknown;
130
130
  }
131
- export type ViewPlan = ListViewPlan | FormViewPlan | CustomViewPlan;
131
+ /** A content list of the block (D56): what `content/<id>.gen.ts` exports. */
132
+ export interface ContentListPlan {
133
+ id: string;
134
+ /** `faqEntries` → `FAQ_ENTRIES`, the exported constant. */
135
+ constName: string;
136
+ entries: ContentEntry[];
137
+ }
138
+ export interface ContentViewPlan {
139
+ kind: "content";
140
+ id: string;
141
+ componentName: string;
142
+ list: ContentListPlan;
143
+ }
144
+ export type ViewPlan = ListViewPlan | FormViewPlan | CustomViewPlan | ContentViewPlan;
132
145
  export interface ScreenBehaviorPlan {
133
146
  raw: string;
134
147
  behavior: ParsedScreenBehavior;
@@ -151,8 +164,15 @@ export interface ScreenPlan {
151
164
  bodyName: string;
152
165
  propsName: string;
153
166
  routeFactoryName: string;
154
- /** The read the container's ScreenState waits for. */
155
- primary: ListViewPlan | CustomViewPlan;
167
+ /** The read the container's ScreenState waits for — null for a screen of content alone (D56): the container renders the body directly. */
168
+ primary: ListViewPlan | CustomViewPlan | null;
169
+ /** The content lists of the block, in order (D56). */
170
+ contentLists: ContentListPlan[];
171
+ /** D56: the extension point the screen mounts on, and the id of its contribution there — null when the screen has a route or mounts nowhere. */
172
+ mount: {
173
+ point: ExtensionPoint;
174
+ id: string;
175
+ } | null;
156
176
  operations: ScreenOperationPlan[];
157
177
  reads: ScreenOperationPlan[];
158
178
  writes: ScreenOperationPlan[];
@@ -183,5 +203,8 @@ export interface ScreenPlan {
183
203
  export declare function labelOf(fieldName: string): string;
184
204
  /** A distinct sample of a model at `variant`: uuids and dates differ by variant so two rows never share a key. */
185
205
  export declare function distinctSampleOf(model: FieldModel, variant: number): unknown;
206
+ /** `faqEntries` → `FAQ_ENTRIES`. */
207
+ export declare function contentConstNameOf(listId: string): string;
186
208
  export declare function buildScreenPlan(input: ScreenPlanInput): ScreenPlan;
187
- export type { CustomView, FormView, ListView };
209
+ export type { ContentView, CustomView, FormView, ListView };
210
+ export { isOperationView };
@@ -1273,7 +1273,7 @@ export type YamlBlockRead = {
1273
1273
  };
1274
1274
  /** Reads the YAML text of a `# Génération` block into a raw value — one refusal per YAML error, at its line. */
1275
1275
  export declare function readYamlBlock(yamlText: string, location: BlockLocation): YamlBlockRead;
1276
- /** D41: a raw block is a SCREEN block when it carries `consumes` — a feature block carries `contracts`; `check` refuses a block that carries both or neither. */
1276
+ /** D41: a raw block is a SCREEN block when it carries `consumes` or `views` (a content screen consumes nothing, D56) — a feature block carries `contracts`; `check` refuses a block that carries both or neither. */
1277
1277
  export declare function blockKindOf(raw: unknown): "feature" | "screen" | "both" | "neither";
1278
1278
  /** Reads the YAML text of the block, then validates it. */
1279
1279
  export declare function parseGenerationBlock(yamlText: string, location: BlockLocation): GenerationBlockParse;
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@ import {
3
3
  AlterStepSchema,
4
4
  BEHAVIOR_UNBOUND,
5
5
  BODY_BANS,
6
+ CONTENT_RENDERS,
7
+ CONTENT_STATES,
6
8
  CONTRACTS_SECTION_TITLE,
7
9
  CONTRACT_VALIDATION_FAILED,
8
10
  ConsumedFeatureSchema,
@@ -187,7 +189,7 @@ import {
187
189
  viewCandidateTextOf,
188
190
  viewExampleTestName,
189
191
  vocabularyOf
190
- } from "./chunk-ASXSTGY5.js";
192
+ } from "./chunk-ZPRW6QDW.js";
191
193
 
192
194
  // src/prompt-card.ts
193
195
  var phrase = (caseKey) => caseKey.replace(/^with:/u, "avec ").replace(/^without:/u, "sans ");
@@ -205,7 +207,13 @@ function targetLines(generation) {
205
207
  const lines = [`- base ${target.database}, tenancy ${target.tenancy}, tests ${target.tests}, colonnes en ${target.naming} ; code d'un contrat viol\xE9 : ${contractViolationCodeOf(target)} (\xE9cris CONTRACT_VALIDATION_FAILED dans les lignes, il d\xE9signe ce code).`];
206
208
  if (target.database === "mssql-raw") lines.push("- cette cible n'\xE9met ni `import` ni `appendOnlySupersedes` ni `allOrNothingImport` : lectures, create, update, setOrClear, delete et les quatre autres motifs.");
207
209
  if (generation.front === void 0) lines.push("- aucune cible `front` : pas de spec d'\xE9cran (bloc `consumes`), seulement des features API.");
208
- else lines.push(`- cible front ${generation.front.tests} : une spec d'\xE9cran est possible (bloc \`consumes\`).`);
210
+ else {
211
+ lines.push(`- cible front ${generation.front.tests} : une spec d'\xE9cran est possible (bloc \`consumes\`, ou \`views\` de contenu seules).`);
212
+ const points = generation.front.extensionPoints ?? [];
213
+ lines.push(
214
+ points.length === 0 ? "- aucun point d'extension d\xE9clar\xE9 : un \xE9cran nouveau a sa `route`, jamais `mounts`." : `- points d'extension o\xF9 un \xE9cran peut se MONTER au lieu d'avoir une route (\`mounts: { point, id }\`) : ${points.map((point) => `\`${point.id}\` (${point.kind}, \xE9cran h\xF4te \`${point.folder}\`)`).join(", ")} \u2014 un onglet de plus dans un \xE9cran existant se d\xE9clare ainsi.`
215
+ );
216
+ }
209
217
  return lines;
210
218
  }
211
219
  function generationGrammarCard(generation) {
@@ -222,7 +230,7 @@ function generationGrammarCard(generation) {
222
230
  `- \`motifs\` : ${motifs.join(", ")} \u2014 une instance par motif, et SEULEMENT si une op\xE9ration l'emploie (\`uses: <motif>\` sur l'op\xE9ration, sauf \`signedWrite\` qui s'applique \xE0 toutes les \xE9critures) ; sinon \`motifs: {}\`.`,
223
231
  `- \`holes\` : ${holeKinds.join(", ")} ; un trou \`validate\` = \`{ id, kind: validate, reads: [champs de l'entr\xE9e], error: CODE, effects: [] | [clock], suggestedFix: true, examples: { accept: {\u2026}, reject: {\u2026} } }\` ; un \`derive\` = \`{ id, kind: derive, reads, writes: <champ de la ressource>, error: CODE, effects: [], examples: { accept: [{ input, output }], reject: { input } } }\`. Les exemples sont OBLIGATOIRES ; une date d'exemple se rapporte au 2026-01-01 (horloge fig\xE9e des tests).`,
224
232
  "",
225
- `Bloc d'un \xC9CRAN \u2014 cl\xE9s ${SCREEN_BLOCK_KEYS.map((key) => `\`${key}\``).join(", ")} : \`consumes: [{ feature, operations: [ids] }]\` (features DE LA CARTE), \`views: [{ id, kind: list, operation: <lecture> } | { id, kind: form, operation: <\xE9criture>, fields: [champs] } | { id, kind: custom, operation, hole }]\`, \`holes: [{ id, receives: [champs de la sortie], actions: [\xE9critures], examples: { input, renders: ["texte"] } }]\`, \`route?: /chemin\`.`,
233
+ `Bloc d'un \xC9CRAN \u2014 cl\xE9s ${SCREEN_BLOCK_KEYS.map((key) => `\`${key}\``).join(", ")} : \`consumes: [{ feature, operations: [ids] }]\` (features DE LA CARTE ; vide ou absent quand toutes les vues sont de contenu), \`views: [{ id, kind: list, operation: <lecture> } | { id, kind: form, operation: <\xE9criture>, fields: [champs] } | { id, kind: custom, operation, hole } | { id, kind: content, content: <id d'une liste> }]\`, \`holes: [{ id, receives: [champs de la sortie], actions: [\xE9critures], examples: { input, renders: ["texte"] } }]\`, \`content: [{ id: camelCase, entries: [{ id: kebab-case, title, body, href? }] }]\` (la MATI\xC8RE d'une rubrique \u2014 une aide, une FAQ : du texte brut \xE9crit ici m\xEAme, dans la langue source du produit, un lien au plus par entr\xE9e, jamais un libell\xE9 d'interface), et UNE des deux : \`route?: /chemin\` (une page) ou \`mounts?: { point, id }\` (un emplacement d'un \xE9cran existant, parmi les points d'extension de la cible ci-dessous).`,
226
234
  "",
227
235
  "Les LIGNES de comportement (une par puce, deux lignes au plus, recoll\xE9es) :",
228
236
  "`- \xC9TANT DONN\xC9 <op\xE9ration> avec <cas>[, (avec|sans) <cas>]* ALORS <statut> [<CODE>] [et <attendu>[, <attendu>]*] [[hole:<id>]]`",
@@ -231,7 +239,7 @@ function generationGrammarCard(generation) {
231
239
  ...guardLines(generation),
232
240
  "- statut : le code HTTP (200 pour une lecture, 201 pour create, 200 pour update) ou le refus (400 avec le code du trou ou CONTRACT_VALIDATION_FAILED, 401/403 du guard, 404 du notFound, 409 d'un motif).",
233
241
  "- attendus : `<collection> = lignes`, `<collection> = []`, `ordre <champ> asc|desc`, `updated.<champ> = cl\xE9 vis\xE9e`, `champs envoy\xE9s \xE9crits`, `liste \xE0 jour`, `suggestedFix`, `rien n'est \xE9crit`, `<writes> d\xE9riv\xE9` ; TOUT mot hors de cette liste \u2014 une valeur qu'un trou calcule (`created.<champ d\xE9riv\xE9> = \u2026`), un refus qu'un trou d\xE9cide \u2014 nomme son trou en fin de ligne : `[hole:<id>]`, sinon la ligne est refus\xE9e (OGYGIE_BEHAVIOR_UNBOUND).",
234
- `- \xE9cran : \`- \xC9TANT DONN\xC9 <vue|\xE9cran> avec <\xE9tat> ALORS <rendu>\` ; \xE9tats ${[...READ_STATES, ...FORM_STATES].map((key) => `\xAB ${phrase(key)} \xBB`).join(", ")} ; rendus ${[...READ_RENDERS, ...FORM_RENDERS].map((render) => `\xAB ${render} \xBB`).join(", ")}.`,
242
+ `- \xE9cran : \`- \xC9TANT DONN\xC9 <vue|\xE9cran> avec <\xE9tat> ALORS <rendu>\` ; \xE9tats ${[...READ_STATES, ...FORM_STATES].map((key) => `\xAB ${phrase(key)} \xBB`).join(", ")} ; rendus ${[...READ_RENDERS, ...FORM_RENDERS].map((render) => `\xAB ${render} \xBB`).join(", ")} ; une vue \`content\` : \xE9tat ${CONTENT_STATES.map((key) => `\xAB ${phrase(key)} \xBB`).join(", ")}, rendus ${CONTENT_RENDERS.map((render) => `\xAB ${render} \xBB`).join(", ")}, \xAB ancre <id d'entr\xE9e> \xBB.`,
235
243
  "",
236
244
  "Cible du produit :",
237
245
  ...targetLines(generation),
@@ -95,8 +95,15 @@ export declare const FrontTargetSchema: z.ZodObject<{
95
95
  export: z.ZodString;
96
96
  }, z.core.$strict>;
97
97
  stylesheet: z.ZodOptional<z.ZodString>;
98
+ extensionPoints: z.ZodOptional<z.ZodArray<z.ZodObject<{
99
+ id: z.ZodString;
100
+ kind: z.ZodLiteral<"tab">;
101
+ folder: z.ZodString;
102
+ app: z.ZodOptional<z.ZodString>;
103
+ }, z.core.$strict>>>;
98
104
  }, z.core.$strict>;
99
105
  export type FrontTarget = z.infer<typeof FrontTargetSchema>;
106
+ export type ExtensionPoint = NonNullable<FrontTarget["extensionPoints"]>[number];
100
107
  export declare const GuardDenialSchema: z.ZodObject<{
101
108
  status: z.ZodNumber;
102
109
  code: z.ZodString;
@@ -221,6 +228,12 @@ export declare const ManifestGenerationSchema: z.ZodObject<{
221
228
  export: z.ZodString;
222
229
  }, z.core.$strict>;
223
230
  stylesheet: z.ZodOptional<z.ZodString>;
231
+ extensionPoints: z.ZodOptional<z.ZodArray<z.ZodObject<{
232
+ id: z.ZodString;
233
+ kind: z.ZodLiteral<"tab">;
234
+ folder: z.ZodString;
235
+ app: z.ZodOptional<z.ZodString>;
236
+ }, z.core.$strict>>>;
224
237
  }, z.core.$strict>>;
225
238
  }, z.core.$strict>;
226
239
  export type ManifestGeneration = z.infer<typeof ManifestGenerationSchema>;
@@ -2,9 +2,12 @@
2
2
  * @intent Zod schema AND parser of the `# Génération` block of a SCREEN spec
3
3
  * (DECISIONS.md D41): one fenced YAML block, three keys — `consumes` (the API
4
4
  * features and the operations the screen uses), `views` (`list`, `form`,
5
- * `custom`, each on ONE operation) and `holes` (the custom views: what they
6
- * receive, which writes they may trigger, what their render must show) — plus
7
- * an OPTIONAL `route`, the path the screen mounts on. What the shape alone
5
+ * `custom`, each on ONE operation, or `content`, on a content list) and
6
+ * `holes` (the custom views: what they receive, which writes they may trigger,
7
+ * what their render must show) — plus an OPTIONAL `route`, the path the screen
8
+ * mounts on, and, since D56, `content` (lists of entries, the material of a
9
+ * rubric) and `mounts` (an extension point of a host screen, exclusive of
10
+ * `route`). A screen whose every view is content consumes nothing. What the shape alone
8
11
  * cannot check — do the operations exist in the consumed blocks, are the
9
12
  * fields of the output or the input — is `screen-checks.ts`, which needs the
10
13
  * consumed features; this module checks the block against itself and refuses
@@ -14,7 +17,7 @@
14
17
  import { z } from "zod";
15
18
  import type { OgygieError } from "@ogygie/core/errors";
16
19
  import { type BlockLocation } from "./generation-block.schema.js";
17
- export declare const SCREEN_BLOCK_KEYS: readonly ["route", "consumes", "views", "holes"];
20
+ export declare const SCREEN_BLOCK_KEYS: readonly ["route", "mounts", "consumes", "content", "views", "holes"];
18
21
  /** A feature id of the map, kebab-case (French ids allowed, as everywhere). */
19
22
  export declare const FeatureIdSchema: z.ZodString;
20
23
  /** A front path: `/`, `/rates`, `/settings/archetypes` — no parameter in M1. */
@@ -50,6 +53,12 @@ export declare const CustomViewSchema: z.ZodObject<{
50
53
  id: z.ZodString;
51
54
  operation: z.ZodString;
52
55
  }, z.core.$strict>;
56
+ /** A view of CONTENT (D56): a DisclosureList over one of the block's content lists — no operation. */
57
+ export declare const ContentViewSchema: z.ZodObject<{
58
+ id: z.ZodString;
59
+ kind: z.ZodLiteral<"content">;
60
+ content: z.ZodString;
61
+ }, z.core.$strict>;
53
62
  export declare const ViewSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
54
63
  kind: z.ZodLiteral<"list">;
55
64
  fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -70,6 +79,10 @@ export declare const ViewSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
70
79
  hole: z.ZodString;
71
80
  id: z.ZodString;
72
81
  operation: z.ZodString;
82
+ }, z.core.$strict>, z.ZodObject<{
83
+ id: z.ZodString;
84
+ kind: z.ZodLiteral<"content">;
85
+ content: z.ZodString;
73
86
  }, z.core.$strict>], "kind">;
74
87
  export type View = z.infer<typeof ViewSchema>;
75
88
  export type ListView = Extract<View, {
@@ -81,6 +94,38 @@ export type FormView = Extract<View, {
81
94
  export type CustomView = Extract<View, {
82
95
  kind: "custom";
83
96
  }>;
97
+ export type ContentView = Extract<View, {
98
+ kind: "content";
99
+ }>;
100
+ /** A view bound to an operation — every kind but content. */
101
+ export type OperationView = ListView | FormView | CustomView;
102
+ export declare const isOperationView: (view: View) => view is OperationView;
103
+ /** An entry of a content list (D56): plain text in the product's source language, one optional link; the id anchors it. */
104
+ export declare const ContentEntrySchema: z.ZodObject<{
105
+ id: z.ZodString;
106
+ title: z.ZodString;
107
+ body: z.ZodString;
108
+ href: z.ZodOptional<z.ZodString>;
109
+ }, z.core.$strict>;
110
+ export type ContentEntry = z.infer<typeof ContentEntrySchema>;
111
+ export declare const ContentListSchema: z.ZodObject<{
112
+ id: z.ZodString;
113
+ entries: z.ZodArray<z.ZodObject<{
114
+ id: z.ZodString;
115
+ title: z.ZodString;
116
+ body: z.ZodString;
117
+ href: z.ZodOptional<z.ZodString>;
118
+ }, z.core.$strict>>;
119
+ }, z.core.$strict>;
120
+ export type ContentList = z.infer<typeof ContentListSchema>;
121
+ /** The id of an extension point, as the manifest's front target declares it: `fiche-societe.tabs`. */
122
+ export declare const ExtensionPointIdSchema: z.ZodString;
123
+ /** D56: where a screen mounts instead of a route — a point of a host screen, and the id of this contribution there. */
124
+ export declare const MountSchema: z.ZodObject<{
125
+ point: z.ZodString;
126
+ id: z.ZodString;
127
+ }, z.core.$strict>;
128
+ export type Mount = z.infer<typeof MountSchema>;
84
129
  export declare const ScreenHoleSchema: z.ZodObject<{
85
130
  id: z.ZodString;
86
131
  receives: z.ZodArray<z.ZodString>;
@@ -93,10 +138,23 @@ export declare const ScreenHoleSchema: z.ZodObject<{
93
138
  export type ScreenHole = z.infer<typeof ScreenHoleSchema>;
94
139
  export declare const ScreenBlockSchema: z.ZodObject<{
95
140
  route: z.ZodOptional<z.ZodString>;
96
- consumes: z.ZodArray<z.ZodObject<{
141
+ mounts: z.ZodOptional<z.ZodObject<{
142
+ point: z.ZodString;
143
+ id: z.ZodString;
144
+ }, z.core.$strict>>;
145
+ consumes: z.ZodDefault<z.ZodArray<z.ZodObject<{
97
146
  feature: z.ZodString;
98
147
  operations: z.ZodArray<z.ZodString>;
99
- }, z.core.$strict>>;
148
+ }, z.core.$strict>>>;
149
+ content: z.ZodDefault<z.ZodArray<z.ZodObject<{
150
+ id: z.ZodString;
151
+ entries: z.ZodArray<z.ZodObject<{
152
+ id: z.ZodString;
153
+ title: z.ZodString;
154
+ body: z.ZodString;
155
+ href: z.ZodOptional<z.ZodString>;
156
+ }, z.core.$strict>>;
157
+ }, z.core.$strict>>>;
100
158
  views: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
101
159
  kind: z.ZodLiteral<"list">;
102
160
  fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -117,6 +175,10 @@ export declare const ScreenBlockSchema: z.ZodObject<{
117
175
  hole: z.ZodString;
118
176
  id: z.ZodString;
119
177
  operation: z.ZodString;
178
+ }, z.core.$strict>, z.ZodObject<{
179
+ id: z.ZodString;
180
+ kind: z.ZodLiteral<"content">;
181
+ content: z.ZodString;
120
182
  }, z.core.$strict>], "kind">>;
121
183
  holes: z.ZodDefault<z.ZodArray<z.ZodObject<{
122
184
  id: z.ZodString;
@@ -142,5 +204,5 @@ export declare function validateScreenBlock(raw: unknown, location: BlockLocatio
142
204
  export declare function parseScreenBlock(yamlText: string, location: BlockLocation): ScreenBlockParse;
143
205
  /** The consumed feature an operation belongs to, or null when the block does not consume it. */
144
206
  export declare function consumerOf(block: ScreenBlock, operationId: string): ConsumedFeature | null;
145
- /** The read whose output the screen's ScreenState waits for: the first list or custom view (D40). */
207
+ /** The read whose output the screen's ScreenState waits for: the first list or custom view (D40) — a content view (D56) waits for nothing. */
146
208
  export declare function primaryViewOf(block: ScreenBlock): ListView | CustomView | null;
@@ -12,7 +12,8 @@
12
12
  import { type OgygieError } from "@ogygie/core/errors";
13
13
  import { type ConsumedRoute } from "./consumed-feature.js";
14
14
  import { type GenerationBlock } from "./generation-block.schema.js";
15
- import type { ScreenBlock } from "./screen-block.schema.js";
15
+ import type { FrontTarget } from "./manifest-generation.schema.js";
16
+ import { type ScreenBlock } from "./screen-block.schema.js";
16
17
  import type { FieldModel, SchemaModels } from "./zod-introspection.js";
17
18
  export interface ConsumedFeatureCheckInput {
18
19
  featureId: string;
@@ -28,6 +29,10 @@ export interface ScreenCheckInput {
28
29
  specPath: string;
29
30
  /** The consumed features, as the project resolved them — absent when the map does not know the feature. */
30
31
  consumed: ReadonlyMap<string, ConsumedFeatureCheckInput>;
32
+ /** The front target of the manifest, when known: its extension points are what `mounts.point` may name (D56). */
33
+ front?: FrontTarget | null;
34
+ /** Only the mount is checked (the consumed features were not resolved). */
35
+ mountOnly?: boolean;
31
36
  }
32
37
  /** The element fields of a read output — the one array of objects it carries. */
33
38
  export declare function elementFieldsOf(outputModel: FieldModel | undefined): string[] | null;
@@ -6,8 +6,10 @@
6
6
  * the error banner, the access explanation, the empty state, the rows, their
7
7
  * order; a `form` view speaks of a valid or invalid form, of a refused write,
8
8
  * and renders the write it triggers, the error sentences, or nothing written.
9
- * A line whose every word is in here is derived without a model; a word
10
- * outside it needs a hole — a custom view.
9
+ * A `content` view (D56) speaks of its one state — with content — and renders
10
+ * its entries, folded, each under its anchor. A line whose every word is in
11
+ * here is derived without a model; a word outside it needs a hole — a custom
12
+ * view.
11
13
  */
12
14
  import { type OgygieError } from "@ogygie/core/errors";
13
15
  import { type ScreenParseResult } from "./screen-grammar.js";
@@ -21,6 +23,8 @@ export declare const READ_STATES: readonly ["with:chargement", "with:erreur", "w
21
23
  export declare const READ_RENDERS: readonly ["squelette", "bandeau d'erreur", "explication d'accès", "état vide", "lignes"];
22
24
  export declare const FORM_STATES: readonly ["with:formulaire valide", "with:formulaire invalide", "with:écriture refusée"];
23
25
  export declare const FORM_RENDERS: readonly ["phrases d'erreur", "rien n'est écrit", "bandeau d'erreur"];
26
+ export declare const CONTENT_STATES: readonly ["with:contenu"];
27
+ export declare const CONTENT_RENDERS: readonly ["entrées", "repliées"];
24
28
  export declare function screenCaseKey(polarity: "with" | "without", phrase: string): string;
25
29
  /** What a line may say of a view — the whole screen speaks like its primary read view. */
26
30
  export declare function screenVocabularyOf(view: View | null, block: ScreenBlock, sortableFields?: readonly string[]): ScreenVocabulary;
@@ -6,10 +6,14 @@ import type { HoleLibraryEntry } from "./hole-shape.js";
6
6
  import { type ManifestGeneration } from "./manifest-generation.schema.js";
7
7
  import { type DeclaredManifestEntry } from "./emit/manifest-entry.js";
8
8
  import type { SchemaModels } from "./zod-introspection.js";
9
+ import { type ExtensionContribution } from "./emit/screen/extensions.js";
10
+ import type { GeneratedFile } from "./generate-feature.js";
9
11
  import type { ScreenBlock } from "./screen-block.schema.js";
10
12
  import type { ConsumedFeatureCheckInput } from "./screen-checks.js";
11
13
  import { type ParsedScreenBehavior } from "./screen-grammar.js";
12
14
  export declare const GENERATED_DRIFT = "OGYGIE_GENERATED_DRIFT";
15
+ /** D56: an extension point declared at the manifest whose host folder never imports its registry. */
16
+ export declare const EXTENSION_POINT_UNWIRED = "OGYGIE_EXTENSION_POINT_UNWIRED";
13
17
  /** The version every emitted header carries — the package's own, so a bump of the lockstep is a drift every product regenerates (D27). */
14
18
  export declare const GENERATOR_VERSION: string;
15
19
  /**
@@ -22,6 +26,8 @@ export declare function normalizeForComparison(text: string): string;
22
26
  export declare const GENERATED_HEADER: RegExp;
23
27
  /** A feature of the map that owns a spec: where its code lives. */
24
28
  export interface SpecOwnerFolder {
29
+ /** The feature id of the map entry (D56: a mounted screen contributes under it). */
30
+ featureId?: string;
25
31
  specPath: string;
26
32
  /** Folder of the feature, relative to the repo root. */
27
33
  folder: string;
@@ -53,7 +59,51 @@ export interface VerifyGenerationInput {
53
59
  apiAppPath?: string;
54
60
  /** D41: resolves the features a screen block consumes (their block, schemas and routes); absent, no screen is regenerated. */
55
61
  loadConsumed?: (block: ScreenBlock) => Promise<ReadonlyMap<string, LoadedConsumedForVerify>>;
62
+ /** D56: the front apps of the map (name → path), where the extension points' host folders live; absent, no registry is verified. */
63
+ frontApps?: ReadonlyArray<{
64
+ name: string;
65
+ path: string;
66
+ }>;
56
67
  }
68
+ /** D56: the repo-relative host folder of an extension point — the one front app, or the one the point names. */
69
+ export declare function extensionHostAppPathOf(point: {
70
+ app?: string | undefined;
71
+ }, frontApps: ReadonlyArray<{
72
+ name: string;
73
+ path: string;
74
+ }>): string | null;
75
+ /** D56: the registry of every declared point — one file each, empty when nothing mounts — from the screens that mount (their feature id, folder and mount id). */
76
+ export declare function extensionRegistriesOf(input: {
77
+ points: ReadonlyArray<{
78
+ id: string;
79
+ kind: "tab";
80
+ folder: string;
81
+ app?: string | undefined;
82
+ }>;
83
+ frontApps: ReadonlyArray<{
84
+ name: string;
85
+ path: string;
86
+ }>;
87
+ mounted: ReadonlyArray<ExtensionContribution & {
88
+ point: string;
89
+ }>;
90
+ generatorVersion: string;
91
+ }): Array<{
92
+ point: {
93
+ id: string;
94
+ kind: "tab";
95
+ folder: string;
96
+ app?: string | undefined;
97
+ };
98
+ hostFolder: string;
99
+ file: GeneratedFile;
100
+ } | {
101
+ point: {
102
+ id: string;
103
+ };
104
+ hostFolder: null;
105
+ file: null;
106
+ }>;
57
107
  export interface VerifiedSpec {
58
108
  specPath: string;
59
109
  generated: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogygie/generate",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Ogygie generator: reads the `# Génération` block of a spec and its schemas.ts, derives everything deterministic without a model (routes, pipeline, database, factories, tests, screens, previews), fills the typed holes of logic.ts by library, draw and escalation, and draws the first schemas.ts of a new feature — judged by the gates. Sixth package of the lockstep (D55).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@ogygie/core": "0.22.0",
32
+ "@ogygie/core": "0.24.0",
33
33
  "tsx": "^4.20.0",
34
34
  "yaml": "^2.9.0",
35
35
  "zod": "^4.0.0"
@@ -38,7 +38,7 @@
38
38
  "@lingui/babel-plugin-lingui-macro": "^5.9.5",
39
39
  "@lingui/core": "^5.9.5",
40
40
  "@lingui/react": "^5.9.5",
41
- "@ogygie/ui": "0.22.0",
41
+ "@ogygie/ui": "0.24.0",
42
42
  "@starter/api": "0.1.0",
43
43
  "@tanstack/react-query": "^5.102.2",
44
44
  "@tanstack/react-router": "^1.170.32",