From 0ae38a576171d3dfbfb43e6a7bf054ee3feafc18 Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Wed, 23 Sep 2026 00:21:13 -0500 Subject: [PATCH] Teach the new authored theme contract Plan 0.21 replaces the sole accepted 0.20 input identity and adds a browser-local Light, Dark, or System preference. Bundle its exact Service schema and the matching CLI 0.5.0 source so authoring and materialization use one compatible contract. Preserve the requested native clients and explain their automatic appearance through the reviewed preference-control gap. Keep dated receipts and the published catalog selection unchanged. --- README.md | 8 +-- RELEASING.md | 6 +- docs/README.md | 4 +- evals/README.md | 4 +- evals/create-full-stack-app/cases.json | 39 ++++++++++-- .../fixtures/analysis-failed-analysis.json | 6 +- .../fixtures/appearance-current-analysis.json | 14 ++--- .../appearance-current.foundation-plan.json | 2 +- .../application-intent-valid-analysis.json | 14 ++--- .../application-intent.foundation-plan.json | 2 +- .../fixtures/empty.foundation-plan.json | 2 +- .../first-preview-movies.foundation-plan.json | 2 +- .../fixtures/issues-found-analysis.json | 6 +- .../mixed-application-issues-analysis.json | 6 +- ...ed-application-issues.foundation-plan.json | 2 +- .../reconcile-habits.foundation-plan.json | 2 +- .../fixtures/recurring-issues-analysis.json | 6 +- .../recurring-issues.foundation-plan.json | 2 +- .../redirect-movies.foundation-plan.json | 2 +- .../reserved-constant.foundation-plan.json | 2 +- .../resume-current.foundation-plan.json | 2 +- .../schema-invalid.foundation-plan.txt | 2 +- .../fixtures/superseded-analysis.json | 6 +- .../supported-enum.foundation-plan.json | 2 +- .../supported-scalars.foundation-plan.json | 2 +- .../fixtures/theme-toggle-analysis.json | 62 +++++++++++++++++++ .../theme-toggle.foundation-plan.json | 49 +++++++++++++++ ...ed-field-capabilities.foundation-plan.json | 2 +- .../fixtures/unsupported-graph-analysis.json | 14 ++--- .../claude-plugin/.claude-plugin/plugin.json | 2 +- packages/claude-plugin/package.template.json | 2 +- release/compatibility.json | 12 ++-- script/check-cli-contract.mjs | 2 +- script/check-release-compatibility.mjs | 2 +- script/claude-plugin-boundaries.mjs | 4 +- script/cli-contract/config.mjs | 12 ++-- script/cli-contract/fixtures.mjs | 2 +- script/cli-contract/local-commands.mjs | 2 +- skills/create-full-stack-app/SKILL.md | 14 ++--- .../references/diagnostics-and-recovery.md | 8 +-- .../references/examples.md | 39 ++++++------ ....json => foundation-plan-0.21.schema.json} | 12 ++-- ...ion-plan-020.md => foundation-plan-021.md} | 59 +++++++++++------- .../references/modeling-guide.md | 23 ++++--- test/release-compatibility.test.mjs | 6 +- test/repository.test.mjs | 15 ++--- test/ui-foundation-authoring.test.mjs | 45 +++++++++++++- 47 files changed, 375 insertions(+), 158 deletions(-) create mode 100644 evals/create-full-stack-app/fixtures/theme-toggle-analysis.json create mode 100644 evals/create-full-stack-app/fixtures/theme-toggle.foundation-plan.json rename skills/create-full-stack-app/references/{foundation-plan-0.20.schema.json => foundation-plan-0.21.schema.json} (99%) rename skills/create-full-stack-app/references/{foundation-plan-020.md => foundation-plan-021.md} (93%) diff --git a/README.md b/README.md index 9e05c5b..89e261a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ canonical Skills and bundled CLI are packaged once for Claude Code and Codex. UI app's own design and components. The public catalog and Drawing Board pins determine what an installed workspace actually receives. -Source candidate `0.4.0` requires CLI `0.4.0`, Service API `0.4`, and Foundation Plan `0.20`. It preserves the planning +Source candidate `0.5.0` requires CLI `0.5.0`, Service API `0.5`, and Foundation Plan `0.21`. It preserves the planning workspace under `.firstdraft/design/` and defaults to current-folder local output. Use explicit `--github` for server Publication. Its identities belong in [release compatibility](release/compatibility.json). The [public catalog](.claude-plugin/marketplace.json) owns the @@ -22,7 +22,7 @@ local folder; a Drawing Board clone and GitHub push are unnecessary. The - the portable create-full-stack-app Skill and its task routing; - deferred UI extension and consistency-review Skill auditions; -- beginner-to-machine-reference authoring guidance for Foundation Plan 0.20; +- beginner-to-machine-reference authoring guidance for Foundation Plan 0.21; - the exact schema, examples, and review checklists packaged with the Skill; - behavioral evaluations for agent workflow changes; - shared Claude/Codex plugin assembly around the canonical Skills and reviewed CLI package; and @@ -39,8 +39,8 @@ behavior. This repository teaches an agent how to use those contracts without cr | Understand the installed workflow | [Skill entrypoint](skills/create-full-stack-app/SKILL.md) | | Continue or review an app's UI | The app's `UI.md` and shared components; [UI Skill status](#ui-continuation) | | Change Plan authoring guidance | [Skill entrypoint](skills/create-full-stack-app/SKILL.md), then [modeling guide](skills/create-full-stack-app/references/modeling-guide.md) | -| Check current Foundation Plan capability | [Foundation Plan reference](skills/create-full-stack-app/references/foundation-plan-020.md) | -| Inspect exact Plan structure | [Bundled schema](skills/create-full-stack-app/references/foundation-plan-0.20.schema.json) | +| Check current Foundation Plan capability | [Foundation Plan reference](skills/create-full-stack-app/references/foundation-plan-021.md) | +| Inspect exact Plan structure | [Bundled schema](skills/create-full-stack-app/references/foundation-plan-0.21.schema.json) | | Add or run behavioral evaluations | [Evaluation guide](evals/README.md) | | Inspect a dated observation | [Evidence archive](evidence/README.md) | | Prepare or promote a release | [Release runbook](RELEASING.md) | diff --git a/RELEASING.md b/RELEASING.md index 0e46a3a..4d6cc19 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,11 +8,15 @@ longer part of an ordinary release. Coordinate the service, CLI, and Skills thro [`release/compatibility.json`](release/compatibility.json) owns the candidate version, compatible CLI/API/Plan identities, and deterministic package SHA-256. The current source candidate is -`@firstdraft.com/claude-code@0.4.0` with CLI `0.4.0`, API `>= 0.4.0`, `< 0.5.0`, and Plan `sketch/0.20`. +`@firstdraft.com/claude-code@0.5.0` with CLI `0.5.0`, API `>= 0.5.0`, `< 0.6.0`, and Plan `sketch/0.21`. The [marketplace manifest](.claude-plugin/marketplace.json) independently selects a published plugin version; retain its selection until the intended new version is actually published. Source compatibility is not public catalog selection. Query npm when releasing rather than treating a dated distribution snapshot as current. +Plan `0.21` adds `appearance.theme: "toggle"` and replaces the sole accepted `0.20` input format. API, CLI, and +plugin `0.5.0` record that input and artifact compatibility break. Target `rails-sketch/2026-09`, local-output +defaults, and `.firstdraft/design/` stay the same. There is no retained-Project migration or compatibility bridge. + Use an ordinary pre-1.0 minor bump for a breaking compatibility change and a patch bump for a compatible change. Never reuse a published npm version, protected release tag, or marketplace version for different package bytes. An unpublished, unpromoted candidate may be revised at a new commit and digest without another version bump. diff --git a/docs/README.md b/docs/README.md index 03ed23e..ba67293 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,8 +13,8 @@ historical observations intentionally have different owners. | How do I repair an existing npm default? | [npm default repair](npm-promotion.md) | | What does the agent execute? | [`SKILL.md`](../skills/create-full-stack-app/SKILL.md) | | How does an agent extend or review generated UI? | The app's `UI.md`; [deferred Skill status](../README.md#ui-continuation) | -| What is exact Plan syntax? | [JSON Schema](../skills/create-full-stack-app/references/foundation-plan-0.20.schema.json) | -| What capability is currently described? | [Foundation Plan reference](../skills/create-full-stack-app/references/foundation-plan-020.md) | +| What is exact Plan syntax? | [JSON Schema](../skills/create-full-stack-app/references/foundation-plan-0.21.schema.json) | +| What capability is currently described? | [Foundation Plan reference](../skills/create-full-stack-app/references/foundation-plan-021.md) | | What happened in a particular run? | [`evidence/README.md`](../evidence/README.md), then one dated record | | What behavior should a fresh agent exhibit? | [`evals/README.md`](../evals/README.md), then one case | | Why do historical pins or limitations exist? | [Repository history](../evidence/repository-history.md) or [release history](../evidence/release-history.md) | diff --git a/evals/README.md b/evals/README.md index 2446772..ee5535a 100644 --- a/evals/README.md +++ b/evals/README.md @@ -4,7 +4,8 @@ fixtures are review inputs, not execution evidence. Each case declares whether the Skill should trigger and which artifacts are attached, staged into the project, or retained only as expected output. -Current authoring cases use Foundation Plan `firstdraft.foundation-plan.sketch/0.20` and target +The current Plan/analysis fixtures below are synthetic contract inputs, not new Service observations. +Current authoring cases use Foundation Plan `firstdraft.foundation-plan.sketch/0.21` and target `rails-sketch/2026-09`. The `appearance-current` Plan/analysis and `resume-current` Plan fixtures replace their older inputs for new evaluations. The original `appearance-issues` Plan/analysis and `resume` Plan retain the exact bytes declared by the [September 14 qualification receipt](../evidence/2026-09-14-ui-authoring-skill-0.2.4-qualification.json). @@ -87,6 +88,7 @@ or live transport of implementation notes through GitHub Publication. - `private-native-request-preserves-current-boundary` - `android-preview-respects-provider-limit` - `preserve-partially-realized-appearance-intent` +- `preserve-browser-theme-and-native-residual` - `correct-source-issue-alongside-capability-gap` - `analysis-failed-stop` - `standalone-status-binds-accepted-generation` diff --git a/evals/create-full-stack-app/cases.json b/evals/create-full-stack-app/cases.json index c3e54c6..465e001 100644 --- a/evals/create-full-stack-app/cases.json +++ b/evals/create-full-stack-app/cases.json @@ -502,7 +502,7 @@ "should_trigger": true, "prompt": "The staged Plan's domain, selected iPhone client, Movie icon and Field, and public-index Scaffold are intentional. A successful plan push was followed by plan status --wait, which printed the attached valid analysis. Review the result, but do not compile it.", "expectations": [ - "Branches on analysis.status valid and reports analyzer release foundation-plan-rails/application-2026-09-19-conventions, graph version 1, and the absence of diagnostics.", + "Branches on analysis.status valid and reports analyzer release foundation-plan-rails/application-2026-09-22-theme, graph version 1, and the absence of diagnostics.", "Reports the complete empty firstdraft.foundation-gaps/2 object and exact attached analysis.gap_set_sha256, which the CLI validated against the attached GapSet bytes, rather than omitting the reviewed support result; never substitutes a fixture, historical, or other Project's digest.", "Preserves the complete staged Plan unchanged, including its domain, native.ios request, semantic icon, required title Field, and exact public-index Scaffold.", "Recognizes that the prepared 2026-09 boundary admits this bounded web public-index and selected iPhone request, while analysis alone does not prove generated output.", @@ -557,7 +557,7 @@ "expectations": [ "Branches on analysis.status valid while explaining that valid applies to the admitted graph and Compilation is not proved.", "Reports the complete GapSet and the exact attached analysis.gap_set_sha256, which the CLI validated against the attached GapSet bytes; does not substitute a fixture, historical, or other Project's digest; and includes the partially_generated target_support_gap foundation_plan.gap.appearance.icon_assets.not_generated record at /application/appearance with its reason and consequence.", - "Distinguishes the attached historical analysis from current target behavior: the authored theme applies across clients, tint and background colors style native shells and Web icon branding, web components retain stock Zinc tokens, and the selected-iOS AppIcon remains stock.", + "Distinguishes the attached analysis from generated or runtime proof: the authored auto theme applies across clients, tint and background colors style native shells and Web icon branding, web components retain stock Zinc tokens, and the selected-iOS AppIcon remains stock.", "Preserves the complete Appearance request and every otherwise admitted Application and Entity member without editing, removing, or weakening them.", "Does not run plan push or plan status again, compile, or claim generated Appearance, web, or iPhone output." ], @@ -578,6 +578,35 @@ } ] }, + { + "id": "preserve-browser-theme-and-native-residual", + "should_trigger": true, + "prompt": "The staged Plan intentionally offers a Light/Dark/System selector in the browser and requests both iPhone and Android clients. The attached valid analysis came back from plan status --wait. Explain the default, persistence, and every gap before I decide whether to compile. Do not edit or submit the Plan.", + "expectations": [ + "Explains that appearance.theme toggle offers Light, Dark, and System in the browser, initially System, persists only a browser-local preference across visits, and follows operating-system changes only while System is selected.", + "Reports both ordered GapSet records and the exact attached analysis.gap_set_sha256, which the CLI validated against the attached GapSet bytes; does not substitute a fixture, historical, or other Project's digest.", + "Explains the partially_generated target_support_gap foundation_plan.gap.appearance.native_theme_preference.not_generated at /application/appearance/theme: emitted iOS and Android clients and their embedded Rails responses use automatic appearance; browser selector and stored browser preferences cannot control native shells, and no native preference UI or bridge is generated.", + "Reports the separate stock native launcher icon residual, while retaining the authored tint and generated Web icon meaning.", + "Preserves toggle and both requested native clients without changing the choice to auto, dropping a client, inventing preference synchronization, or promising native runtime qualification. Valid applies only to the admitted graph and the attached result does not prove Compilation or device behavior.", + "Leaves the complete staged Plan and subject UUIDs unchanged, leaves private state unopened, and does not run plan init, plan push, plan status, or plan compile." + ], + "artifacts": [ + { + "path": "evals/create-full-stack-app/fixtures/theme-toggle-analysis.json", + "role": "input" + }, + { + "path": "evals/create-full-stack-app/fixtures/theme-toggle.foundation-plan.json", + "role": "input", + "stage_as": ".firstdraft/foundation-plan.json" + }, + { + "path": "evals/create-full-stack-app/fixtures/state-placeholder.txt", + "role": "input", + "stage_as": ".firstdraft/state.json" + } + ] + }, { "id": "correct-source-issue-alongside-capability-gap", "should_trigger": true, @@ -819,7 +848,7 @@ "should_trigger": true, "prompt": "Use First Draft to create and send a Foundation Plan for Scalar Showcase, application key scalar_showcase. Model one Movie Entity with one Field of each currently importable scalar type, using a required short_text title as its Primary Descriptor and a decimal rating defaulting to 7.5. The compatible firstdraft CLI is installed, but no JSON Schema 2020-12 validator is available. Do not install or implement one.", "expectations": [ - "Uses Foundation Plan 0.20, the Modeling guide, and Examples for authoring without opening the complete bundled schema.", + "Uses Foundation Plan 0.21, the Modeling guide, and Examples for authoring without opening the complete bundled schema.", "Runs plan init with the provided application key and name, then runs generate uuid --count 11 through the Skill resolver exactly once for one Entity and ten Fields.", "Authors one Movie Entity with exactly one Field of each supported scalar type and a required movie.title Field Primary Descriptor.", "Authors movie.rating as a decimal with literal default value \"7.5\" as a canonical string, never the JSON number 7.5.", @@ -1239,7 +1268,7 @@ { "id": "compile-prepared-movie-catalog", "should_trigger": true, - "prompt": "I approve the exact pre-Compile semantic read-back and reviewed support result for the unchanged staged Movie Catalog with SHA-256 33b267693040b49b527cea77c4c60fcb490e035f9a52e55df5981d6c25a43dd8. Preserve its intentional Appearance request and publish that exact candidate to one private GitHub repository now.", + "prompt": "I approve the exact pre-Compile semantic read-back and reviewed support result for the unchanged staged Movie Catalog with SHA-256 025c7021ad31bdd64b01eb007632c000f96a84273d1a53ea41ab5f342d11aa3c. Preserve its intentional Appearance request and publish that exact candidate to one private GitHub repository now.", "expectations": [ "Requires the installed CLI to expose both plan compile completion modes and no public plan publish command.", "In the same continuing session, treats the prompt as unambiguous approval of the complete semantic model and reviewed nonempty GapSet, rereads the exact local Plan, verifies its SHA-256 and semantic model are unchanged, and does not ask for a second confirmation or repeat the unchanged read-back.", @@ -1265,7 +1294,7 @@ { "id": "compile-prepared-drawing-board-application", "should_trigger": true, - "prompt": "I approve the exact pre-Compile Drawing Board semantic read-back and reviewed support result for the unchanged staged Movie Catalog with SHA-256 33b267693040b49b527cea77c4c60fcb490e035f9a52e55df5981d6c25a43dd8. Materialize that exact candidate into the absent ./application directory. Do not create a GitHub repository.", + "prompt": "I approve the exact pre-Compile Drawing Board semantic read-back and reviewed support result for the unchanged staged Movie Catalog with SHA-256 025c7021ad31bdd64b01eb007632c000f96a84273d1a53ea41ab5f342d11aa3c. Materialize that exact candidate into the absent ./application directory. Do not create a GitHub repository.", "expectations": [ "Requires the installed CLI to expose plan compile --output as a direct local completion mode while retaining plan compile --github as the separate GitHub Publication mode.", "In the same continuing session as precompile-drawing-board-read-back, treats the prompt as unambiguous approval of the complete semantic model, reviewed nonempty GapSet, exact Plan bytes, and selected direct local mode; rereads the Plan, verifies its SHA-256 is unchanged, and asks for no second confirmation.", diff --git a/evals/create-full-stack-app/fixtures/analysis-failed-analysis.json b/evals/create-full-stack-app/fixtures/analysis-failed-analysis.json index 39a19bb..84d46bb 100644 --- a/evals/create-full-stack-app/fixtures/analysis-failed-analysis.json +++ b/evals/create-full-stack-app/fixtures/analysis-failed-analysis.json @@ -6,9 +6,9 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000902", "graph_version": 1, - "head_source_sha256": "9cb632700a7bec3cbd8ca0492581237d8f3061e99cd1c5f148917d5eef60f714", - "analyzer_release": "foundation-plan-rails/application-2026-09-19-conventions", - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "head_source_sha256": "e5d6ed13362d9cddd16a4294239e52761791c2eb52a1a5dbfe4b74f03cad2457", + "analyzer_release": "foundation-plan-rails/application-2026-09-22-theme", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/appearance-current-analysis.json b/evals/create-full-stack-app/fixtures/appearance-current-analysis.json index 05c7560..fd3c50a 100644 --- a/evals/create-full-stack-app/fixtures/appearance-current-analysis.json +++ b/evals/create-full-stack-app/fixtures/appearance-current-analysis.json @@ -6,9 +6,9 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000906", "graph_version": 1, - "head_source_sha256": "33b267693040b49b527cea77c4c60fcb490e035f9a52e55df5981d6c25a43dd8", - "analyzer_release": "foundation-plan-rails/application-2026-09-19-conventions", - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "head_source_sha256": "025c7021ad31bdd64b01eb007632c000f96a84273d1a53ea41ab5f342d11aa3c", + "analyzer_release": "foundation-plan-rails/application-2026-09-22-theme", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -18,16 +18,16 @@ "gap_set": { "format": "firstdraft.foundation-gaps/2", "source": { - "sha256": "33b267693040b49b527cea77c4c60fcb490e035f9a52e55df5981d6c25a43dd8" + "sha256": "025c7021ad31bdd64b01eb007632c000f96a84273d1a53ea41ab5f342d11aa3c" }, "project": { "id": "01900000-0000-7000-8000-000000000021", "graph_version": 1 }, "analysis": { - "release": "foundation-plan-rails/application-2026-09-19-conventions" + "release": "foundation-plan-rails/application-2026-09-22-theme" }, - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -45,7 +45,7 @@ } ] }, - "gap_set_sha256": "1828d17230eb549098229bb0ae479e8abea8909058767daa1d82f73fee9cde1d", + "gap_set_sha256": "73a0f23d81563e4ea932874fb8af8b6ad4d819304b5917a9798086878a85c03c", "started_at": "2026-07-30T12:03:00.000Z", "completed_at": "2026-07-30T12:03:01.000Z" } diff --git a/evals/create-full-stack-app/fixtures/appearance-current.foundation-plan.json b/evals/create-full-stack-app/fixtures/appearance-current.foundation-plan.json index e8be8b4..49198ee 100644 --- a/evals/create-full-stack-app/fixtures/appearance-current.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/appearance-current.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/application-intent-valid-analysis.json b/evals/create-full-stack-app/fixtures/application-intent-valid-analysis.json index 1c618a0..01cd5c2 100644 --- a/evals/create-full-stack-app/fixtures/application-intent-valid-analysis.json +++ b/evals/create-full-stack-app/fixtures/application-intent-valid-analysis.json @@ -6,9 +6,9 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000905", "graph_version": 1, - "head_source_sha256": "90a75b609cf65c05d0f003d907ac0092d845843c6570f222236e4c1a64b01086", - "analyzer_release": "foundation-plan-rails/application-2026-09-19-conventions", - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "head_source_sha256": "25f888477e4ecbbcdcdb8bc32314dab579fb4fe7218e2e4f9915a42ff5f4dc33", + "analyzer_release": "foundation-plan-rails/application-2026-09-22-theme", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -18,23 +18,23 @@ "gap_set": { "format": "firstdraft.foundation-gaps/2", "source": { - "sha256": "90a75b609cf65c05d0f003d907ac0092d845843c6570f222236e4c1a64b01086" + "sha256": "25f888477e4ecbbcdcdb8bc32314dab579fb4fe7218e2e4f9915a42ff5f4dc33" }, "project": { "id": "01900000-0000-7000-8000-000000000020", "graph_version": 1 }, "analysis": { - "release": "foundation-plan-rails/application-2026-09-19-conventions" + "release": "foundation-plan-rails/application-2026-09-22-theme" }, - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" }, "gaps": [] }, - "gap_set_sha256": "c9b1e28edd428ebeff5306c6cf27ebf5e619b301c134b95baa581b72044facff", + "gap_set_sha256": "e7ac6578d55ce5ee0b0747b0c07a1553e22b4997e6b4270b132cff373bbc96a9", "started_at": "2026-07-30T12:02:00.000Z", "completed_at": "2026-07-30T12:02:01.000Z" } diff --git a/evals/create-full-stack-app/fixtures/application-intent.foundation-plan.json b/evals/create-full-stack-app/fixtures/application-intent.foundation-plan.json index 47af598..4d13961 100644 --- a/evals/create-full-stack-app/fixtures/application-intent.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/application-intent.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/empty.foundation-plan.json b/evals/create-full-stack-app/fixtures/empty.foundation-plan.json index c160c05..32423cd 100644 --- a/evals/create-full-stack-app/fixtures/empty.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/empty.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/first-preview-movies.foundation-plan.json b/evals/create-full-stack-app/fixtures/first-preview-movies.foundation-plan.json index d052797..5bf4671 100644 --- a/evals/create-full-stack-app/fixtures/first-preview-movies.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/first-preview-movies.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/issues-found-analysis.json b/evals/create-full-stack-app/fixtures/issues-found-analysis.json index 3988b21..7d5c601 100644 --- a/evals/create-full-stack-app/fixtures/issues-found-analysis.json +++ b/evals/create-full-stack-app/fixtures/issues-found-analysis.json @@ -6,9 +6,9 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000901", "graph_version": 1, - "head_source_sha256": "b16315b2d2de55ea9a08f1642b727bd07e8f090529ab0900f218fbc6d09fa867", - "analyzer_release": "foundation-plan-rails/application-2026-09-19-conventions", - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "head_source_sha256": "cbd591ab9bc46a5efb76383aaec89c26d3d850e473519a5cdedb4e42734cabe0", + "analyzer_release": "foundation-plan-rails/application-2026-09-22-theme", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/mixed-application-issues-analysis.json b/evals/create-full-stack-app/fixtures/mixed-application-issues-analysis.json index 5c91446..84796f5 100644 --- a/evals/create-full-stack-app/fixtures/mixed-application-issues-analysis.json +++ b/evals/create-full-stack-app/fixtures/mixed-application-issues-analysis.json @@ -6,9 +6,9 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000907", "graph_version": 1, - "head_source_sha256": "ec7b0616879575ab9786bcf7f716ad66e867fc854eba03a33d65d52530f775a8", - "analyzer_release": "foundation-plan-rails/application-2026-09-19-conventions", - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "head_source_sha256": "61a3fc005e97fd0e53f5dd799064b06ef17e0eed9dde4764990ab049f1a61c98", + "analyzer_release": "foundation-plan-rails/application-2026-09-22-theme", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/mixed-application-issues.foundation-plan.json b/evals/create-full-stack-app/fixtures/mixed-application-issues.foundation-plan.json index 3737dc5..d92304c 100644 --- a/evals/create-full-stack-app/fixtures/mixed-application-issues.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/mixed-application-issues.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/reconcile-habits.foundation-plan.json b/evals/create-full-stack-app/fixtures/reconcile-habits.foundation-plan.json index ca41de2..2d6477d 100644 --- a/evals/create-full-stack-app/fixtures/reconcile-habits.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/reconcile-habits.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/recurring-issues-analysis.json b/evals/create-full-stack-app/fixtures/recurring-issues-analysis.json index 61f0fad..8bf6752 100644 --- a/evals/create-full-stack-app/fixtures/recurring-issues-analysis.json +++ b/evals/create-full-stack-app/fixtures/recurring-issues-analysis.json @@ -6,9 +6,9 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000904", "graph_version": 2, - "head_source_sha256": "8c75c3c8b94f6fe3f50939d912327fbe98ad258f209cab274cdb176bc369d23d", - "analyzer_release": "foundation-plan-rails/application-2026-09-19-conventions", - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "head_source_sha256": "5b7573b95bb3e1f6d4b92a564995ea6b8bb41084136120a0426f277ea8dd665a", + "analyzer_release": "foundation-plan-rails/application-2026-09-22-theme", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/recurring-issues.foundation-plan.json b/evals/create-full-stack-app/fixtures/recurring-issues.foundation-plan.json index 351fb25..5501d7c 100644 --- a/evals/create-full-stack-app/fixtures/recurring-issues.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/recurring-issues.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/redirect-movies.foundation-plan.json b/evals/create-full-stack-app/fixtures/redirect-movies.foundation-plan.json index 46d8888..78655c8 100644 --- a/evals/create-full-stack-app/fixtures/redirect-movies.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/redirect-movies.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/reserved-constant.foundation-plan.json b/evals/create-full-stack-app/fixtures/reserved-constant.foundation-plan.json index 238874e..ea60916 100644 --- a/evals/create-full-stack-app/fixtures/reserved-constant.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/reserved-constant.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/resume-current.foundation-plan.json b/evals/create-full-stack-app/fixtures/resume-current.foundation-plan.json index 206f165..db675ef 100644 --- a/evals/create-full-stack-app/fixtures/resume-current.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/resume-current.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/schema-invalid.foundation-plan.txt b/evals/create-full-stack-app/fixtures/schema-invalid.foundation-plan.txt index d8130a6..2f09b04 100644 --- a/evals/create-full-stack-app/fixtures/schema-invalid.foundation-plan.txt +++ b/evals/create-full-stack-app/fixtures/schema-invalid.foundation-plan.txt @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/superseded-analysis.json b/evals/create-full-stack-app/fixtures/superseded-analysis.json index 78903c9..4f89c98 100644 --- a/evals/create-full-stack-app/fixtures/superseded-analysis.json +++ b/evals/create-full-stack-app/fixtures/superseded-analysis.json @@ -6,9 +6,9 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000903", "graph_version": 2, - "head_source_sha256": "9cb632700a7bec3cbd8ca0492581237d8f3061e99cd1c5f148917d5eef60f714", - "analyzer_release": "foundation-plan-rails/application-2026-09-19-conventions", - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "head_source_sha256": "e5d6ed13362d9cddd16a4294239e52761791c2eb52a1a5dbfe4b74f03cad2457", + "analyzer_release": "foundation-plan-rails/application-2026-09-22-theme", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json b/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json index 688ecb3..8a14cc5 100644 --- a/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/supported-scalars.foundation-plan.json b/evals/create-full-stack-app/fixtures/supported-scalars.foundation-plan.json index 0ca0878..5653af2 100644 --- a/evals/create-full-stack-app/fixtures/supported-scalars.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/supported-scalars.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/theme-toggle-analysis.json b/evals/create-full-stack-app/fixtures/theme-toggle-analysis.json new file mode 100644 index 0000000..29a82d7 --- /dev/null +++ b/evals/create-full-stack-app/fixtures/theme-toggle-analysis.json @@ -0,0 +1,62 @@ +{ + "project": { + "id": "01900000-0000-7000-8000-000000000024", + "graph_version": 1 + }, + "analysis": { + "id": "01900000-0000-7000-8000-000000000909", + "graph_version": 1, + "head_source_sha256": "15031de0872234349c4ddd7364735355a82c91a8d914d4f4465614bab6ea171a", + "analyzer_release": "foundation-plan-rails/application-2026-09-22-theme", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-09" + }, + "status": "valid", + "diagnostics": [], + "gap_set": { + "format": "firstdraft.foundation-gaps/2", + "source": { + "sha256": "15031de0872234349c4ddd7364735355a82c91a8d914d4f4465614bab6ea171a" + }, + "project": { + "id": "01900000-0000-7000-8000-000000000024", + "graph_version": 1 + }, + "analysis": { + "release": "foundation-plan-rails/application-2026-09-22-theme" + }, + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-09" + }, + "gaps": [ + { + "classification": "target_support_gap", + "code": "foundation_plan.gap.appearance.icon_assets.not_generated", + "kind": "appearance_icon_assets", + "status": "partially_generated", + "pointer": "/application/appearance", + "readable_path": "application.appearance", + "reason": "Application shell colors, theme, and derived Web icon assets are generated, but emitted native clients still use stock Core launcher icons.", + "consequence": "The generated favicon and PWA icons use the derived pair, and emitted native shells apply Appearance colors; native launcher icons remain stock Core assets." + }, + { + "classification": "target_support_gap", + "code": "foundation_plan.gap.appearance.native_theme_preference.not_generated", + "kind": "appearance_theme", + "status": "partially_generated", + "pointer": "/application/appearance/theme", + "readable_path": "application.appearance.theme", + "reason": "The emitted iOS and Android output does not provide the authored Light / Dark / System preference control.", + "consequence": "These native clients and their embedded Rails responses follow system appearance. The browser selector and saved browser preference do not change the native clients." + } + ] + }, + "gap_set_sha256": "ecd8fb2c75bf06c4c131e28f15d1d6017cfa9abb444bbf17251b1ecebe05f186", + "started_at": "2026-07-30T12:03:00.000Z", + "completed_at": "2026-07-30T12:03:01.000Z" + } +} diff --git a/evals/create-full-stack-app/fixtures/theme-toggle.foundation-plan.json b/evals/create-full-stack-app/fixtures/theme-toggle.foundation-plan.json new file mode 100644 index 0000000..980cf00 --- /dev/null +++ b/evals/create-full-stack-app/fixtures/theme-toggle.foundation-plan.json @@ -0,0 +1,49 @@ +{ + "format": "firstdraft.foundation-plan.sketch/0.21", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-09" + }, + "application": { + "key": "movie_catalog", + "name": "Movie Catalog", + "domain": "movies.example.com", + "appearance": { + "theme": "toggle", + "tint_color": "#4F46E5" + }, + "native": { + "ios": {}, + "android": {} + }, + "delivery": {}, + "entities": [ + { + "subject_uuid": "019fb300-0000-7000-8000-000000000001", + "key": "movie", + "name": "Movie", + "icon": "film", + "primary_descriptor": { + "field": "movie.title" + }, + "fields": [ + { + "subject_uuid": "019fb300-0000-7000-8000-000000000002", + "key": "title", + "name": "Title", + "type": "short_text", + "required": true + } + ], + "scaffold": { + "resource_routes": [ + "index" + ], + "index": { + "authorization": "public" + } + } + } + ] + } +} diff --git a/evals/create-full-stack-app/fixtures/unsupported-field-capabilities.foundation-plan.json b/evals/create-full-stack-app/fixtures/unsupported-field-capabilities.foundation-plan.json index d00d409..8ac136a 100644 --- a/evals/create-full-stack-app/fixtures/unsupported-field-capabilities.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/unsupported-field-capabilities.foundation-plan.json @@ -1,5 +1,5 @@ { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" diff --git a/evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json b/evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json index 5c669ea..656c031 100644 --- a/evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json +++ b/evals/create-full-stack-app/fixtures/unsupported-graph-analysis.json @@ -6,9 +6,9 @@ "analysis": { "id": "01900000-0000-7000-8000-000000000908", "graph_version": 1, - "head_source_sha256": "e633470f4d220e85717778016a2b362a0e2ec01696e5108fdfdb330b4c6a123c", - "analyzer_release": "foundation-plan-rails/application-2026-09-19-conventions", - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "head_source_sha256": "021f33c112a683ec1d627518a647b7133bd1997257a436460c336c0d331ccaae", + "analyzer_release": "foundation-plan-rails/application-2026-09-22-theme", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -18,16 +18,16 @@ "gap_set": { "format": "firstdraft.foundation-gaps/2", "source": { - "sha256": "e633470f4d220e85717778016a2b362a0e2ec01696e5108fdfdb330b4c6a123c" + "sha256": "021f33c112a683ec1d627518a647b7133bd1997257a436460c336c0d331ccaae" }, "project": { "id": "01900000-0000-7000-8000-000000000023", "graph_version": 1 }, "analysis": { - "release": "foundation-plan-rails/application-2026-09-19-conventions" + "release": "foundation-plan-rails/application-2026-09-22-theme" }, - "compiler_release": "foundation-plan-rails/compiler-application-2026-09-19-conventions", + "compiler_release": "foundation-plan-rails/compiler-application-2026-09-22-theme", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -55,7 +55,7 @@ } ] }, - "gap_set_sha256": "9382abe451f62e52e4d7565ecbe458f74294cc81cc372f99115e4921210b90f5", + "gap_set_sha256": "998c0641b433adc20b76891bf3c4f30edecf1765510f7898cbb88bf0f3479bc5", "started_at": "2026-07-30T12:04:00.000Z", "completed_at": "2026-07-30T12:04:01.000Z" } diff --git a/packages/claude-plugin/.claude-plugin/plugin.json b/packages/claude-plugin/.claude-plugin/plugin.json index 5d3ab82..496eaa8 100644 --- a/packages/claude-plugin/.claude-plugin/plugin.json +++ b/packages/claude-plugin/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "firstdraft", "displayName": "First Draft", - "version": "0.4.0", + "version": "0.5.0", "description": "Author a Foundation Plan and compile a bounded Rails, iPhone, and Android application", "author": { "name": "First Draft", diff --git a/packages/claude-plugin/package.template.json b/packages/claude-plugin/package.template.json index 4d9f7b5..172e0f5 100644 --- a/packages/claude-plugin/package.template.json +++ b/packages/claude-plugin/package.template.json @@ -1,6 +1,6 @@ { "name": "@firstdraft.com/claude-code", - "version": "0.4.0", + "version": "0.5.0", "description": "First Draft Foundation Plan authoring and UI development for Claude Code and Codex", "license": "MIT", "type": "module", diff --git a/release/compatibility.json b/release/compatibility.json index 69e535c..ee8b63b 100644 --- a/release/compatibility.json +++ b/release/compatibility.json @@ -1,21 +1,21 @@ { "format": "firstdraft.release-compatibility/1", "component": "skills", - "version": "0.4.0", + "version": "0.5.0", "plugin_source": { "package": "@firstdraft.com/claude-code", - "tarball_sha256": "7f796017074ecbfd5a5459f7686a615f53dbb3c95525ca2594601128e706748d" + "tarball_sha256": "d31e5f2026d43a3eee014d41593deebde7e62bdc9e1d35eed2397d3da196533e" }, "requires": { "api_contract": [ - ">= 0.4.0", - "< 0.5.0" + ">= 0.5.0", + "< 0.6.0" ], "cli": [ - "= 0.4.0" + "= 0.5.0" ], "foundation_plan_formats": [ - "firstdraft.foundation-plan.sketch/0.20" + "firstdraft.foundation-plan.sketch/0.21" ] } } diff --git a/script/check-cli-contract.mjs b/script/check-cli-contract.mjs index 85f7375..047b19e 100644 --- a/script/check-cli-contract.mjs +++ b/script/check-cli-contract.mjs @@ -47,7 +47,7 @@ assert.deepEqual( component: "cli", version: cliPackageVersion, requires: { - api_contract: [">= 0.4.0", "< 0.5.0"], + api_contract: [">= 0.5.0", "< 0.6.0"], foundation_plan_formats: [foundationPlanFormat], }, }, diff --git a/script/check-release-compatibility.mjs b/script/check-release-compatibility.mjs index 1efca7a..14dcdbf 100644 --- a/script/check-release-compatibility.mjs +++ b/script/check-release-compatibility.mjs @@ -119,7 +119,7 @@ export function assertSkillsReleaseCompatibility({ assertComparator(requirement); } assert.deepEqual(compatibility.requires, { - api_contract: [">= 0.4.0", "< 0.5.0"], + api_contract: [">= 0.5.0", "< 0.6.0"], cli: [`= ${cliPackageVersion}`], foundation_plan_formats: [foundationPlanFormat], }); diff --git a/script/claude-plugin-boundaries.mjs b/script/claude-plugin-boundaries.mjs index 10fde7a..ebcb9a1 100644 --- a/script/claude-plugin-boundaries.mjs +++ b/script/claude-plugin-boundaries.mjs @@ -6,8 +6,8 @@ export const canonicalClaudePluginSkillFiles = Object.freeze([ "agents/openai.yaml", "references/diagnostics-and-recovery.md", "references/examples.md", - "references/foundation-plan-0.20.schema.json", - "references/foundation-plan-020.md", + "references/foundation-plan-0.21.schema.json", + "references/foundation-plan-021.md", "references/modeling-guide.md", "scripts/firstdraft.sh", ]); diff --git a/script/cli-contract/config.mjs b/script/cli-contract/config.mjs index d2602a1..e2e0f4e 100644 --- a/script/cli-contract/config.mjs +++ b/script/cli-contract/config.mjs @@ -1,8 +1,8 @@ -export const cliRevision = "a555f8d39862109b8c28b392c0439470e88f4ba8"; +export const cliRevision = "013581baa91c1eff0bffafd6e9e20eccb1f24643"; export const cliRuntimeSha256 = - "ddd9b8ee4d83135a668b7a97e2522ba23b9478339662c1f6115e5273851abf81"; + "6386712548a9eb5d3d7c8dbdd031482e66af270cae45838056f72533979cae40"; export const cliPackageName = "@firstdraft.com/cli"; -export const cliPackageVersion = "0.4.0"; +export const cliPackageVersion = "0.5.0"; export const safeGithubReasonCodes = Object.freeze([ "github.configuration_missing", @@ -40,11 +40,11 @@ export const storedApiUrl = "http://127.0.0.1:1"; export const configuredApiUrl = "http://127.0.0.1:2"; export const apiToken = "canary-private-api-token"; export const foundationPlanFormat = - "firstdraft.foundation-plan.sketch/0.20"; + "firstdraft.foundation-plan.sketch/0.21"; export const analyzerRelease = - "foundation-plan-rails/application-2026-09-19-conventions"; + "foundation-plan-rails/application-2026-09-22-theme"; export const compilerRelease = - "foundation-plan-rails/compiler-application-2026-09-19-conventions"; + "foundation-plan-rails/compiler-application-2026-09-22-theme"; export const compilationTarget = { id: "rails", profile: "rails-sketch/2026-09", diff --git a/script/cli-contract/fixtures.mjs b/script/cli-contract/fixtures.mjs index f6161c0..ea384c9 100644 --- a/script/cli-contract/fixtures.mjs +++ b/script/cli-contract/fixtures.mjs @@ -39,7 +39,7 @@ export function analysisProjection( graphVersion = 1, identifier = analysisId, headSourceSha256 = - "90a75b609cf65c05d0f003d907ac0092d845843c6570f222236e4c1a64b01086", + "25f888477e4ecbbcdcdb8bc32314dab579fb4fe7218e2e4f9915a42ff5f4dc33", gaps = [], } = {}, ) { diff --git a/script/cli-contract/local-commands.mjs b/script/cli-contract/local-commands.mjs index 1bf6513..f390135 100644 --- a/script/cli-contract/local-commands.mjs +++ b/script/cli-contract/local-commands.mjs @@ -19,7 +19,7 @@ import { const planSchema = JSON.parse( readFileSync( - new URL("../../skills/create-full-stack-app/references/foundation-plan-0.20.schema.json", import.meta.url), + new URL("../../skills/create-full-stack-app/references/foundation-plan-0.21.schema.json", import.meta.url), "utf8", ), ); diff --git a/skills/create-full-stack-app/SKILL.md b/skills/create-full-stack-app/SKILL.md index 96ca7a0..e47b0a0 100644 --- a/skills/create-full-stack-app/SKILL.md +++ b/skills/create-full-stack-app/SKILL.md @@ -23,11 +23,11 @@ explicit handoff. Follow [writing notes](references/modeling-guide.md#retain-imp ## Current boundary -Targets plugin 0.4.0, CLI 0.4.0, and API 0.4 with Plan 0.20; compatibility does not establish catalog selection. +Targets plugin 0.5.0, CLI 0.5.0, API 0.5, and Plan 0.21; catalog selection is separate. - Bounded generation includes Web Accounts, Policies, Scaffolds, development data, and selected iPhone/Android - clients. Web uses stock Zinc tokens; Appearance controls theme, native colors, and Web icons. Omitted theme means - light; native launcher icons stay stock. + clients. [Appearance](references/foundation-plan-021.md#application-and-clients) selects theme, native colors, + and Web icons; browser preferences and native gaps differ. - Bounded Account/Policy protects Web Scaffolds. Native clients require an admitted public index, are Account/Policy-free, and do not inherit Web privacy. - Preserve unsupported requests; report gaps. Never drop clients or weaken access to get `valid`. @@ -35,13 +35,13 @@ Targets plugin 0.4.0, CLI 0.4.0, and API 0.4 with Plan 0.20; compatibility does - For native preview, use local iOS Simulator or Android Studio Emulator when available. Follow the generated app's guides; native builds and Revyl are not prerequisites for ordinary local Rails development. -Before support claims, read [current evidence](references/foundation-plan-020.md#current-evidence-boundary). +Before support claims, read [current evidence](references/foundation-plan-021.md#current-evidence-boundary). ## Load references only when needed For authoring, choose the relevant section: -- [Foundation Plan reference](references/foundation-plan-020.md): exact envelope, identity, ownership, presence, +- [Foundation Plan reference](references/foundation-plan-021.md): exact envelope, identity, ownership, presence, current evidence, and target support for Application/clients, Fields, relationships, Validations, Accounts/Policies, and Scaffolds. - [Modeling guide](references/modeling-guide.md): interview, Entities/Fields, validations, relationships, and @@ -60,7 +60,7 @@ For CLI work: read [ambiguous mutations](references/diagnostics-and-recovery.md#ambiguous-mutations) only when the named error requires it. -The bundled [JSON Schema](references/foundation-plan-0.20.schema.json) is machine-readable validator input, not prose. +The bundled [JSON Schema](references/foundation-plan-0.21.schema.json) is machine-readable validator input, not prose. Use a compatible JSON Schema 2020-12 command named by the user, exposed by the project, or found through a straightforward check of existing local commands. Pass only its path; never read it end to end. Do not install dependencies or add validation/build plumbing solely for this workflow. Otherwise rely on First Draft exact-byte @@ -82,7 +82,7 @@ firstdraft_cli --version firstdraft_cli --help ``` -Require the version probe to succeed with one exact `0.4.0` output line and no other output, and top-level help that +Require the version probe to succeed with one exact `0.5.0` output line and no other output, and top-level help that lists `generate`, `plan`, and `compilation`. Contract tests own separate stdout and stderr assertions for leaf commands; do not repeat them in a startup shell loop. The compatible CLI supplies these public commands: diff --git a/skills/create-full-stack-app/references/diagnostics-and-recovery.md b/skills/create-full-stack-app/references/diagnostics-and-recovery.md index 67c3ef0..7e3048f 100644 --- a/skills/create-full-stack-app/references/diagnostics-and-recovery.md +++ b/skills/create-full-stack-app/references/diagnostics-and-recovery.md @@ -8,8 +8,8 @@ JSON object. An unrecognized prefixed line, a progress line after the envelope, interleaved output fail closed. Branch on the object's stable `error` and structured fields rather than the human-readable `detail` or broad process exit status. -The source candidate uses `@firstdraft.com/cli@0.4.0`. Its exact reviewed revision and runtime digest are owned by -[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.4.0/script/cli-contract/config.mjs) +The source candidate uses `@firstdraft.com/cli@0.5.0`. Its exact reviewed revision and runtime digest are owned by +[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.5.0/script/cli-contract/config.mjs) at this plugin's protected release tag. Check the command surface rather than assuming the version alone establishes compatibility. These source checks do not prove plugin/catalog publication, service authentication, staging compatibility, or a complete user journey. @@ -133,7 +133,7 @@ local development is unsuitable, not a prerequisite. ### Direct local output -CLI 0.4.0 defaults to current-root adoption and archives the original workspace under `.firstdraft/design/`. +CLI 0.5.0 defaults to current-root adoption and archives the original workspace under `.firstdraft/design/`. CLI 0.3.0 supports the same archive via explicit `--output .`; older 0.2.2 uses top-level `design/`. Preserve the actual layout of an already materialized application; this change does not migrate it. @@ -456,7 +456,7 @@ or provenance-changing response requires reconciling the CLI and service contrac 4. requires the envelope's `head_source_sha256` to equal the retained `compilation.head_source_sha256`; 5. verifies transport metadata and exact bytes against the retained artifact digest, then validates Plan format - `firstdraft.foundation-plan.sketch/0.20`, target profile `rails-sketch/2026-09`, the canonical Foundation Plan + `firstdraft.foundation-plan.sketch/0.21`, target profile `rails-sketch/2026-09`, the canonical Foundation Plan digest, envelope, manifest, paths, modes, Base64 contents, and file digests; and 6. installs an absent private sibling tree with one atomic rename, or applies direct Compile's [current-root transaction and CLI compatibility boundary](#direct-local-output). diff --git a/skills/create-full-stack-app/references/examples.md b/skills/create-full-stack-app/references/examples.md index 2b1592d..db437f6 100644 --- a/skills/create-full-stack-app/references/examples.md +++ b/skills/create-full-stack-app/references/examples.md @@ -1,6 +1,6 @@ # Examples -These examples teach `sketch/0.20` structure. UUIDs are fixed documentation data. Never choose them when authoring +These examples teach `sketch/0.21` structure. UUIDs are fixed documentation data. Never choose them when authoring new subjects in a real Project. Once an exact staged or resumed candidate already contains subject UUIDs, preserve them during read-back and diagnostics unless a user correction or demonstrated identity diagnostic requires a change. @@ -26,7 +26,7 @@ smallest subset accepted by the reviewed conditional PUT. ```json { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -53,7 +53,7 @@ return `valid`; that result is still only the Compilation gate, not proof that o ```json { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -98,20 +98,21 @@ return `valid`; that result is still only the Compilation gate, not proof that o This example requests iPhone. Add `"android": {}` beside `"ios": {}` to request both, or replace the iOS member to request Android alone. Each client requires an admitted public-index Scaffold and an identity within its -[platform limits](foundation-plan-020.md#application-and-clients); otherwise a valid analysis records an unrealized-client +[platform limits](foundation-plan-021.md#application-and-clients); otherwise a valid analysis records an unrealized-client target gap. iPad remains outside this boundary. Domain configures the native origin/identifier and Rails production mailer host; it does not provision DNS, TLS, or deployment. Web-only plans may use the same exact Scaffold with `native: {}` and may include or omit a domain. The admitted Scaffold makes Movie records readable on the web without authentication. Confirm that exposure with the user before adding it; do not add it merely to satisfy the iPhone navigation requirement or silently discard private or broader access intent. -Adding `appearance` to this candidate selects the cross-client theme, native shell colors, and branding for the -adaptive SVG and deterministic PNG used by Rails favicon and PWA references. Omitted theme means light; web -components keep stock Zinc tokens. Because this example -also emits iOS, a matching valid AnalysisRun still records the partial -`foundation_plan.gap.appearance.icon_assets.not_generated` record at `/application/appearance` solely for the stock -iOS AppIcon. Preserve the intentional Appearance request and report that precise reviewed delta rather than -describing Appearance as wholly unsupported. +Adding `appearance` to this candidate selects the theme, native shell colors, and branding for the adaptive SVG +and deterministic PNG used by Rails favicon and PWA references. Omitted theme means fixed light; web components +keep stock Zinc tokens. Because this example also emits iOS, a matching valid AnalysisRun records the partial +`foundation_plan.gap.appearance.icon_assets.not_generated` record at `/application/appearance` for the stock +AppIcon. With `toggle`, the browser offers Light/Dark/System while the iOS shell and embedded Rails responses stay +automatic; the run also records `foundation_plan.gap.appearance.native_theme_preference.not_generated` at +`/application/appearance/theme`. Preserve the intentional Appearance request and report the complete reviewed +GapSet rather than describing Appearance as wholly unsupported. ## Conditional text length @@ -270,13 +271,13 @@ do not claim it works or silently replace it with a resource/default destination ## One Entity with required and optional scalar Fields -This complete document is structurally valid v0.20 and accepted by the reviewed bounded importer. That does not +This complete document is structurally valid v0.21 and accepted by the reviewed bounded importer. That does not prove complete semantic analysis, target support, Compilation, or generated output. `required` is mandatory even when the value is `false`; omitting it from the optional Details Field would be structurally invalid. ```json { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -322,12 +323,12 @@ UUID. The reviewed importer also accepts `boolean`, `date`, `datetime`, `decimal ## Ordinal enum Field -Use an enum for a closed set of named choices. This complete document is structurally valid v0.20 and accepted by +Use an enum for a closed set of named choices. This complete document is structurally valid v0.21 and accepted by the reviewed bounded importer. Here, priority order carries semantic rank, so `ordinal` is `true`. ```json { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -410,7 +411,7 @@ make this Account or profile available natively. ```json { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -560,13 +561,13 @@ GapSet consequences. ## Stored and reverse relationship -This complete document is structurally valid v0.20 and lies within the current ordinary Reference and direct inverse +This complete document is structurally valid v0.21 and lies within the current ordinary Reference and direct inverse Compiler subset. `Task` owns the stored `project` Reference. `Project` owns the meaningful reverse `tasks` Association. The forward `task.project` Association is derived and therefore omitted. ```json { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -659,5 +660,5 @@ The generated collection is distinct. In this example, the `through` step is an inverse, the `source` is an admitted mechanically derived forward Association, and both underlying References have `one_to_one: false`. This is one supported shape, not the current boundary statement: the reviewed Case Chat result also realizes selected predicated sources, several first-level indirect Associations, and one nested-through form. -Use [the exact Association reference](foundation-plan-020.md#references-and-associations) and the reviewed GapSet to decide whether +Use [the exact Association reference](foundation-plan-021.md#references-and-associations) and the reviewed GapSet to decide whether another authored path is realized. diff --git a/skills/create-full-stack-app/references/foundation-plan-0.20.schema.json b/skills/create-full-stack-app/references/foundation-plan-0.21.schema.json similarity index 99% rename from skills/create-full-stack-app/references/foundation-plan-0.20.schema.json rename to skills/create-full-stack-app/references/foundation-plan-0.21.schema.json index 32df379..ebceef9 100644 --- a/skills/create-full-stack-app/references/foundation-plan-0.20.schema.json +++ b/skills/create-full-stack-app/references/foundation-plan-0.21.schema.json @@ -1,8 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://firstdraft.com/schemas/foundation-plan/sketch-0.20", + "$id": "https://firstdraft.com/schemas/foundation-plan/sketch-0.21", "title": "Foundation Plan", - "description": "The working sketch/0.20 JSON import and export representation for one First Draft Project graph.", + "description": "The working sketch/0.21 JSON import and export representation for one First Draft Project graph.", "type": "object", "additionalProperties": false, "required": [ @@ -14,7 +14,7 @@ "format": { "title": "Format", "description": "Names the Foundation Plan serialization vocabulary and its version.", - "const": "firstdraft.foundation-plan.sketch/0.20" + "const": "firstdraft.foundation-plan.sketch/0.21" }, "target": { "$ref": "#/$defs/target" @@ -254,7 +254,7 @@ "theme": { "title": "Theme", "default": "light", - "description": "Light and dark handling across web and native clients. Omission means light; auto explicitly follows the system.", + "description": "Omission means fixed light. Auto follows the system. Toggle offers Light, Dark, and System, initially System, with a browser-local preference; native support is target-specific.", "oneOf": [ { "const": "auto", @@ -267,6 +267,10 @@ { "const": "dark", "title": "Always dark" + }, + { + "const": "toggle", + "title": "Let users choose Light, Dark, or System" } ] } diff --git a/skills/create-full-stack-app/references/foundation-plan-020.md b/skills/create-full-stack-app/references/foundation-plan-021.md similarity index 93% rename from skills/create-full-stack-app/references/foundation-plan-020.md rename to skills/create-full-stack-app/references/foundation-plan-021.md index d51e5d5..a82979d 100644 --- a/skills/create-full-stack-app/references/foundation-plan-020.md +++ b/skills/create-full-stack-app/references/foundation-plan-021.md @@ -1,8 +1,8 @@ -# Foundation Plan 0.20 +# Foundation Plan 0.21 This reference and [Examples](examples.md) guide authoring for the experimental -`firstdraft.foundation-plan.sketch/0.20` boundary. The bundled -[exact JSON Schema](foundation-plan-0.20.schema.json) is the machine-readable structural contract. Never read it +`firstdraft.foundation-plan.sketch/0.21` boundary. The bundled +[exact JSON Schema](foundation-plan-0.21.schema.json) is the machine-readable structural contract. Never read it end to end. Use a compatible JSON Schema 2020-12 validator when the user names its command, the project exposes a specific validation command, or a straightforward check finds an existing compatible local command. Confirm that command is available, then pass the schema file to it without loading its contents into context. A declared library @@ -42,9 +42,9 @@ deployed journey, and an older observation does not define current support. **Current design and machine authority** -- The bundled JSON Schema owns v0.20 transport shape. The Service's - [Rails target profile](https://github.com/firstdraft/firstdraft/blob/ee38cafcff43d70fdb9f28626f25ebaecb257b0c/docs/architecture/targets/rails/profile.md) - records lowering at selected Service source `ee38cafcff43d70fdb9f28626f25ebaecb257b0c`. +- The bundled JSON Schema owns v0.21 transport shape. The Service's + [Rails target profile](https://github.com/firstdraft/firstdraft/blob/87760d2717d914b69a7bbf7e5d372ffe8aac50d2/docs/architecture/targets/rails/profile.md) + records lowering at selected Service source `87760d2717d914b69a7bbf7e5d372ffe8aac50d2`. A source reference does not establish the deployed service revision. - Read `analyzer_release` and `compiler_release` from the matching Analysis result; Compilation results carry `compiler_release` only. @@ -53,14 +53,14 @@ deployed journey, and an older observation does not define current support. - The importer preserves each schema-valid exact source as the Project Head and imports a bounded relational graph. Meaning skipped before semantic analysis remains in the Head and appears as ordered `service_support_gap` records. Admitted meaning that the selected target cannot fully realize appears as `target_support_gap` records. -- Service API 0.4 returns the complete canonical `firstdraft.foundation-gaps/2` object and its SHA-256 for every valid +- Service API 0.5 returns the complete canonical `firstdraft.foundation-gaps/2` object and its SHA-256 for every valid AnalysisRun, including an empty `gaps` array. `valid` applies only to the admitted graph; it is not proof of Compilation or of meaning skipped before analysis. - Current public Compilation has bounded scalar, required-enum, relationship, Validation, Predicate, Ordering, State Machine, Appearance theme/native-color/Web-icon, Web Account, Action Policy, generalized Web Scaffold, development-data, and selected-iPhone/Android slices. Their prerequisites matter: unsupported children and consumers remain exact gaps rather than widening the supported shape. When Appearance is authored, emitted native clients - retain one named partial gap for their stock launcher icons. + retain a named partial gap for their stock launcher icons; `toggle` also records the absent native preference control. - Required enums emit string storage with Rails enum inclusion, presence validation, and native helpers. An admitted required enum accepts its compatible in-domain literal-key default. Optional enums, database membership constraints, general rank behavior, and broader enum consumers remain unsupported. @@ -75,7 +75,7 @@ deployed journey, and an older observation does not define current support. **Implementation and observation evidence** - The Service's generated - [evidence index](https://github.com/firstdraft/firstdraft/blob/ee38cafcff43d70fdb9f28626f25ebaecb257b0c/docs/evidence/status.md) + [evidence index](https://github.com/firstdraft/firstdraft/blob/87760d2717d914b69a7bbf7e5d372ffe8aac50d2/docs/evidence/status.md) distinguishes implemented, exercised, generated-output, hosted, and observed claims. The September 12 [Android qualification](https://github.com/firstdraft/firstdraft/blob/89a2d6866f9448f4e75b58cac26f61c52daaa0b0/docs/solutions/2026-09-12-generated-android-preview.md) records GitHub APK delivery and local Android public navigation, forms, theme, and layout checks. Revyl loaded @@ -88,7 +88,7 @@ deployed journey, and an older observation does not define current support. journey, physical-device installation, or production deployment. - The [iPhone Revyl receipt](https://github.com/firstdraft/firstdraft/blob/89a2d6866f9448f4e75b58cac26f61c52daaa0b0/docs/solutions/2026-09-12-revyl-preview-release.md) separately records an emitted iPhone index and live Rails refresh. It did not exercise native forms or Back in - Revyl. The [native target](https://github.com/firstdraft/firstdraft/blob/ee38cafcff43d70fdb9f28626f25ebaecb257b0c/docs/architecture/targets/rails/capabilities/native.md) + Revyl. The [native target](https://github.com/firstdraft/firstdraft/blob/87760d2717d914b69a7bbf7e5d372ffe8aac50d2/docs/architecture/targets/rails/capabilities/native.md) owns generated iPhone detail/form routing and its separate source, Simulator, and browser evidence. Android runtime observations do not qualify iPhone runtime behavior. - The September 13 @@ -111,18 +111,18 @@ deployed journey, and an older observation does not define current support. The bundled schema was copied byte-for-byte from `docs/architecture/design/foundation-plan.schema.json` at Service revision -`ee38cafcff43d70fdb9f28626f25ebaecb257b0c` and has SHA-256 -`5576ec5e10d108f0a2d0f9fa336249324642f092e4444f6c11e4ab738f3fa58b`. This is exact contract provenance, not +`87760d2717d914b69a7bbf7e5d372ffe8aac50d2` and has SHA-256 +`bef23a55f7d09509f0beab14df7620a49660908d7fb6c4435c99489a0dc9553d`. This is exact contract provenance, not release or execution evidence. The source candidate and pinned contract check use the exact reviewed CLI revision and runtime digest in -[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.4.0/script/cli-contract/config.mjs) +[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.5.0/script/cli-contract/config.mjs) at this plugin's protected release tag, as contract provenance rather than release or execution evidence. The CLI exposes `generate uuid`, `generate application-key`, `plan init`, `plan push`, `plan status`, local `plan compile` (equivalent to `--output .`), explicit `plan compile --github`, `plan compile --output`, `compilation status`, and `compilation download`. It has no public `plan subject-id` or `plan publish`. The coordinated checkout declares the -`@firstdraft.com/cli@0.4.0` package. Direct output accepts the ordinary absent destination and, on POSIX, +`@firstdraft.com/cli@0.5.0` package. Direct output accepts the ordinary absent destination and, on POSIX, current-root adoption by default or with `--output .`; the recovery reference owns its preconditions. Check commands rather than inferring compatibility from a version number. These source checks do not prove plugin/catalog publication, authentication, staging compatibility, or a complete user journey. @@ -161,7 +161,7 @@ The root contains exactly three required properties: ```json { - "format": "firstdraft.foundation-plan.sketch/0.20", + "format": "firstdraft.foundation-plan.sketch/0.21", "target": { "id": "rails", "profile": "rails-sketch/2026-09" @@ -268,7 +268,7 @@ an explicit `.invalid` origin and `invalid.firstdraft` identifier prefix. Each selected client is emitted only when it has an admitted public-index Scaffold and its generated identity fits platform rules. Structurally valid long names can exceed those rules: the application key must form one DNS-safe label of at most 63 ASCII bytes, and Android application IDs must fit 223 bytes after domain conversion. -The [native target](https://github.com/firstdraft/firstdraft/blob/ee38cafcff43d70fdb9f28626f25ebaecb257b0c/docs/architecture/targets/rails/capabilities/native.md) +The [native target](https://github.com/firstdraft/firstdraft/blob/87760d2717d914b69a7bbf7e5d372ffe8aac50d2/docs/architecture/targets/rails/capabilities/native.md) owns the full rules. A missing public entry or unusable identity omits that client and records `foundation_plan.gap.native_client.not_generated`; a missing domain alone does not. Preserve the user's requested clients and access rather than changing product meaning to avoid that gap. @@ -276,10 +276,27 @@ clients and access rather than changing product meaning to avoid that gap. Public detail and form links use Hotwire; Web Account and Policy support does not implement native sign-in, profile, or protected navigation. Confirm that public native access is intentional. -Appearance theme applies to web and native clients. Omitted theme means `light`; explicit `auto` follows the system, -and `dark` fixes dark mode. Generated pages expose no theme toggle. Native tint and background colors do not replace -the stock Zinc web component tokens. Rails derives the adaptive SVG and deterministic PNG used by its favicon and -PWA references from the authored branding. When native clients are emitted, their stock launcher icons are the reason for the precise +`application.appearance.theme` accepts four modes: + +| Choice | Browser behavior | +|---|---| +| Omitted or `light` | Fixed light, ignoring saved and system preferences | +| `dark` | Fixed dark, ignoring saved and system preferences | +| `auto` | Follows the system; no selector or saved preference | +| `toggle` | Light, Dark, and System selector; initially System; browser-local preference persists across visits | + +Only `toggle` reads or stores a browser preference. It follows system changes only while System is selected. +Fixed modes emit no preference controller, storage, or system listener. Both palettes remain available for reusable +styling. Native `light`, `dark`, and `auto` keep the corresponding shell and embedded-page appearance. +For `toggle`, emitted iOS and Android clients and their embedded Rails responses use automatic appearance, omit +the browser selector/storage, and disclose the missing native preference control once in the reviewed GapSet: +`foundation_plan.gap.appearance.native_theme_preference.not_generated` at `/application/appearance/theme` with +`partially_generated` status, naming the affected emitted targets. Preserve the authored choice and selected +clients; no native settings bridge is generated. This source mapping does not establish native runtime qualification. + +Native tint and background colors do not replace the stock Zinc web component tokens. Rails derives the adaptive +SVG and deterministic PNG used by its favicon and PWA references from the authored branding. When native clients +are emitted, their stock launcher icons are the reason for the precise `foundation_plan.gap.appearance.icon_assets.not_generated` partial gap. Web-only output has no Appearance icon-assets gap. Other admitted but unconsumed Application configuration remains a target gap. @@ -344,7 +361,7 @@ multiple-per-Entity, guarded, cross-Entity, multi-effect, unresolved helper coll retain storage with behavior gaps. Failed transitions retain ordinary AASM/Rails object and transaction behavior; an application may need to reload or reset a failed object before reusing it. AASM 6.0.0 namespaced state scopes can query a prefixed value instead of the stored state. Consult the selected -Service's [automatic state scopes](https://github.com/firstdraft/firstdraft/blob/ee38cafcff43d70fdb9f28626f25ebaecb257b0c/docs/architecture/design/state-machines.md#automatic-state-scopes) +Service's [automatic state scopes](https://github.com/firstdraft/firstdraft/blob/87760d2717d914b69a7bbf7e5d372ffe8aac50d2/docs/architecture/design/state-machines.md#automatic-state-scopes) guidance before using them in application work. `attachment` and `image` are schema-valid Field types, but they are skipped from the admitted graph and recorded as service-support gaps; they cannot reach diff --git a/skills/create-full-stack-app/references/modeling-guide.md b/skills/create-full-stack-app/references/modeling-guide.md index 5e2ce7e..018fbed 100644 --- a/skills/create-full-stack-app/references/modeling-guide.md +++ b/skills/create-full-stack-app/references/modeling-guide.md @@ -177,7 +177,7 @@ Selecting an index preserves its resource URL, query, and authorization. A prote A missing Entity or an Entity without a selected index is invalid. If the selected index is genuinely unsupported, keep that intended choice in the Plan and review the dependent Home gap; the residual app uses the welcome page. Do not substitute another index or weaken access. See the -[Application reference](foundation-plan-020.md#application-and-clients) for the serialized choice. +[Application reference](foundation-plan-021.md#application-and-clients) for the serialized choice. ## Model Entities and Fields @@ -199,7 +199,7 @@ storage using Rails `enum` with inclusion and presence validation plus native sc Compiler selects Rails prefix or suffix options when helper names would collide. Compatible in-domain literal-key defaults work regardless of whether the order has semantic rank. Database membership constraints, general rank semantics, optional enums, and unsupported consumers remain gaps. Preserve -product meaning instead of replacing an enum with a scalar; the [enum reference](foundation-plan-020.md#enums) +product meaning instead of replacing an enum with a scalar; the [enum reference](foundation-plan-021.md#enums) owns the exact lowering. ### Choose text normalization @@ -224,7 +224,7 @@ also applies when `downcase` occurs between them: `["trim", "downcase", "blank_t This rule gives `downcase` no fixed position. Do not silently reorder an existing pipeline or repeat it until stable. `trim` also removes invisible edge characters that `collapse_whitespace` preserves; both preserve interior joiners. -The Service's [Field catalog](https://github.com/firstdraft/firstdraft/blob/ee38cafcff43d70fdb9f28626f25ebaecb257b0c/docs/architecture/design/field-catalog.md#normalization-and-comparison) +The Service's [Field catalog](https://github.com/firstdraft/firstdraft/blob/87760d2717d914b69a7bbf7e5d372ffe8aac50d2/docs/architecture/design/field-catalog.md#normalization-and-comparison) owns the exact character policies and operation semantics. These are authoring choices, not automatic Compiler defaults; include consequential choices in the semantic read-back. @@ -250,7 +250,7 @@ custom validator: ordinary Rails I18n can customize application error copy after comparison or uniqueness remains on the Entity when appropriate, independently of that error target. These examples explain kind selection, not a promise that every shape emits today. Read the compatible -[Validation support reference](foundation-plan-020.md#validations), use the bundled schema for exact syntax, and +[Validation support reference](foundation-plan-021.md#validations), use the bundled schema for exact syntax, and inspect the real analysis result. Schema-valid cross-field comparisons, conditions, and exclusions can still be service or target gaps. No general Rails `validates` option or custom Ruby callback becomes Plan syntax merely because Rails supports it. @@ -313,7 +313,7 @@ silently narrow a broader requested Scaffold or make it public merely to obtain Select `native.ios` and `native.android` independently when the user wants those owned projects. Ordinary Compilation emits each with at least one admitted public navigation entry and an identity that fits its -[platform limits](foundation-plan-020.md#application-and-clients); otherwise the valid run records an unrealized-client +[platform limits](foundation-plan-021.md#application-and-clients); otherwise the valid run records an unrealized-client target gap. Domain supplies a native HTTPS origin and platform identifier; it also configures the Rails production mailer host. It does not provision DNS, deployment, TLS, or mail delivery. Without a domain the native identifiers are explicit placeholders. Semantic icons inform Web, SF Symbol, and @@ -322,14 +322,19 @@ does not establish native authentication or protected navigation. Confirm public private requirements and requested clients, then review the support gap. Do not recommend removing a requested client to quiet gaps, or adding public indexes to satisfy native prerequisites. The user may change product scope; target support alone is not that decision. -Appearance sets the cross-client theme, native colors, and Web icon branding. Omitted theme means light; explicit -`auto` follows the system. Web components retain the stock Zinc theme, and native launcher icons remain stock. Android shows +Appearance sets the theme, native colors, and Web icon branding. Omitted theme or `light` means fixed light; +`dark` means fixed dark, and `auto` follows the system with no manual control or saved preference. Use `toggle` +when the user wants Light, Dark, and System choices: the browser starts at System and remembers only its local +preference, following OS changes only while System is selected. Fixed modes ignore old saved preferences. +Native `toggle` output stays automatic with one reviewed gap for the absent native preference control; preserve +the authored choice and requested clients instead of replacing `toggle` with `auto` or dropping a client. +Web components retain the stock Zinc theme, and native launcher icons remain stock. Android shows one stack, up to five tabs, or four tabs plus More for every overflow destination. After Compilation, follow the -emitted platform preview guide and the [native preview boundary](foundation-plan-020.md#preview-generated-native-apps). +emitted platform preview guide and the [native preview boundary](foundation-plan-021.md#preview-generated-native-apps). Use local Android Studio Emulator or iOS Simulator for native checks when available. Ordinary Rails iteration uses the local web app. Revyl is an optional preview destination, not a release or development prerequisite. Nonempty delivery, broader Account/Policy shapes, and broader clients remain unsupported or incomplete. Requirements without a -v0.20 shape, including notification trigger/template definitions, deployment, and iPad, remain in +v0.21 shape, including notification trigger/template definitions, deployment, and iPad, remain in [implementation notes](#retain-implementation-requirements) and the semantic read-back as currently unplannable rather than being invented as Plan JSON or promised a GapSet record. The authored `delivery` channel block itself remains in the Plan and receives its expected service-support gap. diff --git a/test/release-compatibility.test.mjs b/test/release-compatibility.test.mjs index 79b7b32..107de87 100644 --- a/test/release-compatibility.test.mjs +++ b/test/release-compatibility.test.mjs @@ -27,7 +27,7 @@ test("release compatibility matches the installable plugin manifest", async () = const cliConfigurationUrl = `https://github.com/firstdraft/skills/blob/claude-v${compatibility.version}/script/cli-contract/config.mjs`; - for (const name of ["diagnostics-and-recovery.md", "foundation-plan-020.md"]) { + for (const name of ["diagnostics-and-recovery.md", "foundation-plan-021.md"]) { const reference = await readText(`skills/create-full-stack-app/references/${name}`); assert.equal( [...reference.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)] @@ -42,13 +42,13 @@ test("release compatibility matches the installable plugin manifest", async () = assert.deepEqual(compatibility, { format: "firstdraft.release-compatibility/1", component: "skills", - version: "0.4.0", + version: "0.5.0", plugin_source: { package: "@firstdraft.com/claude-code", tarball_sha256: compatibility.plugin_source.tarball_sha256, }, requires: { - api_contract: [">= 0.4.0", "< 0.5.0"], + api_contract: [">= 0.5.0", "< 0.6.0"], cli: [`= ${cliPackageVersion}`], foundation_plan_formats: [foundationPlanFormat], }, diff --git a/test/repository.test.mjs b/test/repository.test.mjs index 3b9fec7..1c66b78 100644 --- a/test/repository.test.mjs +++ b/test/repository.test.mjs @@ -44,9 +44,9 @@ const reviewedFixtureAnalyzerRelease = "foundation-plan-rails/application-2026-08-28-reviewed-realization"; const reviewedFixtureCompilerRelease = "foundation-plan-rails/compiler-application-2026-08-28-reviewed-realization"; -const currentFoundationPlanServiceBaseline = "ee38cafcff43d70fdb9f28626f25ebaecb257b0c"; +const currentFoundationPlanServiceBaseline = "87760d2717d914b69a7bbf7e5d372ffe8aac50d2"; const foundationPlanSchemaDigest = - "5576ec5e10d108f0a2d0f9fa336249324642f092e4444f6c11e4ab738f3fa58b"; + "bef23a55f7d09509f0beab14df7620a49660908d7fb6c4435c99489a0dc9553d"; const currentFoundationPlanSchemaBaseline = currentFoundationPlanServiceBaseline; const prettyJsonSha256 = (value) => createHash("sha256") @@ -215,8 +215,8 @@ test("Claude Code packaging selects canonical authoring source exactly once", as version: marketplace.plugins[0].version, registry: "https://registry.npmjs.org/", }); - assert.equal(packageTemplate.version, "0.4.0"); - assert.equal(installableManifest.version, "0.4.0"); + assert.equal(packageTemplate.version, "0.5.0"); + assert.equal(installableManifest.version, "0.5.0"); assert.equal(packageTemplate.dependencies, undefined); assert.deepEqual(installableManifest.skills, checkoutManifest.skills); assert.equal(installableManifest.userConfig, undefined); @@ -648,7 +648,7 @@ test("authored JSON examples parse and retain the pinned Plan contract", async ( skillsDirectory, "create-full-stack-app", "references", - "foundation-plan-020.md", + "foundation-plan-021.md", ), ); const fixture = JSON.parse( @@ -1122,7 +1122,7 @@ test("validator evals stage the required Plan and private state", async () => { test("complete examples and eval Plans validate against the bundled exact schema", async () => { const skillDirectory = path.join(skillsDirectory, "create-full-stack-app"); const schemaSource = await readFile( - path.join(skillDirectory, "references", "foundation-plan-0.20.schema.json"), + path.join(skillDirectory, "references", "foundation-plan-0.21.schema.json"), "utf8", ); assert.equal( @@ -1130,7 +1130,7 @@ test("complete examples and eval Plans validate against the bundled exact schema foundationPlanSchemaDigest, ); const referenceSource = await readFile( - path.join(skillDirectory, "references", "foundation-plan-020.md"), + path.join(skillDirectory, "references", "foundation-plan-021.md"), "utf8", ); assert(referenceSource.includes(foundationPlanSchemaDigest)); @@ -2141,6 +2141,7 @@ test("analysis evals preserve fixture identity and recovery expectations", async "recurring-issues-analysis.json", "application-intent-valid-analysis.json", "appearance-current-analysis.json", + "theme-toggle-analysis.json", "mixed-application-issues-analysis.json", "unsupported-graph-analysis.json", ]; diff --git a/test/ui-foundation-authoring.test.mjs b/test/ui-foundation-authoring.test.mjs index 71a22a7..25de3ff 100644 --- a/test/ui-foundation-authoring.test.mjs +++ b/test/ui-foundation-authoring.test.mjs @@ -1,4 +1,5 @@ import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import test from "node:test"; @@ -7,7 +8,7 @@ import Ajv2020 from "ajv/dist/2020.js"; import { canonicalPluginSkillNames } from "../script/claude-plugin-boundaries.mjs"; const schema = JSON.parse(await readFile( - new URL("../skills/create-full-stack-app/references/foundation-plan-0.20.schema.json", import.meta.url), + new URL("../skills/create-full-stack-app/references/foundation-plan-0.21.schema.json", import.meta.url), "utf8", )); const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false }); @@ -65,3 +66,45 @@ test("Entity validation errors belong to a Field or Reference", () => { error_target: { record: "self" }, })); }); + + +test("Appearance offers only the authored theme choices", () => { + assert(validate("appearance", { tint_color: "#4F46E5" })); + for (const theme of ["light", "dark", "auto", "toggle"]) { + assert(validate("appearance", { theme })); + } + for (const theme of ["system", "Light", "user", { default: "system" }]) { + assert(!validate("appearance", { theme })); + } +}); + +test("the bundled Plan contract replaces the previous input identity", () => { + const validatePlan = ajv.getSchema(schema.$id); + const plan = { + format: "firstdraft.foundation-plan.sketch/0.21", + target: { id: "rails", profile: "rails-sketch/2026-09" }, + application: { key: "theme_app", name: "Theme App", native: {}, delivery: {}, entities: [], appearance: { theme: "toggle" } }, + }; + assert(validatePlan(plan)); + assert(!validatePlan({ ...plan, format: "firstdraft.foundation-plan.sketch/0.20" })); +}); + + +test("the toggle review fixture binds native residuals to the authored Plan", async () => { + const fixture = (name) => new URL(`../evals/create-full-stack-app/fixtures/${name}`, import.meta.url); + const source = await readFile(fixture("theme-toggle.foundation-plan.json"), "utf8"); + const plan = JSON.parse(source); + const { analysis } = JSON.parse(await readFile(fixture("theme-toggle-analysis.json"), "utf8")); + const digest = (value) => createHash("sha256").update(value).digest("hex"); + assert.equal(plan.application.appearance.theme, "toggle"); + assert.deepEqual(Object.keys(plan.application.native), ["ios", "android"]); + assert.equal(analysis.head_source_sha256, digest(source)); + assert.equal(analysis.gap_set.source.sha256, digest(source)); + assert.equal(analysis.gap_set_sha256, digest(`${JSON.stringify(analysis.gap_set, null, 2)}\n`)); + const themeGaps = analysis.gap_set.gaps.filter(({ kind }) => kind === "appearance_theme"); + assert.equal(themeGaps.length, 1); + assert.equal(themeGaps[0].pointer, "/application/appearance/theme"); + assert.equal(themeGaps[0].status, "partially_generated"); + assert.match(themeGaps[0].reason, /iOS and Android/); + assert.match(themeGaps[0].consequence, /embedded Rails responses follow system appearance/); +});