openuispec 0.1.32 → 0.1.34

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/cli/init.ts CHANGED
@@ -347,7 +347,9 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
347
347
  type: scroll_vertical
348
348
  \`\`\`
349
349
  4. **Extract tokens** — scan for colors, fonts, spacing and create files in \`${specDir}/tokens/\`.
350
- 5. **Update the manifest** — fill in \`data_model\`, \`api.endpoints\`, and \`generation.code_roots.backend\` in \`${specDir}/openuispec.yaml\`.
350
+ 5. **Create contract extensions** — define visual variants for the 7 built-in contracts (action_trigger, data_display, input_field, collection, nav_container, feedback, surface) in \`${specDir}/contracts/\`. These encode the design system's visual identity (shapes, token overrides, platform mappings). Read \`schema/contract.schema.json\` and examples in the package for the format.
351
+ 6. **Create locale files** — for each locale in \`i18n.supported_locales\`, create \`${specDir}/locales/<locale>.json\` with all \`$t:\` keys used in screens and flows.
352
+ 7. **Update the manifest** — fill in \`data_model\`, \`api.endpoints\` in \`${specDir}/openuispec.yaml\`.
351
353
 
352
354
  ## OpenUISpec Source Of Truth
353
355
 
@@ -762,10 +764,14 @@ Done! Your spec project is ready at ./${answers.specDir}/
762
764
 
763
765
  Getting started (new project):
764
766
  1. Edit ${answers.specDir}/openuispec.yaml — define your data model and API
765
- 2. Create screens in ${answers.specDir}/screens/ (one YAML per screen)
766
- 3. Create flows in ${answers.specDir}/flows/ (multi-step navigation)
767
- 4. Ask AI to generate native code from the spec
768
- 5. Run \`openuispec drift --snapshot --target ${answers.targets[0]}\` to baseline the first accepted target state after that target output directory exists
767
+ 2. Create tokens in ${answers.specDir}/tokens/ (colors, typography, spacing, etc.)
768
+ 3. Create contract extensions in ${answers.specDir}/contracts/ (visual variants for the 7 built-in contracts)
769
+ 4. Create screens in ${answers.specDir}/screens/ (one YAML per screen)
770
+ 5. Create flows in ${answers.specDir}/flows/ (multi-step navigation)
771
+ 6. Create locale files in ${answers.specDir}/locales/ (one JSON per supported locale)
772
+ 7. Run \`openuispec validate\` and \`openuispec validate semantic\` to check everything
773
+ 8. Ask AI to generate native code from the spec
774
+ 9. Run \`openuispec drift --snapshot --target ${answers.targets[0]}\` to baseline the first accepted target state after that target output directory exists
769
775
 
770
776
  Getting started (existing project):
771
777
  1. Ask AI to read your existing UI code and generate spec files:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A semantic UI specification format for AI-native, platform-native app development",
package/prepare/index.ts CHANGED
@@ -1050,14 +1050,13 @@ function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult
1050
1050
  const codeRoots = suggestCodeRoots(target, outputDir);
1051
1051
  const missingDecisions = missingPlatformDecisions(target, platformDef);
1052
1052
  const backendRoot = resolveBackendRoot(projectDir, manifest);
1053
- const backendContextRequired = hasApiEndpoints(manifest);
1054
- const backendContextReady = !backendContextRequired || (backendRoot !== null && existsSync(backendRoot));
1053
+ const backendContextReady = true; // backend is optional — not a generation blocker
1055
1054
  const pendingUserConfirmation = platformConfig.stack_confirmation.requires_user_confirmation;
1056
1055
 
1057
1056
  const nextSteps = [
1058
- ...(!backendContextReady
1057
+ ...(hasApiEndpoints(manifest) && (backendRoot === null || !existsSync(backendRoot))
1059
1058
  ? [
1060
- "Set `generation.code_roots.backend` in openuispec.yaml to the backend folder used to implement the declared API endpoints.",
1059
+ "Optional: set `generation.code_roots.backend` in openuispec.yaml to help AI locate your backend code.",
1061
1060
  ]
1062
1061
  : []),
1063
1062
  ...(pendingUserConfirmation
@@ -17,6 +17,7 @@ export interface Includes {
17
17
  export interface UsageLint {
18
18
  path: string;
19
19
  message: string;
20
+ severity?: "error" | "warning";
20
21
  }
21
22
 
22
23
  interface SemanticContext {
@@ -545,27 +546,18 @@ function lintLocaleCoverage(context: SemanticContext): UsageLint[] {
545
546
  function lintManifestGenerationContext(projectDir: string, manifest: unknown): UsageLint[] {
546
547
  if (!isRecord(manifest)) return [];
547
548
 
548
- const hasApiEndpoints =
549
- isRecord(manifest.api) &&
550
- isRecord(manifest.api.endpoints) &&
551
- Object.keys(manifest.api.endpoints).length > 0;
552
- if (!hasApiEndpoints) return [];
553
-
549
+ // code_roots.backend is optional — it's a hint for AI generation, not a hard requirement
554
550
  const generation = isRecord(manifest.generation) ? manifest.generation : {};
555
551
  const codeRoots = isRecord(generation.code_roots) ? generation.code_roots : null;
556
552
  const backendRoot = codeRoots && typeof codeRoots.backend === "string" ? codeRoots.backend.trim() : "";
557
553
 
558
- if (!backendRoot) {
559
- return [{
560
- path: "openuispec.yaml",
561
- message: 'api endpoints require generation.code_roots.backend to point at the backend folder',
562
- }];
563
- }
554
+ if (!backendRoot) return [];
564
555
 
565
556
  const resolvedBackendRoot = resolve(projectDir, backendRoot);
566
557
  if (!existsSync(resolvedBackendRoot)) {
567
558
  return [{
568
559
  path: "openuispec.yaml",
560
+ severity: "warning",
569
561
  message: `generation.code_roots.backend points to a missing folder: ${backendRoot}`,
570
562
  }];
571
563
  }
@@ -577,14 +569,24 @@ function printSemanticErrors(label: string, errors: UsageLint[]): number {
577
569
  if (errors.length === 0) return 0;
578
570
  const previewLimit = 10;
579
571
 
580
- console.log(` FAIL ${label} (${errors.length} semantic error(s))`);
581
- for (const error of errors.slice(0, previewLimit)) {
582
- console.log(` [${error.path}] ${error.message}`);
572
+ const realErrors = errors.filter((e) => e.severity !== "warning");
573
+ const warnings = errors.filter((e) => e.severity === "warning");
574
+
575
+ if (realErrors.length > 0) {
576
+ console.log(` FAIL ${label} (${realErrors.length} semantic error(s))`);
577
+ for (const error of realErrors.slice(0, previewLimit)) {
578
+ console.log(` [${error.path}] ${error.message}`);
579
+ }
580
+ if (realErrors.length > previewLimit) {
581
+ console.log(` ... and ${realErrors.length - previewLimit} more`);
582
+ }
583
583
  }
584
- if (errors.length > previewLimit) {
585
- console.log(` ... and ${errors.length - previewLimit} more`);
584
+
585
+ for (const warning of warnings) {
586
+ console.log(` WARN ${label}: ${warning.message}`);
586
587
  }
587
- return errors.length;
588
+
589
+ return realErrors.length;
588
590
  }
589
591
 
590
592
  export function collectSemanticLint(projectDir: string, includes: Includes): UsageLint[] {