Skip to content

Rebuild native themes via CSS compiler (phase 2: module split)#4793

Merged
shai-almog merged 93 commits into
masterfrom
rebuild-themes
Apr 27, 2026
Merged

Rebuild native themes via CSS compiler (phase 2: module split)#4793
shai-almog merged 93 commits into
masterfrom
rebuild-themes

Conversation

@liannacasper
Copy link
Copy Markdown
Collaborator

Summary

First slice of the native-themes refactor planned in /plan. Extracts the Codename One CSS compiler into a thin maven/css-compiler module with clean dependencies (core + flute + sac) so the shipped iOS Modern (liquid-glass) and Android Material themes can be generated at framework build time without pulling in JavaSE / JavaFX / CEF / the Designer GUI.

  • Phase 1strictNoCef flag + enforceNoCef() pre-scan in CSSTheme + -no-cef CLI arg on CN1CSSCLI + new NoCefCSSCLI minimal entry point.
  • Phase 2EditableResources physically moved into maven/css-compiler; designer/javase imports stripped; four throwing hooks introduced; EditableResourcesEditor subclass in the Designer overrides the hooks. New com.codename1.ui.util.SVGDocument interface in core replaces the compile-time dep on impl.javase.SVG. CSSTheme, ResourcesMutator, Color, helpers, and the com.codename1.ui.util.xml package moved alongside. maven/designer now depends on codenameone-css-compiler.
  • Build pipelinescripts/build-native-themes.sh + smoke CSS at native-themes/{ios-modern,android-material}/theme.css + Themes/.gitignore.
  • CIpr.yml gains a step that installs css-compiler and runs the native-themes build; designer.yml switched to Maven for building the Designer jar + CLI CSS smoke + native-themes smoke under xvfb.

Plan: /Users/shai/.claude/plans/at-the-moment-we-dapper-origami.md (approved earlier).

Known follow-ups (not in this PR)

  • Ant Designer build (CodenameOneDesigner/build.xml) still expects all CSS classes under src/; needs source-root awareness of maven/css-compiler or NetBeans/Ant devs to switch to Maven.
  • ResourceEditorView ~line 2382 still calls EditableResources.open(...) returning a base instance — fine for the override-resource side path but a future EditableResourcesEditor.open(...) factory would be tidier.
  • Phase 3+ (real component CSS, port installNativeTheme() rewrites, simulator bundling + override menu, cn1.nativeTheme build hint, screenshot fidelity tests).

Test plan

  • pr.yml matrix (Java 8/17/21) builds green including the new "Build CSS compiler and smoke native-themes" step.
  • designer.yml builds the Designer jar via Maven, the CLI CSS compile smoke produces a non-empty .res, and the native-themes smoke step writes Themes/iOSModernTheme.res + Themes/AndroidMaterialTheme.res.
  • ant.yml (Java CI) full reactor mvn install -Plocal-dev-javase passes.
  • Manual: ./scripts/build-native-themes.sh locally after mvn -pl css-compiler -am install produces both .res files and an intentional forbidden rule (e.g. box-shadow: 0 2px 4px #000 in a theme CSS) fails the build with the offending-UIID list.
  • Manual: open an existing .res file in the Designer GUI and confirm all resource types still open editors correctly (proves the EditableResourcesEditor wiring).

🤖 Generated with Claude Code

@shai-almog
Copy link
Copy Markdown
Collaborator

shai-almog commented Apr 22, 2026

Compared 65 screenshots: 65 matched.

Native Android coverage

  • 📊 Line coverage: 8.72% (4665/53524 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 6.83% (22929/335748), branch 3.27% (1055/32302), complexity 4.06% (1254/30918), method 7.10% (1024/14432), class 11.67% (224/1920)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 8.72% (4665/53524 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 6.83% (22929/335748), branch 3.27% (1055/32302), complexity 4.06% (1254/30918), method 7.10% (1024/14432), class 11.67% (224/1920)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 923.000 ms
Base64 CN1 encode 156.000 ms
Base64 encode ratio (CN1/native) 0.169x (83.1% faster)
Base64 native decode 817.000 ms
Base64 CN1 decode 356.000 ms
Base64 decode ratio (CN1/native) 0.436x (56.4% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 22, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog
Copy link
Copy Markdown
Collaborator

shai-almog commented Apr 22, 2026

Compared 7 screenshots: 7 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

@shai-almog
Copy link
Copy Markdown
Collaborator

shai-almog commented Apr 22, 2026

Compared 34 screenshots: 34 matched.
✅ JavaScript-port screenshot tests passed.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 22, 2026

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: 15218 alert(s) (1111 errors, 4495 warnings, 9612 suggestions) (exit code 1) (report)
  • Image references: No unused images detected (report)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 22, 2026

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 644 total, 0 failed, 2 skipped

Benchmark Results

  • Execution Time: 10652 ms

  • Hotspots (Top 20 sampled methods):

    • 23.85% java.lang.String.indexOf (445 samples)
    • 18.06% com.codename1.tools.translator.Parser.isMethodUsed (337 samples)
    • 16.45% java.util.ArrayList.indexOf (307 samples)
    • 4.93% java.lang.Object.hashCode (92 samples)
    • 3.22% java.lang.System.identityHashCode (60 samples)
    • 2.95% com.codename1.tools.translator.Parser.addToConstantPool (55 samples)
    • 2.89% com.codename1.tools.translator.BytecodeMethod.addToConstantPool (54 samples)
    • 2.04% com.codename1.tools.translator.ByteCodeClass.updateAllDependencies (38 samples)
    • 2.04% com.codename1.tools.translator.ByteCodeClass.markDependent (38 samples)
    • 1.39% com.codename1.tools.translator.Parser.getClassByName (26 samples)
    • 1.29% com.codename1.tools.translator.ByteCodeClass.calcUsedByNative (24 samples)
    • 1.18% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (22 samples)
    • 0.91% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.91% java.lang.StringBuilder.append (17 samples)
    • 0.91% java.lang.StringCoding.encode (17 samples)
    • 0.80% java.util.TreeMap.getEntry (15 samples)
    • 0.70% com.codename1.tools.translator.BytecodeMethod.appendMethodC (13 samples)
    • 0.64% com.codename1.tools.translator.Parser.writeOutput (12 samples)
    • 0.59% com.codename1.tools.translator.BytecodeMethod.appendMethodSignatureSuffixFromDesc (11 samples)
    • 0.59% com.codename1.tools.translator.BytecodeMethod.optimize (11 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@shai-almog
Copy link
Copy Markdown
Collaborator

shai-almog commented Apr 22, 2026

Compared 65 screenshots: 65 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 148 seconds

Build and Run Timing

Metric Duration
Simulator Boot 75000 ms
Simulator Boot (Run) 2000 ms
App Install 10000 ms
App Launch 4000 ms
Test Execution 223000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 996.000 ms
Base64 CN1 encode 1354.000 ms
Base64 encode ratio (CN1/native) 1.359x (35.9% slower)
Base64 native decode 826.000 ms
Base64 CN1 decode 909.000 ms
Base64 decode ratio (CN1/native) 1.100x (10.0% slower)
Base64 SIMD encode 420.000 ms
Base64 encode ratio (SIMD/native) 0.422x (57.8% faster)
Base64 encode ratio (SIMD/CN1) 0.310x (69.0% faster)
Base64 SIMD decode 424.000 ms
Base64 decode ratio (SIMD/native) 0.513x (48.7% faster)
Base64 decode ratio (SIMD/CN1) 0.466x (53.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 64.000 ms
Image createMask (SIMD on) 10.000 ms
Image createMask ratio (SIMD on/off) 0.156x (84.4% faster)
Image applyMask (SIMD off) 143.000 ms
Image applyMask (SIMD on) 60.000 ms
Image applyMask ratio (SIMD on/off) 0.420x (58.0% faster)
Image modifyAlpha (SIMD off) 148.000 ms
Image modifyAlpha (SIMD on) 54.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.365x (63.5% faster)
Image modifyAlpha removeColor (SIMD off) 142.000 ms
Image modifyAlpha removeColor (SIMD on) 79.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.556x (44.4% faster)
Image PNG encode (SIMD off) 1014.000 ms
Image PNG encode (SIMD on) 832.000 ms
Image PNG encode ratio (SIMD on/off) 0.821x (17.9% faster)
Image JPEG encode 486.000 ms

@liannacasper liannacasper force-pushed the rebuild-themes branch 2 times, most recently from ba23584 to 4059455 Compare April 25, 2026 10:17
@github-actions
Copy link
Copy Markdown
Contributor

Cloudflare Preview

shai-almog and others added 18 commits April 26, 2026 11:17
First slice of the native-themes refactor: the CSS compiler now lives in
its own Maven module with clean dependencies (core + flute + sac) so the
shipped platform themes (iOS Modern liquid-glass + Android Material) can
be generated at framework build time without pulling in JavaSE / JavaFX /
CEF / the Designer GUI.

Phase 1 (no-cef enforcement):
- CSSTheme.strictNoCef static flag + enforceNoCef() pre-scan that lists
  every UIID state requiring CEF-backed image rasterization and throws
  before any WebView call.
- CN1CSSCLI gained a -no-cef CLI arg.
- New NoCefCSSCLI minimal entry point (no JavaSEPort/BrowserComponent
  bootstrap) with a throwing WebViewProvider as a safety net.

Phase 2 (module split):
- New maven/css-compiler Maven module; registered in the reactor between
  factory and sqlite-jdbc. Produces a jar and a fat
  jar-with-dependencies whose main class is NoCefCSSCLI.
- maven/designer now depends on codenameone-css-compiler.
- EditableResources physically moved into maven/css-compiler, with its
  com.codename1.designer.* and com.codename1.impl.javase.* imports
  stripped. GUI functionality exposed as protected throwing hooks
  (persistUIContainer, onOpenFileComplete, writeUIXml,
  getRuntimeNativeTheme) plus a settable loadedBaseFile field and an
  inline IS_MAC constant replacing ResourceEditorApp.IS_MAC.
- New EditableResourcesEditor subclass lives in the Designer and
  overrides every hook, reinstating the GUI behavior (UserInterfaceEditor,
  ThemeEditor, JavaSEPortWithSVGSupport, getResourceEditor, ...).
- New com.codename1.ui.util.SVGDocument interface in core; javase-svg's
  SVG class implements it. EditableResources casts to SVGDocument so the
  thin module avoids the compile-time dep on impl.javase.SVG.
- EditableResourcesForCSS, CSSTheme, ResourcesMutator, Color,
  MissingNativeBrowserException, PollingFileWatcher, and the
  com.codename1.ui.util.xml package moved alongside EditableResources.
- Designer callers bulk-updated: new EditableResources(...) ->
  new EditableResourcesEditor(...) with imports added, in
  ResourceEditorView, ResourceEditorApp, AddThemeResource, AddUIResource,
  CodenameOneTask, CN1CSSCLI, CN1CSSCompiler, CN1CSSInstallerCLI.
- ResourceEditorView.loadedResources retyped to EditableResourcesEditor.

Build pipeline:
- scripts/build-native-themes.sh drives the thin jar (prefers a fresh
  target/ build, falls back to ~/.m2). Writes iOSModernTheme.res and
  AndroidMaterialTheme.res under Themes/ (gitignored).
- Smoke CSS sources in native-themes/{ios-modern,android-material}/theme.css
  with light+dark tokens and includeNativeBool:false to avoid the
  self-inheriting recursion trap.
- native-themes/README.md documents the CEF-free subset.

CI:
- pr.yml gains a step that installs css-compiler and runs the
  native-themes build, failing on missing outputs.
- designer.yml switched from Ant to Maven for building the Designer jar
  and running the CLI CSS smoke test (the Ant Designer build is broken
  until its source roots are taught about maven/css-compiler; Maven is
  the preferred path per CLAUDE.md anyway). Also runs the native-themes
  smoke under xvfb.

Known follow-ups (not in this commit):
- Ant-based Designer build (CodenameOneDesigner/build.xml) still expects
  all CSS classes under src/; local NetBeans/Ant developers will need
  source-tree awareness of maven/css-compiler or a switch to Maven.
- ResourceEditorView line ~2382 still calls EditableResources.open(...)
  returning a base instance; fine for the override-resource side path
  but a future EditableResourcesEditor.open(...) factory would be tidier.
- Phase 3+ (real CSS themes, port integration, simulator bundling,
  build hints, screenshot tests) pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maven/designer/pom.xml declared a dependency on codenameone-css-compiler
without a version, expecting the root pom's dependencyManagement to fill
it in. The entry was missing, so every downstream module failed to
resolve the POM (observed in PR CI). Add the managed version entry next
to codenameone-core.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI (Java CI + Designer CI) surfaced two classes of errors that the
refactor missed:

1. Accessor/helper classes declared in core-like packages but living in
   the Designer source tree (EditorFont, EditorTTFFont, CodenameOneAccessor,
   animations.AnimationAccessor, plaf.Accessor, plaf.ProtectedUIManager)
   were left behind when EditableResources moved to the css-compiler
   module. They use package-private access into core, so they must
   travel with EditableResources. Moved them into the css-compiler src
   tree. Designer still sees them via the codenameone-css-compiler
   dependency.

2. EditableResources.openFile() directly instantiated
   CodenameOneDesigner's UIBuilderOverride to materialize an XML-stored
   UI container before re-serializing. UIBuilderOverride imports
   com.codename1.designer.* (ActionCommand, UserInterfaceEditor) so it
   cannot live in the thin module. Introduced a new protected hook
   loadUIContainerFromXml(ComponentEntry) that returns null in the
   base (triggering the binary-blob fallback already in the loop) and
   is overridden by EditableResourcesEditor to drive UIBuilderOverride.

3. SimpleWebServer and WebviewSnapshotter (used by ResourcesMutator's
   CEF image rasterization) had clean imports and were still referenced
   by the compile path, so they moved to the css-compiler module too.
   In strict-no-cef builds they are still never invoked.

4. SVGDocument.java switched from /** classic Javadoc to /// markdown
   comments per the repo's java25-markdown-docs style validator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two leftover references prevented the css-compiler module from compiling:

- EditableResources.saveXMLFile() still instantiated UIBuilderOverride
  directly in the MAGIC_UI branch to materialize a container from a
  binary UI resource before writing it back as XML. Wrapped in a new
  materializeUIContainer(resourceName) hook; base throws, the Designer
  EditableResourcesEditor overrides with the UIBuilderOverride call.
- ResourcesMutator.createScreenshots() used Logger.getLogger(CN1CSSCompiler
  .class.getName()) purely as a logger name. Rerouted to
  Logger.getLogger(ResourcesMutator.class.getName()).

Also tightened NoCefCSSCLI's header comment (plain text instead of a
broken {@link CN1CSSCLI} reference that javadoc-plugin would flag).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
.claude/scheduled_tasks.lock slipped into the previous commit because it
wasn't covered by .gitignore. It's a Claude Code session-local
scheduled-wakeup lock, not repo content. Untrack and ignore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ditor

Retyping ResourceEditorView.loadedResources to EditableResourcesEditor
broke generateStateMachineCodeEx (takes a base EditableResources and
assigns it to the field). Narrower fix: field stays base-typed and the
single .getResourceEditor(...) call site casts to the editor subclass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two CI fixes on top of the now-green Java CI:

- build-native-themes.sh: ensure_jar() used log() (which went to stdout)
  AND echo "$jar" inside the same function whose output was captured via
  $(...) by the caller. Result: the log line "Using CSS compiler jar:
  <path>" got concatenated with the path and handed to java -jar, which
  responded with "Unable to access jarfile". Redirect log() to stderr so
  only the jar path lands on stdout.
- designer.yml: the Maven-produced
  codenameone-designer-*-jar-with-dependencies.jar is actually a ZIP
  wrapper around the runnable designer_1.jar (see the antrun
  add-designer-jar-with-dependencies execution in maven/designer/pom.xml).
  Unzip to a temp dir and run the inner jar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both PR CI and Designer CI are hitting 'Cannot assign field "cssFile"
because "theme" is null' at NoCefCSSCLI.java - meaning CSSTheme.load
returned null without any diagnostic. The Designer's original NPE
catch logged nothing for non-"encoding properties" NPEs (the
Logger.log line was commented out). Re-enable logging for the general
case, null-guard the message check, and have NoCefCSSCLI fail with a
helpful message if the parser returns null. Next CI run should show
the real stack trace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stack trace from PR CI shows:
  at com.codename1.io.Util.copy(Util.java:211)
  at com.codename1.io.Util.readInputStream(Util.java:402)
  at com.codename1.designer.css.CSSTheme.load(CSSTheme.java:7110)
  at com.codename1.designer.css.NoCefCSSCLI.main(NoCefCSSCLI.java:55)

Util.copy(in, out, bufferSize) unconditionally dereferences
Util.getImplementation() to route cleanup() through the platform impl.
In the native-themes build the css-compiler runs headless - no Display
has been initialized, no Util implementation is set, and the unwrapped
null crashes before CSSTheme can even parse the CSS. Guard the cleanup
path: if no implementation is set, close the streams directly (which is
what every impl's cleanup(Object) ends up doing for InputStream/OutputStream
anyway).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Designer CI logs (now with working stack traces) show:
  CSSException: Unsupported CSS condition type 10 for ElementSelectorImpl
  at com.codename1.designer.css.CSSTheme.getElementForSelector(CSSTheme.java:6561)

My smoke CSS used :pressed / :disabled pseudo-classes. The CN1 CSS
compiler actually handles state selectors as dot-class conditions
(.pressed, .disabled) - see docs/developer-guide/css.asciidoc line 38
("Button.pressed defines styles for the 'Button' UIID's 'pressed' state")
and the SAC_CLASS_CONDITION branch in CSSTheme.getElementForSelector.
The pseudo-class syntax (condition type 10) is not recognized. Switch
smoke themes to .state syntax and clarify the native-themes README.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Next NPE after the pseudo-class fix:
  at com.codename1.ui.Font.<init>(Font.java:176)
  at com.codename1.ui.Font.createSystemFont(Font.java:452)
  at CSSTheme$Element.getThemeFont(CSSTheme.java:4671)
  at CSSTheme.updateResources(CSSTheme.java:1887)

Font(int, int, int) dereferences Display.getInstance().getImplementation()
to create a native font - null in the headless css-compiler run. The
smoke themes don't need a font to exercise the no-cef pipeline end to
end, so drop font-family. Phase 3 will add a minimal headless impl (or
make Font creation degrade gracefully when Display is uninitialized) so
real themes can specify fonts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3: replace the smoke placeholder CSS with real component coverage.
Both themes now style ~25 UIIDs each with light/dark palettes, including:

- base (Component, Form, ContentPane, Container)
- typography (Label, SecondaryLabel, TertiaryLabel, SpanLabel*)
- buttons (Button, RaisedButton, FlatButton + .pressed / .disabled)
- text input (TextField, TextArea, TextHint + focused/disabled)
- selection controls (CheckBox, RadioButton, OnOffSwitch + .selected)
- toolbar (Toolbar, TitleArea, Title, MainTitle, Back/Title commands)
- tabs (Tabs, TabsContainer, Tab, Selected/UnselectedTab)
- side menu (SideNavigationPanel, SideCommand)
- list + MultiButton (List, ListRenderer, MultiButton, MultiLine1..4)
- dialog/sheet (Dialog, DialogBody, DialogTitle, Dialog{Content,Command}Area)
- FAB (FloatingActionButton + .pressed)
- misc (Separator, PopupContent)

Palettes:
- iOS Modern — Apple system colors (accent=#007aff light / #0a84ff dark,
  Apple grouped-background surfaces, separator colors); liquid-glass feel
  is approximated via solid fills with subtle tonal surface variants.
- Android Material — Material 3 baseline tonal palette (primary=#6750a4
  light / #d0bcff dark, Material surface-container tiers). Elevation is
  approximated with surface-container-high tonal values since box-shadow
  would force CEF rasterization.

Font.java (core) small fix: the package-private Font(int,int,int)
constructor used to NPE when Display.impl was null. The css-compiler
native-themes build is headless (no Display.init) and needs to serialize
font descriptors without actually allocating native font handles. Guard
the createFont call; headless serialization writes face/style/size only
and the native handle is recreated when the resource is loaded in a
running app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 Designer CI revealed:
  RuntimeException: Unsupported CSS property cn1-pill-border
  RuntimeException: Unsupported CSS property cn1-round-border

Those are not top-level CSS properties in the CN1 compiler; they are
values of the cn1-background-type property. Rewrite to
  cn1-background-type: cn1-pill-border;
  cn1-background-type: cn1-round-border;

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…iter)

Phase 3 CI error was a cascade: the CSS compiler's
transformDarkModeMediaQueries turns any selector inside
@media (prefers-color-scheme: dark) into $DarkSelector. For component
selectors this produces the wanted $DarkButton etc. But for :root the
rewrite emits $DarkComponent:root which Flute rejects ("encountered
' '. Was expecting ':' <IDENT> <FUNCTION>"), and every declaration
inside that dark :root block is skipped. The light :root block then
survives just fine, but because Flute aborts the dark block early the
parser never registers those variables. When update_resources later
tries to serialize a fg color it finds a raw var() FUNCTION
lexical-unit instead of a resolved color and throws
"Unsupported color type 41".

Simplest path that keeps the compiler as-is: drop CSS variables from
the shipped themes and inline hex values per UIID. Light values go in
the top-level rules, dark values go in the @media
(prefers-color-scheme: dark) block which the compiler maps to
$DarkUIID. Every UIID now has a matching dark entry. When the
compiler grows real :root-in-@media support (separate change), we can
re-introduce variables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two more headless NPEs surfaced by the real themes:

1. EditorTTFFont.refresh() loaded /com/codename1/impl/javase/Roboto-*.ttf
   via getClass().getResourceAsStream. That resource ships in the javase
   port jar, not in our thin css-compiler jar, so the stream is null and
   java.awt.Font.createFont(null) throws IOException. Guard the null
   stream and return early; the .res serialization only needs the
   nativeFontName descriptor, and the native AWT font is recreated at
   app runtime when the platform impl is available.

2. RoundBorder.<init> calls Display.getInstance().convertToPixels(2) to
   set its shadowSpread - which dereferences a null impl in the headless
   build. Make convertToPixels return a 1:1 fallback when impl is null.
   Conversions are recomputed at runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Next NPE in Phase 3:
  at com.codename1.ui.Font.getFace(Font.java:742)
  at com.codename1.ui.util.EditableResources.saveTheme(EditableResources.java:2095)

EditableResources serializes system fonts by calling Font.getFace(),
getSize(), getStyle(), each of which dereferences Display.impl. In the
headless css-compiler build impl is null. Capture face/style/size in
the Font(int,int,int) constructor into headlessFace/Style/Size fields
and return them from the three accessors when impl is null. Non-system
fonts (TTF, bitmap) never enter this path and keep the fields at zero.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Feedback: "SVGDocument should be package private to avoid polluting the
UI. Looking at the code I don't see why it needs to be in the core API
to begin with."

Deleted com.codename1.ui.util.SVGDocument from core. Reverted
javase-svg's SVG class to a plain class (no implements). The few
EditableResources code paths that need SVG fields now go through a
package-private static SvgBridge inside EditableResources itself,
which reflectively calls SVG's methods. This is cold code from the
css-compiler point of view (SVG paths only fire when the resource
being serialized contains SVG images, which the native-themes build
never produces) so reflection overhead is a non-issue. Bridge lives
where it is used, no cross-module interface or API surface is added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port integration for the CSS-generated iOSModernTheme.res and
AndroidMaterialTheme.res:

- IOSImplementation.installNativeTheme() resolves theme by the
  existing ios.themeMode hint. "modern" / "liquid" / "auto" loads
  /iOSModernTheme.res (with a graceful fall-through to iOS7Theme if
  the generator hasn't produced it yet, so apps still boot in a
  partial build); "ios7" / "flat" keeps the flat theme; everything
  else falls back to the pre-flat iPhone theme. "auto" now defaults
  to modern, per the decided release plan.

- AndroidImplementation.installNativeTheme() reads a new
  cn1.androidTheme property ("material" | "hololight" | "legacy");
  and.hololight=true still maps to hololight for back-compat. Default
  is material. Drops the SDK_INT<14 gate (universal Android today)
  and swaps the holo-unless-hint logic for the cleaner hint-first
  path. Falls back to holo light if the apk doesn't contain the
  modern .res (partial build).

- Ports/iOSPort/build.xml -pre-compile copies
  ../../Themes/iOSModernTheme.res into nativeSources/ so it ends up
  in dist/nativeios.jar alongside the legacy .res files.
  failonerror=false lets the port still build if
  scripts/build-native-themes.sh hasn't produced the file yet
  (runtime fallback kicks in).

- Ports/Android/build.xml -pre-compile copies
  ../../Themes/AndroidMaterialTheme.res into src/ so it lands on the
  APK classpath via the existing <fileset dir="src" includes="*.res"/>
  jar packaging. Same failonerror=false guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog and others added 14 commits April 26, 2026 11:18
Generated projects now ship with ios.themeMode=modern,
cn1.androidTheme=material, and the cross-platform
cn1.nativeTheme=modern hint already set in the project's
codenameone_settings.properties. New apps get the iOS liquid-glass
+ Android Material 3 themes by default; existing projects are
unaffected because the cn1 framework itself still defaults to
legacy when these hints are unset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets ios.themeMode=modern, cn1.androidTheme=material,
cn1.nativeTheme=modern in the playground's
codenameone_settings.properties so users browsing the playground
see the iOS liquid-glass and Material 3 looks while they explore
components.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pass-4 revert that removed headless fallbacks left an unused
import. PMD flagged it as UnnecessaryImport. Drop it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…emplates

* Root-cause fix for the iOS cn1-derive hang: UIManager.resetThemeProps
  unconditionally injected a defensive Toolbar.derive=TitleArea
  default. When a user theme declared TitleArea.derive=Toolbar (the
  iOS modern theme does), themeProps ended up with both directions
  and createStyle's derive resolver looped forever between them.
  Diagnosed via instrumented Log.p tracing on the
  derive-investigation branch (PR #4810): Toolbar resolution
  derived TitleArea, TitleArea derived Toolbar, lather/rinse/SIGTERM.
  Skip the legacy default whenever the installed theme already wires
  TitleArea -> Toolbar.

  iOS modern theme reverts the workaround that inlined Toolbar's
  props into TitleArea; cn1-derive: Toolbar now works correctly.

* iOS Tabs polish: TabbedPane (the inner content area) gets an
  explicit surface fill in both light and dark blocks so the tab
  pages stay opaque against the textured backdrop in both modes
  (was opaque-light / transparent-dark). The pill TabsContainer is
  the only translucent piece. Tab text + icons stay neutral
  (#000 light / #fff dark) regardless of selection state - the
  selected pill now uses surface-tertiary for visible contrast
  instead of an accent colour. Reduced TabsContainer / Tab padding
  so the selected pill centers vertically inside the group.

* iOS Dark Dialog opacity bumped from 0.78 to 0.95 - lower opacity
  let bright textured-backdrop stripes bleed through and clash
  with the white text. The light Dialog stays at 0.78 because the
  light backdrop palette is gentle enough that text contrast is
  fine at that level.

* Generative templates: scripts/initializr and scripts/cn1playground
  ship a common.zip that the host app expands into newly-generated
  CN1 projects. Both zips's bundled common/codenameone_settings.properties
  now include the three modern-theme hints by default so any project
  spawned by initializr or by the playground starts with the
  liquid-glass + Material 3 themes pre-selected. The host apps'
  own preview settings (which I touched earlier) keep the modern
  themes for their in-app preview.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DialogTheme tests use SpanLabel for the dialog body text. The
default Label fill is opaque (no background-color in my CSS, so it
falls to defaultStyle = surface white / dark). Inside a translucent
Dialog the SpanLabel's white block was painting over the parent
Dialog's translucency and reading as a visible different shade.

Set SpanLabel + SpanLabelText to background-color: transparent on
both themes; Label itself stays as it was.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SpanLabel (CodenameOne/src/com/codename1/components/SpanLabel.java
line 91) sets its outer UIID to "Container", not "SpanLabel". The
SpanLabel { background-color: transparent; } rule I added in the
previous pass therefore never reached the actual rendered widget -
the resolved Container style filled an opaque surface-coloured
block over whatever parent lay below. In the Dialog test that block
read as a visibly different shade inside the dialog body.

Make Container transparent by default in both modern themes.
Container is a layout-only UIID; the specific UIIDs that DO want a
surface (Form, Dialog, Tab, FloatingActionButton, etc.) all declare
their bgColor explicitly so this change does not regress them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SpanLabel.java line 97 sets its internal TextArea's UIID to
"Label". Default Label had no explicit background-color and was
falling back to the opaque defaultStyle, painting a visible white
block behind the wrapped dialog body text - distinct from the
Dialog's own translucent surface.

Set Label background-color: transparent on both modern themes.
UIIDs that need a label-with-fill (RaisedButton, FAB, etc.)
declare their bgColor explicitly so this change doesn't regress
them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Tabs.java: gate setSafeArea on `tabsSafeAreaBool` theme constant so a
  floating-pill tab bar can opt out of the internal safe-area padding
  (back-compat default true).
- iOS Modern theme: tabsSafeAreaBool=false, tighten Tab vertical padding,
  bump TabsContainer bottom margin to clear the home indicator since the
  internal inset is gone.
- UIManager.refreshTheme: switch to entrySet iteration to silence
  SpotBugs WMI_WRONG_MAP_ITERATOR.
- cn1playground CN1Playground: layerIosTheme/layerAndroidTheme prefer
  the modern Theme.res files for the live preview, fall back to legacy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Tabs.java: when tabsSafeAreaBool=false the pill (tabsContainer) gets a
  parent Container (tabsContainerHost) that absorbs the safe-area inset
  as bottom padding. The pill draws tightly while still clearing the
  home indicator, instead of either inflating its own background into
  the indicator zone (the previous behaviour) or losing the indicator
  clearance entirely (the prior fix). Legacy themes leave
  tabsSafeAreaBool=true and behave exactly as before.
- iOS Modern theme: revert the 5mm bottom-margin hack and the squeezed
  Tab vertical padding now that the wrapper handles indicator clearance.
- cn1playground: drop the legacy iOS7/iPhone/androidTheme fallbacks in
  layerIosTheme/layerAndroidTheme so the live preview always uses the
  modern themes.
- initializr: override Lifecycle.init to layer iOSModernTheme on top of
  whatever the JavaScript port supplied. The deployed JS port doesn't
  yet pick up ios.themeMode=modern, so the live preview previously
  inherited the legacy iOS7 base.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PMD UnnecessaryFullyQualifiedName: java.util.Map is already imported,
so reference Map.Entry directly instead of qualifying it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CSS compiler doesn't carry cn1-background-type forward from the
light Tab.selected style into the $DarkTab.selected override, so dark
mode rendered the selected tab as a flat rectangular block. Re-declare
cn1-background-type: cn1-pill-border on the dark overrides so the pill
keeps its rounded shape in dark mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous Lifecycle.init() override added iOSModernTheme to the
global UIManager BEFORE runApp() built the initializr's host UI, so
every host component (panels, inputs, buttons, the toolbar) inherited
modern-iOS styling instead of the initializr's own brand styles. After
the user changed templates, restoreThemeDefaults() wiped the overlay so
the preview itself ended up with no modern styling either.

Move the overlay into TemplatePreviewPanel.createBarebonesPreviewForm:

- Lazy-load /iOSModernTheme.res once and cache it.
- After restoreThemeDefaults() wipes UIManager to the host CSS theme,
  layer the modern props on top, then build the preview Form. The
  Form's components resolve against host+modern, while the host UI
  (already constructed at startup with host theme alone) keeps its
  original Style refs and is not refreshed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The device theme (modern iOS or Material) was being added to the global
UIManager and left there. Any cn1playground host UI created or refreshed
afterwards (toasts, dialogs, dynamic mode/activity panels) inherited
modern-iOS / Material styling instead of the playground's own brand,
breaking the host design just like the initializr issue.

Reshape applyCurrentCss to:

1. Stage host CSS + the active device theme + the user's custom CSS in
   the global UIManager.
2. Refresh the preview tree so the merged styles cache into its
   components' Style refs.
3. Revert the global UIManager to host CSS only - so any host UI built
   or refreshed afterwards keeps the playground's own design.

The preview's already-resolved Style refs survive the revert and keep
the modern look. applyDeviceTheme is simplified to just record the new
device key and delegate to applyCurrentCss.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- scripts/cn1playground/common/pom.xml + scripts/initializr/common/pom.xml:
  copy Themes/iOSModernTheme.res + Themes/AndroidMaterialTheme.res into
  target/classes during process-resources so they ship in the JavaScript
  webapp's /assets/ directory. Resources.openLayered("/iOSModernTheme")
  was returning EOFException (404 on the deployed bundle); the cn1
  playground preview rendered with no theme, and the initializr stayed
  on the legacy iOS7 base.

- scripts/website/build.sh: ensure_native_themes() runs
  scripts/build-native-themes.sh before each playground / initializr
  build so the gitignored .res files exist when the antrun copy fires.

- Tabs.java: applyTabIconUIID() reads a tabIconUIID theme constant and
  routes the tab icon's styling through that separate UIID via
  Label.setIconUIID. FontImage.setIcon copies the Button's bgColor
  into the rendered glyph image, so over a cn1-pill-border selected
  pill it left a visible square fill behind the glyph that didn't
  follow the rounded shape. Routing the icon style to its own UIID
  lets the theme declare it transparent. Themes without the constant
  keep the legacy behavior.

- iOS Modern theme: tabIconUIID=TabIcon constant + a TabIcon UIID
  declared transparent (light + dark + selected + pressed) so only the
  glyph itself paints over the pill.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shai-almog and others added 12 commits April 26, 2026 11:50
ensure_jar's mvn install of the css-compiler module printed maven log
lines to stdout, which the calling \`jar=\$(ensure_jar)\` captured along
with the final jar path. compile_theme then ran \`java -jar \$jar ...\`
with a multi-line concatenation as the jar argument and failed with
"Unable to access jarfile [ERROR]      [exec] Result: 128 ...".

The 128 itself came from the parent pom's antrun \`<exec executable="git">\`
clone of cn1-binaries (default failonerror=false, so the build kept
going), which is benign on a fresh CI VM where the directory may
already exist.

Redirect the mvn invocation's stdout to stderr inside ensure_jar so
only the final jar-path printf reaches the caller. Switch the two
echoes to printf for explicitness about the trailing newline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tton

- Initializr: revert the TemplatePreviewPanel modern overlay - the JS
  port's installNativeTheme already loads /iOSModernTheme.res when
  ios.themeMode=modern, so as long as the .res is bundled (the antrun
  copy added in the previous round) the preview gets the modern look
  for free. The overlay's lazy load was breaking the initializr's own
  startup.

- cn1playground: skip applyDeviceTheme at initial setup (line 215). The
  JS port's installNativeTheme has already loaded iOS Modern as the
  native base, and the host UI was just built against host CSS + that
  native base. Triggering applyDeviceTheme there ran setThemeProps
  multiple times, each re-invoking installNativeTheme, clearing image
  caches and firing theme-change listeners - which restyled host UI
  elements (the left icon, the "Live" pill label) into a state that
  no longer matched the host CSS. Just record activeDeviceThemeKey so
  later device-switch callbacks see a real previous value; the first
  applyCurrentCss inside executeRunScript stages the device + custom
  CSS overlay for the preview tree.

- iOS Modern + Android Material themes: explicit transparency: 255 on
  the dark Form / ContentPane styles. The CSS compiler's $Dark<UIID>
  emit path doesn't carry the implicit bgTransparency from the light
  declaration, leaving the Form looking transparent in dark mode (the
  textured backdrop bled through the test captures). Form must always
  paint a fully opaque surface.

- Android Material Button: switch from border-radius (RoundRectBorder)
  to cn1-pill-border (RoundBorder) so the rounded fill scales cleanly
  with button height instead of leaving artefacts on heights that
  don't divide evenly. Padding bumped to 4mm horizontal so short
  labels still get clear pill ends. cn1-derive doesn't carry
  cn1-background-type forward, so RaisedButton / FlatButton plus all
  state and dark variants re-declare cn1-background-type explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of the persistent "Form is transparent in dark mode"
report: the @media (prefers-color-scheme: dark) rewriter
(prefixSelectorsWithDark) didn't strip block comments before
prefixing selectors. A multi-line `/* note */` immediately preceding
a rule like `Form { ... }` was concatenated into the selector text
and rewritten as `CN1DARK_/* note */ Form`, which Flute then rejected
as a malformed selector and silently dropped the whole rule. No
$DarkForm entry ever made it into the .res file, so the dark Form
fell back to default styling (no bg, transparent).

Two UIIDs in the iOS Modern theme were affected: Form and Dialog.
Both had explanatory comments inline. After the fix the dark variant
count climbs from 45 to 47.

Changes:
- CSSTheme.java prefixSelectorsWithDark: pass-through `/* ... */`
  blocks alongside whitespace BEFORE capturing the next selector
  string. This is the framework-level fix that prevents future
  themes from hitting the same trap.

- iOS Modern + Android Material themes: drop the explanatory
  comments that triggered the bug. They were verbose anyway -
  any future reader can find the why in this commit.

- Revert cn1playground CN1Playground.java to the pre-"scope to
  preview" flow. The staging+revert dance broke the host UI's icon
  colors (multiple setThemeProps calls clear caches and fire theme
  listeners that re-style host components against intermediate
  states). With $DarkForm now actually present in the compiled
  theme, the original layered approach gives the right preview look
  without the side effects.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
resetThemeProps decides whether to install the legacy
\`Toolbar.derive=TitleArea\` default by inspecting only the immediate
installedTheme it was handed. When that theme has
\`@includeNativeBool: true\`, buildTheme later layers in a native theme
(e.g. iOS Modern's \`TitleArea.derive=Toolbar\`) and the user theme on
top - and those layers can flip the derive direction without the outer
reset noticing.

Once both \`Toolbar.derive=TitleArea\` and \`TitleArea.derive=Toolbar\`
exist in the merged themeProps, createStyle recurses and the catch
logs \`Error creating style TitleArea\`. The fallback to defaultStyle
leaves the chrome unstyled and the playground app effectively stuck
at boot - exactly the regression the latest cn1playground deployment
was hitting.

Add a post-build pass in setThemePropsImpl that drops the legacy
default once we can see the merged state with the layered native
theme applied. Idempotent - safe to run on every theme refresh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The website-preview deploys cn1playground / initializr against the
published cn1.version (7.0.235), so my UIManager fix that suppresses
the legacy `Toolbar.derive=TitleArea` default isn't in the framework
the deployment runs against. Pairing that legacy default with the
modern theme's `TitleArea.derive=Toolbar` creates a cycle in
createStyle - the playground's chrome stays unstyled and the page
freezes at boot with `Error creating style TitleArea` in the console.

Drop the derive on TitleArea and inline the Toolbar properties
directly. Same visual result; no inheritance cycle for any framework
version that reads the .res. The matching change in Android Material
was already in place.

The framework-level fixes (UIManager.breakTitleAreaToolbarDeriveCycle,
resetThemeProps userDeclaredTitleAreaDerivesToolbar guard, Tabs.java
tabIconUIID / tabsSafeAreaBool wiring) stay in place - they're correct
for future CN1 releases. They just don't help today's preview build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores the iOS Modern theme's `TitleArea { cn1-derive: Toolbar }`
declaration (reverts the previous inlining commit) and moves the
cycle-break workaround into cn1playground's applyThemeOverlay where
it's a runtime patch on the loaded theme, not a property of the
shipped .res file.

Why preview-side: the website-preview build pins cn1.version to the
published 7.0.235, so the framework-level fix in UIManager is not in
the deployment's runtime. The modern theme's
`TitleArea.derive=Toolbar` paired with that older
`UIManager.resetThemeProps` (which unconditionally installs the
legacy `Toolbar.derive=TitleArea` default) makes `createStyle`
recurse, logs `Error creating style TitleArea`, and leaves the
playground frozen at boot.

forwardCompatTitleAreaDerive sees the modern overlay declaring
`TitleArea.derive=Toolbar` and rewrites `Toolbar.derive` to
`Component` before the props reach addThemeProps. That overrides the
legacy default with a target that can't cycle back. Toolbar's
explicit bg/padding/etc. still win during style resolution so the
visual outcome is unchanged.

Forward-compatible: when the next CN1 release ships
`UIManager.breakTitleAreaToolbarDeriveCycle`, the framework only
removes `Toolbar.derive` when its value is exactly `TitleArea`. We
set it to `Component`. The two patches don't fight; the cn1playground
override becomes a harmless redundancy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The playground host CSS had several rules using \`background: rgba(...)\`
shorthand for the app icon, the "Live" pill, and dialog chrome. The CSS
compiler's \`background:\` switch only recognised SAC_RGBCOLOR plus
linear/radial gradient functions; \`rgba()\` (and \`rgb()\`) come through
as SAC_FUNCTION and hit the default-throw, which silently dropped the
entire bg property. The host UIIDs ended up with no bgColor /
transparency, fell back to the Component default - and in a dark
playground that surfaced as fully white-filled icons / pills with
white-on-white text inside.

Three layered fixes:

1. CSS compiler \`background:\` switch now treats \`rgb()\` / \`rgba()\` /
   \`cn1rgb()\` / \`cn1rgba()\` the same as SAC_RGBCOLOR and routes them
   to background-color. (Future CN1 releases pick this up; the
   deployed playground builds against the published cn1.version so it
   doesn't help today's preview directly.)

2. Host CSS theme.css switches the affected rules from \`background:
   rgba(...)\` to \`background-color: rgba(...)\`. background-color
   already has working rgba support in 7.0.235's compiler. This is the
   change that actually fixes the deployed playground.

3. Aligned PlaygroundEmbeddedFormDark / PlaygroundEmbeddedTitleAreaDark
   bg with the modern iOS dark Form / Toolbar palette (#1c1c1e / #000)
   instead of the old navy. The user's preview was showing a wide blue
   band around the modern dark Form because the embedded UIID overlay
   hadn't been updated when the underlying native theme switched.

4. Android Material MultiButton no longer derives Button. Button
   carries a cn1-pill-border that rendered as an inconsistent outline
   only in dark mode, and the inherited padding wasn't fitting a list
   row. Inlined a clean rectangular row with explicit padding,
   pressed, and disabled states.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
\`Dialog.show("title", "body", "OK", null)\` was rendering at near-full
screen with the body floating in mostly empty space. The original
themes had \`Dialog { margin: 0; }\`; the rebuild work bumped them to
\`margin: 5mm\` (iOS) and \`margin: 4mm\` (Android) thinking they were
gaps from the screen edge.

CN1's Dialog uses the Dialog UIID's own margin as a positioning
hint that grows the dialog up to screen-minus-margin. With margin=0
the dialog auto-sizes to its content's preferred size and draws
inset by the framework's standard halo. Setting margin to anything
positive turns it into a "wide modal" — which is fine for a custom
sheet but wrong for the default \`Dialog.show()\`.

Restoring margin: 0 on both modern themes; the rounded
border-radius stays so the visual polish is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two preview-only fixes that don't depend on theme changes the user
wants left alone.

1. Wrap a non-Form preview component in a Form-styled BorderLayout
   container before adding it to the device screen / no-skin stage.
   The user's preview can be an arbitrary Component (e.g.
   `new Container(BoxLayout.y())`); without a Form root nothing
   paints the modern Form surface behind it and the playground bezel
   shows through directly. The wrapper UIID="Form" makes the active
   native theme paint its own surface behind the content. Forms pass
   through unchanged - they paint their own surface and they get
   tagged with PlaygroundEmbeddedForm by the website-theme pass.

2. Restore the legacy iOS7 / Holo Light dialog-sizing constants on
   both modern themes:
     - hideEmptyTitleBool: true
     - shrinkPopupTitleBool: true
     - dialogButtonCommandsBool: true
     - dlgCommandGridBool: true
   Without these the framework falls back to a wider default Dialog
   layout. \`Dialog.show("title","short body","OK",null)\` was
   ballooning to near-full screen with the body floating in mostly
   empty space. The legacy themes had these constants; the modern
   ones were missing them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Embedded title area transparent. PlaygroundEmbeddedTitleArea(Dark)
   was painting an opaque colour band across the top of the user's
   Form. With the legacy iOS7 / Holo bezel that contrast worked, but
   against the modern Form surface (#1c1c1e iOS dark / #141218
   Android) it just read as "two different shades of dark" and made
   the title bar look odd. Switching to background-color: transparent
   lets the form's surface paint uniformly under the title; the
   user's Title text styling is what defines the title visually.

2. Refreshed playground built-in demos to use the modern UIIDs:
     - Welcome: Form with default Button + RaisedButton + Switch +
       CheckBox + TextField + FloatingActionButton.
     - Profile Form: TextFields, TextArea, CheckBox, Save (Raised) +
       Cancel; opens a Dialog on Save.
     - Menu List: MultiButton rows with Material icons + secondary
       lines, action callback with Dialog.
     - Tabs: three tabs with Material icons that adapt to each
       platform's tab placement (iOS pill at bottom, Material 3
       underline on top).
     - Added new "UI Showcase" sample showcasing every key UIID:
       Buttons, RaisedButton, CheckBox, RadioButton group, Switch,
       TextField, TextArea, Picker, FloatingActionButton.
   Added "Profile Form" + "UI Showcase" to the SAMPLES list.

3. Refreshed iOS + Android golden screenshots from the latest CI run
   (commits 86ed0f0 / 3b7e023 respectively).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Demo scripts:
- Switch's `setOn()` is no-arg; the boolean variant is `setValue()`.
  Welcome + UI Showcase corrected.
- Removed `form.setScrollable(true)` from Welcome / Profile Form / UI
  Showcase / Menu List - Forms scroll Y by default and setScrollable
  enables both axes (the wrong behaviour for these screens).
- Dropped the multi-line tutorial comment from Welcome.
- Removed the FontImage.createMaterial icons from Menu List - static
  FontImage dispatch is currently broken in the playground bsh
  registry. Will be addressed in a follow-up PR.

Android Material Button - text no longer jumps between states:
  The .pressed / .disabled state declarations were minimal (only the
  three colours / cn1-background-type). The framework's per-state
  Style merge with implicit defaults introduced a tiny vertical drift
  visible while the button was held. Re-declared every state-affecting
  property (padding, margin, font-family, font-size, text-align,
  cn1-background-type) on each state and on RaisedButton's states,
  so $press# / $dis# entries keep IDENTICAL geometry to the base.

Android Material tab placement toggle:
  Added explicit `tabPlacementInt: 0` (TOP) and `tabsSafeAreaBool:
  true` to Android Material's #Constants. Without an explicit value
  the iOS Modern constants (BOTTOM=2, safeAreaBool=false) leak into
  Android-mode previews after a device-toggle reload. Material 3 keeps
  the tab strip at the top edge-to-edge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picker lives in com.codename1.ui.spinner, not com.codename1.ui. The
sample relied on bsh's automatic class discovery to find it, which is
fragile - making the import explicit matches the same pattern the UI
Showcase uses and keeps the script self-contained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog marked this pull request as ready for review April 26, 2026 17:55
The bulk goldens-refresh in 18be8dc swept up landscape.png even
though that capture is flaky (the orientation-lock test occasionally
emits subtly different pixels). Reverting it to the pre-refresh
baseline so it doesn't pin a one-off variation as "expected".

The same refresh missed TabsTheme_dark on iOS - the run that fed the
goldens didn't capture it. Added it from CI run 24962339325 (commit
2f29a8d) where the screenshot-compare report flagged it as
missing_expected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog merged commit 3edb800 into master Apr 27, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFE: Create an example of how to mimic the new liquid glass look and feel in CN1

2 participants