` and the goal will `adb connect` before installing. Both the Android 11+ `adb pair` flow and the legacy `adb tcpip` flow work.
+
+**3. Attach the debugger.**
+
+Switch to **CN1 Attach Android** and click 🐞 Debug. IntelliJ connects to `localhost:5005`. Set breakpoints anywhere; they fire when exercised.
+
+Source resolution covers both the `codenameone-core` and `codenameone-android` sources jars, so breakpoints inside the framework or inside the Android port resolve to the right files. On Android, **native interfaces are themselves Java**, so a breakpoint inside the `Impl` class of your own native interface fires just like a breakpoint anywhere else in your code; you can step through the implementation, inspect locals, and evaluate expressions the same way.
+
+The dev guide has the full reference, including the wireless-pairing flows, the VS Code and Eclipse equivalents, and a troubleshooting section: [iOS on-device debugging](https://www.codenameone.com/developer-guide/#_on_device_debugging_ios) and [Android on-device debugging](https://www.codenameone.com/developer-guide/#_on_device_debugging_android).
+
+### When to use it (and when not to)
+
+For most bugs the JavaSE simulator is still by a large margin the fastest loop. Reach for on-device debugging when the bug is platform-specific: ParparVM-specific threading, an iOS-only layout glitch under the modern native theme, a real-radio Bluetooth interaction, a Touch ID gate, an Android-only manifest interaction, anything that only reproduces under iOS background memory pressure. The kind of bug that previously sent you reaching for `Log.p` and a rebuild loop. That bug now has a debugger pointed at it.
+
+## JUnit 5 against the simulator
+
+The other change in this release is the new JUnit 5 integration in the JavaSE port ([PR #5032](https://github.com/codenameone/CodenameOne/pull/5032)).
+
+To be clear about what this is: it is **standard JUnit 5**. There is no fork of JUnit in `com.codename1.testing.junit`. That package holds a small set of annotations and a `CodenameOneExtension` that plugs into the regular JUnit Jupiter lifecycle. You write `@Test` methods using `org.junit.jupiter.api.Test`, you assert with `org.junit.jupiter.api.Assertions`, and your IDE's native test runner picks them up the way it does on any other Java project.
+
+Why a separate integration at all? The legacy `com.codename1.testing.AbstractTest` framework, driven by the `cn1:test` Maven goal, still exists and is still the only way to run tests on a real iOS or Android device (JUnit Jupiter is not available on ParparVM). The trade-off is that `AbstractTest` tests have to compile under the Codename One device subset, with no reflection, no `java.net.http`, no `java.nio.file`, no Mockito, no AssertJ, no `assertThrows`. JUnit-style tests run only on the JavaSE simulator JVM, but the JVM is a regular JVM, so reflection, Mockito, AssertJ, and parameterised tests are all available.
+
+Both styles coexist in the same project under `common/src/test/java`. You pick per test class. The runners discover disjoint sets (`cn1:test` looks for `UnitTest` implementers; Surefire looks for `@Test` methods), so a `mvn install` runs both passes in the same phase without overlap.
+
+### A minimal test
+
+Tests live in `common/src/test/java`. The shape most apps want is one that boots the project's app class through the same `init` / `start` sequence the simulator uses, then asserts against the form the app actually opens:
+
+```java
+package com.example.myapp;
+
+import com.codename1.testing.junit.CodenameOneTest;
+import com.codename1.testing.junit.RunOnEdt;
+import com.codename1.ui.CN;
+import com.codename1.ui.Display;
+import com.codename1.ui.Form;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@CodenameOneTest
+class GreetingFormTest {
+
+ @Test
+ @RunOnEdt
+ void formShowsExpectedTitle() {
+ MyAppName app = new MyAppName();
+ app.init(null);
+ app.start();
+
+ assertEquals("Hi World", Display.getInstance().getCurrent().getTitle());
+ assertTrue(CN.isEdt(), "@RunOnEdt method runs on the Codename One EDT");
+ }
+}
+```
+
+That is more useful than constructing a `Form` directly in the test because it exercises the same startup path the simulator runs. The assertions check the form your app opens, not a form the test wrote.
+
+The natural way to run it is from the IntelliJ gutter. Click the green ▶ icon next to the class declaration:
+
+
+
+The results land in the standard Run tool window:
+
+
+
+Click the green icon next to a specific `@Test` method to run just that method. The same flow works in VS Code's Test Explorer and in Eclipse's JUnit view.
+
+If you prefer the command line:
+
+```
+mvn -Ptest test # run the JUnit suite
+mvn -Ptest test -Dtest=GreetingFormTest # one class
+mvn -Ptest test -Dtest=GreetingFormTest#formShowsExpectedTitle
+```
+
+`@CodenameOneTest` is the class-level entry point. It wires the simulator extension into the JUnit Jupiter lifecycle, boots `Display.init(null)` once per JVM (idempotent, so subsequent classes share the same `Display`), and skips the class with a `TestAbortedException` if the JVM is genuinely headless (so CI runners that have no display do not poison the rest of the run).
+
+`@RunOnEdt` dispatches the test body through `CN.callSerially`, which is what you want any time the body touches UI state. It rethrows the body's exceptions on the JUnit thread so the stack trace stays clickable in the IDE. Place it on the method for one test, on the class to apply to every test.
+
+### A couple more common cases
+
+A test that exercises a plain validator, with no UI involved at all:
+
+```java
+@CodenameOneTest
+class EmailValidatorTest {
+
+ @Test
+ void rejectsEmptyString() {
+ assertFalse(new EmailValidator().isValid(""));
+ }
+
+ @Test
+ void acceptsCommonAddress() {
+ assertTrue(new EmailValidator().isValid("name@example.com"));
+ }
+}
+```
+
+This is the "pure model code" shape. No `@RunOnEdt`, no UI, runs on the JUnit worker thread, fast.
+
+A test of a form under a specific visual configuration:
+
+```java
+@CodenameOneTest
+class GreetingFormVisualTest {
+
+ @Test
+ @RunOnEdt
+ @DarkMode
+ @LargerText(scale = 1.6f)
+ void titleStillFitsInDarkModeAtAccessibilityScale() {
+ new GreetingForm().show();
+
+ Form current = Display.getInstance().getCurrent();
+ assertEquals("Hello", current.getTitle());
+ assertTrue(current.getPreferredW() <= Display.getInstance().getDisplayWidth());
+ }
+}
+```
+
+The visual-config annotations (`@Theme`, `@DarkMode`, `@LargerText`, `@Orientation`, `@RTL`) apply on the EDT in one batch, followed by a single theme refresh, so the test body sees the simulator in the exact configuration you asked for without flicker.
+
+A test that injects a custom property for the duration of one method:
+
+```java
+@Test
+@RunOnEdt
+@SimulatorProperty(name = "feature.flag", value = "on")
+void newCodePathRunsWhenFlagIsOn() {
+ // Display.getProperty("feature.flag", "off") returns "on" here
+ runFeature();
+ assertEquals("expected", Display.getInstance().getCurrent().getTitle());
+}
+```
+
+Class-level `@SimulatorProperty` applies to every method in the class. Method-level overrides class-level. Use the container `@SimulatorProperties` for more than one (the package source level rules out `@Repeatable`).
+
+The full reference, including the dependency-block YAML for `common/pom.xml` and `javase/pom.xml` and the `@Theme` / `@Orientation` / `@RTL` details, is at [Testing with JUnit 5](https://www.codenameone.com/developer-guide/#_testing_with_junit_5) in the developer guide.
+
+## Wrapping up
+
+That is the workflow half of this release. The next post is on Monday and covers the new platform APIs that moved into the core this week: AI and OIDC are the headline pieces, with WiFi / connectivity and a few smaller items alongside them.
+
+Back to the [weekly index](/blog/metal-default-new-build-cloud-and-a-new-format/).
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/layouts/partials/extend_footer.html b/docs/website/layouts/partials/extend_footer.html
new file mode 100644
index 0000000000..51532722f0
--- /dev/null
+++ b/docs/website/layouts/partials/extend_footer.html
@@ -0,0 +1,3 @@
+{{- /* Intentionally left empty. The {{< mermaid >}} shortcode emits its own
+ loader inline so the diagram works regardless of partialCached behaviour
+ on the surrounding footer. */ -}}
diff --git a/docs/website/layouts/shortcodes/mermaid.html b/docs/website/layouts/shortcodes/mermaid.html
new file mode 100644
index 0000000000..bf5a23a297
--- /dev/null
+++ b/docs/website/layouts/shortcodes/mermaid.html
@@ -0,0 +1,38 @@
+{{/*
+ Mermaid diagram shortcode.
+
+ Usage:
+ {{< mermaid >}}
+ flowchart LR
+ A[IDE] -->|JDWP| B[CN1 Debug Proxy]
+ B -->|wire protocol| C[iOS app]
+ {{< /mermaid >}}
+
+ The loader is inlined here (rather than gated from a footer partial)
+ because the PaperMod footer is rendered with partialCached, which would
+ cache a single result across every page sharing the same layout / kind.
+ Multiple shortcodes on one page emit multiple loader scripts; the
+ __cn1MermaidLoaded guard makes initialisation idempotent.
+*/}}
+
+{{- .Inner | safeHTML -}}
+
+
diff --git a/docs/website/static/blog/developer-workflow-debug-and-junit.jpg b/docs/website/static/blog/developer-workflow-debug-and-junit.jpg
new file mode 100644
index 0000000000..d6dc9c8626
Binary files /dev/null and b/docs/website/static/blog/developer-workflow-debug-and-junit.jpg differ
diff --git a/docs/website/static/blog/developer-workflow-debug-and-junit/intellij-debugger-on-device.png b/docs/website/static/blog/developer-workflow-debug-and-junit/intellij-debugger-on-device.png
new file mode 100644
index 0000000000..21d33818ad
Binary files /dev/null and b/docs/website/static/blog/developer-workflow-debug-and-junit/intellij-debugger-on-device.png differ
diff --git a/docs/website/static/blog/developer-workflow-debug-and-junit/intellij-gutter-run-menu.png b/docs/website/static/blog/developer-workflow-debug-and-junit/intellij-gutter-run-menu.png
new file mode 100644
index 0000000000..39d401078c
Binary files /dev/null and b/docs/website/static/blog/developer-workflow-debug-and-junit/intellij-gutter-run-menu.png differ
diff --git a/docs/website/static/blog/developer-workflow-debug-and-junit/intellij-test-results.png b/docs/website/static/blog/developer-workflow-debug-and-junit/intellij-test-results.png
new file mode 100644
index 0000000000..7d69fe9411
Binary files /dev/null and b/docs/website/static/blog/developer-workflow-debug-and-junit/intellij-test-results.png differ
diff --git a/scripts/website/build.sh b/scripts/website/build.sh
index 7d9d19880a..4f10267f5e 100755
--- a/scripts/website/build.sh
+++ b/scripts/website/build.sh
@@ -17,6 +17,13 @@ HUGO_BIN="${HUGO_BIN:-hugo}"
HUGO_ENVIRONMENT="${HUGO_ENVIRONMENT:-production}"
HUGO_MINIFY="${HUGO_MINIFY:-true}"
HUGO_BASEURL="${HUGO_BASEURL:-https://www.codenameone.com/}"
+# When true, include posts whose front-matter date is in the future
+# (e.g. weekly release posts staged for later in the week). Off by
+# default so the live site only shows posts whose publish date has
+# arrived; PR previews flip this on so reviewers can read the draft.
+HUGO_BUILD_FUTURE="${HUGO_BUILD_FUTURE:-false}"
+# When true, include posts marked draft: true.
+HUGO_BUILD_DRAFTS="${HUGO_BUILD_DRAFTS:-false}"
PYTHON_BIN="${PYTHON_BIN:-python3}"
WEBSITE_INCLUDE_JAVADOCS="${WEBSITE_INCLUDE_JAVADOCS:-false}"
WEBSITE_INCLUDE_DEVGUIDE="${WEBSITE_INCLUDE_DEVGUIDE:-auto}"
@@ -765,11 +772,23 @@ if [ "${HUGO_MINIFY}" = "true" ]; then
MINIFY_FLAG="--minify"
fi
+BUILD_FUTURE_FLAG=""
+if [ "${HUGO_BUILD_FUTURE}" = "true" ]; then
+ BUILD_FUTURE_FLAG="--buildFuture"
+fi
+
+BUILD_DRAFTS_FLAG=""
+if [ "${HUGO_BUILD_DRAFTS}" = "true" ]; then
+ BUILD_DRAFTS_FLAG="--buildDrafts"
+fi
+
HUGO_ENV="${HUGO_ENVIRONMENT}" "${HUGO_BIN}" \
--cleanDestinationDir \
--gc \
--baseURL "${HUGO_BASEURL}" \
- ${MINIFY_FLAG}
+ ${MINIFY_FLAG} \
+ ${BUILD_FUTURE_FLAG} \
+ ${BUILD_DRAFTS_FLAG}
if command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
"${PYTHON_BIN}" "${WEBSITE_DIR}/scripts/generate_lunr_index.py"