Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/blog-syndication.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ jobs:
set -euo pipefail
pip install markdown

- name: Queue browser-extension syndication tasks (Medium)
run: |
set -euo pipefail
python3 scripts/website/queue_browser_syndication.py --platforms medium
# Medium syndication is dropped: Medium detects and blocks the
# automated browser that published to it, even on a freshly cleared
# profile, so queued tasks could only fail. Nothing is queued for it.

# LinkedIn's weekly blog-post syndication is paused. Keep validating
# existing artifacts so malformed inputs still fail CI, but do not add
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/syndication-health.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ jobs:
'',
`Last checked: ${new Date().toISOString()}`,
'',
'The GitHub workflow publishes DEV and creates queue tasks. The signed-in local runner publishes Medium, Hashnode, and LinkedIn, and submits Foojay editorial PRs. This issue stays open until both paths recover.',
'The GitHub workflow publishes DEV and creates queue tasks. The signed-in local runner publishes Hashnode and LinkedIn, and submits Foojay editorial PRs. This issue stays open until both paths recover.',
].join('\n');

const assignees = process.env.ALERT_ASSIGNEE ? [process.env.ALERT_ASSIGNEE] : undefined;
Expand Down
2 changes: 1 addition & 1 deletion scripts/website/check_syndication_health.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Report syndication work that has not been processed.

The GitHub workflow queues Medium and scheduled LinkedIn work, while a
The GitHub workflow queues scheduled LinkedIn work, while a
signed-in browser runner on the maintainer's machine drains that queue. A
successful local run removes completed entries. Anything left beyond its
grace period therefore needs attention. DEV, Foojay, and Hashnode do not all
Expand Down
27 changes: 1 addition & 26 deletions scripts/website/syndication-queue.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,4 @@
{
"_comment": "Queue of browser-only syndication tasks drained by a local syndication tool that runs on the maintainer's machine inside an already-signed-in browser session. Tasks are appended by the website queue scripts invoked by CI, and the local tool writes the resulting URLs back into syndication-state.json.",
"tasks": [
{
"id": "medium:desktop-tools-title-bars-processes-zip",
"site": "medium",
"slug": "desktop-tools-title-bars-processes-zip",
"title": "Give Your Desktop App a Useful Title Bar and Real Tools",
"canonical": "https://www.codenameone.com/blog/desktop-tools-title-bars-processes-zip/",
"description": "Put application controls in native desktop title areas, launch tools with the shared Process API, and stream ZIP exports using familiar Java APIs.",
"cover_image_url": "https://www.codenameone.com/blog/desktop-tools-title-bars-processes-zip.jpg",
"body_html": "<p><img alt=\"A desktop application combines title controls with process and archive tools\" src=\"https://www.codenameone.com/blog/desktop-tools-title-bars-processes-zip.jpg\"></p>\n<p>A document editor needs room for the document. Giving an entire row to a title, then adding another row for Save and Search, spends that room quickly.</p>\n<aside style=\"border-left: 4px solid #007aff; padding: 14px 18px; margin: 24px 0; border-radius: 4px; font-size: 0.95em;\"><strong>What is Codename One?</strong> Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at <a href=\"https://www.codenameone.com/\">codenameone.com</a>.</aside>\n\n<p>You can now put Codename One components into the title area of native macOS, Windows and Linux apps. It's a niche capability until you're building an editor. Then the details of window dragging and button hit testing become part of your product.</p>\n<p>This <a href=\"https://www.codenameone.com/blog/create-an-uber-clone-in-1-minute/\">weekly follow-up</a> also covers two less visible desktop capabilities: launching external tools and reading or writing ZIP streams. Together they make it easier to build an application that fits into the user's existing workflow.</p>\n<p><a href=\"https://www.codenameone.com/blog/release-2026-10-16/native-editor-titlebar.jpg\"><img alt=\"Native macOS editor title area with Save, Undo, document status and command search beside the OS window controls\" src=\"https://www.codenameone.com/blog/release-2026-10-16/native-editor-titlebar.jpg\"></a></p>\n<p><em>Our native macOS editor prototype, cropped to its title area and ribbon. Save, Undo, document status and command search use the top row; the document begins below the ribbon.</em></p>\n<h2>Put Save where the user is already looking</h2>\n<p>Use <code>desktop.titleBar=custom</code>, then supply a title component through the ordinary toolbar API. This is the <a href=\"https://github.com/codenameone/CodenameOne/blob/master/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/DesktopTitleContent.java\">guide's editor example</a>:</p>\n<pre><code class=\"language-java\">public static Form createEditor(Command save, Command search) {\n Display.getInstance().setProperty(&quot;desktop.titleBar&quot;, &quot;custom&quot;);\n Toolbar.setGlobalToolbar(true);\n Form editor = new Form(&quot;Quarterly report&quot;, new BorderLayout());\n Container title = new Container(new BorderLayout());\n title.add(BorderLayout.WEST, new Button(save));\n title.add(BorderLayout.CENTER, new Label(&quot;Quarterly report&quot;));\n title.add(BorderLayout.EAST, new Button(search));\n editor.getToolbar().setTitleComponent(title);\n return editor;\n}\n</code></pre>\n<p>Call it from the application's normal lifecycle, after framework initialization and before constructing other forms that need this mode. The <code>Command</code> arguments are your actions. You can also select the mode with <code>codename1.arg.desktop.titleBar=custom</code> in the build settings.</p>\n<p>Don't add fixed padding for macOS traffic lights or Windows caption buttons. The framework reserves space for the real OS controls, accounting for changes such as window DPI. Your components stay in the Java component tree while the port handles window gestures.</p>\n<table>\n<thead>\n<tr>\n<th>Native desktop</th>\n<th>Who owns the window behavior?</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>macOS</td>\n<td>AppKit keeps the traffic lights, resizing and system menu. Empty title space follows native dragging and the user's double-click preference.</td>\n</tr>\n<tr>\n<td>Windows</td>\n<td>The Desktop Window Manager keeps caption controls. Native hit testing handles empty title space and resize edges.</td>\n</tr>\n<tr>\n<td>Linux</td>\n<td>A GTK header bar places rendered application content beside real window controls and follows desktop decoration settings.</td>\n</tr>\n</tbody>\n</table>\n<p>Buttons, editable fields and other interactive descendants keep their input. Disabled buttons are excluded from dragging too. Plain labels and passive container backgrounds can move the window. For a custom painted background, <code>Toolbar.setTitleBarDragRegion(component, true)</code> opts it into dragging while preserving interactive children.</p>\n<p><img alt=\"Diagram\" src=\"https://mermaid.ink/img/Zmxvd2NoYXJ0IFRECiAgICBQb2ludGVyW1BvaW50ZXIgaW4gdGhlIHRpdGxlIGFyZWFdIC0tPiBUYXJnZXR7V2hhdCBpcyB1bmRlciBpdD99CiAgICBUYXJnZXQgLS0-fEFwcGxpY2F0aW9uIGNvbnRyb2x8IEphdmFbRGVsaXZlciBjb21wb25lbnQgaW5wdXRdCiAgICBUYXJnZXQgLS0-fFBhc3NpdmUgdGl0bGUgYmFja2dyb3VuZHwgT1NbTmF0aXZlIHdpbmRvdyBnZXN0dXJlXQogICAgVGFyZ2V0IC0tPnxXaW5kb3cgY29udHJvbCBvciBlZGdlfCBOYXRpdmVbT1MgY29tbWFuZCBvciByZXNpemVd?type=png&amp;bgColor=ffffff\"></p>\n<p>There are platform differences. Windows custom mode omits the separate Win32 menu row; provide visible access through your toolbar, side menu or ribbon. Linux keeps its menu row below the header, and macOS keeps the system menu bar. This applies to the main form in native desktop builds. Separate dialogs retain their decorations, and browser pages don't gain control of browser chrome.</p>\n<p>The <a href=\"https://www.codenameone.com/developer-guide/desktop-integration/\">desktop integration guide</a> has the full contract, introduced in <a href=\"https://github.com/codenameone/CodenameOne/pull/6019\">PR #6019</a>. The useful feature is the complete interaction, including the parts that keep a click on Search from moving the window.</p>\n<h2>Call the tool your user already has</h2>\n<p>We originally wanted a Process API for backend work. It made sense for desktop applications too: an editor can invoke a converter, inspect a tool's version or run an existing command-line utility.</p>\n<p>The shared classes are <code>com.codename1.io.ProcessBuilder</code> and <code>com.codename1.io.Process</code>. Check support before offering the action:</p>\n<pre><code class=\"language-java\">boolean canRunTools = com.codename1.io.ProcessBuilder.isSupported();\n</code></pre>\n<p>The API works in JavaSE desktop builds, the native Windows/macOS/Linux ports, the JVM backend and the packaged Linux ParparVM backend. Android, iOS and the browser don't support process launching through this API.</p>\n<p>Here is the launch portion of a worker task, assuming <code>toolPath</code> names an installed executable:</p>\n<pre><code class=\"language-java\">try (com.codename1.io.Process child =\n new com.codename1.io.ProcessBuilder(toolPath, &quot;--version&quot;)\n .redirectErrorStream(true)\n .start()) {\n child.getOutputStream().close(); // This command needs no stdin.\n // Start a reader for child.getInputStream() before waiting.\n // Apply a timeout, inspect the exit code, and handle the output.\n}\n</code></pre>\n<p>This is a launch excerpt, not a complete runner. The <a href=\"https://www.codenameone.com/developer-guide/processes/\">complete guide example</a> drains output on a separate thread and uses a timed wait. Waiting first can deadlock if the child fills its output pipe. Keep both process I/O and waits off the UI event thread.</p>\n<p>Arguments are passed literally; the API doesn't insert a shell. Working directories are native path strings. File redirection, pipelines and <code>ProcessHandle</code> aren't part of this subset. Closing the process requests termination and closes its streams, but doesn't kill descendants or wait for exit. <a href=\"https://github.com/codenameone/CodenameOne/pull/6010\">PR #6010</a> contains the implementation.</p>\n<h2>Give users an archive they can use</h2>\n<p>ParparVM now implements the streaming subset of <code>java.util.zip</code>. That includes ZIP entry streams, GZIP, deflate/inflate and checksums. JVM and Android builds use their platform implementations; ParparVM uses the existing Java compression engine.</p>\n<p>Writing a small UTF-8 entry looks familiar. Here <code>destination</code> is the output stream your caller supplies; closing the ZIP also closes it:</p>\n<pre><code class=\"language-java\">try (java.util.zip.ZipOutputStream zip =\n new java.util.zip.ZipOutputStream(destination)) {\n zip.putNextEntry(new java.util.zip.ZipEntry(&quot;report.txt&quot;));\n zip.write(&quot;Completed rides\\n&quot;.getBytes(&quot;UTF-8&quot;));\n zip.closeEntry();\n}\n</code></pre>\n<p>A project export has an extra requirement: its launcher should still be executable after extraction. <code>ZipArchiveOutputStream</code> adds a permission argument to the familiar entry operation:</p>\n<pre><code class=\"language-java\">ZipEntry launcher = new ZipEntry(&quot;project/run.sh&quot;);\nlauncher.setTime(0);\nzip.putNextEntry(launcher, 0755);\n// Copy the launcher bytes to zip, then call closeEntry().\n</code></pre>\n<p>Here <code>zip</code> is a <code>com.codename1.io.zip.ZipArchiveOutputStream</code>. The <a href=\"https://www.codenameone.com/developer-guide/backend-operations/#backend-zip\">server download example</a> streams directly to an HTTP response and includes the full copy loop. This avoids building an entire archive in memory, although entry metadata still takes space.</p>\n<p>This isn't every Java SE archive feature. ParparVM's subset excludes random-access <code>ZipFile</code>, ZIP64 and encrypted archives. Reading to the end of an entry verifies its CRC and size; closing an archive early doesn't verify unread entries. An importer still needs to validate paths and bound extracted data. <a href=\"https://github.com/codenameone/CodenameOne/pull/6006\">PR #6006</a> introduced the streaming support.</p>\n<p>A useful title area, an external tool and an export users can open are modest features individually. They are also the things that decide whether a desktop app can become part of someone's daily work.</p>\n<h2>Discussion</h2>\n<p><em>What would you put in your app's title area if the window controls were already handled?</em></p>",
"queued_at": "2026-10-27T19:01:51+00:00",
"scheduled_at": "2026-10-30T07:00:55+00:00"
},
{
"id": "medium:install-your-java-web-app-again",
"site": "medium",
"slug": "install-your-java-web-app-again",
"title": "Let Users Install Your Java Web App Again",
"canonical": "https://www.codenameone.com/blog/install-your-java-web-app-again/",
"description": "PWA installation and cached offline launch return to ParparVM JavaScript builds. The generated service worker versions application files while leaving network data to your app.",
"cover_image_url": "https://www.codenameone.com/blog/install-your-java-web-app-again.jpg",
"body_html": "<p><img alt=\"A Java web app moves from a browser tab to an installed app\" src=\"https://www.codenameone.com/blog/install-your-java-web-app-again.jpg\"></p>\n<p>We lost progressive web app support when we moved the JavaScript port from TeaVM to ParparVM. It's back. Users can install your browser app on supporting browsers, and its cached application files can load when the network is unavailable.</p>\n<aside style=\"border-left: 4px solid #007aff; padding: 14px 18px; margin: 24px 0; border-radius: 4px; font-size: 0.95em;\"><strong>What is Codename One?</strong> Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at <a href=\"https://www.codenameone.com/\">codenameone.com</a>.</aside>\n\n<p>For a small business, a link can be the quickest way to put an app in someone's hands. Installation lets that same app remain within reach after the tab closes. This is Wednesday's follow-up to the <a href=\"https://www.codenameone.com/blog/create-an-uber-clone-in-1-minute/\">Wayline release story</a>.</p>\n<h2>The build makes the installation files</h2>\n<p>A ParparVM JavaScript application build now produces <code>manifest.json</code>, home-screen icons and <code>sw.js</code> automatically. There is no separate PWA enablement switch.</p>\n<p>Deploy the whole generated bundle, including subdirectories, over HTTPS. Localhost also works for testing. Relative manifest and asset URLs let an application live under a path such as <code>/dispatch/</code> rather than requiring a dedicated domain root.</p>\n<p>You can customize the app's identity through build hints in <code>codenameone_settings.properties</code>:</p>\n<pre><code class=\"language-properties\">codename1.arg.javascript.manifest.name=Station Dispatch\ncodename1.arg.javascript.manifest.short_name=Dispatch\ncodename1.arg.javascript.manifest.description=Your station's dispatch app\ncodename1.arg.javascript.manifest.display=standalone\n</code></pre>\n<p>The default manifest already requests a standalone window. <code>name</code> and <code>short_name</code> default to the application's display name. Installation UI and eligibility depend on the browser, so avoid making your onboarding depend on one particular prompt appearing.</p>\n<h2>Offline launch has a precise boundary</h2>\n<p>The service worker caches generated application files: the runtime, translated JavaScript chunks, themes and bundled resources. It must finish installing while online before those files can support an offline launch.</p>\n<p><img alt=\"Diagram\" src=\"https://mermaid.ink/img/Zmxvd2NoYXJ0IFRECiAgICBPbmxpbmVbRmlyc3QgdmlzaXQgd2hpbGUgb25saW5lXSAtLT4gSW5zdGFsbFtJbnN0YWxsIHNlcnZpY2Ugd29ya2VyXQogICAgSW5zdGFsbCAtLT4gQ2FjaGVbQ2FjaGUgZ2VuZXJhdGVkIGFwcGxpY2F0aW9uIGZpbGVzXQogICAgQ2FjaGUgLS0-IE9mZmxpbmVbTGF1bmNoIGNhY2hlZCBhcHAgd2l0aG91dCBhIG5ldHdvcmtdCiAgICBPZmZsaW5lIC0tPiBMb2NhbFtVc2UgYXZhaWxhYmxlIGxvY2FsIGRhdGEgYW5kIGZlYXR1cmVzXQogICAgT2ZmbGluZSAtLT4gQVBJW05ldHdvcmsgb3BlcmF0aW9ucyBzdGlsbCBuZWVkIHRoZSBzZXJ2ZXJd?type=png&amp;bgColor=ffffff\"></p>\n<p>It doesn't cache API responses, proxy requests or external resources. Application data and synchronization remain your responsibility.</p>\n<p>In Wayline, that means a cached UI is not permission to accept a ride offline. Matching needs the server, and the map's external tiles and routes aren't bundled application assets. A dispatch app should say that it is disconnected rather than show a stale assignment as confirmed.</p>\n<p>This distinction is useful outside ride hailing too. A locally stored draft can remain editable; publishing that draft still needs a connection. Design the interaction around what you actually have on the device.</p>\n<h2>An update should belong to one version</h2>\n<p>A running app needs its runtime and resources to agree. The worker creates a new cache when content changes, even if the application's version string stays the same.</p>\n<p>The new worker waits until the old app's tabs and windows close before activating. That keeps an open app from mixing an old runtime with new chunks. Each deployment path has its own cache, so two apps on the same host don't share one undifferentiated bucket.</p>\n<p><img alt=\"Diagram\" src=\"https://mermaid.ink/img/c2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBPbGQgYXMgT3BlbiBhcHAKICAgIHBhcnRpY2lwYW50IFdvcmtlciBhcyBOZXcgd29ya2VyCiAgICBwYXJ0aWNpcGFudCBDYWNoZSBhcyBOZXcgY2FjaGUKICAgIFdvcmtlci0-PkNhY2hlOiBTdG9yZSBjaGFuZ2VkIGJ1bmRsZQogICAgV29ya2VyLT4-V29ya2VyOiBXYWl0IHdoaWxlIG9sZCBhcHAgcmVtYWlucyBvcGVuCiAgICBPbGQtPj5PbGQ6IFVzZXIgY2xvc2VzIG9sZCB0YWJzIGFuZCB3aW5kb3dzCiAgICBXb3JrZXItPj5Xb3JrZXI6IEFjdGl2YXRlIG5ldyB2ZXJzaW9uCiAgICBXb3JrZXItPj5DYWNoZTogVXNlIG1hdGNoaW5nIGFwcGxpY2F0aW9uIGZpbGVz?type=png&amp;bgColor=ffffff\"></p>\n<p>Deploy the files together and allow the browser to revalidate <code>sw.js</code>. Cache versioning helps the client choose a coherent bundle; it doesn't turn a partially uploaded deployment into a complete one.</p>\n<h2>Check the behavior your users will get</h2>\n<p>After deploying a test build, open the browser's Application tools and inspect the manifest, service worker and cache. Wait for installation to finish, then reload offline. Check both the UI that should work and the operations that should report a missing connection.</p>\n<p>For an update check, keep one old tab open, deploy a visibly changed build and confirm that the new worker waits. Close the old tabs and reopen the app. Test a subdirectory deployment too if that is how you host it.</p>\n<p>The repository includes a <a href=\"https://github.com/codenameone/CodenameOne/tree/master/tests/pwa\">browser smoke test and service-worker contract checks</a>. Those tests exercise caching and update behavior; they don't establish that every browser offers the same install experience.</p>\n<p><a href=\"https://github.com/codenameone/CodenameOne/pull/6022\">PR #6022</a> restores the capability, and the <a href=\"https://www.codenameone.com/developer-guide/working-with-javascript/\">JavaScript guide</a> explains manifest customization. A web deployment can be a useful first distribution channel for your application, with native apps alongside it when their device integration matters.</p>\n<h2>Discussion</h2>\n<p><em>What should your app still let a user do after the connection drops?</em></p>",
"queued_at": "2026-10-28T21:01:00+00:00",
"scheduled_at": "2026-10-30T01:00:44+00:00"
}
]
"tasks": []
}
Loading