# Sage desktop client sample This directory is an independent Compose Desktop sample app for Sage on top of the root `bricks-mp` shared module. It is intentionally **not** included from the root `settings.gradle.kts`, so normal library builds are unchanged. ## Build From any directory: ```bash /path/to/bricks-mp/test/sageclient/build.sh ``` The script: 1. resolves its own directory, 2. uses JDK 17 or JDK 21; on macOS it auto-selects an installed 21/17 via `/usr/libexec/java_home` when the active Java is unsupported, 3. uses the repository Gradle wrapper when present, otherwise system `gradle`, 4. runs `gradle build` for this standalone sample. Kotlin 2.1.0 / this Gradle setup does not work with newer Java versions such as `26.0.1`. If your default `java -version` is 26, install JDK 21 or 17 and either let `build.sh` auto-detect it or export `JAVA_HOME` explicitly: ```bash brew install --cask temurin@21 export JAVA_HOME=$(/usr/libexec/java_home -v 21) ./test/sageclient/build.sh ``` Extra Gradle arguments can be appended, for example: ```bash ./test/sageclient/build.sh --info ``` ## Run Run without arguments to open the sample shell: ```bash cd /path/to/bricks-mp/test/sageclient ../../gradlew run \ -Dsage.baseUrl=http://localhost:8080 \ -Dsage.centerUi=/center.ui ``` Run with a startup URL to enter that UI directly without showing the sample base URL / login input shell: ```bash cd /path/to/bricks-mp/test/sageclient ../../gradlew run --args="https://ai.atvoe.com/center.ui" ``` The packaged macOS app also accepts the URL as its first argument: ```bash open build/compose/binaries/main/app/sageclient.app --args https://ai.atvoe.com/center.ui ``` If the argument is an absolute `http://` or `https://` URL, `sageclient` uses the URL origin as `baseUrl` and dispatches that same absolute URL through the Bricks `urlwidget` loading path. If the argument is a relative path such as `/center.ui`, it uses `sage.baseUrl` and loads that path. The actual HTTP request is still issued through `BricksHttp`, so startup URLs ending in `.ui` / `.dspy` are requested with `_webbricks_=1`, `_width`, `_height`, `_is_mobile` and `_lang`; they must not be fetched as raw HTML/template pages. Optional properties: - `sage.baseUrl` defaults to `http://localhost:8080` - `sage.centerUi` defaults to `/center.ui` - `sage.loginAction` defaults to `/rbac/user/login` - `sage.loginUi` defaults to `/rbac/user/login.ui` - `sage.username` / `sage.password` prefill the login form - `sage.lang` overrides the default JVM locale tag ## What the sample demonstrates - Uses the generic `BricksHttp` client from the shared module for Sage login and `center.ui` loading. Sage-specific bootstrapping lives here, not in the shared library package. - Loads `center.ui` via `ActionDispatcher.dispatch(BricksBind(actiontype = "urlwidget", ...))`, so HTTP handling stays in the shared ActionDispatcher / BricksHttp flow. - `BricksHttp` automatically appends the correct WebBricks query parameters for `.ui` / `.dspy` backend requests: `_webbricks_`, `_width`, `_height`, `_is_mobile`, `_lang`. - `BricksHttp` surfaces HTTP 403, 401 and 3xx (including 301) as `BricksHttpException`. `ActionDispatcher` handles them generically: - 403 loads the configured login UI (`sage.loginUi`) in a dialog, - 401 shows the server response as an unauthorized message, - 3xx follows the `Location` header as a UI navigation target. The sample is wired as a Gradle composite build through `includeBuild("../..")` and depends on the root project module using `implementation("com.bricks.mp:shared")` with dependency substitution to `:shared`.