Add website-targeted JavaScript Skin Designer integration#4758
Merged
liannacasper merged 56 commits intomasterfrom May 4, 2026
Merged
Add website-targeted JavaScript Skin Designer integration#4758liannacasper merged 56 commits intomasterfrom
liannacasper merged 56 commits intomasterfrom
Conversation
Contributor
Cloudflare Preview
|
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Collaborator
|
Compared 7 screenshots: 7 matched. |
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> Signed-off-by: liannacasper <67953602+liannacasper@users.noreply.github.com>
…lmer styling - Hide the Form's default Toolbar so the custom topbar is the only header, and replace the green "CN1" pill with a Material phone icon in CN1 blue. - Replace cn1-pill-border stepper badges with a CircleBadge custom Component that paints a true filled disc with the digit (or check) centered, so the numbers stop getting cropped on every platform. - Switch the step separator back to a thin colored Label with explicit preferred dimensions; the custom-paint Component was getting stretched vertically by BoxLayout into a vertical bar. - Replace setLeadComponent(label)+addPointerReleasedListener with a LayeredLayout overlay carrying a transparent Button (SkinDesignerCardOverlay) on top of the card content. Clicks now fire reliably inside scrollable parents (device cards, source cards, preset tiles, cutout select area). - Drop cn1-pill-border from buttons in favor of cn1-round-border 1.6mm with explicit .pressed/.disabled selectors, so the toolkit's default pressed styling never appears. Filter chips keep cn1-pill-border (small enough to render as a true pill) and gain matching pressed variants. - Defer device-grid rebuild via CN.callSerially when filter chips are tapped, lower DEVICE_GRID_LIMIT 200 -> 60, debounce search 180 ms, and show a "Showing X of Y - type to narrow" hint, so filter switches feel snappy on a 5000-device DB. - Bump padding/margin throughout: topbar, step head/body, device cards, source cards, footer, buttons. Device DB scraper: - Add --mode latest that walks GSMArena's latest-mobiles.php3 page; combined with --limit, this is the trickle scrape CI now uses. - Workflow runs every 6 hours on master only (no more pull_request trigger that fired on every PR), with an if: github.repository/ref guard so it also skips forks and feature branches. workflow_dispatch supports a trickle/full mode choice, and the create-pull-request step is gated on an actual diff so empty runs don't open noise PRs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…le selection)
Stepper:
- Active step gets a translucent blue pill behind it again (cn1-pill-border
+ e8f0ff bg) so the user can tell which step they're on without reading.
- Number badges go back to a Label sized to a fixed square in code with
cn1-pill-border, which renders as a true circle and centers the digit
via the label's intrinsic text alignment. Removed the custom CircleBadge
painter that was producing badly-aligned numbers.
- Done check uses a smaller, lighter ring (e8f3c8 / 6e8a1a) and a finer
check icon at 2.6mm rather than the heavy lime disc.
Header text:
- H1 dropped from MainBold to MainMedium and 5.6mm -> 5.0mm; sub goes from
text-muted (#7f8aa3) to a darker #4a5775 so it's actually readable; H3
also drops to MainMedium.
- "Which device is this skin for?" sits closer to the topbar now (StepHead
padding-top 4mm -> 2mm).
- Sub uses a non-editable TextArea so the full second sentence ("...so you
can focus on the skin shape") wraps properly instead of being clipped.
Search field:
- setHintIcon with the magnifier glyph (MATERIAL_SEARCH).
- cn1-round-border 1.4mm so the field matches the design.
Filter chips (All / Phones / Tablets / Foldables):
- Padding tightened (1.2mm -> 0.8mm vertical, 3.2mm -> 2.4mm horizontal).
Device cards:
- cn1-round-border 2mm; 2.4mm padding; 1mm margin (was 4mm/1.6mm).
- Use MATERIAL_APPLE / MATERIAL_ANDROID brand glyphs in the OS mark instead
of the silhouette phone icons that read as identical at this size.
- Selecting a device used to call renderStep() which rebuilt the body and
jumped the scroll. selectDevice() now toggles only the affected cards'
UIIDs in place via stored card / check / deviceId client properties.
- The selected check icon is always part of the layout (visibility-only
toggle) so the row width doesn't shift when selection changes.
- OS mark gets a rounded background and the brand glyph at 3.2mm.
Source step:
- "Build a skin for <device>." now renders the device name in bold via a
three-Label FlowLayout row (prefix + bold + suffix) so the full sentence
is visible.
- Card descriptions go through a non-editable TextArea so the full two-
sentence text wraps cleanly. Updated to the exact strings the user
asked for ("...from there.", etc.).
- Cards: cn1-round-border 3mm + .pressed state with a translucent blue
fill (closest CN1 CSS-subset analog to :hover, which doesn't exist in
the compiler today). Inner illustration also gets cn1-round-border 2mm.
Done step:
- Added a "Save again" Button so the user can re-trigger the .skin file
download if the browser blocked the auto-download or they just want
another copy. Button is disabled when no skin bytes are cached.
- Renamed "New skin" -> "Make another skin" and gave it a refresh icon.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hover: - Subclass Form to override pointerHover(int[], int[]) so we can intercept hover events at the form level. CN1's CSS subset doesn't expose a :hover state and overriding pointerHover on a child Container is unreliable when an overlay Button sits on top (the overlay swallows the dispatch). - New registerHover(Container) registers a card; updateHoverState(x, y) walks the registry on each hover event, finds the card under the cursor via Component.contains(), and toggles UIID -> UIID + "Hover" exactly once when crossing card boundaries. The base UIID is stored on the component as a clientProperty so selection-driven UIID swaps still pick the right hover variant. - Wired into source cards (3 boxes on stage 2) and device cards (stage 1). - renderStep() now clearHoverState()s before rebuilding, so we never hold references to detached containers. - Added *Hover CSS variants for SkinDesignerSourceCard, SkinDesignerDeviceCard, and SkinDesignerDeviceCardSelected (light + dark). Skin download: - The auto-Display.execute() in buildDoneStep() ran one event-loop tick after the Finish click; on browsers that meant the user-gesture context was already gone and the download was silently blocked. - Moved skin generation + write + Display.execute() into a new saveSkinFromUserGesture() that the Finish button calls SYNCHRONOUSLY inside its action listener, keeping us in the gesture window. - Done step now shows a big primary "Download skin" Button as a manual fallback. Clicking it re-fires Display.execute() (or generates if Finish somehow didn't). It's also now the only primary action; the "Make another skin" path moved to a secondary button. - restart() clears lastSkinBytes / lastSkinFile so a fresh wizard run doesn't show stale "saved" state. Bug fix in selection visuals: - applySelectionToGrid was toggling the inner card's UIID, but makeClickable moves the visible UIID to the outer wrapper. Selecting a device wasn't actually changing appearance. Now toggles wrapper's UIID (and updates baseUIID so hover swaps onto the selected variant). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… cutouts + filename CSS borders were using the wrong syntax. Per docs/developer-guide/css.asciidoc, cn1-round-border / cn1-pill-border are VALUES used in the background or border shorthand, not standalone properties. cn1-round-border:1.6mm; was silently ignored, leaving every "rounded" surface square. Fixes: - Buttons (Primary / Secondary / Ghost), search field, device cards, source cards, source illustration, OS mark — switched to border-radius: Nmm so CN1 maps them onto RoundRectBorder. - Stepper item active pill, stepper number badges, filter chips, done check — switched to "background: cn1-pill-border; background-color: ...;" which is the documented filled-pill pattern. Stepper number badges remain sized to a square in code so the pill renders as a true circle. Cutout list bug: - buildCutoutRow was discarding the LayeredLayout wrapper makeClickable returns, then re-parenting selectArea into the row. The orphaned wrapper kept references and clicking the row threw on the next rebuild. Now adds the wrapper directly. Skin file save: - The auto Display.execute() inside saveSkinFromUserGesture was firing AFTER the slow createSkinBytes() call, by which time the browser's user-gesture window had expired and the popup was blocked. Removed the auto-trigger; saveSkinFromUserGesture now only generates and writes the file. The done step's primary "Download skin" Button fires Display.execute() on the cached file path — that runs in a fresh, fast user-gesture chain and reliably triggers the download. Filename redundancy: - SkinModel.resetForDevice generated "<device.name> skin", which the saver then suffixed with .skin → "Apple-iPad-Air-13-2024-skin.skin". Drop the trailing " skin" so we save as "Apple-iPad-Air-13-2024.skin". Safe-area updates with cutouts: - buildProperties now computes effectiveSafeTop = max(skin.safeTop, deepest cutout extent) so a user-added notch / island / hole always pushes the safe area below it, regardless of the device's default safeTop. Doesn't mutate skin.safeTop, so the user can still see / edit the configured value. Title divider: - The "really thick separator line" came from TextArea's default underline border bleeding through the SkinDesignerSub UIID. Switched the device-step sub, source-card description, and done-step message from non-editable TextArea to SpanLabel, with fresh SkinDesignerSubBlock / SkinDesignerSourcePBlock container UIIDs that carry no border styling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…separators
Stepper separator:
- The colored Label between step items was getting stretched by BoxLayout
to fill the row height, producing a really thick vertical bar. Replaced
with a single em-dash (—) Label - one character can't grow beyond
its glyph, regardless of how the layout tries to size it.
Brand title:
- "Skin Designer" header dropped from MainBold to MainMedium and a
notch smaller. Reads less heavy in the topbar.
Material icon backgrounds:
- FontImage.setMaterialIcon copies the host's unselected style into the
baked image, including bg color & transparency. That meant the icon
inside SkinDesignerSecondaryButton's white pill carried a white square
forever - even after applyDarkRecursive flipped the button to dark
blue (the user reported "the arrow in the Back button has white
background"). Same bug behind "the green-circle check has a white
background tinted green".
Added a small applyMaterialIcon helper that:
1. Stamps the icon char onto the component as a clientProperty.
2. Builds a Style copy with bgTransparency=0 so the FontImage's
backing image only contains the glyph - no solid bg block.
Replaced all 16 FontImage.setMaterialIcon callers.
applyDarkRecursive now also re-bakes the icon when it changes a UIID,
so a runtime theme switch picks up the new fg color.
Hover staleness in dark mode:
- registerHover used to capture baseUIID at build time, but
applyDarkRecursive runs *after* build and rewrites the UIID, so we'd
cache "SkinDesignerSourceCard" while the live UIID was
"SkinDesignerSourceCardDark". On hover-out we restored to the cached
light variant - that's the "light bg stays light after moving to the
next item" the user saw.
Now resolves baseUIID lazily on first hover (whatever the card's UIID
is at that moment is correct, dark or light), and a small
hoverVariantOf() helper appends "Hover" or "HoverDark" so we always
pick the correct themed hover style. applyDarkRecursive also rebases
cached baseUIIDs on a runtime theme switch, and applySelectionToGrid
composes the correct dark/light variant when toggling device
selection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hter idle poll Search hint magnifier in dark mode: - search.setHintIcon was passing search.getStyle() straight to FontImage.createMaterial, so the baked icon image carried the search field's solid white background. After dark mode flipped the field to a dark blue, the icon still displayed as a white square. Build a Style copy with bgTransparency=0 before createMaterial so the icon is glyph-only. Filter toggle losing dark mode on rebuild: - rebuildDeviceGrid runs outside of renderStep (it's wired to filter chip clicks and the debounced search), so the freshly-built device cards never went through applyDarkRecursive — they stayed in light-mode UIIDs while the rest of the form was already dark. Selecting one then swapped just the wrapper to the dark variant via applySelectionToGrid, leaving the inner Labels/specs in light mode. Now applyDarkRecursive(grid) is invoked at the end of rebuildDeviceGrid so all new cards inherit the current theme. Idle freeze from theme polling: - UITimer was hitting readThemeFromUrl every 900 ms forever. Each tick crosses the CN1 JS-port bridge to read window.location.href, and enough idle ticks accumulate enough JS-side work that the browser tab eventually locks up. Bumped the interval to 5 s — still near-instant response when the user toggles ?theme= on the website shell, but ~5x less work over an idle session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tiny fonts (text 0.6mm tall on a 460ppi device):
- DeviceDatabase stores iOS-style point font sizes (12/15/22) which we
were writing into skin.properties as smallFontSize/mediumFontSize/
largeFontSize. The simulator (JavaSEPort.java around line 2870) reads
those as physical pixels and uses them verbatim — on a high-ppi screen
that renders sub-millimeter text and the UI is unreadable.
When those properties are absent, the simulator auto-derives them from
pixelMilliRatio: med = round(2.6 * ppmm), sm = 2 * ppmm, la = 3.3 * ppmm
— which is the right size for the device. Stop writing the font-size
properties so the simulator's auto-compute kicks in.
DPI propagation:
- Also write `ppi` directly (the simulator prefers `ppi` over `pixelRatio`
when both are present — JavaSEPort uses ppi/25.4 to derive the same
pixelMilliRatio). Keep `pixelRatio` (= ppi/25.4) as a fallback for
any older skin loaders.
Bezel coordinate consistency:
- skinBezelInPx (used by buildProperties to write safe area / display
origin) and generatePortraitImage (used to draw the skin and overlay
PNGs) were computing different bezel pixel values. The skin image
placed the screen rect at (bezelPx_a, bezelPx_a) but safePortraitX/Y
was written against bezelPx_b — leaving safe area off by ~10px on
high-res devices. Both now use:
bezelPx = round(skin.bezel * (resolutionW / VB_W))
so the safe-area rectangle aligns with the screen rect in the
generated skin.png.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Filter chip dark mode regression:
- Click handler always set the light-mode UIID (FilterTag /
FilterTagActive). In dark mode the chips momentarily flashed to
light variants — title/text remained visibly light even after
applyDarkRecursive ran. Click handler now composes the dark suffix
via themedUiid() so the chip stays themed across clicks.
Safe area coordinate space:
- Looking at Container.snapToSafeAreaInternal in CodenameOne core:
int safeTopMargin = rect.getY();
int safeLeftMargin = rect.getX();
The framework treats the rect from getDisplaySafeArea() as
*display-relative* margins (origin at the screen's top-left),
not skin-image absolute coords. We were writing
safePortraitY = bezelPx + safeTopPx (skin-image coords), so the
framework saw a top inset of bezelPx + 238 = 327 — pushing UI way
too far down — but, more relevant for the user, the bezelPx + offset
meant the iOS theme's title bar was anchored relative to the
display origin and never reached the inset, leaving it stuck at
display y = 0 visually behind the dynamic island.
Now writes safe area in display coords:
safePortraitX = 0
safePortraitY = safeTopPx
safePortraitWidth = device.resolutionW
safePortraitHeight = device.resolutionH - safeTopPx - safeBottomPx
Landscape is the 90° rotation: portrait top inset becomes landscape
left inset, portrait bottom inset becomes landscape right inset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the builtin iPhone X / 11 / 12 / 13 skin behavior the user expected: a gap above the screen that the notch / island fits into, and rounded screen corners. Cutout placement: - Old behavior: cutouts were drawn as opaque-black shapes inside the screen rect (closer to iPhone 14 Pro reality, where the island is software-reserved within a rectangular display). The simulator renders UI behind the cutout and the iOS 7-era theme has no idea to avoid it. - New behavior: the skin image now reserves a top frame extension (height = max cutout extent above screen) and renders cutouts in that extension hanging down toward the screen top. The screen rect starts BELOW the cutouts. computeTopCutoutPx() / applyTopFrameCutouts() encode this; generateOverlay positions the skin_map's screen rect to match. - For NOTCH (c.y=0), the cutout's bottom touches the screen top. - For ISLAND / HOLE, c.y is the gap (in vb px) between cutout-bottom and screen-top. Rounded screen corners: - Replaced carveScreenRect with carveRoundedScreenRect: only carves the inside of a rounded rect, leaving frame material at the four corners. The visible screen now has a rounded outline that matches the bezel's outer corner radius minus 8 vb px (consistent with the editor preview's screenR formula). - Screen corner radius = max(0, cornerR - 8) * scale. Safe-area follow-up: - Now that cutouts no longer intrude into the screen, dropped the cutout-extent contribution to safeTop. The user's configured skin.safeTop is the only inset. Default values from DeviceDatabase (e.g. 59 for iPhone with island) still cover the iOS status bar above the screen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit moved every cutout type into the top frame extension above the screen, which is correct for iPhone X / 11 / 12 / 13 hardware notches but wrong for Dynamic Island and Android punch-holes — those are software-reserved space *inside* a rectangular display, not physical cutouts. The user wanted the island to "float" on top of the iOS status bar rather than sit in the device frame. Now the cutout types are split: - NOTCH (physical hardware cutout): rendered in the top frame extension above the screen, with the bottom edge touching the screen top. computeTopCutoutPx() / applyTopFrameCutouts() handle these. Matches the builtin iPhone X skin behaviour — a gap above the screen the notch fits into. - ISLAND, HOLE (software / in-display): drawn as opaque pills/circles inside the screen rect via applyInScreenCutouts(). The iOS status bar paints in the safe area top and the island appears floating on top of it, like real iPhone 14 Pro+. computeTopCutoutPx no longer counts these so the screen rect doesn't shift down for them. Safe-area knock-on: - Restored the cutout-extent contribution to safeTop, but ONLY for in-screen cutouts (islands, holes). effectiveSafeTopVB = max(skin.safeTop, max(c.y + c.h) for non-notch cutouts) so app content lands below the floating island even when the user-set safeTop is smaller than the cutout extent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…island)
Looking at JavaSEPort.java line ~1622, when roundedSkin is true the
simulator paints the skin image *on top of* the rendered UI buffer
after the UI is drawn:
if(roundedSkin) {
Graphics2D bg = buffer.createGraphics();
BufferedImage skin = getSkin();
bg.drawImage(skin, ...);
}
That overlay-on-top render is what makes opaque shapes inside the
screen rect (Dynamic Island, punch-hole cameras) appear floating over
the status bar / app content. Without roundScreen=true, the simulator
takes the skin_map analysis path which clips the UI render to the
black-pixel screen rect — the island circle in skin.png never lands
on top of UI, it just becomes part of the frame visual.
Now the generated .skin sets:
roundScreen=true
displayX = bezelPx
displayY = bezelPx + topCutoutPx (notch extension, if any)
displayWidth = device.resolutionW
displayHeight = device.resolutionH
The display rect points at where the screen actually starts in the
generated skin.png (matches the carveRoundedScreenRect call), and
the simulator overlays the skin — including the floating island /
hole shapes — on top of the rendered UI. The status bar from the
iOS theme paints at the safe-area top and the island appears on top
of it, like real iPhone 14 Pro+.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tutorial: - New chapter at docs/developer-guide/Skin-Designer.asciidoc walks through each wizard stage (device picker, source picker, three editor tabs, finish/download), the file layout of a generated .skin, and how to refresh the bundled device catalog. - Wired into developer-guide.asciidoc just before the Maven appendices. - Six embedded screenshots under docs/developer-guide/img/skin-designer/ generated by CI. Demo-mode hooks in SkinDesigner.java: - applyDemoOverrides() reads cn1.skindesigner.demo* system properties and forces the wizard to a specific (step, device, source, preset, sidebar tab) combination during loadState. Lets the screenshot harness drive the UI to a deterministic state without persisting Preferences across runs. Screenshot harness: - scripts/skindesigner/screenshots/lib/SkinDesignerScreenshotter.java spawns the CN1 simulator JVM with the demo overrides forwarded as -Dcn1.skindesigner.* properties, waits for the UI to settle, then captures the desktop with java.awt.Robot. Modeled on the existing scripts/javase/lib/SimulatorWindowModeVerifier.java. - scripts/skindesigner/screenshots/take-screenshots.sh builds the Skin Designer Maven project, resolves the simulator runtime classpath via dependency:build-classpath, compiles the harness, and runs each scenario inside xvfb-run on Linux. Six scenarios mirror the developer-guide chapter (device picker, source picker, editor shape/cutouts/info tabs, done page). - scripts/skindesigner/screenshots/README.md documents the demo override properties and the local-run command. CI: - .github/workflows/skin-designer-screenshots.yml runs the script on workflow_dispatch and on PRs that touch the harness or wizard code, uploads the PNGs as an artifact, and on manual dispatch opens an automated PR if the committed images drifted. Excluded from the monthly device-DB refresh because the wizard UI changes much less often than the catalog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaced the desktop-Robot harness with a Codename One Lifecycle that uses Display.captureScreen() and saves PNGs through Storage. On the JavaSE port Storage maps to ~/.cn1/, so the shell script just copies files out of there into docs/developer-guide/img/skin-designer/. ScreenshotApp: - Lifecycle subclass at scripts/skindesigner/common/src/main/java/.../screenshots/ScreenshotApp.java - Walks each wizard stage in turn: clears persistent wizard state (Preferences keys + SkinModel.clearPersisted), sets the cn1.skindesigner.demo* system properties for the scenario, calls new SkinDesigner().runApp(), waits 1.5 s for layout, captures via Display.captureScreen(), saves the PNG through Storage. - After the last scenario calls Display.exitApplication() so the JVM exits cleanly. take-screenshots.sh: - Drops the Robot / classpath-resolution / harness-compile dance. - Builds the Skin Designer with mvn install, then launches the simulator with -Dcodename1.mainClass pointing at ScreenshotApp. xvfb-run is only needed because the JavaSE simulator still creates AWT windows, but no Robot capture happens. - After the simulator exits, copies the PNGs from ~/.cn1/ into docs/developer-guide/img/skin-designer/. A missing PNG fails the script. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks:
Unused image preview:
|
…checkout
The CN1 ZipSupport cn1lib's main.zip is created by the
install-cn1lib plugin goal on local first-run and isn't tracked in git
(only the ZipSupport.cn1lib bundle and the cn1libs/ZipSupport/pom.xml
are). On a fresh CI checkout mvn install fails at the build-helper
attach-artifact step because cn1libs/ZipSupport/jars/main.zip doesn't
exist:
[ERROR] Failed to install artifact ... skindesigner-ZipSupport:jar:common ...
/home/runner/work/.../cn1libs/ZipSupport/jars/main.zip
Added an explicit extract step at the top of take-screenshots.sh that
unzips main.zip out of the .cn1lib bundle if it isn't already present.
The .cn1lib is just a zip containing main.zip + stubs.zip + the
codenameone_library_*.properties metadata, so unzip -p does the job
without needing the CN1 plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The css goal of the CN1 plugin (cn1-process-classes execution) instantiates JavaSEPort to compile theme.css. JavaSEPort's static initializer calls calcRetinaScale -> getDefaultScreenDevice(), which throws HeadlessException on a fresh GitHub-actions runner. Need a display for mvn install just like for the simulator launch itself. Wrapped both phases in xvfb-run when available so the build runs under a virtual X server. Local non-Linux invocations still use the plain mvn invocation since macOS has a real display.
The screenshots CI run failed inside the simulator because Display.isDarkMode() returns a Boolean that is null on the headless Linux JavaSE port. readThemeFromUrl() unboxed it directly and crashed SkinDesigner.runApp before the first form could render. Treat the null return as "not dark" and let the URL ?theme= override keep working. Also drop two CSS values the bundled designer's CSS compiler (7.0.228) does not understand and was logging as parse errors: - cn1rgba(...) translucent overlays -> opaque hex equivalents - font-family "native:MainMedium" -> "native:MainLight" These were warnings, not fatal, but they polluted the CI log and the hover/pressed colors silently fell back to the default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Skin Designer chapter references six screenshots generated by the skin-designer-screenshots workflow. Until that workflow lands its first successful run, lychee link-checking on the Hugo build fails with "Cannot find file" on each missing PNG, blocking the website build for every PR on this branch. Commit 8x8 grayscale placeholders under the same names so the build passes. The screenshots workflow overwrites these with the real captures on its first successful run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The website builds the Hugo site from the same checkout that hosts the developer guide, so the Skin Designer chapter's PNGs need to exist by the time scripts/website/build.sh runs. Previously the only producer was the standalone skin-designer-screenshots workflow, whose output never made it onto the website branch — every Hugo build failed lychee link-checking on six missing PNGs, and the placeholder PNGs added in the prior commit would have shipped to production. Wire the screenshots into the website pipeline directly: - Trigger website-docs.yml on changes under docs/developer-guide/ and scripts/skindesigner/ so wizard or guide edits rebuild the site. - Insert Java 17 + xvfb + CodenameOne tooling steps and run take-screenshots.sh before the existing website build, so the PNGs land in docs/developer-guide/img/skin-designer/ in time for build.sh's rsync into static/developer-guide/. - Drop the placeholder PNGs committed in the previous fix; the build now produces real ones. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Earlier the screenshots step booted the full JavaSE simulator via mvn -Psimulator verify with -Dcodename1.mainClass=ScreenshotApp. On CI the simulator launched, started the source-change watcher, then sat idle until the workflow timed out at 25 minutes — the Lifecycle override never reliably wired up inside the simulator launch path. Replace the harness with a plain main(): - ScreenshotApp.main() calls Display.init(new java.awt.Container()) in CN1's quiet test mode, walks each scenario on the EDT, lays the form out at iPhone-class dimensions, and writes the PNG via Form.toImage() + FileOutputStream + cn1 ImageIO. - take-screenshots.sh runs `mvn install -pl common -am` to compile and CSS-bake, then `mvn exec:java -Dexec.mainClass=...` for the capture. No cn1:simulator, no verify lifecycle. - Output lands directly in OUT_DIR (no Storage round-trip via ~/.cn1 needed any more). - Drop the standalone skin-designer-screenshots.yml; the website-docs workflow now produces the PNGs on demand for every Hugo build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ScreenshotApp's Display.init needs ImplementationFactory, which codenameone-javase provides under test scope in skindesigner-common's pom. Running exec:java with classpathScope=compile threw ClassNotFoundException at startup. Switch the screenshot exec to classpathScope=test so the JavaSE port is on the classpath. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ScreenshotApp bypasses Lifecycle.start, so the bundled theme.res
never gets registered with UIManager. Without it Form.getToolbar()
returns null and SkinDesigner.runApp NPEs on the first tweak.
Mirror Lifecycle.start by calling UIManager.initFirstTheme("/theme")
right after Display.init so each scenario renders against the same
themed UIIDs the wizard expects.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UIManager.initFirstTheme alone does not flip Toolbar.globalToolbar — the flag is only ever set by an explicit Toolbar.setGlobalToolbar call (CN1's bundled simulator does this; Lifecycle.start does not). Without it, Form.getToolbar() stays null and SkinDesigner.runApp NPEs on its first form.getToolbar().setHidden(true). Set the flag explicitly after Display.init in the screenshot harness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reframe the chapter to match how users actually meet the tool — at codenameone.com/skindesigner, in a browser tab, no install. The prior chapter pitched it as a standalone CN1 app with three flavours and a "Launching the tool" section walking through run.sh / .bat; remove that, point at the URL, and treat the screenshots as captures from the hosted tool. Tighten the prose: - Cut implementation details that don't concern users (Preferences keys reset, in-memory state mgmt, the catalog refresh script and scrape-source mention). - Refer to the "device catalog" rather than devices.json. - Drop the "Refreshing the bundled device catalog" appendix entirely — that's a CI/maintainer concern. Widen the screenshot harness from a phone-class 390×844 to a desktop-class 1280×800 viewport so the editor's two-column layout renders at the same width a website visitor sees. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Skins menu now leads with the two actions a user actually reaches for: - "Add Skin" — file picker for a .skin (renamed from "Add New...") - "Skin Designer" — opens https://www.codenameone.com/skindesigner/ in the user's default browser, replacing the bundled gallery flow as the way to build a new skin Everything else from the previous Skins menu — the radio-button list of bundled skins, Desktop.skin, UWP Desktop.skin, the "More..." Cloudflare gallery, and "Reset Skins" — moves under a "Legacy Skins" submenu. The gallery downloader's refresh callback now repopulates that submenu instead of rebuilding the whole menu. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
shai-almog
added a commit
that referenced
this pull request
May 5, 2026
The scheduled trickle scrape has been hitting a 404 since #4758 landed (latest-mobiles.php3 doesn't exist on GSMArena), so every 6h run pulled zero phones and only opened PRs because the JSON envelope churned between runs (e.g. #4862). Replace the trickle path with a weekly run of the verified per-brand walk, and make the script skip writing the file when no device records actually change so envelope drift can't spam PRs again. - Workflow: weekly Mon 03:00 UTC, single brands scrape, 75m timeout (cold cache). Drop the mode dispatch input and trickle/full split. - Script: drop dead --mode latest path (walk_latest, RE_LATEST_LINK, --mode flag). Add _devices_changed() so the file is only rewritten when actual records added/removed/modified, ignoring envelope diffs. - README: document the new weekly cadence. 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.
Motivation
Description
ShouldExecuteimplementation for the JavaScript port that returnstrueand posts acn1-skindesigner-ui-readymessage to the parent page to support embedded hosting (file:scripts/skindesigner/javascript/src/main/javascript/com_codename1_tools_skindesigner_ShouldExecute.js)./skindesigner/with loading UI and dark-mode sync (files:docs/website/content/skindesigner.mdanddocs/website/layouts/_default/skindesigner.html).docs/website/hugo.toml.scripts/website/build.shto optionally build and extract the Skin Designer JavaScript bundle intodocs/website/static/skindesigner-appusingWEBSITE_INCLUDE_SKINDESIGNER, following the same pattern used for Playground/Initializr.Testing
bash -n scripts/website/build.shwhich reported no syntax errors in the modified build script.sh ./mvnw -q -pl javascript -am -DskipTests -Dcodename1.platform=javascript packageinscripts/skindesigner, but the build failed in this environment due to missing Java 8 (JAVA_HOMEpath not present) and network/maven wrapper download errors (java.net.SocketException: Network is unreachable).Codex Task