diff --git a/.github/workflows/blog-prose.yml b/.github/workflows/blog-prose.yml
index e4599f5f7fd..1afc145da4b 100644
--- a/.github/workflows/blog-prose.yml
+++ b/.github/workflows/blog-prose.yml
@@ -43,7 +43,7 @@ jobs:
# base version of each changed post.
fetch-depth: 0
- - name: Detect changed blog posts
+ - name: Detect changed blog and social prose
id: detect
run: |
set -euo pipefail
@@ -52,9 +52,9 @@ jobs:
if [ -z "$mb" ]; then
count=0
else
- count="$(git diff --name-only --diff-filter=ACMR "$mb" HEAD -- 'docs/website/content/blog/*.md' | wc -l | tr -d ' ')"
+ count="$(git diff --name-only --diff-filter=ACMR "$mb" HEAD -- 'docs/website/content/blog/*.md' 'docs/website/social/linkedin/*.md' | wc -l | tr -d ' ')"
fi
- echo "Changed blog posts: $count"
+ echo "Changed blog and social files: $count"
if [ "$count" -gt 0 ]; then
echo "has_posts=true" >> "$GITHUB_OUTPUT"
else
diff --git a/docs/website/content/blog/accessibility-semantics.md b/docs/website/content/blog/accessibility-semantics.md
new file mode 100644
index 00000000000..b5834aa5324
--- /dev/null
+++ b/docs/website/content/blog/accessibility-semantics.md
@@ -0,0 +1,169 @@
+---
+title: "Accessibility Semantics: The UI Tree You Cannot See"
+slug: accessibility-semantics
+url: /blog/accessibility-semantics/
+date: '2026-07-20'
+author: Shai Almog
+description: "Codename One now builds a portable accessibility semantics tree for its lightweight components and maps it to VoiceOver, TalkBack, UI Automation, AT-SPI, Java accessibility, and web ARIA."
+feed_html: '
Codename One now exposes a portable semantics tree to VoiceOver, TalkBack, UI Automation, AT-SPI, Java accessibility, and web ARIA.'
+series: ["release-2026-07-17"]
+---
+
+
+
+Accessibility has become personal for me. I am getting older, and large type is no longer an abstract preference somebody else needs. It is how I read a phone comfortably.
+
+I worked with accessibility experts at Sun Microsystems and learned how deep the problem goes. A label is the easy part. Real accessibility needs roles, values, ranges, actions, traversal order, live announcements, collections, focus, platform conventions, and a way to test all of it. That complexity is why full Codename One accessibility support sat dormant for a decade.
+
+We eventually added `setAccessibilityText()`. It was useful, but it was the poor man's version. [PR #5363](https://github.com/codenameone/CodenameOne/pull/5363) replaces that single-label model with a portable semantics tree we can be proud of.
+
+## Lightweight UI needs a second tree
+
+Codename One paints lightweight components into its own native surface. VoiceOver cannot inspect a `Button` as a UIKit button because there is no UIKit button there. TalkBack cannot walk an Android `View` hierarchy because most of the painted controls are not Android views.
+
+The new accessibility manager builds an immutable virtual tree beside the visual component tree. Standard controls infer their semantics. Custom controls can replace or extend them. Each port exposes that virtual tree through the platform accessibility API.
+
+{{< mermaid >}}
+flowchart TD
+ A["Codename One component tree"] --> B["Portable semantics tree"]
+ B --> C["VoiceOver
UIAccessibilityElement"]
+ B --> D["TalkBack
AccessibilityNodeProvider"]
+ B --> E["Windows
UI Automation"]
+ B --> F["Linux
ATK / AT-SPI"]
+ B --> G["Java SE
AccessibleContext"]
+ B --> H["Web
off-screen ARIA DOM"]
+{{< /mermaid >}}
+
+The visual and semantic hierarchies can differ. A card made from five labels might need to read as one item. A chart may paint 200 points from one component, but expose each meaningful point as a virtual child. A renderer-backed list can expose stable rows even though those rows are not component instances.
+
+## Standard components work without annotations
+
+Buttons, checkboxes, radio buttons, sliders, text fields, lists, tables, tabs, labels, dialogs, and containers infer their normal roles, values, states, and actions. Existing `setAccessibilityText()` calls continue to work as a compatibility alias for the semantic label.
+
+You only add code when the inferred result is incomplete or the UI represents something more specific:
+
+```java
+Button save = new Button("Save");
+save.getSemantics()
+ .setHint("Saves the edited profile")
+ .setIdentifier("profile-save");
+```
+
+A custom switch can supply its role and checked state:
+
+```java
+wifiSwitch.getSemantics()
+ .setRole(AccessibilityRole.SWITCH)
+ .setLabel("Wi-Fi")
+ .setChecked(AccessibilityCheckedState.CHECKED)
+ .setEnabled(Boolean.TRUE)
+ .setHint("Double tap to turn Wi-Fi off");
+```
+
+Identifiers are for tooling and stable tests. Labels are for people. Keeping them separate avoids tests that break when product copy changes.
+
+## Semantics are more than labels
+
+The API covers the parts a label-only layer cannot express:
+
+- `AccessibilityRange` describes minimum, maximum, current value, step size, and spoken value for sliders and progress controls.
+- `AccessibilityAction` exposes standard activation plus named actions such as Archive or Delete.
+- `AccessibilityGrouping` merges descendants, treats a container as a group, or hides decorative subtrees.
+- Sort keys and traversal constraints change reading order without changing paint order.
+- Collection metadata describes row and column counts, spans, position in a set, and selection behavior.
+- Live regions announce status changes with polite or assertive priority.
+- Virtual child providers expose semantic items that have no component instance.
+
+Here is a chart exposing each data point as an accessible virtual child:
+
+```java
+chart.getSemantics().setChildProvider(owner -> {
+ List result = new ArrayList<>();
+ for (ChartPoint point : points) {
+ AccessibilityNode node = new AccessibilityNode(
+ "point-" + point.getId());
+ node.setRole(AccessibilityRole.IMAGE)
+ .setLabel(point.getLabel())
+ .setValue(point.getFormattedValue())
+ .setBounds(point.getBounds());
+ result.add(node);
+ }
+ return result;
+});
+```
+
+That same tree can represent a list cell, a map marker, a game menu item, or any custom renderer where the meaningful objects do not map one-to-one to components.
+
+## Preferences can change the UI before a screen reader arrives
+
+Accessibility includes users who never enable VoiceOver or TalkBack. The new APIs expose high contrast, reduce motion, reduce transparency, differentiate without color, and known color-vision deficiency preferences.
+
+```java
+if (CN.isHighContrastEnabled()) {
+ chart.setUIID("HighContrastChart");
+}
+if (CN.isReduceMotionEnabled()) {
+ chart.putClientProperty("animate", Boolean.FALSE);
+}
+if (CN.isReduceTransparencyEnabled()) {
+ chart.setUIID("OpaqueChart");
+}
+AccessibilityColorVisionDeficiency colorVision =
+ CN.getColorVisionDeficiency();
+if (CN.isDifferentiateWithoutColorEnabled()
+ || (colorVision != AccessibilityColorVisionDeficiency.NONE
+ && colorVision != AccessibilityColorVisionDeficiency.UNKNOWN)) {
+ status.setText("Disconnected: action required");
+}
+```
+
+Color must never be the only signal for important state. The preference is an extra input, not permission to hide the text or icon when the preference is absent.
+
+The simulator now lets you force these states, including combinations that are awkward to reproduce on a physical device.
+
+
+
+## The inspector audits the resolved tree
+
+The Component Inspector has an Accessibility tab that shows the tree the platform will receive. It flags unlabeled interactive nodes, duplicate identifiers, invalid ranges, contradictory state, traversal cycles, and other machine-detectable failures.
+
+
+
+You can put the same checks in a unit or screenshot test:
+
+```java
+AccessibilityTreeSnapshot tree =
+ AccessibilityInspector.snapshot(form);
+
+AccessibilityAssertions.assertNoErrors(tree);
+AccessibilityAssertions.assertNoUnlabeledInteractiveNodes(tree);
+
+AccessibilityNodeSnapshot save =
+ tree.getNodeByIdentifier("profile-save");
+if (save.getRole() != AccessibilityRole.BUTTON) {
+ throw new AssertionError("profile-save must expose the button role");
+}
+if (save.getAction(AccessibilityAction.ACTIVATE) == null) {
+ throw new AssertionError("profile-save must expose the activate action");
+}
+```
+
+The snapshot is immutable and can be serialized as JSON. That makes failures reviewable in CI and gives a bug report something more precise than “VoiceOver skipped my button.”
+
+## Every platform still gets a human pass
+
+An automated audit can prove that an interactive node has a label. It cannot prove that VoiceOver speaks the right sentence, that TalkBack focus recovers after a dialog closes, or that a Windows screen-reader user understands a custom collection.
+
+The final check still uses VoiceOver, TalkBack, Narrator, Orca, Java Access Bridge, or browser accessibility tools. Navigate in both directions. Activate every action. Change adjustable values. Enter and leave collections. Trigger errors and live updates. Confirm focus after navigation, deletion, and modal dialogs.
+
+That is the boundary. The portable semantics tree removes the architectural wall and makes most behavior testable once. Platform assistive technologies still apply their own presentation rules.
+
+The surprise is that this tree also describes the screen to software. Tomorrow's post shows how the same immutable snapshot lets an AI agent inspect and drive the simulator over the Model Context Protocol without relying on screenshot coordinates.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/codename-one-mcp-server.md b/docs/website/content/blog/codename-one-mcp-server.md
new file mode 100644
index 00000000000..6101b3c6215
--- /dev/null
+++ b/docs/website/content/blog/codename-one-mcp-server.md
@@ -0,0 +1,130 @@
+---
+title: "Your Codename One App Can Be an MCP Server"
+slug: codename-one-mcp-server
+url: /blog/codename-one-mcp-server/
+date: '2026-07-21'
+author: Shai Almog
+description: "The Codename One JavaSE port can expose a running simulator or desktop tool over the Model Context Protocol. An agent reads the accessibility semantics tree, performs UI actions on the EDT, and can call application-defined tools."
+feed_html: '
A running Codename One simulator or JavaSE tool can expose its semantic UI and application tools to coding agents over MCP.'
+series: ["release-2026-07-17"]
+---
+
+
+
+[Yesterday's accessibility work](/blog/accessibility-semantics/) created an immutable tree that describes what is on screen, what each item means, and which actions it supports. VoiceOver and TalkBack consume that tree for people.
+
+[PR #5377](https://github.com/codenameone/CodenameOne/pull/5377) lets an agent consume it too.
+
+The JavaSE port can expose a running simulator or Codename One desktop tool as a local [Model Context Protocol](https://modelcontextprotocol.io/) server. Codex, Claude Code, Claude Desktop, opencode, or another MCP host can inspect the current form, find a field by label, enter text, activate a button, and call tools published by the application.
+
+## The agent reads meaning, not coordinates
+
+Screenshot automation sees colored rectangles and guesses where to click. MCP exposes the same resolved semantics that accessibility technology receives:
+
+```json
+{
+ "id": "profile-save",
+ "role": "button",
+ "label": "Save",
+ "enabled": true,
+ "actions": ["activate"]
+}
+```
+
+The agent can ask for `ui_snapshot`, find `profile-save`, then invoke `activate`. It does not need to assume the button stayed at yesterday's x and y coordinates.
+
+{{< mermaid >}}
+sequenceDiagram
+ participant Agent as MCP host
+ participant Server as Codename One MCP server
+ participant Tree as Accessibility snapshot
+ participant EDT as Codename One EDT
+ Agent->>Server: ui_snapshot
+ Server->>Tree: build immutable semantics tree
+ Tree-->>Agent: roles, labels, values, actions
+ Agent->>Server: ui_set_text(profile-name, "Ada")
+ Server->>EDT: dispatch action
+ EDT-->>Server: success + fresh snapshot
+ Server-->>Agent: updated UI state
+{{< /mermaid >}}
+
+The built-in tools are small on purpose:
+
+| Tool | Purpose |
+|---|---|
+| `ui_snapshot` | Return the current semantic UI tree as JSON |
+| `ui_find` | Find nodes by identifier, label, or screen coordinate |
+| `ui_perform_action` | Run a semantic action with an optional argument |
+| `ui_activate` | Activate a node |
+| `ui_set_text` | Set editable text through the UI action model |
+
+Every action runs on the Codename One event dispatch thread. The agent never mutates the live component tree from the MCP transport thread. Each action returns a fresh snapshot so the next decision uses current state.
+
+## Starting a server is explicit
+
+There is no build hint that quietly exposes an application. Calling the API is the switch:
+
+```java
+MCP.startSocketServer(8765);
+```
+
+The socket mode is useful for a running simulator session a person can watch. A tool launched directly by an MCP host can use standard input and output instead:
+
+```java
+MCP.startStdioServer();
+```
+
+The JavaSE port owns the stdio transport because process standard input is not available on every Codename One target. While the transport is active, normal application logging is redirected away from standard output so it cannot corrupt the newline-delimited JSON-RPC stream.
+
+## Desktop tools get a menu without application code
+
+The JavaSE port adds an MCP menu to the simulator and Codename One desktop tools, including the new Settings editor. The menu can expose the running tool, detect installed MCP hosts, install or remove the local host registration, and control debug logging.
+
+Registration uses a small bridge. Most local MCP hosts launch servers over stdio. The visible Codename One tool is already running and listens on a loopback socket. `MCPStdioLauncher` relays between the host's stdio connection and that socket.
+
+```text
+Coding agent <-- stdio --> MCPStdioLauncher <-- loopback --> running tool
+```
+
+This is how an agent can drive the actual Certificate Wizard or Settings window in front of you instead of launching a hidden copy with different state.
+
+## Your app can publish domain tools
+
+UI actions are useful, but some operations should not be simulated as clicks. An application can expose a typed `Tool` with a JSON schema and handler:
+
+```java
+MCP.addTool(new Tool(
+ "current_user",
+ "Returns the signed in user",
+ "{\"type\":\"object\",\"properties\":{}}",
+ argumentsJson -> "{\"name\":\"" + signedInUser + "\"}"
+));
+```
+
+The server merges application tools with the built-in UI tools. Codename One already uses the same `com.codename1.ai.Tool` contract for model tool calls inside an app, so one definition can serve an in-app model and an external MCP host.
+
+Treat these tools as a privileged API. Do not publish a tool that returns signing passwords, API tokens, or unrestricted file contents because the handler happens to be local. The current server is local, but the agent still receives whatever the tool returns.
+
+## Screenshots remain available
+
+The semantic tree means a vision model does not need a screenshot for routine navigation. Some UI facts remain visual, such as a chart shape or a rendering defect. The server therefore exposes the current form as an optional PNG resource too.
+
+The two sources complement each other. The tree says “this is an enabled Save button with an activate action.” The PNG says “the button overlaps the footer.” A screenshot alone cannot reliably provide the first fact. A semantic tree cannot provide the second.
+
+## The scope is JavaSE today
+
+This release supports the JavaSE port, which covers the simulator and JavaSE-hosted desktop tools. It does not make every packaged Codename One application an MCP server. Packaged executable jars, cloud desktop builds, mobile targets, the JavaScript port, and the native macOS, Linux, and Windows ports do not yet have the launcher and transport plumbing.
+
+If you need MCP in one of those native targets, let us know which port and deployment model you need. The protocol engine and semantic tools are portable. The missing work is the transport, startup, registration, and security boundary for that target.
+
+The PR includes 13 protocol and UI-driving tests plus an end-to-end run against the reference MCP Inspector client. It also builds the core into an iOS application to verify that unused MCP code is pruned and that referenced core classes stay within the ParparVM API surface.
+
+Tomorrow's post covers another machine-readable view of Codename One. The new port status page turns the test suite into a dated support matrix instead of asking you to trust a manually maintained table.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/pixel-perfect-is-a-test.md b/docs/website/content/blog/pixel-perfect-is-a-test.md
new file mode 100644
index 00000000000..3ee71a08b14
--- /dev/null
+++ b/docs/website/content/blog/pixel-perfect-is-a-test.md
@@ -0,0 +1,211 @@
+---
+title: "Own Your Pixels: Native Fidelity on Your Schedule"
+slug: pixel-perfect-is-a-test
+url: /blog/pixel-perfect-is-a-test/
+date: '2026-07-17'
+author: Shai Almog
+description: "Codename One statically links its lightweight UI into your native app, so an OS update cannot silently redesign it. New native-reference fidelity tests let us adopt iOS 26 Liquid Glass and Material 3 on our schedule without surrendering control of the pixels."
+feed_html: '
Codename One now tests its statically linked UI against native iOS 26 and Material 3 references, so themes can move forward without an OS update changing your shipped app.'
+series: ["release-2026-07-17"]
+---
+
+
+
+An iOS or Android update can change a screen you shipped without you changing a line of code. If your app builds its UI from UIKit, SwiftUI, Compose, or Material widgets, Apple or Google owns those widget implementations. Codename One does something different. It statically links our lightweight component implementation into your native app. The UI you test is the UI your users keep after the next OS update. An update can still break a platform API or permission contract, but it cannot swap our button implementation for a new one.
+
+Lightweight does not mean a Java paint loop limping behind the platform. On iOS, components paint through our Metal pipeline. The moving Liquid Glass tab lens in this post is a Metal shader on the frame's existing command buffer, with no transfer of pixels back to the CPU. At the same time, [last week's ParparVM work](/blog/beating-hotspot-performance/) brought our ahead-of-time VM to geomean parity with warmed Java 25 across ten benchmarks. Six finished at or ahead of HotSpot.
+
+The tradeoff is that our UI does not inherit Apple's or Google's latest redesign for free. We have to study it, reproduce the parts that make sense, and test the result. That is work we take on so you can work on your app instead of working for the Apple and Google design teams. You decide when your app adopts a new look. The OS does not decide for you on upgrade day.
+
+The ParparVM and theme-fidelity branches ran in parallel. We wanted them in the same release, but each became too large to merge together safely. The fidelity work took longer. [PR #5274](https://github.com/codenameone/CodenameOne/pull/5274) alone reports 53,000 additions across 1,147 changed files. Generated access registries, resources, screenshots, and native goldens account for much of that number, but the scale is still real. Five follow-up PRs fixed what the first pass exposed.
+
+Owning the component stack also lets us work below the pixels. [PR #5363](https://github.com/codenameone/CodenameOne/pull/5363) adds a portable accessibility hierarchy, virtual semantic nodes, simulator audits, and platform preference detection. That took 6,965 additions across 57 files because proper accessibility is not a label bolted onto a button. Monday's post goes through that work.
+
+We will never make every pixel of every Codename One component land in exactly the same place as every native widget on every device. That is a fool's errand. The useful target is a point where 98% or 99% of people cannot tell which side is Codename One, and where we notice when a later change pulls us away from that level. We are not there on every component yet. These changes get us much closer, and now the gap is measured instead of argued over.
+
+## The themes were modern, but they were not finished
+
+We introduced the [iOS Modern and Android Material 3 themes](/blog/liquid-glass-material-3-modern-native-themes/) in May. They gave new projects a current starting point, but the first iOS implementation used translucent colors where iOS 26 uses a live glass material. Several controls still carried general Codename One geometry. The Material floating action button was a good example: it inherited the old circular shape and icon-derived size instead of Material 3's fixed 56dp rounded rectangle.
+
+This is the Android floating action button before and after the fidelity pass. The older render is on the left. The current Material 3 render is on the right.
+
+
+
+The iOS theme changed just as visibly. Buttons became full capsules, state glyphs moved to SF Symbols, and the sizes and spacing moved toward the iOS 26 references. The May render is on the left and the current render is on the right.
+
+
+
+Why is the floating action button still circular on iOS? iOS has no native floating action button equivalent. Importing Material's rounded rectangle into the iOS theme would make it less native, not more. Android follows Material 3. iOS keeps the established circular accent action until there is an iOS component we can target.
+
+## A native app produces the answer sheet
+
+The fidelity suite contains two standalone reference applications. One is written with UIKit and Swift. The other uses Android's Material components. We run those apps locally on pinned native toolchains and capture real controls in light and dark appearances, including normal, pressed, selected, and disabled states.
+
+Glass needs something behind it. A plain white tile can let a translucent fill pass for blur because there is nothing useful to blur. The glass tests place both implementations over the same photo and gradient backgrounds. That gives the lens edges, refraction, saturation, and backdrop blur real detail to distort. A tinted rounded rectangle cannot fake its way through that comparison.
+
+The native captures are committed as versioned golden sets:
+
+```text
+scripts/fidelity-app/goldens/
+ ios-26-metal/
+ ios-26-metal-anim/
+ ios-26-metal-frames/
+ android-m3/
+ android-m3-anim/
+```
+
+CI never invents the native side. It renders the Codename One component under the matching theme, compares it with the committed native image, and reports both visual and geometric differences. A one-way gate fails if a score drops below its recorded baseline.
+
+{{< mermaid >}}
+flowchart LR
+ A["UIKit reference app
iOS 26 simulator"] --> B["Versioned native goldens"]
+ C["Material reference app
API 36 emulator"] --> B
+ D["Codename One fidelity app
rendered in CI"] --> E["Pixel comparison"]
+ B --> E
+ E --> F["Visual score"]
+ E --> G["Geometry metrics"]
+ E --> H["Fixed animation frames"]
+ F --> I["One-way baseline gate"]
+ G --> I
+ H --> I
+{{< /mermaid >}}
+
+The iOS golden set is pinned to iOS 26. The Android set is pinned to Material 3 on API 36 at 160dpi. When iOS 27 arrives, we will capture a new `ios-27` set, add a theme variant and a CI matrix row, then test both generations until we deliberately retire the older one. We will not silently replace the iOS 26 answer sheet and call the movement an improvement.
+
+## What the percentage means, and what it does not
+
+The current Android baseline contains 54 pairs. Its median tolerant visual score is 95.5%. The worst pair is the dark outlined button in its pressed state at 91.25%. The iOS baseline contains 68 pairs with a 94.4% median. Its worst pair is the dark tab bar at 83.45%.
+
+Those percentages are useful for a regression gate. They are not a substitute for eyes, and they are harsher on larger, information-dense components. A button has one label and a compact outline. The tab bar has three glyphs, three labels, a lens, a long glass surface, and far more edges. A small repeated mismatch affects more of the tab image. The tab can score 83.45% even when it looks closer to the native reference than a button scoring above 90%.
+
+Thomas made this point in the [PR discussion](https://github.com/codenameone/CodenameOne/pull/5274). A radio button can look identical to a person and still lose points to antialiasing. A large tab bar can hide a wrong icon inside thousands of matching backdrop pixels. A high overlay score can also conceal a wrong width or corner radius.
+
+We changed the suite in response. It now reports bounding-box offsets, width and height ratios, center offsets, and an estimated corner radius separately from the visual score. The comparison mode also comes from an explicit `material: normal|glass|lens` declaration instead of an image heuristic. Human review remains part of the process, especially for motion and translucent effects where one score can hide the wrong detail.
+
+Here are four current iOS pairs from the automated report. Native is on the left. Codename One is on the right, separated by the thin vertical line. The dark picker makes the limitation obvious: the selected row is close, but the off-row contrast still needs work.
+
+
+
+The Android report uses the same layout and divider. The floating action button now has Material 3 geometry, while the tab typography and outlined button still show smaller differences that the baseline tracks.
+
+
+
+The suite deliberately stops at the component boundary. Screen spacing, hierarchy, and composition are application design decisions whether you use SwiftUI, Compose, or Codename One. The themes should provide sensible defaults that work across devices. The final layout still belongs to the developer building the product.
+
+## The tab bar became a rendering project
+
+The iOS 26 tab selection is not a tinted pill sliding under icons. During a touch-driven transition, the selection becomes a magnifying lens. It travels across the bar, refracts the background and glyphs under it, stretches during flight, and settles with a small spring overshoot.
+
+This animation compares the native references with Codename One. The top row is light appearance and the bottom row is dark. Native is on the left. Codename One is on the right.
+
+
+
+The static comparison below freezes the useful stages so you can inspect the geometry.
+
+
+
+The first native recording sent us in the wrong direction. We changed `selectedIndex` automatically and captured a flat platter sliding across the bar. That is what UIKit shows for a programmatic selection. The full Liquid Glass lens only appears after a real touch. We eventually caught the difference and built a small XCUITest driver that taps the actual tab bar while the simulator records it. The animation above comes from that touch-driven reference.
+
+The shared motion lives in `TabSelectionMorph`. It calculates the pill and lens geometry for each frame from the old tab, the new tab, and the current touch progress. The result also carries the magnification, color separation, tint, and spring settle. `Tabs` paints it. `SwitchThumbDroplet` does the same job for the glass switch thumb, including the stretch and vertical squash during travel.
+
+The public theme surface is intentionally smaller than the internal model. A theme selects `tabsMorphPreset: ios26` or `subtle`, then adjusts duration, lens intensity, and spring percentage. Thirteen low-level motion constants from the first implementation were removed because they made it too easy to tune one screenshot while breaking the path between screenshots.
+
+The test freezes the animation at 0%, 10%, 25%, 50%, 75%, 90%, and 100%. It checks that the frames are distinct, travel is monotonic, and overshoot stays bounded. The committed intermediate frames are Codename One goldens, not native intermediate-frame comparisons. We still review native motion from the captured video because the intermediate frames are not compared automatically yet.
+
+## Glass is a typed material, not a pile of constants
+
+The first glass pass exposed saturation, blur, scale, offset, refraction, and specular values as unrelated theme constants. That did not look good. A toolbar, panel, button, and moving lens could each be tuned into a different material by accident.
+
+`GlassRecipe` now defines four bounded material intents:
+
+```java
+GlassRecipe.plainBlur();
+GlassRecipe.liquidChrome(dark); // edge bars and toolbars
+GlassRecipe.liquidPill(dark); // floating tab bar
+GlassRecipe.liquidPanel(dark); // general glass surface
+```
+
+The theme assigns a recipe to a UIID. `Component` resolves it and passes the parameters through `Graphics.glassRegion(...)`. Ports receive a material recipe instead of reading iOS-specific constants during paint.
+
+On iOS, the moving lens is a Metal fragment shader on the frame's existing command buffer. It does not transfer pixels from GPU memory back to the CPU. The larger glass patch does need backdrop pixels, so it uses a cache keyed by bounds, recipe parameters, and a hash of the actual backdrop bytes. In a profiled suite run, full composition averaged about 90ms when the backdrop changed and a stable cache hit averaged 5.3ms, with 475 hits and 253 misses. That is development instrumentation, not a general app benchmark, but it gave us a concrete cache policy.
+
+JavaSE originally had no `glassRegion` implementation. It silently reduced the bar to a plain blur, leaving the lens to magnify a gray slab. JavaScript discarded the material parameters and used a flat tint plus uniform zoom. [PR #5388](https://github.com/codenameone/CodenameOne/pull/5388) implements the material on both ports and pins 14 constants across JavaScript, JavaSE, the iOS CPU reference, and the Metal shader. The JavaScript lens now produces the same pixel CRC as JavaSE at all seven probe points.
+
+There is still a platform tradeoff. The iOS path uses the native Metal shader. JavaSE and JavaScript reproduce the pixels in software. They do not get the same GPU path, so a complex moving backdrop can look less smooth there. The component and its state model remain portable. The implementation cost is not identical.
+
+## CSS had to grow with the themes
+
+Several differences could not be fixed by editing a color. The framework and CSS compiler gained new vocabulary:
+
+```css
+#Constants {
+ tabsMorphPreset: "ios26";
+ buttonReleaseFadeDurationInt: 180;
+ tabsEqualWidthBool: true;
+}
+
+RaisedButton {
+ cn1-background-type: cn1-pill-border;
+ border: 0.1mm solid rgba(136,254,255,0.58);
+ cn1-stroke-gradient: #ffffff;
+ cn1-stroke-gradient-angle: 135deg;
+}
+```
+
+`RoundBorder` now supports a gradient stroke. Button gained an opt-in release overlay so iOS Modern can fade the held state over 180ms instead of snapping it away. `Dialog` and `InteractionDialog` gained opt-in centered-title layouts while keeping command rows flush with the card edges. Tabs gained equal-width cells and a correctly scaled Material indicator. `Style` gained letter spacing. Resource format revisions carry gradients, filters, and gradient strokes into the shipped `.res` files.
+
+The icon problem also needed a platform answer. Material icons looked wrong inside iOS controls even when their meaning was correct. `FontImage.createSFOrMaterial(...)` selects an Apple SF Symbol on iOS and the Material fallback elsewhere. Toolbar commands can now hide their text visually while retaining the command title for accessibility.
+
+The follow-ups matter as much as the headline PR:
+
+- [PR #5373](https://github.com/codenameone/CodenameOne/pull/5373) restored application accent bindings that the first fidelity pass accidentally replaced with fixed colors. The regression also exposed a test that could accept default colors after a retry, so the test was fixed too.
+- [PR #5379](https://github.com/codenameone/CodenameOne/pull/5379) added the iOS 26 button capsules, the 180ms release fade, and gradient strokes.
+- [PR #5376](https://github.com/codenameone/CodenameOne/pull/5376) added opt-in centered dialog-title layouts and fixed edge-to-edge command grids in both dialog classes.
+- [PR #5387](https://github.com/codenameone/CodenameOne/pull/5387) tightened toolbar icons, spinner contrast and insets, slider thumbs, progress tracks, disabled switches, and glass-panel corners.
+- [PR #5388](https://github.com/codenameone/CodenameOne/pull/5388) brought JavaSE and JavaScript glass rendering back in line with the shared model.
+
+The suite also found a 14-year-old iOS gradient bug. The on-screen `fillLinearGradientGlobal` path had horizontal and vertical axes reversed since 2012. The mutable-image path was correct. A controlled gradient backdrop finally made the difference attributable.
+
+## Most of the week was invisible backend work
+
+While the theme diff was easy to photograph, most of our time went into replacing the Codename One account login system.
+
+The new [Account Security page](https://cloud.codenameone.com/account/security) puts the controls in one place. You can now sign in with Google or GitHub, enable a time-based two-factor authentication app, register passkeys, inspect active sessions, revoke one session, or sign out every other device. A passkey can use the fingerprint, face, PIN, or other device-unlock method supported by your platform.
+
+This work changes no pixel in your app, but it changes how we protect build credentials, signing assets, and account data. It also removes several account responsibilities from the desktop Settings tool. Saturday's post explains that smaller tool.
+
+## Three smaller changes with large failure modes
+
+Three other PRs deserve a note because each fixes a problem that can waste hours.
+
+**Aligned text no longer jumps when editing starts.** [PR #5374](https://github.com/codenameone/CodenameOne/pull/5374) makes the Android inline editor and JavaSE Swing editor honor `getAbsoluteAlignment()`. A right-aligned number now stays on the right when the lightweight field hands control to the native editor. Multi-line Swing text areas remain unchanged because Swing has no per-line horizontal alignment there.
+
+**Local tooling failures can offer Get Help.** [PR #5383](https://github.com/codenameone/CodenameOne/pull/5383) adds `mvn cn1:get-help` after install, project creation, configuration, local run, or build submission failures. Nothing is telemetry and nothing is sent until you press Send. The report includes the failed step, command, Java and proxy environment, and a capped error trace. If you supplied an email, support can reply asynchronously. The UI does not promise round-the-clock live staffing.
+
+**A stale class now fails before upload.** [PR #5390](https://github.com/codenameone/CodenameOne/pull/5390) scans the staged application jar with ASM and verifies that your own package references close over classes that actually exist. The old failure appeared roughly 11,000 build-log lines later as a missing generated Objective-C header. The new failure runs on the client and tells you to run `mvn clean`.
+
+```text
+The compiled application classes are inconsistent.
+ - com.example.Gone (referenced from com.example.Caller)
+Run 'mvn clean' and rebuild.
+```
+
+## The rest of this release series
+
+The fidelity work could fill the week, but five other changes need their own explanations:
+
+- **Saturday:** {{< post-link path="/blog/standalone-codename-one-settings" text="Codename One Settings is now a standalone project tool" >}}.
+- **Sunday:** {{< post-link path="/blog/widgets-live-activities-dynamic-island" text="Widgets, Live Activities, and Dynamic Island from one Java API" >}}.
+- **Monday:** {{< post-link path="/blog/accessibility-semantics" text="Accessibility semantics and the parallel UI tree" >}}.
+- **Tuesday:** {{< post-link path="/blog/codename-one-mcp-server" text="How that accessibility tree lets an agent drive a Codename One app over MCP" >}}.
+- **Wednesday:** {{< post-link path="/blog/tested-port-support" text="A support matrix generated from current CI evidence" >}}.
+
+If you use the modern themes, the most useful thing you can send us is still a screenshot with a precise component, state, appearance, and device. The ratchet stops known pixels from getting worse. It does not tell us which missing screen matters most to you.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/standalone-codename-one-settings.md b/docs/website/content/blog/standalone-codename-one-settings.md
new file mode 100644
index 00000000000..de3466d3810
--- /dev/null
+++ b/docs/website/content/blog/standalone-codename-one-settings.md
@@ -0,0 +1,101 @@
+---
+title: "Codename One Settings Is Now a Standalone Tool"
+slug: standalone-codename-one-settings
+url: /blog/standalone-codename-one-settings/
+date: '2026-07-18'
+author: Shai Almog
+description: "Codename One Settings now launches as a standalone Maven-distributed desktop tool for project identity, build hints, themes, and extensions. Accounts, signing, and build monitoring moved to the tools that own those jobs."
+feed_html: '
Codename One Settings is now a focused Maven-distributed desktop tool for project identity, build hints, themes, and extensions.'
+series: ["release-2026-07-17"]
+---
+
+
+
+Codename One Settings used to be a screen inside the old GUI Builder jar. It edited project properties, managed accounts, opened signing workflows, monitored builds, installed extensions, and accumulated every job that did not have a better home.
+
+[PR #5359](https://github.com/codenameone/CodenameOne/pull/5359) replaces it with a standalone Codename One desktop application. It does fewer things, which is the point.
+
+## One command, one project
+
+Run the new tool from a Codename One Maven project:
+
+```bash
+mvn cn1:settings
+```
+
+The Maven plugin resolves the `com.codenameone:codenameone-settings` artifact, launches it against the current project, and writes changes back to that project's `codenameone_settings.properties` and Maven configuration. The tool has its own release lifecycle instead of borrowing the GUI Builder's jar and version.
+
+{{< mermaid >}}
+flowchart LR
+ A["Your Maven project"] -->|"mvn cn1:settings"| B["Codename One Maven plugin"]
+ B --> C["codenameone-settings artifact"]
+ C --> D["Basic project settings"]
+ C --> E["Build hints"]
+ C --> F["Extensions and themes"]
+ D --> G["Project files"]
+ E --> G
+ F --> G
+{{< /mermaid >}}
+
+This is the new Basic screen. It keeps the properties that belong to the source project: display name, package name, version, main class, icon, and related build choices.
+
+
+
+## Build hints are searchable project data
+
+Build hints used to feel like an untyped text file with a dialog in front of it. The new editor preserves direct key-value control, but adds descriptions, known value types, filtering, and a focused editing flow.
+
+
+
+Nothing prevents you from editing the property file by hand. The Settings tool is useful when you do not remember whether the current spelling is `ios.themeMode`, `and.themeMode`, or a platform-specific signing key. It also keeps project values visible without mixing them with account state from the cloud.
+
+For example, selecting the modern native themes still produces ordinary project settings:
+
+```properties
+nativeTheme=modern
+ios.themeMode=modern
+and.themeMode=modern
+```
+
+The file remains the source of truth. The UI is an editor, not a second configuration system.
+
+## Extensions keep compatibility warnings
+
+The Extensions screen browses the live catalog and installs or removes both Maven dependencies and legacy cn1lib packages. It also retains bundled compatibility metadata when the live catalog omits it. That matters for older entries such as AdMob full-screen ads, where installing an obsolete library without a warning is worse than showing stale-looking metadata.
+
+
+
+The implementation includes install and uninstall tests for both dependency models. It also handles light and dark appearance, keyboard focus, text caret behavior, native menus, the application icon, and a real desktop About dialog. The tool itself is built with Codename One, which gives us a useful test of the JavaSE desktop path every time we edit it.
+
+## What moved out
+
+The smaller scope is easier to understand:
+
+| Job | Where it lives now |
+|---|---|
+| Project name, version, package, icon | Codename One Settings |
+| Build hints and themes | Codename One Settings |
+| Extensions | Codename One Settings |
+| Apple and Android signing assets | [Certificate Wizard](/blog/standalone-certificate-wizard/) |
+| Account login and security | [Account Security](https://cloud.codenameone.com/account/security) |
+| Cloud build monitoring | Codename One website |
+
+Accounts do not belong inside a project-property editor. Certificates have enough platform rules to justify their own tool. Build monitoring belongs next to the builds. Removing those sections leaves Settings with one durable responsibility: edit the project you launched it from.
+
+## The migration cost
+
+The new editor is distributed as a separate Maven artifact and its application is built on Java 17. The release workflow now has to publish that reactor after the core release and keep its versions aligned with the Maven plugin. That is more release plumbing than embedding one more screen in `guibuilder.jar`.
+
+The payoff is separation. We can change Settings without shipping a GUI Builder update, and the GUI Builder no longer carries account, certificate, catalog, and project-editor code that it does not own.
+
+The old settings UI is gone from `cn1:settings`. If your documentation or script tells users to open the Control Center for project properties, change it to the command above. Signing instructions should point to `mvn cn1:certificatewizard` instead.
+
+Tomorrow's post covers the opposite kind of extraction. Widgets and Live Activities live outside your app's window, but one declarative Java model now updates them across iOS, Android, Windows, Linux, and the simulator.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/tested-port-support.md b/docs/website/content/blog/tested-port-support.md
new file mode 100644
index 00000000000..0b13b621c3d
--- /dev/null
+++ b/docs/website/content/blog/tested-port-support.md
@@ -0,0 +1,99 @@
+---
+title: "Port Support You Can Trace Back to a Green Test"
+slug: tested-port-support
+url: /blog/tested-port-support/
+date: '2026-07-22'
+author: Shai Almog
+description: "The Codename One port status page maps 49 feature groups across 10 targets to current conformance reports, environment details, skip explanations, and dated CI evidence instead of a manually maintained support table."
+feed_html: '
The Codename One port status page maps 49 feature groups across 10 targets to dated CI evidence and explicit skip explanations.'
+series: ["release-2026-07-17"]
+---
+
+
+
+“Supported on iOS, Android, desktop, and web” sounds useful until you need one method on one target. Does WebSocket work on watchOS? Which Linux architectures do we build? Was the JavaScript media test green this week, or did somebody update a table six months ago and forget it?
+
+[PR #5389](https://github.com/codenameone/CodenameOne/pull/5389) turns those questions into the [Codename One Port Status page](/port-status/). It maps 49 user-facing feature groups across 10 portability targets to current conformance results, environment data, skip reasons, and the date of the run.
+
+## The table is an output, not an opinion
+
+The HelloCodenameOne suite already exercises APIs and screenshot goldens on Android, iOS, tvOS, watchOS, JavaScript, native Linux, native Windows, and Mac Catalyst. The missing part was a contract that translated thousands of test cases into a stable public vocabulary.
+
+The new conformance mapping connects registered tests and screenshots to rows such as networking, media, databases, maps, notifications, input, accessibility, and 3D. CI normalizes each port's result into the same report format. A publishing workflow writes the latest reports to a data-only branch. The website consumes those reports and renders the matrix.
+
+{{< mermaid >}}
+flowchart LR
+ A["Port CI jobs"] --> B["HelloCodenameOne tests"]
+ B --> C["Normalized conformance report"]
+ D["Feature-to-test contract"] --> E["49 public feature rows"]
+ C --> F["Data-only status branch"]
+ E --> F
+ F --> G["/port-status/"]
+ G --> H["490 target-feature cells"]
+ G --> I["Environment and run date"]
+ G --> J["Skip and scope explanations"]
+{{< /mermaid >}}
+
+The page currently renders 490 feature cells. Ten targets appear because architectures and renderer variants matter. iOS Metal and legacy OpenGL are separate evidence paths. Windows x64 and ARM64 are separate. Linux x64 and ARM64 are separate.
+
+JavaSE is deliberately excluded from the public portability matrix. It is the simulator and development runtime, not one of the deployed native targets the table is meant to prove.
+
+## A green cell has a chain of evidence
+
+Each status report records the commit, environment, registered tests, outcome, duration, and skipped cases. The website data also records the runtime used for browser and platform evidence. For example, the current browser environment file names the Chromium, Firefox, and WebKit engine versions rather than saying “modern browsers.”
+
+The contract itself is validated in CI:
+
+```bash
+python3 scripts/hellocodenameone/conformance/port_status.py validate
+python3 -m unittest -v \
+ scripts/hellocodenameone/conformance/test_port_status.py
+node scripts/website/validate_port_status.mjs
+```
+
+Validation fails if a registered conformance test or screenshot golden is orphaned from the public mapping, if the mapping points to a test that no longer exists, or if the published reports do not satisfy the schema. That makes the page part of the test system instead of a second table someone has to remember to edit.
+
+## Skipped does not always mean unsupported
+
+Some tests cannot run meaningfully on a target even when the API works. A device-only test may need hardware. A store API may require credentials. A screenshot can be irrelevant on watchOS because the phone-sized fixture is cropped before it tests the intended behavior.
+
+The status page keeps skips visible and attaches a reason. It does not silently convert every skip to “unsupported,” and it does not turn every skip into a green claim either. This distinction is important when a feature is supported but its current CI proof covers only part of the behavior.
+
+The deployment section applies the same standard to minimum versions. It separates the declared floor from the environment CI actually ran. An iOS build may compile with an iOS 14 deployment target while hosted CI runs the current Xcode 26 simulator. The page says both. A compiled floor is evidence, but it is not the same as running on an iOS 14 device.
+
+## Benchmarks use the same application
+
+The page also carries ten common workloads through each generated application: integer and long arithmetic, transcendental math, sequential and random arrays, allocation, map churn, string building, recursion, and quicksort.
+
+```text
+3 warm-up runs
+5 measured runs
+report the minimum measured time
+verify the workload checksum
+```
+
+These are absolute per-target timings, not a claim that an ARM watch should beat a desktop CPU. Their value is trend detection and a common workload inside the actual generated port application.
+
+Binary size and memory are intentionally absent. The current artifacts mix compressed Android and web packages with unpacked Apple bundles and native executables. The ports also report different memory concepts. Publishing those numbers in one comparison row would look precise while measuring different things. They will return when a dedicated release-mode fixture packages and samples every target consistently.
+
+## What a green test cannot prove
+
+A green status means the mapped tests passed in the named environment at the recorded commit. It does not prove that no application can hit a bug. It does not extend the test to OS versions, devices, drivers, or permissions that the run did not exercise.
+
+That boundary is why the page exposes details instead of collapsing everything to a marketing checkmark. You can inspect the target environment, last run, mapped coverage, and reason for an exception. If the proof is narrower than your requirement, the page should make that visible before you commit to a platform.
+
+This also changes how we review a new API. Adding the Java class is no longer enough. A feature needs a conformance test, a mapping to a public capability, and green results on the ports we claim. If a port intentionally does not implement it, that scope must be explicit.
+
+## One place to start a platform decision
+
+Use the [Port Status page](/port-status/) when you need the current deployment floors, architecture coverage, API evidence, browser engines, or common-workload results. Then follow the linked test detail for the part your application depends on.
+
+This closes the week's series: [measured native-theme fidelity](/blog/pixel-perfect-is-a-test/), a [standalone Settings tool](/blog/standalone-codename-one-settings/), [external surfaces](/blog/widgets-live-activities-dynamic-island/), [portable accessibility semantics](/blog/accessibility-semantics/), and an [MCP server built on that semantic tree](/blog/codename-one-mcp-server/). The common thread is not the number of features. It is turning claims into artifacts you can inspect.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/content/blog/widgets-live-activities-dynamic-island.md b/docs/website/content/blog/widgets-live-activities-dynamic-island.md
new file mode 100644
index 00000000000..1562165d3a6
--- /dev/null
+++ b/docs/website/content/blog/widgets-live-activities-dynamic-island.md
@@ -0,0 +1,181 @@
+---
+title: "Widgets, Live Activities, and Dynamic Island From One Java API"
+slug: widgets-live-activities-dynamic-island
+url: /blog/widgets-live-activities-dynamic-island/
+date: '2026-07-19'
+author: Shai Almog
+description: "The new com.codename1.surfaces API describes home-screen widgets, Live Activities, Dynamic Island regions, Android ongoing notifications, and desktop widgets as data that can render while the main app is not active."
+feed_html: '
One declarative Java API now targets home-screen widgets, Live Activities, Dynamic Island, Android ongoing notifications, and desktop widgets.'
+series: ["release-2026-07-17"]
+---
+
+
+
+Widget support was one of the earliest Codename One requests. We dismissed it for years because a widget must render while the application UI is not running. A normal Codename One `Component` needs the application renderer, event dispatch thread, and live object graph. A home-screen widget gets none of those.
+
+The missing piece had been under our nose for a decade. Steve added background processes so an app could refresh data without showing its UI. That solves the update side. The rendering side becomes possible once the widget is data rather than a live component.
+
+[PR #5365](https://github.com/codenameone/CodenameOne/pull/5365) turns that observation into `com.codename1.surfaces`, one API for home-screen widgets, Live Activities, Dynamic Island, Android ongoing notifications, and desktop floating widgets.
+
+## The dead-process rule
+
+An external surface is a piece of application state that the operating system can render outside the app. The app publishes a serializable layout and a timeline of state maps. The platform persists that data, then renders it with its own surface technology.
+
+You cannot attach a Java listener to a widget. There may be no Java process to invoke. You assign a string action ID instead. A tap launches the app and delivers that action after startup.
+
+{{< mermaid >}}
+flowchart LR
+ A["Codename One app or background fetch"] --> B["Surface layout + timeline state"]
+ B --> C["Canonical JSON + content-addressed PNGs"]
+ C --> D["iOS WidgetKit / ActivityKit"]
+ C --> E["Android RemoteViews / notification"]
+ C --> F["Desktop floating widget"]
+ D --> G["Tap action ID"]
+ E --> G
+ F --> G
+ G -->|"cold start if needed"| A
+{{< /mermaid >}}
+
+The simulator implements the same model. Open **Widgets > Widgets Preview** to inspect every registered kind, move through its timeline, change size and appearance, and click actions without creating a device build.
+
+
+
+## Widget kinds exist at build time
+
+iOS and Android compile widget galleries into the native application. The kinds must therefore be known during the build. Add a `surfaces.json` resource:
+
+```json
+{
+ "liveActivities": true,
+ "kinds": [
+ {
+ "id": "delivery_status",
+ "name": "Delivery",
+ "description": "Track your order",
+ "iosFamilies": ["systemSmall", "systemMedium"]
+ }
+ ]
+}
+```
+
+Mirror that declaration in `init()` so the simulator and runtime know the same kind:
+
+```java
+Surfaces.registerWidgetKind(new WidgetKind("delivery_status")
+ .setDisplayName("Delivery")
+ .setDescription("Track your order")
+ .addSupportedSize(WidgetSize.SMALL)
+ .addSupportedSize(WidgetSize.MEDIUM));
+```
+
+Referencing the surfaces package is the build gate. Apps that never use it get no WidgetKit extension, Android receiver, app group, or surface resources.
+
+## A timeline carries future state
+
+A widget publishes one layout and dated entries. Placeholders such as `${status}` resolve from each entry's state map. The operating system advances to the next entry without waking the app.
+
+```java
+private static Map state(
+ String status, long eta, float progress) {
+ Map values = new HashMap();
+ values.put("status", status);
+ values.put("eta", Long.valueOf(eta));
+ values.put("progress", Float.valueOf(progress));
+ return values;
+}
+
+long now = System.currentTimeMillis();
+long eta = now + 4 * 60000L;
+
+WidgetTimeline timeline = new WidgetTimeline()
+ .setContent(buildDeliveryLayout())
+ .addEntry(new Date(now), state("Preparing", eta, 0.1f))
+ .addEntry(new Date(now + 60000L), state("Out for delivery", eta, 0.4f))
+ .addEntry(new Date(eta), state("Delivered", eta, 1f))
+ .setReloadPolicy(WidgetTimeline.RELOAD_AT_END);
+
+Surfaces.publish("delivery_status", timeline);
+```
+
+`SurfaceDynamicText` is the useful trick here. Give it an ETA and `STYLE_TIMER_DOWN`. WidgetKit renders a timed `Text`. Android uses a `Chronometer`. The countdown changes every second with no Java wake-up.
+
+```java
+return new SurfaceColumn().setPadding(12).setSpacing(6)
+ .add(new SurfaceText("${status}")
+ .setFontSize(15)
+ .setFontWeight(SurfaceFontWeight.SEMIBOLD))
+ .add(new SurfaceDynamicText(
+ SurfaceDynamicText.STYLE_TIMER_DOWN, "eta")
+ .setFontSize(24)
+ .setColor(SurfaceColor.ACCENT))
+ .add(new SurfaceProgress(SurfaceProgress.STYLE_LINEAR)
+ .setValueState("progress"))
+ .setAction("open_order", params);
+```
+
+For longer-lived data, background fetch loads fresh state and republishes the timeline. On Android, an exhausted widget can request that fetch, throttled to once per 15 minutes per kind. iOS does not let a WidgetKit extension wake the host app whenever it wants, so iOS timelines should span the expected gap between background fetch opportunities.
+
+## Dynamic Island is another layout region
+
+A Live Activity uses the same nodes and state maps. Its descriptor adds the regions ActivityKit needs: compact leading and trailing content, minimal content, and the expanded leading, trailing, center, and bottom areas.
+
+```java
+LiveActivityDescriptor descriptor = new LiveActivityDescriptor("delivery")
+ .setContent(buildDeliveryLayout())
+ .setCompactLeading(new SurfaceImage(courierAvatar)
+ .setSize(24, 24).setCornerRadius(12))
+ .setCompactTrailing(new SurfaceDynamicText(
+ SurfaceDynamicText.STYLE_TIMER_DOWN, "eta"))
+ .setExpandedCenter(new SurfaceText("${status}"))
+ .setExpandedBottom(new SurfaceProgress(
+ SurfaceProgress.STYLE_LINEAR).setValueState("progress"));
+
+LiveActivity activity = LiveActivity.start(descriptor, initialState);
+activity.update(arrivingState);
+activity.end(deliveredState);
+```
+
+On iOS, that becomes a lock-screen Live Activity and Dynamic Island presentation. On Android, it becomes an ongoing notification. On desktop, it becomes a floating pill window.
+
+
+
+## Widgets on Linux are not a typo
+
+The same publish call reaches desktop targets. A JavaSE or native desktop build can expose kinds from a tray menu and pin them as frameless, always-on-top windows. Windows can also generate a Windows 11 Widgets Board provider when `windows.msix=true`.
+
+Linux uses GTK floating windows. Compositors with the layer-shell protocol can place them like desktop applets. GNOME Wayland does not expose that positioning contract, so it falls back to a normal floating window. Desktop widgets are process-bound in this release. They exist while the application process runs, unlike iOS and Android system widgets.
+
+The analog clock below uses `SurfaceVector`, a retained set of fill, stroke, line, arc, text, and rotation operations. Sixty timeline entries update the hand angles once per minute.
+
+
+
+## The common model has a floor
+
+The surface node catalog is deliberately smaller than the Codename One component set. Android `RemoteViews` is the constrained renderer, so it defines several compromises:
+
+| Feature | iOS | Android |
+|---|---|---|
+| Layout rows and columns | SwiftUI stacks | LinearLayout |
+| Countdown | Native timed Text | Chronometer |
+| Circular progress | Gauge | Linear fallback |
+| Relative date | Native update | Static until refresh |
+| Vector drawing | SwiftUI Canvas | Rasterized bitmap |
+| Small widget actions | Root action only | Per-node actions |
+
+Descriptors stop at eight nesting levels. JSON and image payloads should stay comfortably below 200KB. Android eventually has to send a rendered widget through a 1MB binder transaction, while the iOS extension runs with a small memory budget.
+
+The PR verified serialization, timeline ordering, image deduplication, live-activity lifecycle, cold-start action queues, and generated Swift compilation. The simulator sample ran end to end. The PR did not claim on-device verification for every platform path, and the Windows MSIX plus native Linux window paths could not be executed on the author's Mac. Those are real gaps to keep in mind for the first release.
+
+
+
+Push-driven widget updates are not in this version. The app publishes timelines, background fetch republishes them, and live activities accept app-driven updates. Server-pushed state and ActivityKit push tokens are planned on top of the same wire format.
+
+Tomorrow's post covers the parallel tree that makes lightweight Codename One components visible to VoiceOver, TalkBack, UI Automation, AT-SPI, ARIA, and, unexpectedly, AI agents.
+
+---
+
+## Discussion
+
+_Join the conversation via GitHub Discussions._
+
+{{< giscus >}}
diff --git a/docs/website/social/linkedin/2026-07-17-1800-shai-pixel-perfect-is-a-test.md b/docs/website/social/linkedin/2026-07-17-1800-shai-pixel-perfect-is-a-test.md
new file mode 100644
index 00000000000..2ae6e1469d3
--- /dev/null
+++ b/docs/website/social/linkedin/2026-07-17-1800-shai-pixel-perfect-is-a-test.md
@@ -0,0 +1,22 @@
+---
+title: "Shai: own your pixels"
+slug: 2026-07-17-1800-shai-pixel-perfect-is-a-test
+platform: linkedin
+account: shai
+source_slug: pixel-perfect-is-a-test
+publish_at: '2026-07-17T18:00:00'
+timezone: Asia/Jerusalem
+review_by: '2026-07-17'
+status: approved
+image: /blog/pixel-perfect-is-a-test.jpg
+---
+
+An OS update can change the UIKit, SwiftUI, Compose, or Material widgets in an app you already shipped. The platform owns those widget implementations.
+
+Codename One statically links its lightweight UI into the native app. The screen you test stays that way until you decide to change it.
+
+The price is that we do not inherit a new iOS or Android look for free. This week we built native reference apps and made CI compare our themes against them. PR #5274 alone changed 1,147 files. It covers pixels, geometry, glass backgrounds, and fixed animation frames.
+
+That is work we take on so you can work on your app instead of working for the Apple and Google design teams. You adopt their new look on your schedule, without giving them control of your shipped UI.
+
+Full write-up: https://www.codenameone.com/blog/pixel-perfect-is-a-test/
diff --git a/docs/website/social/linkedin/2026-07-18-1200-shai-standalone-codename-one-settings.md b/docs/website/social/linkedin/2026-07-18-1200-shai-standalone-codename-one-settings.md
new file mode 100644
index 00000000000..7f3a9152b1d
--- /dev/null
+++ b/docs/website/social/linkedin/2026-07-18-1200-shai-standalone-codename-one-settings.md
@@ -0,0 +1,22 @@
+---
+title: "Shai: refreshing the tools around Codename One"
+slug: 2026-07-18-1200-shai-standalone-codename-one-settings
+platform: linkedin
+account: shai
+source_slug: standalone-codename-one-settings
+publish_at: '2026-07-18T12:00:00'
+timezone: Asia/Jerusalem
+review_by: '2026-07-17'
+status: approved
+image: /blog/standalone-codename-one-settings.jpg
+---
+
+Old developer tools rarely become bad in one dramatic step. They accumulate jobs until nobody can explain where one tool ends and another begins.
+
+Codename One Settings had reached that point. Project configuration lived beside account login, certificates, build monitoring, and extensions inside the old GUI Builder jar.
+
+We rewrote it as a standalone tool, but the larger goal is to refresh the development environment around Codename One. Settings now owns project configuration. Signing, accounts, and cloud builds have their own homes.
+
+This is less about a new command and more about removing old assumptions one tool at a time. Smaller tools with clear boundaries are easier to understand, replace, and improve.
+
+Full write-up: https://www.codenameone.com/blog/standalone-codename-one-settings/
diff --git a/docs/website/social/linkedin/2026-07-19-1200-shai-widgets-live-activities-dynamic-island.md b/docs/website/social/linkedin/2026-07-19-1200-shai-widgets-live-activities-dynamic-island.md
new file mode 100644
index 00000000000..f0dff6c6490
--- /dev/null
+++ b/docs/website/social/linkedin/2026-07-19-1200-shai-widgets-live-activities-dynamic-island.md
@@ -0,0 +1,22 @@
+---
+title: "Shai: the widget request we called impossible"
+slug: 2026-07-19-1200-shai-widgets-live-activities-dynamic-island
+platform: linkedin
+account: shai
+source_slug: widgets-live-activities-dynamic-island
+publish_at: '2026-07-19T12:00:00'
+timezone: Asia/Jerusalem
+review_by: '2026-07-17'
+status: approved
+image: /blog/widgets-live-activities-dynamic-island.jpg
+---
+
+We dismissed widget support as impossible for years.
+
+A home-screen widget has to render when the Codename One UI is not running. The answer was to stop treating it as UI. The app publishes a serializable layout plus dated state. WidgetKit, RemoteViews, or the desktop renderer owns the pixels after that.
+
+Steve's decade-old background-process work supplies fresh data. Timelines carry future updates without waking the app. The same model turned out to fit Live Activities and Dynamic Island too.
+
+The first version has real limits, especially around Android RemoteViews and desktop process lifetime. The model finally fits the operating systems instead of fighting them.
+
+Full write-up: https://www.codenameone.com/blog/widgets-live-activities-dynamic-island/
diff --git a/docs/website/social/linkedin/2026-07-20-1200-shai-accessibility-semantics.md b/docs/website/social/linkedin/2026-07-20-1200-shai-accessibility-semantics.md
new file mode 100644
index 00000000000..942a3168848
--- /dev/null
+++ b/docs/website/social/linkedin/2026-07-20-1200-shai-accessibility-semantics.md
@@ -0,0 +1,20 @@
+---
+title: "Shai: accessibility became personal"
+slug: 2026-07-20-1200-shai-accessibility-semantics
+platform: linkedin
+account: shai
+source_slug: accessibility-semantics
+publish_at: '2026-07-20T12:00:00'
+timezone: Asia/Jerusalem
+review_by: '2026-07-17'
+status: approved
+image: /blog/accessibility-semantics.jpg
+---
+
+Accessibility has become personal for me. I am getting older, and large type is how I read a phone comfortably now.
+
+I also remember working with accessibility experts at Sun and seeing the real complexity. A label is easy. Roles, actions, virtual children, collection position, focus recovery, live announcements, and platform behavior are not.
+
+Our old `setAccessibilityText()` API was the poor man's version. The new semantics tree is a parallel, testable hierarchy with mappings for every current Codename One UI port. Automated audits help, but the final pass still belongs to a person using the actual screen reader.
+
+Full write-up: https://www.codenameone.com/blog/accessibility-semantics/
diff --git a/docs/website/social/linkedin/2026-07-21-1200-shai-codename-one-mcp-server.md b/docs/website/social/linkedin/2026-07-21-1200-shai-codename-one-mcp-server.md
new file mode 100644
index 00000000000..04c48e5b53a
--- /dev/null
+++ b/docs/website/social/linkedin/2026-07-21-1200-shai-codename-one-mcp-server.md
@@ -0,0 +1,22 @@
+---
+title: "Shai: accessibility led to MCP"
+slug: 2026-07-21-1200-shai-codename-one-mcp-server
+platform: linkedin
+account: shai
+source_slug: codename-one-mcp-server
+publish_at: '2026-07-21T12:00:00'
+timezone: Asia/Jerusalem
+review_by: '2026-07-17'
+status: approved
+image: /blog/codename-one-mcp-server.jpg
+---
+
+Accessibility led to something I did not expect.
+
+Once a Codename One screen had an immutable tree of roles, labels, values, and actions, an agent could read that tree too. The JavaSE port can now expose the running simulator or a desktop tool as an MCP server.
+
+The agent finds `profile-save` and invokes its activate action. It does not guess yesterday's screen coordinates. Application tools can expose domain operations directly, and a PNG resource remains available when the issue is genuinely visual.
+
+This is JavaSE-only today. Native desktop packages and mobile targets still need transport and launcher work.
+
+Full write-up: https://www.codenameone.com/blog/codename-one-mcp-server/
diff --git a/docs/website/social/linkedin/2026-07-22-1200-codenameone-tested-port-support.md b/docs/website/social/linkedin/2026-07-22-1200-codenameone-tested-port-support.md
new file mode 100644
index 00000000000..fff85cc5acd
--- /dev/null
+++ b/docs/website/social/linkedin/2026-07-22-1200-codenameone-tested-port-support.md
@@ -0,0 +1,22 @@
+---
+title: "Codename One: support should be evidence"
+slug: 2026-07-22-1200-codenameone-tested-port-support
+platform: linkedin
+account: codenameone
+source_slug: tested-port-support
+publish_at: '2026-07-22T12:00:00'
+timezone: Asia/Jerusalem
+review_by: '2026-07-17'
+status: approved
+image: /blog/tested-port-support.jpg
+---
+
+A support matrix should not be a collection of promises somebody remembers to update.
+
+We wanted the Codename One matrix to be an output of the test system. Each result points back to a commit, environment, run date, and the tests behind it. A skipped test needs an explanation instead of silently turning into a green checkmark.
+
+That also means publishing the gaps. A green cell says a specific test passed in a specific environment. It does not pretend we exercised every device or OS release. Binary-size and memory comparisons are missing because the current numbers would compare different things across platforms.
+
+The page will change as CI changes. That is the point. Support claims should move with evidence, not with marketing copy.
+
+Full write-up: https://www.codenameone.com/blog/tested-port-support/
diff --git a/docs/website/static/blog/accessibility-semantics.jpg b/docs/website/static/blog/accessibility-semantics.jpg
new file mode 100644
index 00000000000..ffefd8df972
Binary files /dev/null and b/docs/website/static/blog/accessibility-semantics.jpg differ
diff --git a/docs/website/static/blog/accessibility-semantics/component-inspector-audit.png b/docs/website/static/blog/accessibility-semantics/component-inspector-audit.png
new file mode 100644
index 00000000000..923240ed932
Binary files /dev/null and b/docs/website/static/blog/accessibility-semantics/component-inspector-audit.png differ
diff --git a/docs/website/static/blog/accessibility-semantics/simulator-preferences.png b/docs/website/static/blog/accessibility-semantics/simulator-preferences.png
new file mode 100644
index 00000000000..01d578b5285
Binary files /dev/null and b/docs/website/static/blog/accessibility-semantics/simulator-preferences.png differ
diff --git a/docs/website/static/blog/codename-one-mcp-server.jpg b/docs/website/static/blog/codename-one-mcp-server.jpg
new file mode 100644
index 00000000000..13fc20ae548
Binary files /dev/null and b/docs/website/static/blog/codename-one-mcp-server.jpg differ
diff --git a/docs/website/static/blog/pixel-perfect-is-a-test.jpg b/docs/website/static/blog/pixel-perfect-is-a-test.jpg
new file mode 100644
index 00000000000..ce730be6dc0
Binary files /dev/null and b/docs/website/static/blog/pixel-perfect-is-a-test.jpg differ
diff --git a/docs/website/static/blog/pixel-perfect-is-a-test/android-fab-before-after.jpg b/docs/website/static/blog/pixel-perfect-is-a-test/android-fab-before-after.jpg
new file mode 100644
index 00000000000..abaf0135cef
Binary files /dev/null and b/docs/website/static/blog/pixel-perfect-is-a-test/android-fab-before-after.jpg differ
diff --git a/docs/website/static/blog/pixel-perfect-is-a-test/android-native-vs-cn1.jpg b/docs/website/static/blog/pixel-perfect-is-a-test/android-native-vs-cn1.jpg
new file mode 100644
index 00000000000..dc86ead52d7
Binary files /dev/null and b/docs/website/static/blog/pixel-perfect-is-a-test/android-native-vs-cn1.jpg differ
diff --git a/docs/website/static/blog/pixel-perfect-is-a-test/ios-native-vs-cn1.jpg b/docs/website/static/blog/pixel-perfect-is-a-test/ios-native-vs-cn1.jpg
new file mode 100644
index 00000000000..e4b7ae9a53d
Binary files /dev/null and b/docs/website/static/blog/pixel-perfect-is-a-test/ios-native-vs-cn1.jpg differ
diff --git a/docs/website/static/blog/pixel-perfect-is-a-test/ios-showcase-before-after.jpg b/docs/website/static/blog/pixel-perfect-is-a-test/ios-showcase-before-after.jpg
new file mode 100644
index 00000000000..e393d5a23c3
Binary files /dev/null and b/docs/website/static/blog/pixel-perfect-is-a-test/ios-showcase-before-after.jpg differ
diff --git a/docs/website/static/blog/pixel-perfect-is-a-test/tab-morph-fidelity.png b/docs/website/static/blog/pixel-perfect-is-a-test/tab-morph-fidelity.png
new file mode 100644
index 00000000000..7c388049cd6
Binary files /dev/null and b/docs/website/static/blog/pixel-perfect-is-a-test/tab-morph-fidelity.png differ
diff --git a/docs/website/static/blog/pixel-perfect-is-a-test/tab-morph-native-vs-cn1.gif b/docs/website/static/blog/pixel-perfect-is-a-test/tab-morph-native-vs-cn1.gif
new file mode 100644
index 00000000000..1af7d6a8b0e
Binary files /dev/null and b/docs/website/static/blog/pixel-perfect-is-a-test/tab-morph-native-vs-cn1.gif differ
diff --git a/docs/website/static/blog/standalone-codename-one-settings.jpg b/docs/website/static/blog/standalone-codename-one-settings.jpg
new file mode 100644
index 00000000000..5ee45b4f908
Binary files /dev/null and b/docs/website/static/blog/standalone-codename-one-settings.jpg differ
diff --git a/docs/website/static/blog/standalone-codename-one-settings/settings-basic.png b/docs/website/static/blog/standalone-codename-one-settings/settings-basic.png
new file mode 100644
index 00000000000..565f29729a0
Binary files /dev/null and b/docs/website/static/blog/standalone-codename-one-settings/settings-basic.png differ
diff --git a/docs/website/static/blog/standalone-codename-one-settings/settings-build-hints.png b/docs/website/static/blog/standalone-codename-one-settings/settings-build-hints.png
new file mode 100644
index 00000000000..74616046dc2
Binary files /dev/null and b/docs/website/static/blog/standalone-codename-one-settings/settings-build-hints.png differ
diff --git a/docs/website/static/blog/standalone-codename-one-settings/settings-extensions.png b/docs/website/static/blog/standalone-codename-one-settings/settings-extensions.png
new file mode 100644
index 00000000000..4cdffb59768
Binary files /dev/null and b/docs/website/static/blog/standalone-codename-one-settings/settings-extensions.png differ
diff --git a/docs/website/static/blog/tested-port-support.jpg b/docs/website/static/blog/tested-port-support.jpg
new file mode 100644
index 00000000000..09a073fcdad
Binary files /dev/null and b/docs/website/static/blog/tested-port-support.jpg differ
diff --git a/docs/website/static/blog/widgets-live-activities-dynamic-island.jpg b/docs/website/static/blog/widgets-live-activities-dynamic-island.jpg
new file mode 100644
index 00000000000..5db25a6ec36
Binary files /dev/null and b/docs/website/static/blog/widgets-live-activities-dynamic-island.jpg differ
diff --git a/docs/website/static/blog/widgets-live-activities-dynamic-island/clock-widget.png b/docs/website/static/blog/widgets-live-activities-dynamic-island/clock-widget.png
new file mode 100644
index 00000000000..90502301660
Binary files /dev/null and b/docs/website/static/blog/widgets-live-activities-dynamic-island/clock-widget.png differ
diff --git a/docs/website/static/blog/widgets-live-activities-dynamic-island/dynamic-island.png b/docs/website/static/blog/widgets-live-activities-dynamic-island/dynamic-island.png
new file mode 100644
index 00000000000..8ff87009b4b
Binary files /dev/null and b/docs/website/static/blog/widgets-live-activities-dynamic-island/dynamic-island.png differ
diff --git a/docs/website/static/blog/widgets-live-activities-dynamic-island/sample-form.png b/docs/website/static/blog/widgets-live-activities-dynamic-island/sample-form.png
new file mode 100644
index 00000000000..7bbc87b6f72
Binary files /dev/null and b/docs/website/static/blog/widgets-live-activities-dynamic-island/sample-form.png differ
diff --git a/docs/website/static/blog/widgets-live-activities-dynamic-island/widget-preview.png b/docs/website/static/blog/widgets-live-activities-dynamic-island/widget-preview.png
new file mode 100644
index 00000000000..bd9018bbd02
Binary files /dev/null and b/docs/website/static/blog/widgets-live-activities-dynamic-island/widget-preview.png differ
diff --git a/scripts/website/blog_prose_gate.py b/scripts/website/blog_prose_gate.py
index 3edf92f2ee2..859089c45eb 100644
--- a/scripts/website/blog_prose_gate.py
+++ b/scripts/website/blog_prose_gate.py
@@ -26,6 +26,7 @@
import collections
import json
import os
+import re
import subprocess
import sys
import tempfile
@@ -42,6 +43,16 @@
DEV_GUIDE_ACCEPT = "docs/developer-guide/languagetool-accept.txt"
BLOG_ACCEPT = "scripts/website/languagetool-accept-blog.txt"
+# Self-certifying language weakens technical copy. State the limitation, evidence,
+# or boundary directly instead of telling the reader how to judge the claim.
+SELF_CERTIFYING_RE = re.compile(
+ r"\b(?:honest|honestly|honesty|truthful|truthfully|truthfulness|truthefully|"
+ r"frankly|candid|candidly|candor)\b"
+ r"|\b(?:to be honest|in all honesty|the truth is|to tell the truth|"
+ r"to be transparent|in all candor)\b",
+ re.IGNORECASE,
+)
+
# --------------------------------------------------------------------------- #
# git helpers
@@ -69,6 +80,7 @@ def changed_posts(base_sha, repo_root):
"HEAD",
"--",
"docs/website/content/blog/*.md",
+ "docs/website/social/linkedin/*.md",
],
repo_root,
)
@@ -145,6 +157,25 @@ def run_capcheck(text, rel):
return out
+def run_self_certifying_language(text, rel):
+ front_matter, body, body_start = blog_text.split_front_matter(text)
+ segments = ((front_matter, 1), (blog_text.author_body(body), body_start))
+ out = []
+ for segment, start_line in segments:
+ for match in SELF_CERTIFYING_RE.finditer(segment):
+ phrase = match.group(0)
+ out.append({
+ "signature": ("house", "SelfCertifyingLanguage", phrase.lower()),
+ "file": rel,
+ "line": start_line + segment.count("\n", 0, match.start()),
+ "message": (
+ "SelfCertifyingLanguage: state the evidence, limitation, or boundary "
+ f"directly instead of using [{phrase}]"
+ ),
+ })
+ return out
+
+
def _lt_module():
"""Import run_languagetool lazily; return None if unavailable."""
try:
@@ -273,6 +304,10 @@ def gate_file(path, base_sha, repo_root, lt_ctx):
new = []
new += net_new(run_vale(head, path, repo_root), run_vale(base, path, repo_root))
new += net_new(run_capcheck(head, path), run_capcheck(base, path))
+ new += net_new(
+ run_self_certifying_language(head, path),
+ run_self_certifying_language(base, path),
+ )
if lt_ctx:
head_lt = lt_findings(head, path, lt_ctx)
base_lt = lt_findings(base, path, lt_ctx)
diff --git a/scripts/website/test_blog_prose_gate.py b/scripts/website/test_blog_prose_gate.py
new file mode 100644
index 00000000000..7ae9b0b6896
--- /dev/null
+++ b/scripts/website/test_blog_prose_gate.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+
+import unittest
+
+import blog_prose_gate
+
+
+class SelfCertifyingLanguageTest(unittest.TestCase):
+ def findings(self, text):
+ return blog_prose_gate.run_self_certifying_language(text, "post.md")
+
+ def test_rejects_self_certifying_terms(self):
+ text = "---\ntitle: Test\n---\n\nThat is the honest boundary. Truthfully, it is not done.\n"
+ findings = self.findings(text)
+ self.assertEqual(2, len(findings))
+ self.assertEqual("SelfCertifyingLanguage", findings[0]["signature"][1])
+
+ def test_accepts_direct_boundary(self):
+ text = "---\ntitle: Test\n---\n\nThat is the boundary. The native pass is still required.\n"
+ self.assertEqual([], self.findings(text))
+
+ def test_checks_front_matter(self):
+ text = "---\ntitle: An Honest Result\n---\n\nThe test reports its inputs.\n"
+ findings = self.findings(text)
+ self.assertEqual(1, len(findings))
+ self.assertEqual(2, findings[0]["line"])
+
+
+if __name__ == "__main__":
+ unittest.main()