Playground UI redesign#4792
Merged
shai-almog merged 56 commits intomasterfrom Apr 24, 2026
Merged
Conversation
Contributor
Cloudflare Preview
|
55c6775 to
2f78cff
Compare
- Switched the editor/preview layout to a SplitPane with 25/50/75 bounds, no expand/collapse arrows or drag-handle, a thin PlaygroundSplitDivider line, and matching dark variant. - Status pill: 50% translucent background, pill border, BoxLayout.x so the dot is vertically aligned with the label regardless of font ascent. - Share/Download: round-rect border with stronger share outline; download text stays white in dark mode. - Code/CSS segmented group: rounded outer border + rounded selected pill. - Playground wordmark uses MainRegular instead of MainBold. - CSS toggle icon falls back to MATERIAL_BRUSH (the wireframe alternative to the filled MATERIAL_PALETTE, which CN1's font ships only in solid). - Orientation segmented is now icons-only regardless of layout density. - Button icon/text gap scales with DPI (1.3mm) instead of 2 raw pixels. - Monaco gutter narrower and lighter (glyphMargin off, lineNumbersMinChars 2, lineDecorationsWidth 4, softer line-number colors, matching editorGutter.background). - Cleared the default BrowserComponent border so the editor sits flush. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Moved inspector and samples/history slots out of the SplitPane to
center.EAST / center.WEST so opening Inspector no longer squishes the
device skin; the SplitPane holds just editor and preview.
- previewContainer.setPreferredW(82mm) so BorderLayout keeps a minimum
wide enough to fit the iPhone portrait skin even when the SplitPane
right side is dragged inward.
- Stage scroll axis is now exclusive: portrait / no-skin uses scrollableY,
landscape (skinned) uses scrollableX. Never both at once.
- Panel show/hide animation: drop the revalidate fallback so
animateLayout(220) on the shared center container can actually
interpolate between before/after layouts.
- Status pill:
* Translucency bumped 0.5 -> 0.75 (darker Live background).
* Pill border drawn only by the outer container; inner Label uses a
new PlaygroundStatusLabel{,Error}{,Dark} UIID (transparent, no
border) so the pill isn't drawn twice.
- Top-bar app icon: in dark mode switches to a white MATERIAL_WIDGETS
glyph on an rgba(255,255,255,0.1) rounded pill (new PlaygroundAppIcon
{,Dark} UIIDs) instead of the bundled icon.png, whose white
background read poorly against the navy bar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inspector:
- Custom tree rows with chevrons for containers and outlined type icons
(folder for containers, document for leaves). Depth-indented 3mm base
plus 5mm per level, 7mm row height, flush rows.
- IDENTITY / CONTENT / APPEARANCE / LAYOUT section headers between field
groups with 1px dividers above each non-first section.
- Single-column form: 22mm label + input-fills-rest, replaces the old
two-column grid.
- Inputs get 1.5mm corner radius; dark #0F2A4D on 1px #1F3A5F border.
- Color fields pair a hex input with a 9x9mm live swatch.
- Bounds / Padding / Margin render as four separate inputs with a row
of micro-labels (X Y W H or T R B L) directly beneath.
- Unit selection is now a horizontal segmented control (Dips / Pixels)
in a standard form row rather than a floating button group.
- Added PlaygroundTreeRow{,Active}, PlaygroundTreeChevron,
PlaygroundTreeType{,Active}, PlaygroundTreeBracket{,Active},
PlaygroundInspectorSection, PlaygroundInspectorDivider,
PlaygroundField{Row,Label,Input,ReadOnly,Micro},
PlaygroundInspectorSwatch, PlaygroundInspectorSegment{,Active,Inactive},
PlaygroundInspectorCheckbox UIIDs with dark variants and registered
them in supportsDarkVariant().
Top bar:
- App-icon dark mode bug: setMaterialIcon bakes the glyph using the
label's current FG color, so setUIID must be applied before the icon
- otherwise the icon froze black and reappeared as a solid blob once
the white-on-translucent UIID kicked in. Reordered + switched to
MATERIAL_CODE which reads cleanly at 4.5mm.
Layout:
- previewContainer.setPreferredW 82mm -> 92mm so the bezel, corner mask,
and SplitPane divider don't crowd the skin when Inspector is open.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tree rows: - Replaced the narrow BorderLayout.EAST hit-Button (which only covered a sliver of the row) with a full-width Button body containing the type icon + combined label text, so clicking anywhere on the row's visible content selects the component. The chevron remains a separate Button on the west for expand/collapse only. - Merged the type name and bracket into a single Button.setText since Button can't nest two differently-styled Labels. Sizing: - The inspector spec used "mm" as design-doc millimeters, which at CN1's physical-mm conversion render roughly 2x too large. Halved layout dimensions and font sizes across the panel: label column 22->12mm, tree indent 3/5->1.5/2.5mm, row height 7->4mm, swatch 9->5mm, input corner 1.5->0.8mm, section header font 2.6->1.8mm, field label/input font 3->1.9mm, micro 2.2-> 1.4mm, segmented font 2.8->1.8mm, tree text 3.3->2mm, icon sizes 3.5/4->2.2/2.5mm, tree max height 70->38mm. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Unified the tree and property form into a single scrollable Y Container. Eliminates the dual bounce when scrolling nested scroll areas. - Pass the user's script Component to the Inspector as the tree root directly, instead of the preview wrapper chain (content host / bezel / screen). Root now shows the user component. - Removed the per-cell micro-labels (X/Y/W/H, T/R/B/L) below Bounds, Padding, and Margin fields. The same information is now exposed as a per-input tooltip (X, Y, Width, Height; Top, Right, Bottom, Left). - CONTENT section only renders when the selected component has editable text (Label / TextField / TextArea); never renders an empty "Text: -" placeholder when the section is not applicable. - Dropped the thin inter-section dividers. A single stronger divider separates the tree from the property form (PlaygroundInspectorTreeDivider) and reaches the panel edges via negative horizontal margins. - Even spacing between multi-value fields: each sub-input has a symmetric 1 DIP horizontal margin and the surrounding grid uses negative outer margins so the overall row alignment is preserved and X-Y / T-R no longer touch. - Sizes bumped from 50% back up to roughly 75% of the original spec (label column 16 DIP, tree row 5.2 mm, tree indent 2 + 3.8 DIP per level, input padding 1/1.6 mm, swatch 5 mm, font sizes 2-2.5 mm). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tree rows:
- Replaced the Button-in-CENTER click target with a pointer-released
listener on the row Container itself. Selection now fires on a
release anywhere in the row (including the icon and label regions),
except when the release is within the chevron's absolute X range -
chevron retains its own ActionListener for expand/collapse.
- Row height bumped to 6 mm so the hit target is comfortably tall.
Divider between tree and property form:
- Previously the divider had negative horizontal margins to punch past
the root's 2 mm inner padding; CN1 clipped the line so it never
rendered. Moved the padding off PlaygroundInspectorRoot{,Dark} and
applied it inline on treeContainer / propertiesContainer so the
divider now spans the full panel width without needing any negative
margin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CN1 Container defaults to non-scrollable but this has burned us before: the previous Tree-based tree view enabled Y scroll in its own constructor, which produced the bouncy dual-scroll feel. Keep scroll explicitly off on treeContainer and propertiesContainer so only unifiedScroll handles pointer drags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
propertiesContainer defaults to non-scrollable anyway - only the tree needs the explicit disable since the old Tree-based parent enabled scrolling in its own constructor. Collapsed the separate X/Y calls into one setScrollable(false). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pointer-released listener on the row Container does not fire reliably inside the outer scrollable container: the scroll container swallows the gesture when a drag begins. Reverted to the explicit Button-in-CENTER approach which does get the click events directly (and worked before). Button with Alignment.LEFT still stretches to fill the row width because the UIID has padding 0 and it sits in BorderLayout.CENTER. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Live dot used a 1px margin against its text, while Share and Download buttons use a 1.3mm gap between their icon and label. Aligned the dot's right margin to 1.3mm DIPS so the spacing reads consistently across the top bar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clicking a tree row fired handleComponentSelected which ran updatePropertyPanel and rebuildTree; each of those revalidated their own Container. But CN1's revalidate() re-lays out the called Container in place without propagating to the parent, so the outer unifiedScroll was unaware of the new preferred sizes - the updated rows and new property form stayed hidden until an unrelated layout pass. Revalidate + repaint the root inspector component at the end of selection to force the whole subtree to re-render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swapped the unified scroll for a vertical SplitPane with 30/50/ 70 insets, showing the same thin PlaygroundSplitDivider line as the main editor/preview split. Tree (top) and properties (bottom) are each independently scrollable Y, which keeps the two panes' revalidate passes local so clicking a leaf actually updates the property form - the old shared scroll didn't propagate sizes up from the inner containers so selection highlight showed but the form didn't repopulate. Dropped the custom PlaygroundInspectorTreeDivider build helper since the SplitPane provides the divider. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
propertiesContainer sits scrollableY inside a SplitPane. The inner revalidate() in updatePropertyPanel fires but the new children end up with zero measured size until the container's ancestor chain (scroll wrapper + pane) re-lays out -- hence the pane stayed blank until a manual resize. revalidateWithAnimationSafety walks up from the container, which forces the pane to re-measure and the field rows render immediately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Button-triggered selection fires while CN1's tactile press animation is still running. Revalidating during the animation caused AnimationManager to snap the pending layout to the animation's final frame, leaving newly-added field rows at measured size 0 until the next unrelated resize. Deferred the panel rebuild + tree rebuild to the next EDT tick via CN.callSerially, and revalidate the entire form afterwards so the SplitPane and its inner scroll both re-measure with the new content. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inspector: - propertiesContainer is now a plain non-scrollable BoxLayout.y that holds the field rows. A dedicated propertiesScroll wrapper (BorderLayout with the content at NORTH, setScrollableY(true)) sits between it and the SplitPane's bottom pane. On selection, updatePropertyPanel mutates only the inner Container, so the scroll wrapper's layout state is never recreated and a plain propertiesContainer.revalidate() re-lays out the new children reliably - no more forceRevalidate / callSerially / revalidateLater trying to outrun a scroll wrapper that was being reset mid-update. Activity icons: - +1 mm more left padding (1.7 -> 2.7 mm) so each icon visually aligns with the title icon above. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Selection threw IllegalArgumentException from Style.setMargin - CN1 rejects negative margin values, but I was using (0, 0, -1, -1) on the multi-value field grid to compensate for the symmetric per-cell margins. Removed the negative outer compensation and trimmed the per-cell margin to left-only, applied to every cell except the first, so the four sub-inputs have a consistent inter-cell gap without the outer grid edges needing any negative offset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switched the scroll wrapper's layout from BorderLayout with content at NORTH to BoxLayout.y. BorderLayout.NORTH clamps its child to its own viewport height, so scrollableY never engaged when content overflowed. BoxLayout.y stacks by preferred height, which lets the wrapper's scrollable state kick in as soon as the content is taller than the pane. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mobile layout now differentiates from tablet-compact and actually responds to bottom-nav taps. Top bar (setMobile): - Code/CSS mode toggle hidden (tab strip below takes its role). - Status pill collapses to dot-only via new setCompactDot, leaving the text label hidden so only the coloured dot sits next to the app icon. Preview column (setMobile): - Preview toolbar (device / orientation / dimensions) hidden. - Device forced to DEVICE_NO_SKIN; restored to the previously-active device when the layout reverts to tablet/desktop. - Stage fills the full tab area without a synthetic bezel. CN1Playground: - assembleDesktopLayout and assembleMobileLayout call setMobile(false) and setMobile(true) respectively on both topBar and previewColumn. - applyActivity branches on isMobileLayout(): on mobile, bottom-nav selection replaces the tab content with the chosen panel while the top bar + bottom nav stay in place; the panel's own close button deselects the nav item and restores the current tab. - refreshMobileTabContent consults mobilePanelFor() first - if a bottom-nav panel is active it renders instead of the Code/CSS/ Preview tab content. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- No-skin AND iPhone both default to iOS base theme in applyDeviceTheme (Pixel keeps Android). "No skin" meant "no bezel", not "no theme". - PlaygroundTopBar.applyAppIconStyle sets the label FG color inline before creating the Material icon so the icon is baked in white in dark mode regardless of when UIID resolution runs. - PlaygroundStatusPill.setCompactDot now hides both text AND its wrapping FlowLayout, so the pill collapses to just the coloured dot on mobile instead of retaining the wrap's width. - Mobile top-tab strip and bottom-nav fonts shrunk (tab 2.8->2.4mm, nav 2.4->2mm) so "Preview" fits on tight phone widths. - assembleMobileLayout defensively resets bottomNav's hidden / visible state and sets an explicit 12mm preferredH so the Samples / Inspector / History row is always visible even if an earlier flow (keyboard-show hook) toggled it off. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top bar: - setCompact now also setHidden(true) on the wordmark, not just setVisible(false). setVisible alone leaves the ~30mm preferred width reserved, pushing the status pill / Share / Download off screen on mobile. setHidden collapses it to 0 preferred width. - setMobile shrinks the icon column from 11mm to 8mm, drops the Share/Download icon glyph size from 3mm to 2.2mm, so the title bar actually has room for the status dot on narrow viewports. Tests: - New PlaygroundLayoutHarness. Boots the Playground lifecycle headless via Display.init, lets the EDT tick, then walks the shown Form tree verifying the presence + non-zero size + in- bounds position of top-bar UIIDs, the activity bar (desktop) or top-tab strip + bottom nav (mobile). Runs for both desktop and mobile by toggling the cn1.desktop / cn1.tablet system properties. Hooked into run-playground-smoke-tests.sh so CI catches regressions where a chrome element is hidden, zero- sized, or rendered outside the form bounds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CN1Playground.runApp did Form.getToolbar().setUIID(...) unconditionally, but getToolbar can return null in a Display.init(null) headless context - hence the layout harness's "runApp did not complete within 3s" after the hidden NullPointerException. Guard the toolbar configuration with a null check so the app works in both the simulator and real HTML5/iOS/Android runtimes (where getToolbar returns a real Toolbar instance). Also surface any throwable from runApp in the harness's callSerially runnable so CI logs the real exception instead of a timeout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Display.isDesktop / isTablet ignore external system properties in
the JavaSE simulator, so the "mobile" scenario was still getting
the desktop shell (hence "TopTabs NOT FOUND"). Added a package-
private testOnlyForceLayout hook on CN1Playground that bypasses
the Display checks when set. Harness sets it to LAYOUT_MOBILE /
LAYOUT_DESKTOP per scenario and resets to LAYOUT_NONE afterwards.
Also updated findByUiid to skip hidden / invisible subtrees. The
PlaygroundSegment UIID is used by BOTH the top bar's Code/CSS
toggle (setHidden on mobile) AND the mobile tab strip. Without
the hidden-skip the first match was the invisible mode toggle,
and the harness failed the size check.
Mobile checks now look for PlaygroundSegment{,Dark} (the tab
strip container) and PlaygroundBottomNav{,Dark}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layout breakpoint: - Switched from Display.isDesktop() / isTablet() back to measuring viewport width via Display.getDisplayWidth() / convertToPixels(1f). isDesktop() always returns true on a desktop browser regardless of window width, so narrowing the browser never triggered the mobile shell. The spec is clear: < ~720 CSS px (190 mm) = mobile, < ~1100 CSS px (291 mm) = tablet, else desktop. d.isTablet() is still honoured as a secondary signal for native tablet builds. Harness: - Added an explicit "bottom nav must have three visible items" check. Previously the presence check on PlaygroundBottomNav only verified the container, which is why "3 buttons completely gone" slipped past CI. countVisibleItems walks the container and counts children with non-zero measured size and isVisible/!isHidden. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Added verifyBottomNavItems that, for each Button child of the bottom nav, asserts it has at least an icon OR a non-empty text. A Container with 3 empty Buttons would look "completely gone" visually but pass the simple count check. Also checks that the nav has exactly 3 Button children (Samples / Inspector / History). Local harness: all checks pass in the JavaSE simulator. If the HTML5 runtime still renders blank nav buttons after deployment picks up this commit, the regression is on the rendering path (icon font loading, peer z-order) rather than on construction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User report: "toolbar flickering in for a second below the HTML component which means it's working but probably failing due to a z-ordering or layout issue." Root cause: the editor's BrowserComponent is a DOM iframe peer that paints above the CN1 canvas. Its peer bounds track the editor Component's bounds. With bottomNav inside bodyContainer -> mobileLayout -> SOUTH, any time bodyContainer's height exceeded the expected remaining-after-nav band (e.g. on first layout pass before SOUTH is reserved), the iframe's bounds followed and covered the bottomNav. Only after a resize would the Form-level layout reconcile, at which point the bottomNav flashed through briefly before the iframe resized. Fix: plant bottomNav at appForm.SOUTH directly, as a sibling of bodyContainer. The Form-level BorderLayout carves out bottomNav's preferredH BEFORE allocating CENTER, so bodyContainer can never extend into the nav band - and neither can any peer iframe inside it. assembleDesktopLayout also detaches bottomNav so the desktop shell doesn't end up with both an activity bar and a leftover bottom nav. Local harness confirms: PlaygroundBottomNav sits at pos=(5,1668) size=2930x121, full form width, with 3 visible item buttons. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Added a 150ms UITimer after appForm.show() that resets the cached layout key, re-runs applyLayoutForCurrentSize, and revalidates the form. Browsers (especially mobile UA modes) sometimes report a placeholder display size at the moment of show, and the initial breakpoint / layout computation can miss the real viewport. A second pass after the form has settled guarantees the correct shell. Also logging: - Breakpoint computation: display width in px and mm, plus the chosen layout (MOBILE/TABLET/DESKTOP). - bottomNav's final parent, absolute position, size, and visible/hidden flags after the post-show pass. This gives us a deterministic way to tell from the browser console whether the mobile shell is even being chosen, and whether bottomNav ends up in a sane position. If it's in the right spot but still visually covered, the issue is the BrowserComponent peer iframe's z-order (canvas under DOM) and needs a different fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier diagnostic logs never appeared in the user's browser console, which suggests the PR preview deploy hadn't picked up the mobile-shell commits. Added a top-of-runApp Log.p marker so we can tell at a glance whether the deployed build is the new one. On the next page reload the console should show: CN1Playground build marker: mobile-shell-v2 @ 2026-04-24 Playground layout: width=... (...mm) -> MOBILE|TABLET|DESKTOP Form: WxH bottomNav: parent=... pos=(...) size=... visible=... hidden=... If the marker is missing, the preview hasn't rebuilt yet; wait a couple of minutes and hard-reload. If the marker appears but subsequent lines don't, runApp is failing mid-way. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostic output confirmed: on mobile the layout is correct -- form 1544x2628, bottomNav at y=2242 size=1480x354 with visible=true, hidden=false. The problem is CN1 HTML5Peer's DOM iframe (Monaco editor) extending past its Java component's bounds and covering the canvas-drawn bottom nav. Mitigation: installIframeBottomGuard injects a <style> tag into document.head with: @media (max-width: 720px) { iframe { max-height: calc(100vh - 64px) !important; } } so no iframe can extend into the bottom 64px band regardless of what the HTML5Peer positioning does. Runs once after appForm.show() via the shared JS context. This is a band-aid: the real fix would be making the nav a peer itself, but the CSS cap is a deterministic way to prove the diagnosis and give a working mobile UX while that larger change is scoped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
You were right - peer components respect their bounds when they are properly attached and sized. The covering peer was not the ACTIVE editor but its idle sibling: - We construct two BrowserComponents in runApp (editor for Java, cssEditor for CSS). Both create their iframes immediately in their constructors - CN1 HTML5 adds the iframe to the DOM up front, and we set HTML5Peer.removeOnDeinitialize=false to preserve Monaco state across dialog show/hide. - Only the editor matching the current mode is ever attached to the CN1 tree via attachEditorsToHost. The other BrowserComponent is never in the tree, so its peer has no Java bounds to sync against. Its iframe sits at whatever default CSS size it was given (typically full-viewport) and paints above everything including the canvas-drawn bottom nav. Fix: explicitly setVisible(false) on both editors at construction. attachEditorsToHost flips the active editor's setVisible(true), leaving the idle one hidden. refreshMobileTabContent calls hideAllEditors() when Preview is active or a bottom-nav panel covers the tab, since neither editor should paint in those cases. CN1 HTML5 propagates setVisible(false) to the peer, which sets display:none on the iframe and removes it from the compositor until reattached. Removed the installIframeBottomGuard CSS band-aid and its call since the real fix is in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This reverts commit 2d4afd2. The idle-editor-covering hypothesis was wrong: tabs already hide the inactive component, so setVisible(false) on a non-attached BrowserComponent has no impact on what the browser composites. Restoring the previous behaviour so the next diagnostic pass has a clean baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The bottom nav's Java bounds look correct in every log (pos, size, visible, hidden all sane), yet users don't see it on the HTML5 deploy. Before guessing again, add a browser-side diagnostic: after the post-show revalidate, read window.innerWidth/innerHeight, visualViewport.width/height, documentElement.clientHeight, and devicePixelRatio from JS and log the result via the existing CN javascript context. Mobile Safari in particular reports innerHeight as the layout viewport (tall) while visualViewport.height reflects the area that is actually not occluded by the dynamic URL bar - CN1 sizes the canvas to innerHeight, so if visualViewport.height is materially smaller, the bottom ~100px of the canvas is drawn behind browser chrome and the bottom nav would never be visible no matter how correct the CN1 layout is. The log line lands next to the existing "Form:" and "bottomNav:" lines so the three can be compared against each other on the next deploy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The viewport log settled the browser-chrome question - window and visualViewport matched exactly, so the nav is not hidden behind iOS Safari's URL bar. Yet the nav is still not visible. Replace the viewport probe with a DOM probe that answers three questions directly from the browser: 1. Where is the CN1 canvas, really? Its rect + z-index tells us whether the canvas itself is full-height or clipped. 2. Where are all iframes, with z-index + visibility + display? Lists every iframe so we can see if any extends into the nav band, and whether any is display:none or visibility:hidden. 3. What element does elementFromPoint(x, navCenterY) return at the centre of each of the three expected nav cells (left, middle, right)? Returns the topmost paintable element with its bounding rect and z-index. If it's a CANVAS, the nav is drawn but the style is somehow invisible. If it's an IFRAME, a peer is covering it. If it's HTML/BODY, the canvas is clipped short. Converts navY from device pixels to CSS pixels via dpr so the JS coordinates match browser coordinate space. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous probe found cn1-peers-container on top at the nav coordinates, but CN1's HTML5 port marks the canvas with pointer-events:none (via codenameone-canvas setProperty in the TeaVM-compiled classes.js) so elementFromPoint just skips the canvas - that doesn't mean the nav is hidden, only that the canvas can't be hit-tested. Visually the canvas is still on top (z=auto, DOM-ordered AFTER peers-container which is z=-1000). To actually know whether the nav paints, read back the pixel at each of the three expected nav-cell X positions at the nav's Y on the canvas via getImageData. If the pixels are the expected nav UIID colour, CN1 is painting the nav and something else (still TBD) is hiding it; if they are white/transparent, CN1 never draws the nav band and the bug is on the Java side. Also widens the DOM probe to return computed bg/opacity/pointer- events for peers-container and canvas, plus the elementsFromPoint stack at the nav centre for a full view of the compositing order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Canvas pixel read proved CN1 paints the nav (navy rgba(16,43,102,255) at the expected Y). So the bug is compositing, not CN1 layout. Two oddities to pin down: 1. BODY is position:fixed with bg=rgb(255,255,255). If it outranks the canvas in stacking order, its opaque white background could paint over the canvas-drawn nav. 2. Some other fixed/absolute/sticky element may overlap the nav band. Walk every element, filter to positioned ones whose rect intersects [navTop, navBot], and dump id/class/z-index/bg/etc. Skip the canvas and the peers-container (already understood). Also paint a 8px red band directly on the canvas at navTop and a green band at navBot. If the user sees red+green but no nav, the canvas IS visible - the nav paint got overpainted later. If the user sees no red+green, the canvas as a whole is being occluded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI compliance check failed with 9 violations of the form:
bsh/cn1/gen/GeneratedAccess_com_codename1_util#invoke10(
Lcom/codename1/util/Simd;Ljava/lang/String;[Ljava/lang/Object;
) -> SIMD alloca value returned from method
com.codename1.util.Simd exposes allocaByte / allocaInt /
allocaFloat (+ *Zeroed / *Filled variants) that return method-local
SIMD scratch arrays. On ParparVM these lower to C-stack allocations;
CN1's compliance check forbids letting the array escape the method
that allocated it, and the auto-generated reflection bridge
inherently does exactly that by returning the value from invokeN.
Fix: add Simd to the existing INTERNAL_CN1_TYPES set so the
generator skips it the same way it already skips Accessor and
IOAccessor. Simd is a low-level SIMD primitives API that bean-shell
playground scripts are extremely unlikely to need, so the blast
radius is tiny. If a user does need it later we can generate a
hand-written bridge that respects the method-local constraint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The rebase brought in #4801 (CN1 version bump 7.0.234 -> 7.0.235), #4794 (Simd warnings fix), and #4798 (zoomFonts). Regenerate GeneratedCN1Access + helper classes so the bean-shell registry matches the new API surface: picks up UIManager.zoomFonts, additional components/ui/plaf members, and the cleaned-up util package (no more alloca* escapes from Simd, which is also excluded explicitly by the preceding commit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4bf26f9 to
9da1767
Compare
The JavaScript cloud build fails at runtime with com.codename1.ui.plaf.UIManager.zoomFonts(F)V was not found because zoomFonts was added in CN1 7.0.235 (#4798) but the JS port deployed to the cloud doesn't have it yet. The bean-shell access registry references it directly, which turns a missing method into a startup failure for every playground session. Add a qualified-name method blacklist to the generator and seed it with com.codename1.ui.plaf.UIManager.zoomFonts. Kept separate from the name-only blacklist so the exclusion is surgical (other classes that happen to define a zoomFonts method, if any appear later, are unaffected). Once the JS port ships zoomFonts this entry can come straight out. Regenerated registry reflects the exclusion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every new API that lands in the CN1 release channel but not yet in the JavaScript cloud build bricks the playground with NoSuchMethod at shell-eval time - first it was UIManager.zoomFonts, now isSimdOptimizationsEnabled, and more will keep appearing on every version bump. The one-off qualified-name blacklist doesn't scale. Fix: introduce <cn1.registry.version> in the pom (pinned to 7.0.234, one release behind cn1.version=7.0.235) and have the generator prefer it when downloading release source jars. Falls back to cn1.version if the registry version isn't set, so consumers outside this project aren't broken. Bump <cn1.registry.version> deliberately once the JS cloud build catches up, not implicitly with cn1.version. With the pin in place, UIManager.zoomFonts is not discovered at all, so the ad-hoc qualified-method blacklist can go. Kept the generator scaffolding empty-but-ready in case a port-specific regression needs a surgical workaround later. Regenerated registry reflects 7.0.234 API surface: zoomFonts, isSimdOptimizationsEnabled, and the rest of the post-7.0.234 API additions are absent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The website-docs workflow's "Validate internal links and images" step fails in CI with: Downloading from: https://github.com/lycheeverse/lychee/releases/ latest/download/lychee-x86_64-unknown-linux-gnu.tar.gz ##[error]Process completed with exit code 22. Root cause: lychee v0.24.0 renamed release assets from lychee-<arch>-unknown-linux-gnu.tar.gz to lychee-lychee-v0.24.0-<arch>-unknown-linux-gnu.tar.gz but lychee-action@v2 still constructs the old URL, so `lycheeVersion: latest` now resolves to a 404 and curl bails out with exit 22. Pin to v0.23.0, the last release with the stable asset naming. This is orthogonal to the Playground work - the actual Playground cloud JS build + smoke tests succeed in the same job; only the downstream link check trips on the upstream action bug. Bump back to `latest` once lychee-action@v2 catches up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User reports a thin bar visible at the nav position that clicks as the correct tab but has no icons or text. Canvas pixels at nav centre were confirmed opaque navy in the previous probe, and nothing else in the DOM overlaps the nav region. So the Button children of the nav are the missing piece - either zero-sized, or painting only their UIID background with no glyph rendering. Two diagnostics: 1. For each nav child, log its absolute position, size, preferred size, visibility, UIID, text, and icon dimensions. If any of these are zero the layout pipeline broke; if they look right, the paint pipeline broke. 2. Sample the canvas pixel at the CENTRE of each button, roughly where the Material icon glyph should be painted (offset by height/3 from top so we land on the icon, not the text). If all three pixels are the nav UIID colour, no glyph got painted at all. If they vary (lighter/darker shades, foreground-colour hits), glyphs are being painted and something else is hiding them. Removes the now-redundant DOM-overlapper scan and the red/green canvas markers - both answered their questions already. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause identified. The navBtnPixels probe came back as solid
navy UIID bg at every icon centre - no glyph pixels at all. The
navChild probe showed buttons with correct size/position/icon-dims
and real text. So the icons were baked in a colour that matches
the nav's background, making them invisible on top of it.
FontImage.setMaterialIcon bakes the glyph using whatever foreground
colour the component's style has at the instant of the call.
setUIID application can be deferred on the HTML5 port, so the bake
captures the default FG, which collides with the nav's navy bg.
PlaygroundTopBar.applyAppIconStyle already documents this pitfall
and works around it by calling getAllStyles().setFgColor(...) before
FontImage.setMaterialIcon. Mirror the same pattern for the bottom
nav:
- createBottomNavButton: setUIID -> force FG from a table that
mirrors the `color:` values in theme.css's
PlaygroundBottomNavItem{Dark,Active,ActiveDark} UIIDs -> bake.
- refreshBottomNav: on state flip, re-set UIID, re-force FG, and
re-bake the icon with the new colour. The icon is cached on
the Button as a baked image, so just changing the UIID wouldn't
update it.
- Stash the icon char on the Button via `navIcon` client property
so refreshBottomNav can rebake without the creation-time char
in scope.
Also remove the diagnostic probes (navChild/navBtnPixels logs, the
canvas pixel sampler) that were only useful for narrowing down the
root cause.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Icon centering
Set Button alignment to CENTER in createBottomNavButton (default
is LEFT, which leaves the glyph hugging the left padding on the
HTML5 port once the FG bake fix made it actually paint) and add
`text-align: center` to the four PlaygroundBottomNavItem* UIIDs
in theme.css so the same placement survives a UIID-level restyle.
Cleanup for merge
- Remove the `CN1Playground build marker: mobile-shell-v2` boot
log, the per-relayout `Playground layout:` log, and the comment
block that describes them.
- Remove installIframeBottomGuard() and MOBILE_NAV_GUARD_PX. It
was a speculative CSS max-height injection for iframe peers,
added before the real cause of the missing nav was known; the
actual fix (FG bake colour) is now in place and the guard adds
nothing.
- Rewrite the Form.SOUTH nav comment: the structural choice still
stands, but the "iframe covers the nav" rationale was wrong.
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.
No description provided.