diff --git a/tutorials/ai/prompt-scaffolding/README.md b/tutorials/ai/prompt-scaffolding/README.md
new file mode 100644
index 0000000..1d18804
--- /dev/null
+++ b/tutorials/ai/prompt-scaffolding/README.md
@@ -0,0 +1,55 @@
+This setup allows you to scaffold a Java desktop app with JxBrowser for the
+webview, a React UI (with shadcn/ui), and Protobuf/gRPC for communication
+between Java and JavaScript.
+
+## Prerequisites
+
+- Java 17 or higher
+- [Node.js](https://nodejs.org/en/download) 20.11.0 or higher
+- [Cursor](https://cursor.com/)
+- Claude 4 Sonnet
+
+## How to use
+
+Download the [`docs`](docs) and [`template`](template)
+directories and put them into an empty project.
+
+Add the [`architecture-overview.md`](docs/architecture-overview.md)
+document to the context for a new project in Cursor, and ask the LLM to
+generate the app using the prompt below:
+
+
+
+```markdown
+Please read and analyse the architecture in architecture-overview.md we've used
+in our previous project, and let's create a new demo project from it.
+
+Let's create a simple desktop application with the functionality of a basic
+filesystem viewer. The front-end part will be responsible for displaying the
+file system structure, like folders and files. The backend part will access the
+Java filesystem API and provide the data to the front-end.
+```
+
+## How to run
+
+When all the project files are generated, you can start the app by first running
+the Vite dev server:
+
+```bash
+cd web-app
+npm run dev
+```
+
+Then, in another terminal, launch the application itself:
+
+```bash
+./gradlew run
+```
+
+You should see the desktop app window with shadcn/ui (React) that allows viewing
+local files/folders and navigating through them:
+
+
+
+After the application successfully started, you can remove the [`docs`](docs)
+and [`template`](template) directories.
diff --git a/tutorials/ai/prompt-scaffolding/docs/architecture-overview.md b/tutorials/ai/prompt-scaffolding/docs/architecture-overview.md
new file mode 100644
index 0000000..508f2d3
--- /dev/null
+++ b/tutorials/ai/prompt-scaffolding/docs/architecture-overview.md
@@ -0,0 +1,1269 @@
+# Desktop Web App Architecture Overview
+
+This document describes the reusable architecture pattern for creating cross-platform desktop applications using JxBrowser with Java backend and modern web frontend technologies.
+
+## β οΈ CRITICAL PREREQUISITES FOR LLMs β οΈ
+
+### JxBrowser License Requirement
+- **COMMERCIAL PRODUCT** requiring license from [TeamDev JxBrowser](https://www.teamdev.com/jxbrowser)
+- **MUST SET** environment variable: `JXBROWSER_LICENSE_KEY=your-license-key`
+- **USE IN CODE**: `System.getenv("JXBROWSER_LICENSE_KEY")`
+
+### π¦ Required Gradle Imports
+**MANDATORY**: Always include these imports at the top of `build.gradle.kts`:
+```kotlin
+// MANDATORY at top of build.gradle.kts
+import com.google.protobuf.gradle.id
+import org.gradle.api.JavaVersion.VERSION_17
+```
+
+### SwingUtilities Threading Rule
+- β NEVER wrap entire application in `SwingUtilities.invokeLater()`
+- β
Only wrap Swing component creation
+
+## High-Level Architecture
+
+Hybrid desktop architecture combining:
+- **Java Backend**: Business logic, gRPC services, data persistence
+- **Web Frontend**: React + TypeScript + Tailwind CSS
+- **JxBrowser**: Chromium engine bridging Java and JavaScript
+- **gRPC Communication**: Protocol Buffers for type-safe communication
+
+```
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β Desktop Application β
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
+β βββββββββββββββββββ βββββββββββββββββββββββββββββββββββ β
+β β Java Backend βββββΊβ JxBrowser Engine β β
+β β β β β β
+β β β’ Business Logicβ β β’ Chromium Browser β β
+β β β’ gRPC Services β β β’ JavaScript-Java Bridge β β
+β β β’ Data Models β β β’ Custom URL Schemes β β
+β β β’ File I/O β β β β
+β βββββββββββββββββββ β βββββββββββββββββββββββββββββ β β
+β β β Web Frontend β β β
+β β β β’ React + TypeScript β β β
+β β β β’ shadcn/ui + Tailwind β β β
+β β β β’ gRPC-Web Client β β β
+β β βββββββββββββββββββββββββββββ β β
+β βββββββββββββββββββββββββββββββββββ β
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+```
+
+## Project Structure
+
+```
+desktop-web-app/
+βββ build.gradle.kts # Main build configuration
+βββ proto/ # Protocol Buffer definitions
+βββ src/main/java/ # Java backend
+β βββ App.java # Entry point
+β βββ AppInitializer.java # Bootstrap & configuration
+β βββ AppDetails.java # App metadata & paths
+β βββ [domain]/ # Business domains
+β β βββ *Service.java # gRPC implementations
+β β βββ *.java # Data models
+β βββ production/ # Production utilities
+β βββ MimeTypes.java
+β βββ UrlRequestInterceptor.java
+βββ web-app/ # Frontend application
+ βββ package.json
+ βββ vite.config.ts
+ βββ buf.gen.yaml # Protobuf generation
+ βββ src/
+ βββ main.tsx # Entry point
+ βββ gen/ # Generated protobuf types
+ βββ rpc/ # gRPC clients
+ βββ components/ # React components
+```
+
+### Frontend Structure
+```
+web-app/
+βββ package.json # NPM dependencies and scripts
+βββ vite.config.ts # Build tool configuration
+βββ tailwind.config.js # Styling configuration
+βββ buf.gen.yaml # Protobuf code generation config
+βββ src/
+β βββ main.tsx # Application entry point
+β βββ App.tsx # Main React component
+β βββ gen/ # Generated protobuf types (auto-generated)
+β βββ rpc/ # gRPC client configuration
+β β βββ client.ts # gRPC transport setup
+β β βββ *-client.ts # Service-specific clients
+β βββ components/ # React components
+β β βββ ui/ # Reusable UI components (shadcn/ui)
+β β βββ *.tsx # Feature-specific components
+β βββ hooks/ # Custom React hooks
+β βββ lib/ # Utility functions
+β βββ context/ # React context providers
+β βββ converter/ # Data conversion utilities
+β βββ storage/ # Client-side storage utilities
+βββ dist/ # Built assets (auto-generated)
+```
+
+## Build Configuration
+
+### Complete build.gradle.kts Template
+
+```kotlin
+// β οΈ MANDATORY IMPORTS - Always include at the top
+import com.google.protobuf.gradle.id
+import org.gradle.api.JavaVersion.VERSION_17
+
+plugins {
+ java
+ id("com.google.protobuf") version "0.9.1"
+ id("application")
+
+ // β οΈ CRITICAL: Official JxBrowser Gradle plugin
+ id("com.teamdev.jxbrowser") version "1.2.1"
+}
+
+java {
+ sourceCompatibility = VERSION_17
+ targetCompatibility = VERSION_17
+}
+
+jxbrowser {
+ version = "8.9.4" // Set JxBrowser version
+}
+
+dependencies {
+ // β οΈ CRITICAL: JxBrowser dependencies using official plugin
+ implementation(jxbrowser.currentPlatform) // Auto-detects platform
+ implementation(jxbrowser.swing) // For Swing integration
+
+ // gRPC & Protocol Buffers
+ implementation("com.linecorp.armeria:armeria-grpc:1.30.1")
+ implementation("com.linecorp.armeria:armeria:1.30.1")
+ implementation("com.linecorp.armeria:armeria-grpc-protocol:1.30.1")
+ implementation("com.google.protobuf:protobuf-java:3.21.12")
+
+ // Utilities
+ implementation("com.google.code.gson:gson:2.10.1")
+}
+
+application {
+ applicationDefaultJvmArgs = listOf("-Dapp.dev.mode=true")
+ mainClass.set("com.teamdev.jxbrowser.examples.App")
+}
+sourceSets {
+ main {
+ proto {
+ srcDir("proto")
+ include("*.proto")
+ }
+ }
+}
+
+protobuf {
+ protoc {
+ artifact = "com.google.protobuf:protoc:3.21.12"
+ plugins {
+ // β
CORRECT - requires import above
+ id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.67.1" }
+ }
+ generateProtoTasks {
+ all().forEach { task ->
+ task.plugins {
+ // β
CORRECT - requires import above
+ id("grpc") {}
+ }
+ }
+ }
+ }
+}
+
+// Build pipeline tasks
+tasks.register("buildWeb") {
+ dependsOn("installNpmPackages", "generateJsProto")
+ // Builds frontend assets into web-app/dist/
+}
+
+tasks.jar {
+ archiveFileName.set(mainJar) // Explicitly set JAR name
+ dependsOn(tasks.named("buildWeb"))
+ manifest {
+ attributes["Main-Class"] = application.mainClass.get()
+ }
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ from({
+ configurations.runtimeClasspath.get().map {
+ if (it.isDirectory) it else zipTree(it)
+ }
+ })
+
+ doLast {
+ copy {
+ from("build/libs/$mainJar")
+ into("build/dist")
+ }
+ }
+}
+```
+
+### 2. β
JxBrowser License Usage
+```java
+// β
CORRECT - Always use environment variable directly
+var optionsBuilder = EngineOptions.newBuilder(HARDWARE_ACCELERATED)
+ .licenseKey(System.getenv("JXBROWSER_LICENSE_KEY")); // Direct env var access
+```
+
+### 3. β
Required Environment Variable
+```bash
+# Set this before running the application
+export JXBROWSER_LICENSE_KEY=your-license-key-from-teamdev
+```
+
+### 4. β οΈ CRITICAL SwingUtilities Usage
+```java
+// β WRONG - Never wrap entire application
+SwingUtilities.invokeLater(() -> {
+ // All application code here - BLOCKS AWT thread!
+});
+
+// β
CORRECT - Only wrap UI creation
+public void initialize() throws InterruptedException {
+ setupLogging(); // Main thread
+ var engine = createEngine(); // Main thread
+ var browser = engine.newBrowser(); // Main thread
+ setupUI(engine, browser); // Uses invokeLater internally for UI only
+ initializeRpc(browser); // Main thread - server.blockUntilShutdown() OK
+}
+```
+
+### 6. π οΈ Enable DevTools in Development Mode
+```java
+// β
CORRECT - Always enable devtools in development mode
+private static void setupBrowserCallbacks(Browser browser) {
+ browser.set(InjectJsCallback.class, params -> {
+ JsObject window = params.frame().executeJavaScript("window");
+ if (window != null) {
+ window.putProperty("rpcPort", RPC_PORT);
+ }
+ return InjectJsCallback.Response.proceed();
+ });
+
+ if (!isProductionMode()) {
+ browser.devTools().show(); // β
Enable devtools in dev mode
+ }
+}
+```
+
+### Frontend Dependencies (package.json)
+
+```json
+{
+ "dependencies": {
+ // Core React ecosystem
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "react-router-dom": "^6.22.1",
+
+ // gRPC communication (ConnectRPC codegen v2)
+ "@connectrpc/connect": "2.0.0-rc.2",
+ "@connectrpc/connect-web": "2.0.0-rc.2",
+ "@bufbuild/protobuf": "2.2.1",
+
+ // UI framework
+ "@radix-ui/react-avatar": "^1.1.3",
+ "@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-dropdown-menu": "^2.1.6",
+ "@radix-ui/react-label": "^2.1.2",
+ "@radix-ui/react-popover": "^1.1.6",
+ "@radix-ui/react-switch": "^1.1.3",
+ "tailwindcss": "^4.1.1", // Tailwind 4
+ "@tailwindcss/postcss": "^4.1.1",
+ "lucide-react": "^0.507.0", // Icons
+ "clsx": "^2.1.0",
+ "tailwind-merge": "^3.1.0",
+
+ // Development tools
+ "vite": "^5.1.0", // Build tool
+ "typescript": "^5.2.2" // Type safety
+ },
+ "devDependencies": {
+ // Protocol Buffer generation (codegen v2)
+ "@bufbuild/buf": "1.46.0",
+ "@bufbuild/protoc-gen-es": "2.2.1"
+ }
+}
+```
+
+### Frontend Development Guidelines
+
+#### π¨ Use shadcn/ui Components (NOT Custom Components)
+
+**CRITICAL**: Always use shadcn/ui components instead of building components from scratch.
+
+**β
CORRECT - Install and use shadcn/ui components:**
+```bash
+# Install components as needed
+npx shadcn@latest add button
+npx shadcn@latest add input
+npx shadcn@latest add card
+npx shadcn@latest add dialog
+npx shadcn@latest add dropdown-menu
+npx shadcn@latest add form
+npx shadcn@latest add table
+npx shadcn@latest add tabs
+npx shadcn@latest add select
+npx shadcn@latest add checkbox
+```
+
+**Then use in React components:**
+```tsx
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+
+export function MyComponent() {
+ return (
+
+
+ Settings
+
+
+
+
+
+
+ )
+}
+```
+
+**TypeScript/Frontend Issues:**
+- **"Cannot mix BigInt and other types"**: This is fixed by protobuf utility functions that handle BigInt conversion
+
+**β WRONG - Don't build basic components from scratch:**
+```tsx
+// Don't do this - use shadcn/ui instead
+export function CustomButton({ children, onClick }) {
+ return (
+
+ )
+}
+```
+
+**Available shadcn/ui Components:**
+- `button`, `input`, `textarea`, `select`, `checkbox`, `radio-group`
+- `card`, `dialog`, `popover`, `tooltip`, `sheet`
+- `table`, `form`, `tabs`, `accordion`, `collapsible`
+- `dropdown-menu`, `navigation-menu`, `breadcrumb`
+- `alert`, `badge`, `progress`, `skeleton`, `spinner`
+- And many more - check [shadcn/ui docs](https://ui.shadcn.com)
+
+## Data Model Architecture
+
+### Protocol Buffer Definitions
+Data models are defined once in `.proto` files and code is generated for both Java and TypeScript:
+
+```protobuf
+// Example: prefs.proto
+syntax = "proto3";
+package com.teamdev.jxbrowser.prefs;
+
+// Service definition
+service PrefsService {
+ rpc GetAccount(google.protobuf.Empty) returns (Account);
+ rpc SetAccount(Account) returns (google.protobuf.Empty);
+ // ... other methods
+}
+
+// Data models
+message Account {
+ string email = 1;
+ string full_name = 2;
+ TwoFactorAuthentication two_factor_authentication = 3;
+ bool biometric_authentication = 4;
+}
+
+enum TwoFactorAuthentication {
+ EMAIL = 0;
+ SMS = 1;
+ PASS_KEY = 2;
+}
+```
+
+### Code Generation Process
+1. **Java**: Gradle plugin generates classes in `build/generated/source/proto/`
+2. **TypeScript**: Buf CLI generates types in `web-app/src/gen/` using **codegen v2**
+
+### Proto Generation Configuration
+The proto files should be located in the **root `proto/` directory**, and the `buf.gen.yaml` should reference it:
+
+```yaml
+# web-app/buf.gen.yaml
+version: v2
+inputs:
+ - directory: ../proto # Points to root proto directory
+plugins:
+ - local: protoc-gen-es
+ opt: target=ts
+ out: src/gen
+```
+
+## Communication Layer
+
+### gRPC Service (Java)
+```java
+public final class PrefsService extends PrefsServiceImplBase {
+ @Override
+ public void getAccount(Empty request, StreamObserver responseObserver) {
+ responseObserver.onNext(appPrefs.account());
+ responseObserver.onCompleted();
+ }
+}
+```
+
+### gRPC Client (TypeScript)
+```typescript
+import { createGrpcWebTransport } from "@connectrpc/connect-web";
+import { createClient } from "@connectrpc/connect";
+import { PrefsService } from "@/gen/prefs_pb.ts";
+
+declare const rpcPort: Number; // Injected by JxBrowser
+
+const transport = createGrpcWebTransport({
+ baseUrl: `http://localhost:${rpcPort}`,
+});
+
+const prefsClient = createClient(PrefsService, transport);
+```
+
+### JavaScript-Java Bridge
+JxBrowser injects the gRPC port into the JavaScript context. **The RPC port should be the same for both web UI and Java app (e.g., 9090 or 50051)**:
+
+```java
+// Java side (AppInitializer.java)
+private static final int RPC_PORT = 50051; // Same port for both frontend and backend
+
+browser.set(InjectJsCallback.class, params -> {
+ JsObject window = params.frame().executeJavaScript("window");
+ if (window != null) {
+ window.putProperty("rpcPort", RPC_PORT);
+ }
+ return InjectJsCallback.Response.proceed();
+});
+```
+
+### CORS Configuration for Armeria Server
+The gRPC server must be configured with proper CORS support. This requires the `armeria-grpc-protocol` dependency:
+
+```java
+// AppInitializer.java - initializeRpc method
+private static void initializeRpc(Browser browser) throws InterruptedException {
+ var serverBuilder = Server.builder().http(RPC_PORT);
+ var corsBuilder = CorsService.builder(appUrl())
+ .allowRequestMethods(HttpMethod.POST)
+ .allowRequestHeaders(
+ HttpHeaderNames.CONTENT_TYPE,
+ HttpHeaderNames.of("x-grpc-web"),
+ HttpHeaderNames.of("x-user-agent"))
+ .exposeHeaders(GrpcHeaderNames.GRPC_STATUS,
+ GrpcHeaderNames.GRPC_MESSAGE,
+ GrpcHeaderNames.ARMERIA_GRPC_THROWABLEPROTO_BIN);
+
+ serverBuilder.service(GrpcService.builder()
+ .addService(new YourService())
+ .build(),
+ corsBuilder.newDecorator(),
+ LoggingService.newDecorator());
+
+ try (var server = serverBuilder.build()) {
+ server.start();
+ browser.navigation().loadUrl(appUrl());
+ server.blockUntilShutdown();
+ }
+}
+```
+
+### Frontend Build Pipeline
+1. **NPM Install**: Install Node.js dependencies
+2. **Generate Protobuf**: Create TypeScript types from `.proto` files
+3. **Vite Build**: Compile and bundle React application
+4. **Asset Integration**: Include built assets in Java JAR
+
+## URL Request Interception
+
+### Custom Scheme Handling
+To properly set up URL request interception, you must register a custom scheme as shown in [JxBrowser documentation](https://teamdev.com/jxbrowser/docs/guides/network/#registering-custom-scheme). In production mode, the application uses a custom URL scheme (`jxbrowser://`) to serve frontend assets from JAR resources instead of external files.
+
+### URL Request Interceptor Implementation
+
+**Complete implementation with proper imports and Java 17+ syntax:**
+
+```java
+// Required imports - use these exact imports
+import com.teamdev.jxbrowser.net.HttpHeader;
+import com.teamdev.jxbrowser.net.HttpStatus;
+import com.teamdev.jxbrowser.net.UrlRequestJob;
+import com.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URI;
+
+public final class UrlRequestInterceptor implements InterceptUrlRequestCallback {
+ private static final String CONTENT_ROOT = "/web";
+ private static final String INDEX_HTML = "/index.html";
+ private static final String CONTENT_TYPE = "Content-Type";
+
+ @Override
+ public Response on(Params params) {
+ var url = params.urlRequest().url();
+
+ // Check if URL matches our custom scheme
+ if (url.contains(AppDetails.appUrl())) {
+ var uri = URI.create(url);
+ var fileName = uri.getPath().equals("/") ? INDEX_HTML : uri.getPath();
+
+ // Create URL request job and serve file
+ var job = urlRequestJob(params, fileName);
+ readFile(fileName, job);
+ return InterceptUrlRequestCallback.Response.intercept(job);
+ }
+
+ // Let other requests proceed normally
+ return InterceptUrlRequestCallback.Response.proceed();
+ }
+
+ private static UrlRequestJob urlRequestJob(InterceptUrlRequestCallback.Params params, String file) {
+ var builder = UrlRequestJob.Options.newBuilder(HttpStatus.OK);
+ builder.addHttpHeader(contentType(file));
+ return params.newUrlRequestJob(builder.build());
+ }
+
+ private static void readFile(String fileName, UrlRequestJob job) {
+ try (var stream = UrlRequestInterceptor.class.getResourceAsStream(CONTENT_ROOT + fileName)) {
+ if (stream == null) {
+ throw new FileNotFoundException(CONTENT_ROOT + fileName);
+ }
+ job.write(stream.readAllBytes());
+ job.complete();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static HttpHeader contentType(String file) {
+ return HttpHeader.of(CONTENT_TYPE, MimeTypes.mimeType(file).value());
+ }
+}
+```
+
+### Resource Serving Pipeline
+
+1. **URL Matching**: Interceptor checks if request URL matches the custom scheme
+2. **Path Resolution**: Maps URL path to resource file (defaults to `index.html` for root)
+3. **Resource Loading**: Reads file from JAR classpath (`/web/` directory)
+4. **MIME Type Detection**: Determines appropriate Content-Type header
+5. **Response Creation**: Creates HTTP response with proper headers and content
+
+### MIME Type Handling
+
+The application includes a comprehensive MIME type mapping system:
+
+```java
+import com.teamdev.jxbrowser.net.MimeType;
+import java.util.Map;
+import java.util.HashMap;
+import static java.util.Locale.ENGLISH;
+import static com.teamdev.jxbrowser.net.MimeType.OCTET_STREAM;
+
+public final class MimeTypes {
+ private static final Map MIME_TYPES = Map.of(
+ "html", MimeType.of("text/html"),
+ "css", MimeType.of("text/css"),
+ "js", MimeType.of("application/javascript"),
+ "json", MimeType.of("application/json"),
+ "png", MimeType.of("image/png"),
+ "jpg", MimeType.of("image/jpeg"),
+ "jpeg", MimeType.of("image/jpeg"),
+ "gif", MimeType.of("image/gif"),
+ "svg", MimeType.of("image/svg+xml"),
+ "ico", MimeType.of("image/x-icon"),
+ "woff", MimeType.of("font/woff"),
+ "woff2", MimeType.of("font/woff2"),
+ "ttf", MimeType.of("font/ttf")
+ );
+
+ public static MimeType mimeType(String fileName) {
+ var fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1);
+ return MIME_TYPES.getOrDefault(fileExtension.toLowerCase(ENGLISH), OCTET_STREAM);
+ }
+}
+```
+
+**Supported File Types**:
+- **Web Assets**: `.html`, `.css`, `.js`, `.json`
+- **Images**: `.png`, `.jpg`, `.gif`, `.svg`, `.ico`, `.webp`
+- **Fonts**: `.woff`, `.woff2`, `.ttf`
+- **Media**: `.mp3`, `.mp4`, `.wav`, `.webm`
+- **Documents**: `.pdf`, `.txt`, `.xml`
+
+### Engine Configuration
+```java
+private static Engine createEngine() {
+ var optionsBuilder = EngineOptions.newBuilder(HARDWARE_ACCELERATED)
+ .userDataDir(AppDetails.INSTANCE.chromiumUserDataDir())
+ .licenseKey(System.getenv("JXBROWSER_LICENSE_KEY")); // β
CORRECT - Direct env var access
+
+ // Register custom scheme only in production mode
+ if (isProductionMode()) {
+ // Use Scheme type, not String
+ private static final Scheme SCHEME = of("jxbrowser");
+ optionsBuilder.addScheme(SCHEME, new UrlRequestInterceptor());
+ }
+
+ return Engine.newInstance(optionsBuilder.build());
+}
+```
+
+### Application Details Configuration
+
+The `AppDetails` class provides application-specific configuration:
+
+```java
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class AppDetails {
+ public static final AppDetails INSTANCE = new AppDetails();
+
+ private static final String APP_NAME = "YourApp";
+ private static final String CUSTOM_HOST = "my-app.com";
+
+ public Path chromiumUserDataDir() {
+ return userDataDir().resolve("chromium");
+ }
+
+ public Path appResourcesDir() {
+ return userDataDir();
+ }
+
+ private Path userDataDir() {
+ String os = System.getProperty("os.name").toLowerCase();
+ String userHome = System.getProperty("user.home");
+
+ if (os.contains("win")) {
+ return Paths.get(System.getenv("LOCALAPPDATA"), APP_NAME);
+ } else if (os.contains("mac")) {
+ return Paths.get(userHome, "Library", "Application Support", APP_NAME);
+ } else {
+ return Paths.get(userHome, ".local", "share", APP_NAME);
+ }
+ }
+
+ public static String appUrl() {
+ return isProductionMode() ?
+ CUSTOM_SCHEME + "://" + CUSTOM_HOST :
+ "http://localhost:5173";
+ }
+
+ public static boolean isProductionMode() {
+ return !Boolean.getBoolean("app.dev.mode");
+ }
+
+ public static String getAppIconFileName() {
+ String os = System.getProperty("os.name").toLowerCase();
+ return os.contains("mac") ? "app.icns" : "app.ico";
+ }
+}
+```
+
+### Development vs Production Modes
+
+**Development Mode** (set via JVM property in build.gradle.kts):
+```kotlin
+application {
+ applicationDefaultJvmArgs = listOf("-Dapp.dev.mode=true")
+ mainClass.set("com.teamdev.jxbrowser.examples.App")
+}
+```
+
+- Frontend served by Vite dev server (HMR enabled)
+- JxBrowser loads `http://localhost:5173`
+- No URL interception needed
+- DevTools enabled
+- Live reload for both frontend and backend changes
+- gRPC server runs alongside the main Java application (not as separate Gradle task)
+
+**Production Mode**:
+- Frontend assets bundled into JAR resources
+- JxBrowser loads `jxbrowser://my-app.com` (custom scheme)
+- URL interceptor serves assets from classpath
+- Optimized builds
+- Single executable JAR
+
+### Resource Directory Structure
+
+Frontend build assets are included in the JAR under `/web/`:
+
+```
+JAR resources/
+βββ web/ # Frontend build output
+β βββ index.html # Main HTML file
+β βββ assets/ # Vite-generated assets
+β β βββ *.js # JavaScript bundles
+β β βββ *.css # Stylesheets
+β β βββ *.woff2 # Font files
+β βββ *.ico # Favicon and icons
+βββ mime-types.properties # MIME type mappings
+βββ *.png # Application icons
+```
+
+### Implementation Guidelines for New Projects
+
+// Only wrap UI code in invokeLater
+SwingUtilities.invokeLater(() -> {
+// Only Swing component creation here
+});
+
+// Main thread handles blocking operations
+initializeRpc(browser); // server.blockUntilShutdown() OK here
+```
+
+3. **Set Up Resource Path**:
+ - Ensure frontend builds to correct output directory
+ - Configure Gradle to include assets in JAR
+ - Update interceptor's `CONTENT_ROOT` path
+
+4. **Handle MIME Types**:
+ - Copy `mime-types.properties` or create custom mappings
+ - Implement appropriate Content-Type header logic
+
+5. **Test Both Modes**:
+ - Development: Direct Vite server access
+ - Production: Custom scheme with intercepted requests
+
+This URL interception pattern enables seamless packaging of web assets within the desktop application while maintaining development workflow efficiency.
+
+## Data Persistence
+
+### Java-side Persistence
+- **JSON Files**: Using Gson for preferences (`preferences.json`)
+- **Binary Files**: Using Protocol Buffers binary format for complex data
+- **Platform-specific Locations**:
+ - Windows: `%LOCALAPPDATA%/AppName/`
+ - macOS: `~/Library/Application Support/AppName/`
+
+### Frontend State Management
+- React hooks for local state
+- gRPC clients for server communication
+- Local storage for temporary data
+
+## Packaging and Distribution
+
+### Native Installers
+
+The build system includes tasks to create native installers using Java's `jpackage` tool. These tasks are defined in `build.gradle.kts`:
+
+#### macOS DMG Installer
+```bash
+# Build macOS DMG installer
+./gradlew packageDmg
+```
+
+**Generated Gradle Task:**
+```kotlin
+tasks.register("packageDmg") {
+ dependsOn(tasks.build)
+
+ commandLine(
+ "jpackage",
+ "--input", "./build/dist", // Input directory with JAR
+ "--main-jar", mainJar, // Main JAR file name
+ "--name", applicationName, // Application name
+ "--app-version", version, // Version from build.gradle.kts
+ "--type", "dmg", // macOS DMG format
+ "--main-class", application.mainClass.get(),
+ "--dest", "./build/installer", // Output directory
+ "--icon", "src/main/resources/app.icns", // macOS icon file
+ )
+}
+```
+
+#### Windows EXE Installer
+```bash
+# Build Windows EXE installer
+./gradlew packageExe
+```
+
+**Generated Gradle Task:**
+```kotlin
+tasks.register("packageExe") {
+ dependsOn(tasks.build)
+
+ commandLine(
+ "jpackage",
+ "--input", "./build/dist", // Input directory with JAR
+ "--main-jar", mainJar, // Main JAR file name
+ "--name", applicationName, // Application name
+ "--app-version", version, // Version from build.gradle.kts
+ "--type", "exe", // Windows EXE format
+ "--main-class", application.mainClass.get(),
+ "--dest", "./build/installer", // Output directory
+ "--win-dir-chooser", // Allow user to choose install directory
+ "--win-menu", // Add to Windows Start Menu
+ "--win-shortcut-prompt", // Prompt to create desktop shortcut
+ "--icon", "src/main/resources/app.ico", // Windows icon file
+ )
+}
+```
+
+#### Required Configuration Variables
+```kotlin
+// In build.gradle.kts
+group = "com.teamdev.jxbrowser.gallery"
+version = "1.0"
+
+val applicationName = "JxBrowserWebApp"
+val mainJar = "$applicationName-$version.jar"
+
+application {
+ mainClass.set("com.teamdev.jxbrowser.examples.App")
+}
+```
+
+#### Prerequisites for Native Packaging
+
+**For macOS DMG:**
+- macOS development environment
+- Xcode Command Line Tools installed
+- Valid Apple Developer ID (for code signing)
+
+**For Windows EXE:**
+- Windows development environment
+- [WiX Toolset](https://github.com/wixtoolset/wix3/releases/tag/wix3141rtm) 3.14.1 or higher
+- Valid code signing certificate (optional but recommended)
+
+#### Generated Output
+- **macOS**: `./build/installer/YourApp-1.0.dmg`
+- **Windows**: `./build/installer/YourApp-1.0.exe`
+
+### JAR Distribution
+```bash
+# Fat JAR with all dependencies (platform-independent)
+./gradlew build
+# Generates: ./build/dist/YourApp-1.0.jar
+```
+
+**Important**: The JAR task is configured to use an explicit filename and create a fat JAR:
+
+```kotlin
+tasks.jar {
+ archiveFileName.set(mainJar) // Uses custom name instead of project name
+ dependsOn(tasks.named("buildWeb"))
+ manifest {
+ attributes["Main-Class"] = application.mainClass.get()
+ }
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ from({
+ configurations.runtimeClasspath.get().map {
+ if (it.isDirectory) it else zipTree(it)
+ }
+ })
+
+ doLast {
+ copy {
+ from("build/libs/$mainJar") // Copy from libs to dist
+ into("build/dist") // Native installers expect it here
+ }
+ }
+}
+```
+
+This configuration ensures:
+- **Custom JAR Name**: Uses `applicationName-version.jar` instead of project directory name
+- **Fat JAR**: Includes all dependencies for standalone execution
+- **Proper Location**: Copies JAR to `build/dist/` where native installer tasks expect it
+- **Main Class**: Sets manifest for direct execution via `java -jar`
+
+### Complete Build Pipeline
+```bash
+# Full build with web assets and JAR
+./gradlew clean build
+
+# Build native installers (platform-specific)
+./gradlew packageDmg # On macOS
+./gradlew packageExe # On Windows
+```
+
+## Frontend Framework Requirements
+
+### Tailwind CSS 4 Setup
+This architecture uses Tailwind CSS 4. Here's the complete setup:
+
+**1. Install Dependencies**:
+```json
+// package.json
+{
+ "devDependencies": {
+ "tailwindcss": "^4.1.1",
+ "@tailwindcss/postcss": "^4.1.1",
+ "postcss": "^8.4.35"
+ }
+}
+```
+
+**2. Tailwind Configuration**:
+```javascript
+// tailwind.config.js
+export default {
+ darkMode: ["class"],
+ content: [
+ './index.html',
+ './src/**/*.{js,ts,jsx,tsx}',
+ ],
+ theme: {
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px",
+ },
+ },
+ extend: {
+ colors: {
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ },
+ },
+ plugins: [],
+}
+```
+
+**3. PostCSS Configuration**:
+```javascript
+// postcss.config.js
+export default {
+ plugins: {
+ '@tailwindcss/postcss': {},
+ },
+}
+```
+
+**4. CSS Setup**:
+```css
+/* src/index.css */
+@import "tailwindcss";
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 222.2 84% 4.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 222.2 84% 4.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 222.2 84% 4.9%;
+ --primary: 222.2 47.4% 11.2%;
+ --primary-foreground: 210 40% 98%;
+ --secondary: 210 40% 96%;
+ --secondary-foreground: 222.2 84% 4.9%;
+ --muted: 210 40% 96%;
+ --muted-foreground: 215.4 16.3% 46.9%;
+ --accent: 210 40% 96%;
+ --accent-foreground: 222.2 84% 4.9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 210 40% 98%;
+ --border: 214.3 31.8% 91.4%;
+ --input: 214.3 31.8% 91.4%;
+ --ring: 222.2 84% 4.9%;
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 222.2 84% 4.9%;
+ --foreground: 210 40% 98%;
+ --card: 222.2 84% 4.9%;
+ --card-foreground: 210 40% 98%;
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+ --primary: 210 40% 98%;
+ --primary-foreground: 222.2 47.4% 11.2%;
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+ --accent: 217.2 32.6% 17.5%;
+ --accent-foreground: 210 40% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+ --border: 217.2 32.6% 17.5%;
+ --input: 217.2 32.6% 17.5%;
+ --ring: 212.7 26.8% 83.9%;
+ }
+}
+```
+
+## Reusability Guidelines
+
+### For New Projects
+
+1. **Copy Core Structure**:
+ - `build.gradle.kts` (update app name, main class, set development mode)
+ - `AppInitializer.java` (core bootstrap logic, CORS configuration)
+ - `AppDetails.java` (app-specific configuration)
+ - `UrlRequestInterceptor.java` (for custom scheme handling)
+
+2. **Define Data Models**:
+ - Create `.proto` files in **root `proto/` directory**
+ - Define services and messages for your domain
+ - Ensure `buf.gen.yaml` points to `../proto`
+
+3. **Implement Services**:
+ - Extend `*ServiceImplBase` for each service
+ - Implement business logic and data persistence
+ - Configure CORS with `armeria-grpc-protocol` dependency
+
+4. **Build Frontend**:
+ - Use `web-app/` structure as template
+ - Set up Tailwind CSS 4 configuration
+ - Use **codegen v2** for TypeScript proto generation
+ - Leverage generated TypeScript types with ConnectRPC
+
+5. **Configure Build and Environment**:
+ - Set up development mode with `-Dapp.dev.mode=true`
+ - Configure `JXBROWSER_LICENSE_KEY` environment variable
+ - Update `package.json` with project-specific dependencies
+ - Ensure gRPC server runs in same process (not separate Gradle task)
+ - Use consistent RPC port for frontend and backend
+
+### Recommended Dependency Versions
+
+**Java Dependencies:**
+```kotlin
+// JxBrowser 8.9.4 (latest version)
+implementation(jxbrowser.macArm)
+implementation(jxbrowser.swing)
+
+// Armeria (gRPC server)
+implementation("com.linecorp.armeria:armeria-grpc:1.30.1")
+implementation("com.linecorp.armeria:armeria:1.30.1")
+implementation("com.linecorp.armeria:armeria-grpc-protocol:1.30.1") // Required for CORS
+
+// Protocol Buffers
+implementation("com.google.protobuf:protobuf-java:3.21.12")
+
+// Utilities
+implementation("com.google.code.gson:gson:2.10.1")
+implementation("javax.annotation:javax.annotation-api:1.3.2")
+```
+
+**Frontend Dependencies:**
+```json
+{
+ "dependencies": {
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "react-router-dom": "^6.22.1",
+ "@connectrpc/connect": "2.0.0-rc.2",
+ "@connectrpc/connect-web": "2.0.0-rc.2",
+ "@bufbuild/protobuf": "2.2.1",
+ "@radix-ui/react-avatar": "^1.1.3",
+ "@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-dropdown-menu": "^2.1.6",
+ "@radix-ui/react-label": "^2.1.2",
+ "@radix-ui/react-popover": "^1.1.6",
+ "@radix-ui/react-switch": "^1.1.3",
+ "tailwindcss": "^4.1.1",
+ "lucide-react": "^0.507.0",
+ "vite": "^5.1.0",
+ "typescript": "^5.2.2"
+ },
+ "devDependencies": {
+ "@bufbuild/buf": "1.46.0",
+ "@bufbuild/protoc-gen-es": "2.2.1"
+ }
+}
+```
+
+## Gradle Build Configuration
+
+The [template](../template) directory contains a minimal Gradle setup that serves as a
+foundation for creating demos and examples. This directory is specifically designed to
+help LLMs (Language Learning Models) and developers quickly bootstrap new Gradle
+projects.
+
+**Purpose:**
+- Provides a clean Gradle environment for testing and demonstrations
+- Allows running `gradlew --version` and future Gradle tasks
+- Serves as a starting point for creating new components or features
+
+**Structure:**
+```
+../template/
+βββ gradlew # Gradle wrapper script (Unix/macOS)
+βββ gradlew.bat # Gradle wrapper script (Windows)
+βββ settings.gradle.kts # Basic Gradle settings
+βββ gradle/
+βββ wrapper/
+βββ gradle-wrapper.jar # Gradle wrapper executable
+βββ gradle-wrapper.properties # Gradle wrapper configuration
+
+**Usage:**
+```bash
+cd ../template
+./gradlew --version # Verify Gradle installation
+./gradlew tasks # List available tasks (when build.gradle.kts is added)
+```
+
+### JxBrowser License Configuration
+
+**CRITICAL: JxBrowser license must be provided via environment variable**
+
+JxBrowser is a **commercial library** that requires a valid license. The license is NOT available in Maven Central and must be obtained from TeamDev.
+
+**License Setup:**
+1. **Get License**: Obtain JxBrowser license from [TeamDev](https://www.teamdev.com/jxbrowser)
+2. **Set Environment Variable**: `JXBROWSER_LICENSE_KEY=your-license-key-here`
+3. **Use in Code**: Always read from environment variable
+
+**Important Notes:**
+- JxBrowser binaries are NOT in Maven Central
+- License is required for both development and production
+- TeamDev provides evaluation licenses for testing
+- Contact TeamDev for commercial licensing
+
+## Java Development Guidelines
+
+### Java 17+ Syntax Requirements
+
+This project uses **Java 17** or higher. LLMs and developers should use modern Java syntax including:
+
+### β Common Mistakes
+
+```java
+// WRONG - Don't use String for scheme
+optionsBuilder.addScheme("jxbrowser", interceptor);
+
+// WRONG - Missing protobuf import causes this error
+create("grpc") { ... } // Should be id("grpc")
+
+// WRONG - Blocks AWT thread
+SwingUtilities.invokeLater(() -> {
+ // All application code - BLOCKS when server.blockUntilShutdown()
+});
+```
+
+**β
CORRECT - Only wrap Swing UI code:**
+```java
+public void initialize() throws InterruptedException {
+ // β
CORRECT - Non-UI code runs on main thread
+ setupLogging();
+ var engine = createEngine();
+ var browser = engine.newBrowser();
+ setupUI(engine, browser); // This method uses invokeLater internally
+ setupBrowserCallbacks(browser);
+ initializeRpc(browser); // This calls server.blockUntilShutdown() - OK on main thread
+}
+
+private static void setupUI(Engine engine, Browser browser) {
+ // β
CORRECT - Only UI creation wrapped in invokeLater
+ SwingUtilities.invokeLater(() -> {
+ var frame = new JFrame("Dashboard");
+ frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+ frame.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ engine.close();
+ }
+ });
+
+ setAppIcon(frame);
+ frame.add(BrowserView.newInstance(browser), BorderLayout.CENTER);
+ frame.setSize(1200, 800);
+ frame.setMinimumSize(new Dimension(640, 560));
+ frame.setMaximumSize(new Dimension(1280, 900));
+ frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
+ });
+}
+```
+
+## Frontend Guidelines
+
+### Use shadcn/ui Components
+```bash
+# Install components instead of building custom ones
+npx shadcn@latest add button input card dialog
+```
+
+```tsx
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+
+export function MyComponent() {
+ return (
+
+
+ Settings
+
+
+
+
+
+ )
+}
+```
+
+## Data Persistence
+
+- **JSON Files**: Gson for preferences (platform-specific paths)
+- **Binary Files**: Protocol Buffers for complex data
+- **Locations**:
+ - Windows: `%LOCALAPPDATA%/AppName/`
+ - macOS: `~/Library/Application Support/AppName/`
+
+## Reusability for New Projects
+
+1. **Copy Structure**: `build.gradle.kts`, `AppInitializer.java`, `AppDetails.java`
+2. **Define Models**: Create `.proto` files in root `proto/` directory
+3. **Implement Services**: Extend `*ServiceImplBase` classes
+4. **Build Frontend**: Use shadcn/ui, Tailwind CSS 4, TypeScript
+5. **Configure Environment**: Set `JXBROWSER_LICENSE_KEY`
+
+### Key Benefits
+- **Type Safety**: Protocol Buffers across Java/TypeScript
+- **Modern UI**: React ecosystem with professional components
+- **Cross-platform**: Single codebase for Windows/macOS/Linux
+- **Developer Experience**: Hot reload, TypeScript, modern tooling
+- **Performance**: Native Java with optimized web rendering
+
+This architecture provides a solid foundation for building professional desktop applications combining Java's power with modern web technologies.
\ No newline at end of file
diff --git a/tutorials/ai/prompt-scaffolding/img/file-viewer.png b/tutorials/ai/prompt-scaffolding/img/file-viewer.png
new file mode 100644
index 0000000..28c6e27
Binary files /dev/null and b/tutorials/ai/prompt-scaffolding/img/file-viewer.png differ
diff --git a/tutorials/ai/prompt-scaffolding/img/paste-prompt-in-cursor.png b/tutorials/ai/prompt-scaffolding/img/paste-prompt-in-cursor.png
new file mode 100644
index 0000000..db1fb64
Binary files /dev/null and b/tutorials/ai/prompt-scaffolding/img/paste-prompt-in-cursor.png differ
diff --git a/tutorials/ai/prompt-scaffolding/template/gradle/wrapper/gradle-wrapper.properties b/tutorials/ai/prompt-scaffolding/template/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ccc1a9b
--- /dev/null
+++ b/tutorials/ai/prompt-scaffolding/template/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
\ No newline at end of file
diff --git a/tutorials/ai/prompt-scaffolding/template/gradlew b/tutorials/ai/prompt-scaffolding/template/gradlew
new file mode 100755
index 0000000..77f8035
--- /dev/null
+++ b/tutorials/ai/prompt-scaffolding/template/gradlew
@@ -0,0 +1,223 @@
+#!/bin/sh
+
+#
+# Copyright Β© 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β»,
+# Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»;
+# * compound commands having a testable exit status, especially Β«caseΒ»;
+# * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Gradle template using the template
+# variables listed below.
+
+# Project Name
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS="-Xmx64m -Xms64m"
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MSYS* | MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if [ "$darwin" = "true" ]; then
+ GRADLE_OPTS="$GRADLE_OPTS -Xdock:name=$APP_NAME -Xdock:icon=$APP_HOME/media/gradle.icns"
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain $APP_ARGS
+
+exec "$JAVACMD" "$@"
\ No newline at end of file
diff --git a/tutorials/ai/prompt-scaffolding/template/gradlew.bat b/tutorials/ai/prompt-scaffolding/template/gradlew.bat
new file mode 100644
index 0000000..43fe16c
--- /dev/null
+++ b/tutorials/ai/prompt-scaffolding/template/gradlew.bat
@@ -0,0 +1,97 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd_ return code. This script also supports the following command line options:
+rem
+rem -Dorg.gradle.jvmargs=... JVM arguments
+rem -Dorg.gradle.parallel=... Enable/disable parallel execution
+rem -Dorg.gradle.workers.max=... Maximum number of workers
+rem -Dorg.gradle.daemon=... Enable/disable the Gradle daemon
+rem -Dorg.gradle.debug=... Enable/disable Gradle debugging
+rem
+rem These options and variables can also be set in gradle.properties or in an ~/.gradle/gradle.properties file.
+
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
\ No newline at end of file
diff --git a/tutorials/ai/prompt-scaffolding/template/settings.gradle.kts b/tutorials/ai/prompt-scaffolding/template/settings.gradle.kts
new file mode 100644
index 0000000..da80992
--- /dev/null
+++ b/tutorials/ai/prompt-scaffolding/template/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "demo-sketch"
\ No newline at end of file