diff --git a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java index 6b3ea3341bd..382b9532505 100644 --- a/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java +++ b/scripts/initializr/common/src/main/java/com/codename1/initializr/model/GeneratorModel.java @@ -1501,6 +1501,12 @@ private String applyPathReplacements(String path) { return replaced; } + /// The skill's files and the two pointers to it: [#addAgentSkillEntries(Map)]. + static boolean isAgentSkillPath(String targetPath) { + return targetPath.startsWith(AGENT_SKILL_TARGET_PREFIX) || CLAUDE_SKILL_STUB_PATH.equals(targetPath) + || "AGENTS.md".equals(targetPath); + } + private byte[] applyDataReplacements(String targetPath, byte[] sourceData) throws IOException { if (!isTextFile(targetPath)) { return sourceData; @@ -1511,10 +1517,19 @@ private byte[] applyDataReplacements(String targetPath, byte[] sourceData) throw // Ahead of the replacements below, which the template's hints get too. content = (content.endsWith("\n") ? content : content + "\n") + readResourceToString(template.SETTINGS); } + // The agent skill names the demo applications (Wayline, Savor, ...) on purpose, + // so an agent can go and read them; renaming the template's own name inside it + // would turn "read Wayline's map code" into a pointer at a demo that does not + // exist. It still gets the generic placeholders below. + boolean agentSkillFile = isAgentSkillPath(targetPath); content = StringUtil.replaceAll(content, "com.example.myapp", packageName); - content = StringUtil.replaceAll(content, template.SOURCE_PACKAGE, packageName); + if (!agentSkillFile) { + content = StringUtil.replaceAll(content, template.SOURCE_PACKAGE, packageName); + } content = StringUtil.replaceAll(content, "MyAppName", appName); - content = StringUtil.replaceAll(content, template.SOURCE_MAIN_CLASS, appName); + if (!agentSkillFile) { + content = StringUtil.replaceAll(content, template.SOURCE_MAIN_CLASS, appName); + } content = StringUtil.replaceAll(content, "myappname", toLowerCaseInvariant(appName)); // The name in capitals is how an environment variable spells a setting // that starts with it: myappname.payments.key is read from diff --git a/scripts/initializr/common/src/main/resources/agent-skill-claude-stub.md b/scripts/initializr/common/src/main/resources/agent-skill-claude-stub.md index dba72f48327..16ab5f03da6 100644 --- a/scripts/initializr/common/src/main/resources/agent-skill-claude-stub.md +++ b/scripts/initializr/common/src/main/resources/agent-skill-claude-stub.md @@ -1,6 +1,6 @@ --- name: codename-one -description: Build and modify Codename One cross-platform mobile apps (Java 17, Maven or Gradle, ParparVM/Android/iOS/JavaScript). Use when the project contains a `codenameone_settings.properties` (in `common/` for Maven, at the root for Gradle), a `settings.gradle.kts` applying the `com.codenameone` plugin, depends on `com.codenameone:codenameone-core`, edits CSS files under `src/main/css/`, calls `cn1:run`, `cn1:test`, `cn1:build` or `./gradlew run`, references `com.codename1.ui.*` / `com.codename1.testing.*`, works in the `backend/` project (`cn1:backend`, `./gradlew runBackend`, `com.codename1.backend.*`, `@RestController`, `@Component`, `@Transactional`), or when the user asks to build a UI, write screen tests, generate screenshots, build a server or full-stack feature, or compare to Swing/HTML/Android. +description: Build and modify Codename One cross-platform apps (Java 17, Maven or Gradle; iOS, Android, desktop, web) and their Codename One Backend. Use when the project has `codenameone_settings.properties`, applies the `com.codenameone` Gradle plugin or depends on `codenameone-core`; when editing `src/main/css/` or code using `com.codename1.*`; when running `cn1:run`, `cn1:test`, `cn1:build`, `cn1:backend` or `./gradlew run`; when the user asks for a UI, screen tests, screenshots, a server or a full-stack feature; when porting or running existing Android, Swing, JavaFX, Flutter/Dart or Unity code (`src/main/android|desktop|flutter|unity`, `import-*-project`, `transcode-flutter`); or when asking how a Codename One app is best structured (the Wayline, Savor, Parley and Skirmish demos). metadata: type: skill --- diff --git a/scripts/initializr/common/src/main/resources/skill/SKILL.md b/scripts/initializr/common/src/main/resources/skill/SKILL.md index c7f49e28ceb..e0157362339 100644 --- a/scripts/initializr/common/src/main/resources/skill/SKILL.md +++ b/scripts/initializr/common/src/main/resources/skill/SKILL.md @@ -1,6 +1,6 @@ --- name: codename-one -description: Build and modify Codename One cross-platform mobile apps (Java 17, Maven or Gradle, ParparVM/Android/iOS/JavaScript). Use when the project contains a `codenameone_settings.properties` (in `common/` for Maven, at the root for Gradle), a `settings.gradle.kts` applying the `com.codenameone` plugin, depends on `com.codenameone:codenameone-core`, edits CSS files under `src/main/css/`, calls `cn1:run`, `cn1:test`, `cn1:build` or `./gradlew run`, references `com.codename1.ui.*` / `com.codename1.testing.*`, works in the `backend/` project (`cn1:backend`, `./gradlew runBackend`, `com.codename1.backend.*`, `@RestController`, `@Component`, `@Transactional`, `@Scheduled`), or when the user asks to build a UI, write screen tests, generate screenshots, build a server or full-stack feature, or compare to Swing/HTML. Also use when the project has Dart under `common/src/main/flutter/`, runs the `transcode-flutter` goal, references `com.codename1.flutter.*` / `FlutterUI`, or the user asks to port or embed Flutter/Dart code. +description: Build and modify Codename One cross-platform apps (Java 17, Maven or Gradle; iOS, Android, desktop, web) and their Codename One Backend. Use when the project has `codenameone_settings.properties`, applies the `com.codenameone` Gradle plugin or depends on `codenameone-core`; when editing `src/main/css/` or code using `com.codename1.*`; when running `cn1:run`, `cn1:test`, `cn1:build`, `cn1:backend` or `./gradlew run`; when the user asks for a UI, screen tests, screenshots, a server or a full-stack feature; when porting or running existing Android, Swing, JavaFX, Flutter/Dart or Unity code (`src/main/android|desktop|flutter|unity`, `import-*-project`, `transcode-flutter`); or when asking how a Codename One app is best structured (the Wayline, Savor, Parley and Skirmish demos). metadata: type: skill --- @@ -31,6 +31,8 @@ This skill teaches you how to write code for a Codename One (CN1) cross-platform - `references/html-css-cheatsheet.md` — Converting common HTML/CSS snippets to CN1 components + CSS. - `references/android-to-cn1.md` — Porting Android (XML + Kotlin/Java) screens to Codename One. - `references/react-to-cn1.md` — Bringing a **React / JSX + CSS design** (e.g. a Claude-generated bundle: `*.jsx` + `tokens.css` + `styles.css`) into Codename One: the component/hook/CSS mapping, the `tokens.css` -> `theme.css` token workflow, dark-mode re-skinning, and the **render-on-desktop-then-inspect** loop. Read this when the user hands you a React/HTML design to rebuild in CN1. +- `references/demos.md` — The four full-stack demo applications (Wayline ride hailing, Savor food delivery, Parley encrypted messenger, Skirmish 3D card game): where their source is, which one to read for which feature, and the structural patterns they all share. **Read this before inventing an app structure, networking layer, live-update channel or theme layout** -- copy how a demo does it. +- `references/compatibility.md` — The build-time compatibility tools: run an existing **Android** app (`src/main/android`), **Swing** or **JavaFX** app (`src/main/desktop`), **Flutter** code (`src/main/flutter`) or **Unity 2D** project (`src/main/unity`) on every Codename One target without rewriting it, the `import-*-project` goals, how to mix ported and new code, limits, and sample inputs. Read this first whenever the user has an existing app in one of those frameworks. - `references/flutter-to-cn1.md` — Bringing **Dart / Flutter widget source** into Codename One. Unlike the other porting guides this is mostly not a rewrite: the `transcode-flutter` Maven goal compiles Dart widgets to Java at build time (no Dart SDK involved), and `FlutterUI.wrap` / `FlutterUI.runApp` mount the result. Read this when the user has Dart or Flutter code, asks to embed a Flutter screen in a CN1 app, or asks whether a Flutter app can be ported. - `references/testing-and-screenshots.md` — `AbstractTest`, `TestUtils`, `screenshotTest`, the `cn1:test` Maven goal, the screenshot tolerance algorithm. - `references/mockup-comparison.md` — Building a screen to **match a designer mockup**: `tools/CompareToMockup.java` scores a render against a mockup image and prints a similarity % (with partial/region masking so device chrome doesn't sabotage the score), and `tools/DesignImport.java` turns a Figma/Sketch/Adobe XD file **or an HTML/React design's `tokens.css`/`styles.css`** (e.g. a Claude-generated mockup) into a starter `theme.css` + tokens + layout map. Read this when the user gives you a mockup, a Figma/Sketch/XD file, an HTML/CSS design bundle, or asks "how close is this screen to the design". @@ -378,6 +380,8 @@ If you cannot run the simulator (e.g. headless environment), **say so explicitly | "I have HTML/CSS, convert it" | `references/html-css-cheatsheet.md` | | "I have a React/JSX design (jsx + tokens.css), rebuild it in CN1" / "match this Claude design" | `references/react-to-cn1.md` | | "I have Android XML/Kotlin/Java, convert it" | `references/android-to-cn1.md` | +| "Run my existing Android / Swing / JavaFX / Flutter / Unity app" / `import-*-project` / `src/main/android`, `desktop`, `flutter`, `unity` | `references/compatibility.md` | +| "How should I structure this app" / "show me a real example" / best practice for networking, live updates, maps, chat, payments, games | `references/demos.md` (then read the demo's source) | | "Generate a client for this OpenAPI spec / `.proto` / GraphQL schema" / `@RestClient`, `@GrpcClient`, `@GraphQLClient` | `references/api-clients.md` | | "Write a test for this screen" / "Compare to a baseline" | `references/testing-and-screenshots.md` | | "Match this mockup" / "Compare to a Figma/Sketch/XD design" / "Import this HTML/CSS (Claude) design's tokens" / "How close is this screen to the design" | `references/mockup-comparison.md` | diff --git a/scripts/initializr/common/src/main/resources/skill/references/compatibility.md b/scripts/initializr/common/src/main/resources/skill/references/compatibility.md new file mode 100644 index 00000000000..bfc0aca381e --- /dev/null +++ b/scripts/initializr/common/src/main/resources/skill/references/compatibility.md @@ -0,0 +1,57 @@ +# Bringing existing code in: the compatibility tools + +Before you rewrite an existing application in Codename One, check whether it can +be **built as it is**. Codename One has build-time compatibility tools for four +ecosystems. Each one takes the original sources in a directory under +`common/src/main/` (Gradle: `src/main/`), turns them into ordinary Codename One +code during the build, and runs the result on every target -- iOS, Android, +desktop and web. Nothing interprets the original framework at run time. + +| You have | Put sources in | Import goal (Maven) | Runtime / tool | Deep reference | +| --- | --- | --- | --- | --- | +| A classic Android app (Activities, XML layouts, `res/`, Java/Kotlin) | `src/main/android` | `mvn cn1:import-android-project -Dcn1.android.import=` | `codenameone-android-compat` (the `android.*` API over CN1 components) | `references/android-to-cn1.md`, developer guide "Running Android applications" | +| A Swing / AWT / SwingX app | `src/main/desktop` | `mvn cn1:import-desktop-project -Dcn1.desktop.import=` | Swing compatibility layer | `references/swing-comparison.md`, developer guide "Running Swing and JavaFX applications" | +| A JavaFX app (incl. FXML and JavaFX CSS) | `src/main/desktop` | same goal as Swing | JavaFX compatibility layer; FXML and CSS compiled at build time | developer guide "Running Swing and JavaFX applications" | +| Flutter / Dart widget code | `src/main/flutter` | `mvn cn1:import-flutter-project -Dcn1.flutter.import=` | `transcode-flutter` goal compiles Dart to Java; `FlutterUI.wrap` / `FlutterUI.runApp` | `references/flutter-to-cn1.md` | +| A Unity 2D project | `src/main/unity` | `mvn cn1:import-unity-project -Dcn1.unity.import=` | C# assembly translated to Java; needs the .NET SDK at build time | developer guide "Unity" chapter | + +Gradle projects pick up the same directories automatically (the plugin adds the +runtime and the build steps when the directory exists). The import goals are +Maven goals; in a Gradle project copy the sources into the directory by hand. + +## How to work with them + +1. **Import, then build.** The build is the coverage check: an API the layer does + not implement is a **build error** naming the original class/member and source + line, not a run-time surprise. Read the errors as the porting to-do list. +2. **Fix gaps with Codename One code, not by stubbing the layer.** Replace an + unsupported call with the CN1 equivalent (see `ui-components.md`, + `java-api-subset.md`), or isolate desktop/phone-only behaviour behind a check. +3. **Mix freely.** The ported code and new Codename One code live in one app: + an Android `View` exposes its CN1 component via `getPeer()` (see `android.view.View` in `maven/android-compat`); + `SwingInterop.asComponent` / `FxInterop.asComponent` wrap Swing or JavaFX UI + as a `com.codename1.ui.Component`; `FlutterUI.wrap` returns a `Container`. + This is how to migrate a screen at a time. +4. **Verify behaviour, not just the build.** Run it in the simulator and drive + the flows (see `mcp-agent-control.md`). Translation succeeding does not mean + every callback is honoured -- Flutter in particular is experimental. + +## Limits to tell the user up front + +- No binary drop-in: third-party jars that use `java.awt`, `javax.swing`, + `javafx` or Android APIs are not relocated and fail the compliance check. +- No reflection-driven frameworks, class loaders, `ServiceLoader`, OSGi. +- Android: Jetpack Compose is not covered. +- Desktop coordinates are logical pixels scaled to the device density. + +## Samples to learn from + +Small, complete inputs that CI builds through each tool (in the Codename One +repository, `https://github.com/codenameone/CodenameOne/tree/master/scripts/...`): + +- `android-compat-samples/gallery` -- an Android Studio project (Java + Kotlin, `res/`). +- `desktop-compat-samples/swing-gallery`, `desktop-compat-samples/javafx-gallery`. +- `flutter-apps/field_notes`, `flutter-apps/pet_adoption` -- Flutter apps ported with the transcoder. +- `unity-compat-samples/*` -- Unity 2D scenes and games. + +For idiomatic *native* Codename One code to evolve toward, see `references/demos.md`. diff --git a/scripts/initializr/common/src/main/resources/skill/references/demos.md b/scripts/initializr/common/src/main/resources/skill/references/demos.md new file mode 100644 index 00000000000..5415eadb4d1 --- /dev/null +++ b/scripts/initializr/common/src/main/resources/skill/references/demos.md @@ -0,0 +1,72 @@ +# The demo applications: where to learn idiomatic Codename One + +Codename One ships four complete, production-shaped applications. Each is an app +**and** its server, written by the Codename One team, tested end to end, and kept +building against the current framework. When you are unsure how something is +"meant" to be done -- a screen, a navigation pattern, a live channel, a REST +contract, a test, a theme -- **read how a demo does it before inventing a +pattern.** Copy the shape, not the domain. + +## Where the source is + +- **If this project was generated from one of these templates in the Initializr**, + the demo *is* this project: its packages were renamed to yours, but the + structure below is what you have on disk, and the demo's main class is named + after your app. Read it locally. +- Otherwise read it on GitHub (no clone needed; fetch the raw file you want): + `https://github.com/codenameone/CodenameOne/tree/master/scripts/`, raw + files at `https://raw.githubusercontent.com/codenameone/CodenameOne/master/scripts//`. +- Each demo has an implementor's guide in the developer guide + (`docs/developer-guide/-Implementors-Guide.asciidoc` in the same repository). + It explains *why* the code is laid out the way it is -- read it before making a + structural change modeled on a demo. + +All four share this layout, which is itself the first best practice (Skirmish adds a +`rules` module, the deterministic game simulation the app, server and tests all compile): + +| Module | Holds | +| --- | --- | +| `shared` | The contract: DTOs and `@RestClient` interfaces. The app's build generates the client, the server's build generates the interfaces it implements, so a change one side missed is a compile error. | +| `common` | The application. One codebase for simulator, desktop, Android, iOS and web -- no platform modules. | +| `backend` | The Codename One Backend server (`@RestController`, entities in `domain`, migration scripts in `src/main/resources/db/migration`, runs on SQLite/PostgreSQL/MySQL/MariaDB). | + +Run any of them with `CN1_PROFILE=dev backend/server.sh run` and, in another +terminal, `mvn verify -Psimulator -DskipTests -Dcodename1.platform=javase`. The +README of each lists the demo accounts. + +## Which demo to read for what + +| Demo | What it is | Read it for | +| --- | --- | --- | +| `scripts/wayline` | Ride hailing: rider, driver, admin | Role-based start screens, maps and live markers (`map/`), bottom sheets, wallet and card entry (`pay/`), admin dashboards and charts (`admin/`, `ui/Chart.java`), the reference implementation of the shared-contract full-stack pattern | +| `scripts/savor` | Food delivery: customer, merchant, courier, admin | Catalog/menu browsing, cart and checkout (`customer/`), merchant order queues and menu editing (`merchant/`), two state machines (order + delivery) mirrored between `shared` enums and the server | +| `scripts/parley` | End-to-end encrypted messenger with calls | Chat UI and media (`chat/`, `ui/ChatForm.java`), crypto with core APIs (`crypto/`), WebRTC calls (`com.codename1.webrtc`, `ui/CallScreen.java`), local store (`store/`), push and WebSocket delivery | +| `scripts/skirmish` | Real-time 3D card battler | Games: `GameView`/3D rendering (`battle/`), a deterministic `rules` module shared by app, server and tests, replays, in-app purchases and rewarded ads (`engage/`), matchmaking over sockets, in-app editors (`forge/`) | + +## Patterns the app demos share (copy these) + +Wayline, Savor and Parley follow all of these. Skirmish is a game and differs: +its socket is `net/GameLink.java` rather than `live/LiveChannel.java`, and it has +no `Prefs`, `Telemetry` or l10n bundles. + +- **Thin entry class.** `/.java` extends `Lifecycle`; configuration in + `AppConfig`, preferences in `Prefs`, opt-in analytics in `Telemetry`. +- **One networking layer.** `net/Net.java` + `net/Api.java` wrap the generated + `@RestClient`s, `net/Session.java` holds the signed-in state; screens never + build URLs or parse JSON themselves. +- **Live updates through one channel.** `live/LiveChannel.java` owns the + WebSocket and fans events out on the EDT; screens subscribe rather than poll. +- **UI helpers instead of repeated code.** `ui/Ui.java`, `ui/Layouts.java`, + `ui/Nav.java`, `ui/Look.java`, `ui/Skeleton.java` (loading placeholders), + `ui/BottomSheet.java`. Look for an existing helper before adding a component. +- **Theme driven by variables.** `common/src/main/css/theme.css` opens with a + block of variables (colors, corner radius) everything else is drawn from; dark + mode and color schemes swap those, not individual rules. +- **Localized from day one.** All text in `common/src/main/l10n` bundles, + including an RTL language (Hebrew), so layouts are checked mirrored. +- **Tested end to end.** `run-e2e.sh` starts the server on the `test` profile + and drives the app in the simulator; screenshots land in `target/e2e-shots`. + Backend tests run with `-Dcodename1.platform=backend test`. + +When you borrow from a demo, keep its conventions consistent with the code +already in this project; do not paste a demo's domain classes wholesale. diff --git a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java index 25769ead74a..7cce0cb36f6 100644 --- a/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java +++ b/scripts/initializr/common/src/test/java/com/codename1/initializr/model/GeneratorModelMatrixTest.java @@ -445,8 +445,20 @@ private void assertFullStackProject(Template template, Map entri assertContains(getText(entries, "README.md"), game ? "## The game and its server" : "## The app and its server", label + "README should explain the app and its server"); + // The demo template ships the agent skill like every app, and the skill's + // demo guide is how an agent finds this very demo's upstream source, so the + // template's name survives there -- and only there. + assertNotNull(entries.get(".agent-skills/codename-one/references/demos.md"), + label + "a demo project ships the skill's demo guide"); + assertNotNull(entries.get(".agent-skills/codename-one/references/compatibility.md"), + label + "a demo project ships the skill's compatibility guide"); + assertContains(getText(entries, ".agent-skills/codename-one/references/demos.md"), "scripts/" + lower, + label + "the demo guide still points at this demo's source"); for (Map.Entry entry : entries.entrySet()) { String path = entry.getKey(); + if (GeneratorModel.isAgentSkillPath(path)) { + continue; + } assertFalse(path.indexOf(lower) >= 0 || path.indexOf(name) >= 0, label + "the template's name is still in the path " + path); if (path.endsWith(".png") || path.endsWith(".jar") || path.endsWith(".res") || path.endsWith(".ttf") @@ -990,6 +1002,9 @@ private void validateClaudeSkillBundled() throws Exception { ".agent-skills/codename-one/references/ai-and-speech.md", ".agent-skills/codename-one/references/backend.md", ".agent-skills/codename-one/references/full-stack-loop.md", + ".agent-skills/codename-one/references/demos.md", + ".agent-skills/codename-one/references/compatibility.md", + ".agent-skills/codename-one/references/flutter-to-cn1.md", ".agent-skills/codename-one/tools/README.md", ".agent-skills/codename-one/tools/IsApiSupported.java", ".agent-skills/codename-one/tools/IsCssValid.java" diff --git a/scripts/tests/generate-initializr-fixtures.py b/scripts/tests/generate-initializr-fixtures.py index 66337422d2a..4366aa09467 100644 --- a/scripts/tests/generate-initializr-fixtures.py +++ b/scripts/tests/generate-initializr-fixtures.py @@ -143,6 +143,10 @@ def check_full_stack_fixture(archive, z, template): assert ('## The game and its server' if rules else '## The app and its server') in z.read('README.md').decode(), \ archive.name for name in names: + # The agent skill names the demos on purpose (GeneratorModel.isAgentSkillPath): + # it is how an agent finds this demo's upstream source. + if name.startswith('.agent-skills/') or name in ('AGENTS.md', '.claude/skills/codename-one/SKILL.md'): + continue assert token not in name.lower(), (archive.name, name) if name.endswith(('.png', '.jar', '.res', '.ttf', '.glb')): continue