Rebuild native themes via CSS compiler (phase 2: module split)#4793
Merged
Conversation
Collaborator
|
Compared 65 screenshots: 65 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Collaborator
|
Compared 7 screenshots: 7 matched. |
Collaborator
|
Compared 34 screenshots: 34 matched. |
Contributor
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
290b09d to
e2f4545
Compare
Contributor
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
Collaborator
|
Compared 65 screenshots: 65 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
ba23584 to
4059455
Compare
Contributor
Cloudflare Preview
|
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>
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>
fe1424d to
e5f0e4a
Compare
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First slice of the native-themes refactor planned in /plan. Extracts the Codename One CSS compiler into a thin
maven/css-compilermodule 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.strictNoCefflag +enforceNoCef()pre-scan inCSSTheme+-no-cefCLI arg onCN1CSSCLI+ newNoCefCSSCLIminimal entry point.EditableResourcesphysically moved intomaven/css-compiler; designer/javase imports stripped; four throwing hooks introduced;EditableResourcesEditorsubclass in the Designer overrides the hooks. Newcom.codename1.ui.util.SVGDocumentinterface in core replaces the compile-time dep onimpl.javase.SVG.CSSTheme,ResourcesMutator,Color, helpers, and thecom.codename1.ui.util.xmlpackage moved alongside.maven/designernow depends oncodenameone-css-compiler.scripts/build-native-themes.sh+ smoke CSS atnative-themes/{ios-modern,android-material}/theme.css+Themes/.gitignore.pr.ymlgains a step that installs css-compiler and runs the native-themes build;designer.ymlswitched 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)
CodenameOneDesigner/build.xml) still expects all CSS classes undersrc/; needs source-root awareness ofmaven/css-compileror NetBeans/Ant devs to switch to Maven.ResourceEditorView~line 2382 still callsEditableResources.open(...)returning a base instance — fine for the override-resource side path but a futureEditableResourcesEditor.open(...)factory would be tidier.installNativeTheme()rewrites, simulator bundling + override menu,cn1.nativeThemebuild hint, screenshot fidelity tests).Test plan
pr.ymlmatrix (Java 8/17/21) builds green including the new "Build CSS compiler and smoke native-themes" step.designer.ymlbuilds the Designer jar via Maven, the CLI CSS compile smoke produces a non-empty.res, and the native-themes smoke step writesThemes/iOSModernTheme.res+Themes/AndroidMaterialTheme.res.ant.yml(Java CI) full reactormvn install -Plocal-dev-javasepasses../scripts/build-native-themes.shlocally aftermvn -pl css-compiler -am installproduces both.resfiles and an intentional forbidden rule (e.g.box-shadow: 0 2px 4px #000in a theme CSS) fails the build with the offending-UIID list..resfile in the Designer GUI and confirm all resource types still open editors correctly (proves theEditableResourcesEditorwiring).🤖 Generated with Claude Code