diff --git a/.github/workflows/blog-syndication.yml b/.github/workflows/blog-syndication.yml index 78795c0e300..c743a6f03cb 100644 --- a/.github/workflows/blog-syndication.yml +++ b/.github/workflows/blog-syndication.yml @@ -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 diff --git a/.github/workflows/syndication-health.yml b/.github/workflows/syndication-health.yml index e1da3860cf3..74728eca340 100644 --- a/.github/workflows/syndication-health.yml +++ b/.github/workflows/syndication-health.yml @@ -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; diff --git a/scripts/website/check_syndication_health.py b/scripts/website/check_syndication_health.py index 32ef7ed6ecc..80f793ac42a 100644 --- a/scripts/website/check_syndication_health.py +++ b/scripts/website/check_syndication_health.py @@ -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 diff --git a/scripts/website/syndication-queue.json b/scripts/website/syndication-queue.json index a2aab9e2484..508e5f5de38 100644 --- a/scripts/website/syndication-queue.json +++ b/scripts/website/syndication-queue.json @@ -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": "

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.
\n\n\nYou 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.
\nThis weekly follow-up 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.
\n\nOur 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.
\nUse desktop.titleBar=custom, then supply a title component through the ordinary toolbar API. This is the guide's editor example:
public static Form createEditor(Command save, Command search) {\n Display.getInstance().setProperty("desktop.titleBar", "custom");\n Toolbar.setGlobalToolbar(true);\n Form editor = new Form("Quarterly report", new BorderLayout());\n Container title = new Container(new BorderLayout());\n title.add(BorderLayout.WEST, new Button(save));\n title.add(BorderLayout.CENTER, new Label("Quarterly report"));\n title.add(BorderLayout.EAST, new Button(search));\n editor.getToolbar().setTitleComponent(title);\n return editor;\n}\n\nCall it from the application's normal lifecycle, after framework initialization and before constructing other forms that need this mode. The Command arguments are your actions. You can also select the mode with codename1.arg.desktop.titleBar=custom in the build settings.
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.
\n| Native desktop | \nWho owns the window behavior? | \n
|---|---|
| macOS | \nAppKit keeps the traffic lights, resizing and system menu. Empty title space follows native dragging and the user's double-click preference. | \n
| Windows | \nThe Desktop Window Manager keeps caption controls. Native hit testing handles empty title space and resize edges. | \n
| Linux | \nA GTK header bar places rendered application content beside real window controls and follows desktop decoration settings. | \n
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, Toolbar.setTitleBarDragRegion(component, true) opts it into dragging while preserving interactive children.
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.
\nThe desktop integration guide has the full contract, introduced in PR #6019. The useful feature is the complete interaction, including the parts that keep a click on Search from moving the window.
\nWe 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.
\nThe shared classes are com.codename1.io.ProcessBuilder and com.codename1.io.Process. Check support before offering the action:
boolean canRunTools = com.codename1.io.ProcessBuilder.isSupported();\n\nThe 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.
\nHere is the launch portion of a worker task, assuming toolPath names an installed executable:
try (com.codename1.io.Process child =\n new com.codename1.io.ProcessBuilder(toolPath, "--version")\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\nThis is a launch excerpt, not a complete runner. The complete guide example 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.
\nArguments are passed literally; the API doesn't insert a shell. Working directories are native path strings. File redirection, pipelines and ProcessHandle aren't part of this subset. Closing the process requests termination and closes its streams, but doesn't kill descendants or wait for exit. PR #6010 contains the implementation.
ParparVM now implements the streaming subset of java.util.zip. 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.
Writing a small UTF-8 entry looks familiar. Here destination is the output stream your caller supplies; closing the ZIP also closes it:
try (java.util.zip.ZipOutputStream zip =\n new java.util.zip.ZipOutputStream(destination)) {\n zip.putNextEntry(new java.util.zip.ZipEntry("report.txt"));\n zip.write("Completed rides\\n".getBytes("UTF-8"));\n zip.closeEntry();\n}\n\nA project export has an extra requirement: its launcher should still be executable after extraction. ZipArchiveOutputStream adds a permission argument to the familiar entry operation:
ZipEntry launcher = new ZipEntry("project/run.sh");\nlauncher.setTime(0);\nzip.putNextEntry(launcher, 0755);\n// Copy the launcher bytes to zip, then call closeEntry().\n\nHere zip is a com.codename1.io.zip.ZipArchiveOutputStream. The server download example 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.
This isn't every Java SE archive feature. ParparVM's subset excludes random-access ZipFile, 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. PR #6006 introduced the streaming support.
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.
\nWhat would you put in your app's title area if the window controls were already handled?
", - "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": "
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.
\n\n\nFor 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 Wayline release story.
\nA ParparVM JavaScript application build now produces manifest.json, home-screen icons and sw.js automatically. There is no separate PWA enablement switch.
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 /dispatch/ rather than requiring a dedicated domain root.
You can customize the app's identity through build hints in codenameone_settings.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\nThe default manifest already requests a standalone window. name and short_name 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.
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.
\nIt doesn't cache API responses, proxy requests or external resources. Application data and synchronization remain your responsibility.
\nIn 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.
\nThis 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.
\nA 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.
\nThe 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.
\nDeploy the files together and allow the browser to revalidate sw.js. Cache versioning helps the client choose a coherent bundle; it doesn't turn a partially uploaded deployment into a complete one.
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.
\nFor 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.
\nThe repository includes a browser smoke test and service-worker contract checks. Those tests exercise caching and update behavior; they don't establish that every browser offers the same install experience.
\nPR #6022 restores the capability, and the JavaScript guide 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.
\nWhat should your app still let a user do after the connection drops?
", - "queued_at": "2026-10-28T21:01:00+00:00", - "scheduled_at": "2026-10-30T01:00:44+00:00" - } - ] + "tasks": [] }