Skip to content

improve(HTML): add full flex support#1672

Open
kokekanon wants to merge 16 commits intoopentibiabr:mainfrom
kokekanon:respaldobORRAME
Open

improve(HTML): add full flex support#1672
kokekanon wants to merge 16 commits intoopentibiabr:mainfrom
kokekanon:respaldobORRAME

Conversation

@kokekanon
Copy link
Copy Markdown
Contributor

@kokekanon kokekanon commented Mar 24, 2026

Warning

This was made with AI, I’m a noob in .cpp
I’m not a web designer

A) It doesn’t break anything in the OTUI design, nor any existing HTML module
B) It displays exactly the same as the browser

2026-03-24.17-23-31.mp4

example:

image

Flexbox Complete Support

The engine now implements a full flex layout flow, including container and item properties, wrapping, spacing, reverse directions, and nested flex containers.

Flex Container Properties

  • flex-direction: row, row-reverse, column, column-reverse
  • flex-wrap: nowrap, wrap, wrap-reverse
  • flex-flow: shorthand for flex-direction + flex-wrap
  • justify-content: flex-start, flex-end, center, space-between, space-around, space-evenly
  • align-items: stretch, flex-start, flex-end, center, baseline
  • align-content: stretch, flex-start, flex-end, center, space-between, space-around, space-evenly
  • gap, row-gap, column-gap: spacing between flex items/lines

Flex Item Properties

  • order: visual ordering of items
  • flex-grow: free-space growth factor
  • flex-shrink: shrink factor when space is missing
  • flex-basis: auto, content, <number>, <number>px, <number>%
  • flex: shorthand (auto, none, or grow shrink basis)
  • align-self: auto, stretch, flex-start, flex-end, center, baseline

Flex Behavior Notes

  • margin: auto (or side-specific margin-left/right/top/bottom: auto) is supported for flex items and absorbs free space on the main axis.
  • row-reverse, column-reverse, and wrap-reverse are supported.
  • Absolutely positioned children are excluded from flex flow, matching standard CSS expectations.
  • Nested flex containers are supported with an internal recursion guard for safety.
  • Border widths are considered during flex size distribution to keep wrapping and spacing consistent.

Summary by CodeRabbit

  • New Features

    • In-app Examples tab with preview pane, responsive demo tab, and tabbed interface for browsing examples.
  • Bug Fixes

    • More accurate, stable scrollbar and flex layout sizing; improved relayout behavior and text wrapping/font-size handling.
  • Documentation

    • Added 20+ self-contained Flexbox example pages covering direction, justify/align, wrap/gap, item props, patterns (navbars, cards, dashboards, chat, kanban, etc.).
  • Style

    • Updated demo UI/CSS for improved spacing, preview layout, and responsive preview behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 21 standalone inline-styled HTML Flexbox examples and implements a large flex-layout engine overhaul with related widget API changes, HTML batching, style parsing, text layout, color parsing, and game-module CSS/HTML sample updates.

Changes

Cohort / File(s) Summary
Flexbox Documentation Examples
docs/exampleHTML_flex/*
docs/exampleHTML_flex/01-flex-direction.html, .../02-justify-content.html, .../03-align-items.html, .../04-flex-wrap-gap.html, .../05-flex-item-props.html, .../06-align-content.html, .../07-flex-flow-shorthand.html, .../08-margin-auto.html, .../09-row-gap-column-gap.html, .../10-nested-flex.html, .../11-holy-grail-layout.html, .../12-navbar-patterns.html, .../13-card-grid-wrap.html, .../14-responsive-sidebar.html, .../15-equal-height-cards.html, .../16-center-both-axes.html, .../17-sticky-footer.html, .../18-kanban-advanced.html, .../19-dashboard-shell.html, .../20-chat-layout-flex.html, .../21-master-detail-flex.html
Added 21 static HTML examples (inline CSS + markup) demonstrating many Flexbox concepts and patterns.
Core Flex Layout Engine
src/framework/ui/uilayoutflexbox.cpp
Major rewrite of flex layout: depth-bounded re-entrance, per-depth descendant batching, intrinsic/fit-content handling, content-extent collection, auto-margin and free-space distribution, reverse placement changes, step‑7 remeasure path, and rect/offset propagation.
Widget API & Layout Glue
src/framework/ui/uiwidget.h, src/framework/ui/uiwidget.cpp
Widened SizeUnit to int32, added vertical margin-auto flags/getters/setters, added isInFlexLayout(), getChildrenRef(), flushPendingHtmlWidgets(), scheduleParentHtmlFlexLayout(), and setRect/updateLayout changes to avoid flex re-entrance and enable bulk descendant translation.
HTML Task Batching & Dimension Resolution
src/framework/ui/uiwidgethtml.cpp
Introduced throttled HTML flush (flushPendingHtmlWidgets with budgets), switched traversals to reference-based child access, made updateDimension/updateSize flex-aware, added fit-content recursion and guarded flex-measure behavior.
Style Parsing & Text Layout
src/framework/ui/uiwidgetbasestyle.cpp, src/framework/ui/uiwidgettext.cpp
Flex shorthand parsing defaults changed, border parsing more tolerant, margin-top/bottom accept auto and set vertical auto flags, font-size parsing refined, and text wrapping uses effective WrapOptions with whitespace/wrap defaults fixed.
Widget Layout Integration
src/framework/ui/uilayoutflexbox.cpp, src/framework/ui/uiwidget.cpp, src/framework/ui/uiwidgethtml.cpp
Introduced nested-flex depth tracking, per-depth queues for descendant resets, selective descendant reflows/nested layout runs, and many child traversals converted to getChildrenRef().
Color Parsing
src/framework/util/color.cpp
Hex color parser now expands short forms #RGB#RRGGBB and #RGBA#RRGGBBAA.
Game Module CSS / Example Host
modules/game_forge/game_forge.css, modules/game_htmlsample/htmlsample.css, modules/game_htmlsample/htmlsample.html, modules/game_htmlsample/htmlsample.lua
CSS: enforce explicit non-wrapping flex defaults, remove some fixed sizes, rename footer→appFooter, adjust gaps/margins. HTML/Lua: removed inline script, added tabbed UI, example discovery + preview rendering, responsive demo scaffold and controller state/methods.
Numeric API Changes
src/framework/ui/uiwidgethtml.cpp, src/framework/ui/uiwidgetbasestyle.cpp, src/framework/ui/uiwidget.h
Widened several dimension/parse signatures from int16_t → int32_t and updated related applyDimension signatures.
Small/Other
modules/game_forge/..., modules/game_htmlsample/...
Various layout and spacing tweaks across game modules and example host (gap/margin adjustments, image position changes, selector/ID updates).

Sequence Diagram(s)

sequenceDiagram
    participant Container as Flex Container
    participant Engine as layoutFlex()
    participant Measure as Content Measurement
    participant Distributor as Free-Space Distributor
    participant Items as Flex Items
    participant Nested as Nested Flex / Reflow

    Container->>Engine: layoutFlex(container)
    Engine->>Engine: set in-flex guard / enqueue depth
    Engine->>Measure: collect child bounds & compute content extent
    Measure->>Items: measure contentMainSize / fit-content where required
    Engine->>Distributor: resolve auto-margins, distribute positive/negative space
    Distributor->>Items: assign final main sizes
    Engine->>Nested: trigger nested layout/text reflow when cross sizes change
    Nested->>Items: re-measure cross sizes (step-7 remeasure)
    Engine->>Engine: compute line cross sizes, clamp/stretch, position items (handle reverse)
    Engine->>Container: update versions / flush queued descendant resets
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • majestyotbr

Poem

🐰 I hopped through rows and columns bright,
Margin-auto hugged the light,
Text reflowed when sizes grew,
Cards, navs, kanban — layouts new,
Flexland blooms — a bunny view!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "improve(HTML): add full flex support" clearly and concisely summarizes the main objective of the changeset: implementing full CSS Flexbox layout support for HTML widgets. It is specific, directly related to the core contribution, and follows a professional format.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/framework/ui/uilayoutflexbox.cpp (1)

465-474: ⚠️ Potential issue | 🟠 Major

Snapshot children before calling updateSize().

This loop walks the live child list and then calls child->updateSize(). If that cascades into sibling insert/remove paths, the iteration can dangle or skip items. Iterating over getChildren() here is much safer.

Suggested fix
-    for (const auto& childPtr : container.getChildrenRef()) {
+    const auto children = container.getChildren();
+    for (const auto& childPtr : children) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uilayoutflexbox.cpp` around lines 465 - 474, The loop
currently iterates the live list via container.getChildrenRef() and then calls
child->updateSize(), which can mutate siblings and invalidate the iterator;
change it to first snapshot the children (e.g., auto children =
container.getChildren() or otherwise copy the child pointers into a local
vector) and then iterate that snapshot, preserving the existing checks
(getDisplay, getPositionType) and calling child->updateSize() on the copied list
so mutations during updateSize() cannot dangle or skip items.
src/framework/ui/uiwidgetbasestyle.cpp (1)

214-225: ⚠️ Potential issue | 🟠 Major

Default omitted flex-basis to 0%, not 0px.

In CSS Flexbox, the shorthand flex: 1 expands to flex-grow: 1; flex-shrink: 1; flex-basis: 0% per the CSS specification. The current code defaults to 0px, which diverges when the container's main size is indefinite—now exercised by this PR's nested auto-sized flex containers.

Suggested fix
-        FlexBasis basis = { FlexBasis::Type::Px, 0.f };
+        FlexBasis basis = { FlexBasis::Type::Percent, 0.f };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uiwidgetbasestyle.cpp` around lines 214 - 225, The flex
shorthand default uses 0px currently; change it to 0% to match CSS (flex:1 ->
flex-basis:0%). Specifically, in the flex parsing block where you declare
FlexBasis basis = { FlexBasis::Type::Px, 0.f }; (and later call
parseFlexBasis(filtered[idx]) and widget->setFlexBasis(basis)), initialize basis
to a percent type with value 0 (e.g., FlexBasis::Type::Percent, 0.f) or ensure
the fallback when no token is present sets percent 0 rather than px 0, then
continue to call widget->setFlexBasis(basis).
🟡 Minor comments (18)
docs/exampleHTML_flex/14-responsive-sidebar.html-50-51 (1)

50-51: ⚠️ Potential issue | 🟡 Minor

Incomplete responsive implementation.

The comment at line 50 and description (lines 63-64) claim the sidebar "stacks above the main content on small screens using flex-direction change," but no CSS rule implements this behavior. The responsive stacking rule is missing.

If OTClient's UI system doesn't support media queries, consider updating the description to clarify this is a demonstration of the sidebar pattern rather than actual responsive behavior, or add a note about how responsiveness would be achieved in this system.

Also applies to: 63-64

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/14-responsive-sidebar.html` around lines 50 - 51, The
comment claims the sidebar stacks above main on small screens but no responsive
rule is present; either add a media-query-based rule that switches the layout
container (selecting .container) from row to column so .sidebar appears above
.main on narrow viewports, or, if OTClient UI doesn't support media queries,
update the explanatory text referring to the pattern (lines describing
responsive behavior) to state this is a non-responsive demonstration and explain
how responsiveness would be achieved (e.g., change .container flex-direction to
column via media query) instead of asserting it already happens.
docs/exampleHTML_flex/04-flex-wrap-gap.html-77-82 (1)

77-82: ⚠️ Potential issue | 🟡 Minor

Missing border-style in shorthand.

Same issue: border: 1px #888; needs a style value.

🔧 Proposed fix
   img {
     width: 28px;
     height: 28px;
     border-radius: 5px;
-    border: 1px `#888`;
+    border: 1px solid `#888`;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/04-flex-wrap-gap.html` around lines 77 - 82, The CSS
rule for the img selector uses a shorthand border without a style ("border: 1px
`#888`;"), which is invalid; update the img rule (selector "img") to include a
border-style (for example use "border: 1px solid `#888`;") so the border width,
style and color are all specified.
modules/game_forge/game_forge.css-399-407 (1)

399-407: ⚠️ Potential issue | 🟡 Minor

Remove unnecessary empty line before declaration.

Stylelint flagged an unexpected empty line before the display: flex declaration. This affects consistency with other selectors in this file.

🔧 Proposed fix
 .history-footer {
-
   display: flex;
   flex-direction: row;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_forge/game_forge.css` around lines 399 - 407, There is an extra
blank line before the first declaration in the .history-footer rule; remove the
empty line so the block starts immediately with "display: flex" to satisfy
stylelint and match the file's selector formatting (look for the .history-footer
CSS rule in the stylesheet and delete the lone blank line preceding its
declarations).
docs/exampleHTML_flex/04-flex-wrap-gap.html-31-37 (1)

31-37: ⚠️ Potential issue | 🟡 Minor

Missing border-style in shorthand.

Same issue: border: 2px #96a19b; needs a style value.

🔧 Proposed fix
   .container {
     display: flex;
     width: 420px;
-    border: 2px `#96a19b`;
+    border: 2px dashed `#96a19b`;
     padding: 10px;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/04-flex-wrap-gap.html` around lines 31 - 37, The CSS
.container rule uses a border shorthand missing the border-style (currently
`border: 2px `#96a19b`;`); update the .container declaration (in the selector
`.container`) to include a valid border-style (e.g., `solid`, `dashed`, etc.) so
the shorthand becomes `border: 2px solid `#96a19b`;` (or another chosen style) to
ensure the border renders correctly.
docs/exampleHTML_flex/04-flex-wrap-gap.html-22-29 (1)

22-29: ⚠️ Potential issue | 🟡 Minor

Add missing border-style to border shorthand declarations.

Lines 23, 34, and 81 specify border without a style value (e.g., solid). Without the style component, the border defaults to none and will not render visually. Change to border: 2px solid #2e7d32; (line 23), border: 2px solid #96a19b; (line 34), and border: 1px solid #888; (line 81).

🔧 Proposed fix
  .example {
-   border: 2px `#2e7d32`;
+   border: 2px solid `#2e7d32`;
    border-radius: 10px;
  .container {
    display: flex;
    width: 420px;
-   border: 2px `#96a19b`;
+   border: 2px solid `#96a19b`;
    padding: 10px;
  img {
    width: 28px;
    height: 28px;
    border-radius: 5px;
-   border: 1px `#888`;
+   border: 1px solid `#888`;
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/04-flex-wrap-gap.html` around lines 22 - 29, The border
shorthand declarations are missing the border-style so they render as none;
update the three CSS rules that set border to include a style: change the
.example rule to use "border: 2px solid `#2e7d32`;", update the second selector
that currently uses "border: 2px `#96a19b`;" to "border: 2px solid `#96a19b`;", and
update the third selector that currently uses "border: 1px `#888`;" to "border:
1px solid `#888`;".
docs/exampleHTML_flex/03-align-items.html-116-117 (1)

116-117: ⚠️ Potential issue | 🟡 Minor

Fix the intro text typo.

“Viewtical” should be “Vertical”.

📝 Suggested copy fix
-  <p>Viewtical alignment inside the flex container (row direction).</p>
+  <p>Vertical alignment inside the flex container (row direction).</p>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/03-align-items.html` around lines 116 - 117, Replace
the typo in the paragraph text inside the example HTML (the <p> element
following the <h1>Flexbox: align-items</h1>) by changing "Viewtical alignment
inside the flex container (row direction)." to "Vertical alignment inside the
flex container (row direction)."
docs/exampleHTML_flex/07-flex-flow-shorthand.html-142-143 (1)

142-143: ⚠️ Potential issue | 🟡 Minor

Clean up the example copy.

The title repeats “flex shorthand”, and the intro is missing the space in “combines flex-direction”, so the page reads unfinished.

📝 Suggested copy fix
-  <h1>flex-flow shorthand flex shorthand</h1>
-  <p>flex-flow combinesflex-direction + flex-wrap. flex combines grow + shrink + basis.</p>
+  <h1>Flexbox: flex-flow and flex shorthand</h1>
+  <p>flex-flow combines flex-direction + flex-wrap. flex combines flex-grow + flex-shrink + flex-basis.</p>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/07-flex-flow-shorthand.html` around lines 142 - 143,
Update the example copy to remove repetition and fix spacing: change the H1 text
from "flex-flow shorthand flex shorthand" to a concise title like "flex-flow
shorthand" (referencing the <h1> element) and correct the paragraph text in the
<p> element to read "flex-flow combines flex-direction + flex-wrap. flex
combines grow + shrink + basis." ensuring there is a space between "combines"
and "flex-direction" and the sentence reads clearly.
docs/exampleHTML_flex/13-card-grid-wrap.html-73-77 (1)

73-77: ⚠️ Potential issue | 🟡 Minor

Use valid CSS syntax in the browser-parity example.

text-wrap: true is not valid CSS; the property accepts keyword values only (wrap, nowrap, balance, pretty, stable). Similarly, style="height: 32; width: 32;" uses unitless lengths, which are invalid for size properties—they must include units like px. The same invalid inline sizing pattern repeats on the later cards.

♻️ Suggested cleanup
   .card-text {
     color: `#555`;
     flex: 1;
-    text-wrap: true;
   }
...
-        <img src="/data/images/clienticon.png" style="height: 32; width: 32;" />
+        <img src="/data/images/clienticon.png" style="height: 32px; width: 32px;" />

Also applies to: 107-107

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/13-card-grid-wrap.html` around lines 73 - 77, Update
the invalid CSS and inline sizing: in the .card-text rule replace the
nonstandard "text-wrap: true" with a valid keyword (e.g., "text-wrap: wrap" or
another appropriate keyword) and update all inline style attributes like
style="height: 32; width: 32;" on the card elements to include units (e.g.,
"height: 32px; width: 32px;") and fix the same unitless sizes repeated later so
all size properties use valid CSS units.
docs/exampleHTML_flex/10-nested-flex.html-168-168 (1)

168-168: ⚠️ Potential issue | 🟡 Minor

Fix typo in demo description text.

Line 168 has “Viewy common”; this should be “Very common”.

Suggested fix
-  <p>Flex inside flex. Viewy common in card and list layouts.</p>
+  <p>Flex inside flex. Very common in card and list layouts.</p>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/10-nested-flex.html` at line 168, Fix the typo in the
demo paragraph content: locate the <p> element that currently reads "Flex inside
flex. Viewy common in card and list layouts." and change "Viewy common" to "Very
common" so the text reads "Flex inside flex. Very common in card and list
layouts."
docs/exampleHTML_flex/08-margin-auto.html-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Add the missing <!DOCTYPE html> declaration.

The file currently starts with <html> on line 1, missing the required DOCTYPE declaration for valid HTML5 documents. Add <!DOCTYPE html> as the first line.

Suggested fix
+<!DOCTYPE html>
 <html>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/08-margin-auto.html` at line 1, The document starts
with the <html> tag but lacks the HTML5 DOCTYPE declaration; add the declaration
<!DOCTYPE html> as the very first line of the file so the browser treats the
document as valid HTML5 before the existing <html> element.
docs/exampleHTML_flex/12-navbar-patterns.html-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Add the DOCTYPE declaration at the top of the document.

The file starts with <html> but is missing the <!DOCTYPE html> declaration. HTML5 documents should begin with a DOCTYPE declaration for standards-compliant rendering and validation.

Suggested fix
+<!DOCTYPE html>
 <html>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/12-navbar-patterns.html` at line 1, Add the HTML5
DOCTYPE declaration by inserting <!DOCTYPE html> as the very first line before
the existing <html> tag so the document starts with the standard HTML5 doctype;
ensure the <!DOCTYPE html> appears above the <html> element in the file.
docs/exampleHTML_flex/01-flex-direction.html-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Add HTML doctype declaration.

The file is missing <!DOCTYPE html> at the beginning, which causes linting failures for the doctype-first rule. HTML documents should declare the doctype before the <html> tag.

Suggested fix
+<!DOCTYPE html>
 <html>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/01-flex-direction.html` at line 1, Add the HTML5
doctype declaration before the opening <html> tag to satisfy the doctype-first
rule; update the file so the very first line is the doctype declaration
(<!DOCTYPE html>) followed by the existing <html> element.
docs/exampleHTML_flex/18-kanban-advanced.html-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Add the missing HTML doctype.

Line 1 starts with <html> without <!DOCTYPE html>, which is required for proper HTML document validation.

Suggested fix
+<!DOCTYPE html>
 <html>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/18-kanban-advanced.html` at line 1, Add the HTML5
doctype declaration before the opening <html> tag: insert <!DOCTYPE html>
immediately above the existing <html> element so the document begins with the
standard doctype declaration (<!DOCTYPE html>) followed by the existing <html>
tag.
docs/exampleHTML_flex/10-nested-flex.html-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Add <!DOCTYPE html> before <html>.

The file is missing the DOCTYPE declaration at the beginning. HTML files should declare the document type to ensure proper parsing.

Suggested fix
+<!DOCTYPE html>
 <html>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/10-nested-flex.html` at line 1, Add the HTML5 doctype
declaration by inserting <!DOCTYPE html> immediately before the existing <html>
tag so the document begins with the proper DOCTYPE declaration to ensure correct
parsing by browsers; update the top of the file where the <html> element is
defined.
docs/exampleHTML_flex/20-chat-layout-flex.html-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Add <!DOCTYPE html> at the top of the file.

The file is missing the DOCTYPE declaration. The doctype-first rule is enabled in .htmlhintrc and requires this declaration to be the first line of the HTML file.

Suggested fix
+<!DOCTYPE html>
 <html>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/20-chat-layout-flex.html` at line 1, Add the HTML5
doctype declaration as the very first line by inserting <!DOCTYPE html>
immediately before the existing <html> tag so the page satisfies the
`doctype-first` rule and the document starts with a proper doctype.
docs/exampleHTML_flex/11-holy-grail-layout.html-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Add <!DOCTYPE html> before <html>.

The file starts directly with <html>, which violates the doctype-first linting rule. Note: this issue affects all files in the docs/exampleHTML_flex/ directory and may indicate these OTML template files should be excluded from HTML linting validation.

Suggested fix
+<!DOCTYPE html>
 <html>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/11-holy-grail-layout.html` at line 1, Add the HTML
doctype declaration before the opening <html> tag in the affected OTML template
files (those starting with the <html> tag) so the document begins with <!DOCTYPE
html>; update each template in the exampleHTML_flex set to prepend the doctype
or exclude these OTML templates from HTML linting if they should not be
validated, ensuring the opening <html> element remains unchanged.
modules/game_htmlsample/htmlsample.html-49-50 (1)

49-50: ⚠️ Potential issue | 🟡 Minor

Use vertical-align: middle instead of center.

vertical-align: center is not valid CSS for table cells. The correct keyword is middle. Using an invalid value means browsers ignore it, making this sample unreliable as a reference.

Suggested fix
-              <td style="text-align: center; vertical-align: center">
+              <td style="text-align: center; vertical-align: middle">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_htmlsample/htmlsample.html` around lines 49 - 50, The table cell
uses an invalid CSS value "vertical-align: center"; update the td style on the
<td> element (the cell rendering {{index}}) to use "vertical-align: middle"
instead of "center" so browsers apply vertical centering correctly.
docs/exampleHTML_flex/05-flex-item-props.html-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Add <!DOCTYPE html> before this document.

While omitting the doctype does trigger quirks mode in modern browsers, flexbox is specifically exempted from the percentage sizing quirks, so this demo will still behave consistently as a flex reference. However, including the doctype is a best practice to ensure standards mode for all HTML documents.

Suggested fix
+<!DOCTYPE html>
 <html>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/05-flex-item-props.html` at line 1, Add the HTML5
doctype declaration before the document root so the file that currently starts
with the "<html>" element is preceded by "<!DOCTYPE html>"; update the top of
the document (the part containing the "<html>" tag) to begin with the doctype to
ensure standards mode.
🧹 Nitpick comments (4)
docs/exampleHTML_flex/02-justify-content.html (1)

12-14: Duplicate display declaration.

display: block !important; appears twice (lines 12 and 14). The second declaration overrides the first, making the first one redundant.

🧹 Proposed fix
     display: block !important;
     --image-source: none;
-    display: block !important;
     width: 100% !important;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/02-justify-content.html` around lines 12 - 14, Remove
the duplicate CSS declaration for display: block !important; in the ruleset
shown (the repeated "display" property appears twice); keep a single display:
block !important; and delete the redundant occurrence so the stylesheet contains
only one display declaration for that selector.
docs/exampleHTML_flex/15-equal-height-cards.html (1)

12-14: Duplicate display declaration.

Same pattern as other example files - display: block !important; appears twice.

🧹 Proposed fix
     display: block !important;
     --image-source: none;
-    display: block !important;
     width: 100% !important;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/15-equal-height-cards.html` around lines 12 - 14,
Remove the duplicate CSS declaration by keeping a single "display: block
!important;" in the rule that also defines "--image-source: none" (the block
containing the two identical display lines); edit the CSS so only one display
declaration remains to avoid redundancy and potential confusion.
docs/exampleHTML_flex/14-responsive-sidebar.html (1)

12-14: Duplicate display declaration.

display: block !important; appears on both lines 12 and 14.

🧹 Proposed fix
     display: block !important;
     --image-source: none;
-    display: block !important;
     width: 100% !important;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/14-responsive-sidebar.html` around lines 12 - 14,
Remove the duplicated CSS declaration "display: block !important;" so it only
appears once in the rule that also contains "--image-source: none;"; locate the
rule containing these declarations (the block with "--image-source: none;") and
delete the redundant "display: block !important;" occurrence, leaving a single
display declaration to avoid repetition.
docs/exampleHTML_flex/17-sticky-footer.html (1)

11-13: Duplicate display declaration.

display: block !important; appears on both lines 11 and 13.

🧹 Proposed fix
     display: block !important;
     --image-source: none;
-    display: block !important;
     width: 99% !important;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/17-sticky-footer.html` around lines 11 - 13, Remove the
duplicated CSS declaration so only a single "display: block !important;" remains
in the same rule block (leave the "--image-source: none;" line intact); locate
the rule containing the repeated "display: block !important;" entry and delete
the redundant occurrence to avoid duplicate properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/exampleHTML_flex/20-chat-layout-flex.html`:
- Around line 191-193: The .input CSS rule sets both background-color and color
to `#f1f5f9`, making text unreadable; update the .input selector to use a
contrasting text color (e.g., change color to a dark value like `#0f172a` or
`#111827`) or change the background to a darker color so foreground and background
meet accessible contrast—modify the .input rule in the stylesheet (the `.input`
class) to ensure readable contrast between background-color and color.

In `@modules/corelib/ui/uihtml.lua`:
- Around line 77-87: computeContentExtent currently measures span as
bounds.right - bounds.left which loses the offset from the scroll origin; change
the measurement to compute width as the distance from the padding/scroll origin
to the far edge (e.g. bounds.right - paddingRect.left) and height as
bounds.bottom - paddingRect.top, then clamp each with paddingRect.width/height
(use math.max(bounds.right - paddingRect.left, paddingRect.width) and
math.max(bounds.bottom - paddingRect.top, paddingRect.height)). Update the
computeContentExtent function (references: computeContentExtent, paddingRect,
bounds, collectContentBounds) so scrollWidth/scrollHeight reflect off-screen
content positioned away from the origin.
- Around line 53-55: The loop over widget:getChildren() currently only checks
child:isDestroyed() and child:isExplicitlyVisible(), which lets descendants with
CSS display:none contribute to bounds; update the guard to also skip children
whose computed display is "none" (the equivalent of DisplayType::None used in
C++ draw path) — e.g. query the child's display via its API (for example
child:getDisplayType() or child:getComputedStyle():display) and continue when
it's "none", so child:getMarginRect() is only used for visible, non-display-none
descendants.

In `@src/framework/ui/uilayoutflexbox.cpp`:
- Around line 1050-1055: The current clamping uses >= 0 for max-size checks
causing unset/default max values to act as hard clamps; change the checks that
use container.getMaxWidth(), container.getMaxHeight(), and any other max*
comparisons from ">= 0" to "> 0" so the max is treated as present only when
positive, leaving min-size checks as-is and ensuring desired remains clamped
only when an explicit positive max is provided (apply the same fix to the
analogous blocks around the container.getMaxWidth()/getMaxHeight() uses noted at
the other occurrences).

In `@src/framework/ui/uiwidget.cpp`:
- Around line 1155-1159: The current guard around calling updateLayout() skips
the child's own layout whenever m_parent->m_inFlexLayout is true; instead always
perform the child's local layout but suppress only the parent-relayout path.
Change the call site so it always invokes the child's layout logic (remove the
conditional skip), and modify updateLayout() (or add a new helper like
updateLocalLayout()) to accept a flag or check that prevents running the
parent/anchor relayout when m_parent && m_parent->m_inFlexLayout; reference
updateLayout(), m_parent and m_inFlexLayout to locate the changes.

In `@src/framework/ui/uiwidgetbasestyle.cpp`:
- Around line 425-429: The parser currently skips style keywords like
"none"/"hidden" in the block that checks `lower == "solid" || ...` but later
still requires `hasBorderWidth` and `hasBorderColor`, causing an exception
instead of treating `border: none`/`border: hidden` as a valid reset. Before the
detailed parsing of border tokens, detect the single-token case (token count ==
1) where the lowercased token is "none" or "hidden" and handle it as a reset:
clear/zero the border state (e.g. set width to 0 or an explicit "no border"
sentinel, set color to transparent/clear, and set `hasBorderWidth` and
`hasBorderColor` appropriately to avoid the later exception) and then
continue/return; this change should be applied around the block that currently
checks `lower == "solid" || ...` and the later validation that throws when
`!hasBorderWidth && !hasBorderColor`.

---

Outside diff comments:
In `@src/framework/ui/uilayoutflexbox.cpp`:
- Around line 465-474: The loop currently iterates the live list via
container.getChildrenRef() and then calls child->updateSize(), which can mutate
siblings and invalidate the iterator; change it to first snapshot the children
(e.g., auto children = container.getChildren() or otherwise copy the child
pointers into a local vector) and then iterate that snapshot, preserving the
existing checks (getDisplay, getPositionType) and calling child->updateSize() on
the copied list so mutations during updateSize() cannot dangle or skip items.

In `@src/framework/ui/uiwidgetbasestyle.cpp`:
- Around line 214-225: The flex shorthand default uses 0px currently; change it
to 0% to match CSS (flex:1 -> flex-basis:0%). Specifically, in the flex parsing
block where you declare FlexBasis basis = { FlexBasis::Type::Px, 0.f }; (and
later call parseFlexBasis(filtered[idx]) and widget->setFlexBasis(basis)),
initialize basis to a percent type with value 0 (e.g., FlexBasis::Type::Percent,
0.f) or ensure the fallback when no token is present sets percent 0 rather than
px 0, then continue to call widget->setFlexBasis(basis).

---

Minor comments:
In `@docs/exampleHTML_flex/01-flex-direction.html`:
- Line 1: Add the HTML5 doctype declaration before the opening <html> tag to
satisfy the doctype-first rule; update the file so the very first line is the
doctype declaration (<!DOCTYPE html>) followed by the existing <html> element.

In `@docs/exampleHTML_flex/03-align-items.html`:
- Around line 116-117: Replace the typo in the paragraph text inside the example
HTML (the <p> element following the <h1>Flexbox: align-items</h1>) by changing
"Viewtical alignment inside the flex container (row direction)." to "Vertical
alignment inside the flex container (row direction)."

In `@docs/exampleHTML_flex/04-flex-wrap-gap.html`:
- Around line 77-82: The CSS rule for the img selector uses a shorthand border
without a style ("border: 1px `#888`;"), which is invalid; update the img rule
(selector "img") to include a border-style (for example use "border: 1px solid
`#888`;") so the border width, style and color are all specified.
- Around line 31-37: The CSS .container rule uses a border shorthand missing the
border-style (currently `border: 2px `#96a19b`;`); update the .container
declaration (in the selector `.container`) to include a valid border-style
(e.g., `solid`, `dashed`, etc.) so the shorthand becomes `border: 2px solid
`#96a19b`;` (or another chosen style) to ensure the border renders correctly.
- Around line 22-29: The border shorthand declarations are missing the
border-style so they render as none; update the three CSS rules that set border
to include a style: change the .example rule to use "border: 2px solid
`#2e7d32`;", update the second selector that currently uses "border: 2px `#96a19b`;"
to "border: 2px solid `#96a19b`;", and update the third selector that currently
uses "border: 1px `#888`;" to "border: 1px solid `#888`;".

In `@docs/exampleHTML_flex/05-flex-item-props.html`:
- Line 1: Add the HTML5 doctype declaration before the document root so the file
that currently starts with the "<html>" element is preceded by "<!DOCTYPE
html>"; update the top of the document (the part containing the "<html>" tag) to
begin with the doctype to ensure standards mode.

In `@docs/exampleHTML_flex/07-flex-flow-shorthand.html`:
- Around line 142-143: Update the example copy to remove repetition and fix
spacing: change the H1 text from "flex-flow shorthand flex shorthand" to a
concise title like "flex-flow shorthand" (referencing the <h1> element) and
correct the paragraph text in the <p> element to read "flex-flow combines
flex-direction + flex-wrap. flex combines grow + shrink + basis." ensuring there
is a space between "combines" and "flex-direction" and the sentence reads
clearly.

In `@docs/exampleHTML_flex/08-margin-auto.html`:
- Line 1: The document starts with the <html> tag but lacks the HTML5 DOCTYPE
declaration; add the declaration <!DOCTYPE html> as the very first line of the
file so the browser treats the document as valid HTML5 before the existing
<html> element.

In `@docs/exampleHTML_flex/10-nested-flex.html`:
- Line 168: Fix the typo in the demo paragraph content: locate the <p> element
that currently reads "Flex inside flex. Viewy common in card and list layouts."
and change "Viewy common" to "Very common" so the text reads "Flex inside flex.
Very common in card and list layouts."
- Line 1: Add the HTML5 doctype declaration by inserting <!DOCTYPE html>
immediately before the existing <html> tag so the document begins with the
proper DOCTYPE declaration to ensure correct parsing by browsers; update the top
of the file where the <html> element is defined.

In `@docs/exampleHTML_flex/11-holy-grail-layout.html`:
- Line 1: Add the HTML doctype declaration before the opening <html> tag in the
affected OTML template files (those starting with the <html> tag) so the
document begins with <!DOCTYPE html>; update each template in the
exampleHTML_flex set to prepend the doctype or exclude these OTML templates from
HTML linting if they should not be validated, ensuring the opening <html>
element remains unchanged.

In `@docs/exampleHTML_flex/12-navbar-patterns.html`:
- Line 1: Add the HTML5 DOCTYPE declaration by inserting <!DOCTYPE html> as the
very first line before the existing <html> tag so the document starts with the
standard HTML5 doctype; ensure the <!DOCTYPE html> appears above the <html>
element in the file.

In `@docs/exampleHTML_flex/13-card-grid-wrap.html`:
- Around line 73-77: Update the invalid CSS and inline sizing: in the .card-text
rule replace the nonstandard "text-wrap: true" with a valid keyword (e.g.,
"text-wrap: wrap" or another appropriate keyword) and update all inline style
attributes like style="height: 32; width: 32;" on the card elements to include
units (e.g., "height: 32px; width: 32px;") and fix the same unitless sizes
repeated later so all size properties use valid CSS units.

In `@docs/exampleHTML_flex/14-responsive-sidebar.html`:
- Around line 50-51: The comment claims the sidebar stacks above main on small
screens but no responsive rule is present; either add a media-query-based rule
that switches the layout container (selecting .container) from row to column so
.sidebar appears above .main on narrow viewports, or, if OTClient UI doesn't
support media queries, update the explanatory text referring to the pattern
(lines describing responsive behavior) to state this is a non-responsive
demonstration and explain how responsiveness would be achieved (e.g., change
.container flex-direction to column via media query) instead of asserting it
already happens.

In `@docs/exampleHTML_flex/18-kanban-advanced.html`:
- Line 1: Add the HTML5 doctype declaration before the opening <html> tag:
insert <!DOCTYPE html> immediately above the existing <html> element so the
document begins with the standard doctype declaration (<!DOCTYPE html>) followed
by the existing <html> tag.

In `@docs/exampleHTML_flex/20-chat-layout-flex.html`:
- Line 1: Add the HTML5 doctype declaration as the very first line by inserting
<!DOCTYPE html> immediately before the existing <html> tag so the page satisfies
the `doctype-first` rule and the document starts with a proper doctype.

In `@modules/game_forge/game_forge.css`:
- Around line 399-407: There is an extra blank line before the first declaration
in the .history-footer rule; remove the empty line so the block starts
immediately with "display: flex" to satisfy stylelint and match the file's
selector formatting (look for the .history-footer CSS rule in the stylesheet and
delete the lone blank line preceding its declarations).

In `@modules/game_htmlsample/htmlsample.html`:
- Around line 49-50: The table cell uses an invalid CSS value "vertical-align:
center"; update the td style on the <td> element (the cell rendering {{index}})
to use "vertical-align: middle" instead of "center" so browsers apply vertical
centering correctly.

---

Nitpick comments:
In `@docs/exampleHTML_flex/02-justify-content.html`:
- Around line 12-14: Remove the duplicate CSS declaration for display: block
!important; in the ruleset shown (the repeated "display" property appears
twice); keep a single display: block !important; and delete the redundant
occurrence so the stylesheet contains only one display declaration for that
selector.

In `@docs/exampleHTML_flex/14-responsive-sidebar.html`:
- Around line 12-14: Remove the duplicated CSS declaration "display: block
!important;" so it only appears once in the rule that also contains
"--image-source: none;"; locate the rule containing these declarations (the
block with "--image-source: none;") and delete the redundant "display: block
!important;" occurrence, leaving a single display declaration to avoid
repetition.

In `@docs/exampleHTML_flex/15-equal-height-cards.html`:
- Around line 12-14: Remove the duplicate CSS declaration by keeping a single
"display: block !important;" in the rule that also defines "--image-source:
none" (the block containing the two identical display lines); edit the CSS so
only one display declaration remains to avoid redundancy and potential
confusion.

In `@docs/exampleHTML_flex/17-sticky-footer.html`:
- Around line 11-13: Remove the duplicated CSS declaration so only a single
"display: block !important;" remains in the same rule block (leave the
"--image-source: none;" line intact); locate the rule containing the repeated
"display: block !important;" entry and delete the redundant occurrence to avoid
duplicate properties.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 709d6815-67cb-4ba6-ad42-78ce914082af

📥 Commits

Reviewing files that changed from the base of the PR and between 294896c and 236efb5.

📒 Files selected for processing (33)
  • docs/exampleHTML_flex/01-flex-direction.html
  • docs/exampleHTML_flex/02-justify-content.html
  • docs/exampleHTML_flex/03-align-items.html
  • docs/exampleHTML_flex/04-flex-wrap-gap.html
  • docs/exampleHTML_flex/05-flex-item-props.html
  • docs/exampleHTML_flex/06-align-content.html
  • docs/exampleHTML_flex/07-flex-flow-shorthand.html
  • docs/exampleHTML_flex/08-margin-auto.html
  • docs/exampleHTML_flex/09-row-gap-column-gap.html
  • docs/exampleHTML_flex/10-nested-flex.html
  • docs/exampleHTML_flex/11-holy-grail-layout.html
  • docs/exampleHTML_flex/12-navbar-patterns.html
  • docs/exampleHTML_flex/13-card-grid-wrap.html
  • docs/exampleHTML_flex/14-responsive-sidebar.html
  • docs/exampleHTML_flex/15-equal-height-cards.html
  • docs/exampleHTML_flex/16-center-both-axes.html
  • docs/exampleHTML_flex/17-sticky-footer.html
  • docs/exampleHTML_flex/18-kanban-advanced.html
  • docs/exampleHTML_flex/19-dashboard-shell.html
  • docs/exampleHTML_flex/20-chat-layout-flex.html
  • docs/exampleHTML_flex/21-master-detail-flex.html
  • modules/corelib/ui/uihtml.lua
  • modules/game_forge/game_forge.css
  • modules/game_htmlsample/htmlsample.css
  • modules/game_htmlsample/htmlsample.html
  • modules/game_htmlsample/htmlsample.lua
  • src/framework/ui/uilayoutflexbox.cpp
  • src/framework/ui/uiwidget.cpp
  • src/framework/ui/uiwidget.h
  • src/framework/ui/uiwidgetbasestyle.cpp
  • src/framework/ui/uiwidgethtml.cpp
  • src/framework/ui/uiwidgettext.cpp
  • src/framework/util/color.cpp

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/framework/ui/uiwidgetbasestyle.cpp (1)

716-727: Minor inconsistency with left/right margin handling.

Lines 716-717 unconditionally call setMarginTop(top) and setMarginBottom(bottom), then lines 719-727 override for auto cases. This differs from the left/right handling (lines 729-741) which conditionally calls the setter only in the non-auto branch.

Both approaches work correctly, but the top/bottom path does redundant work when auto is specified. Consider aligning with the left/right pattern for consistency.

Align with left/right pattern
-            setMarginTop(top);
-            setMarginBottom(bottom);
-
             if (topAuto) {
                 m_margin.top = top;
                 setMarginTopAuto(true);
+            } else {
+                setMarginTop(top);
             }

             if (bottomAuto) {
                 m_margin.bottom = bottom;
                 setMarginBottomAuto(true);
+            } else {
+                setMarginBottom(bottom);
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uiwidgetbasestyle.cpp` around lines 716 - 727, The
top/bottom margin code unconditionally calls setMarginTop(top) and
setMarginBottom(bottom) before handling auto, causing redundant work and
inconsistent style with the left/right branch; update the logic in the block
that currently uses setMarginTop/setMarginBottom and the auto checks so that you
only call setMarginTop(top) when topAuto is false and only call
setMarginBottom(bottom) when bottomAuto is false, and keep the auto branch
behavior that sets m_margin.top/m_margin.bottom and calls
setMarginTopAuto(true)/setMarginBottomAuto(true) when the corresponding auto
flag is true, mirroring the left/right pattern.
src/framework/ui/uiwidget.cpp (1)

1156-1176: Confusing variable name: movedOnly doesn't mean "only moved".

The variable movedOnly at line 1156 is actually positionChanged — it's true whenever the position changed, regardless of whether the size also changed. The current name implies exclusivity ("moved AND nothing else changed") but that's not what's computed.

This causes confusion in the condition at line 1176 where movedOnly gates entry, but line 1177 then checks sizeChanged separately. The code works correctly, but the naming is misleading.

Suggested rename for clarity
-    const bool movedOnly = oldRect.topLeft() != clampedRect.topLeft();
+    const bool positionChanged = oldRect.topLeft() != clampedRect.topLeft();
     const bool sizeChanged = oldRect.size() != clampedRect.size();
     ...
-        movedOnly) {
+        positionChanged) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uiwidget.cpp` around lines 1156 - 1176, Rename the
misleading boolean movedOnly to positionChanged and update all references in
uiwidget.cpp (where movedOnly is computed from oldRect.topLeft() !=
clampedRect.topLeft() and used in the flex-entry if) so the name accurately
reflects "position changed" semantics; ensure the declaration and the condition
that currently reads movedOnly (near the if that also checks sizeChanged,
isOnHtml(), m_inFlexLayout, m_parent, and
m_displayType/DisplayType::Flex|InlineFlex) are updated to positionChanged to
improve readability without changing logic, and run a quick compile/search to
update any other uses (e.g., comments, tests) that mention movedOnly.
src/framework/ui/uilayoutflexbox.cpp (1)

157-176: Performance consideration: recursive version reset on all descendants.

resetDescendantVersions recursively traverses all descendants and resets their size versions, forcing recalculation on the next layout pass. While correct, this is called multiple times during a flex pass (lines 534, 857, 1127), potentially causing redundant traversals for deeply nested trees.

For heavily nested layouts, consider batching or deferring these resets to reduce traversal overhead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uilayoutflexbox.cpp` around lines 157 - 176, The recursive
resetDescendantVersions(UIWidget*) causes repeated full-tree traversals; change
to a batched/deferred approach by (1) adding a marker or queue (e.g., a
global/scope vector or a bool flag like pendingVersionReset on UIWidget) to
collect root widgets that need descendant resets instead of calling
resetDescendantVersions immediately from the flex pass callers, (2) modify the
places that currently call resetDescendantVersions (the flex-pass call sites) to
instead mark or enqueue the root widget, and (3) run a single traversal pass at
the end of the flex layout phase that walks each queued root and performs the
same logic currently in resetDescendantVersions (using getChildrenRef(),
getWidthHtml(), getHeightHtml(), isDestroyed(), getDisplay(), Unit checks) and
clears the marker/queue; this avoids redundant traversals for deeply nested
trees while preserving the exact version/reset semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/framework/ui/uilayoutflexbox.cpp`:
- Around line 157-176: The recursive resetDescendantVersions(UIWidget*) causes
repeated full-tree traversals; change to a batched/deferred approach by (1)
adding a marker or queue (e.g., a global/scope vector or a bool flag like
pendingVersionReset on UIWidget) to collect root widgets that need descendant
resets instead of calling resetDescendantVersions immediately from the flex pass
callers, (2) modify the places that currently call resetDescendantVersions (the
flex-pass call sites) to instead mark or enqueue the root widget, and (3) run a
single traversal pass at the end of the flex layout phase that walks each queued
root and performs the same logic currently in resetDescendantVersions (using
getChildrenRef(), getWidthHtml(), getHeightHtml(), isDestroyed(), getDisplay(),
Unit checks) and clears the marker/queue; this avoids redundant traversals for
deeply nested trees while preserving the exact version/reset semantics.

In `@src/framework/ui/uiwidget.cpp`:
- Around line 1156-1176: Rename the misleading boolean movedOnly to
positionChanged and update all references in uiwidget.cpp (where movedOnly is
computed from oldRect.topLeft() != clampedRect.topLeft() and used in the
flex-entry if) so the name accurately reflects "position changed" semantics;
ensure the declaration and the condition that currently reads movedOnly (near
the if that also checks sizeChanged, isOnHtml(), m_inFlexLayout, m_parent, and
m_displayType/DisplayType::Flex|InlineFlex) are updated to positionChanged to
improve readability without changing logic, and run a quick compile/search to
update any other uses (e.g., comments, tests) that mention movedOnly.

In `@src/framework/ui/uiwidgetbasestyle.cpp`:
- Around line 716-727: The top/bottom margin code unconditionally calls
setMarginTop(top) and setMarginBottom(bottom) before handling auto, causing
redundant work and inconsistent style with the left/right branch; update the
logic in the block that currently uses setMarginTop/setMarginBottom and the auto
checks so that you only call setMarginTop(top) when topAuto is false and only
call setMarginBottom(bottom) when bottomAuto is false, and keep the auto branch
behavior that sets m_margin.top/m_margin.bottom and calls
setMarginTopAuto(true)/setMarginBottomAuto(true) when the corresponding auto
flag is true, mirroring the left/right pattern.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1e09397a-dcd5-4322-9e5f-02350043c114

📥 Commits

Reviewing files that changed from the base of the PR and between 236efb5 and 384928f.

📒 Files selected for processing (4)
  • modules/corelib/ui/uihtml.lua
  • src/framework/ui/uilayoutflexbox.cpp
  • src/framework/ui/uiwidget.cpp
  • src/framework/ui/uiwidgetbasestyle.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • modules/corelib/ui/uihtml.lua

Revert "test"

This reverts commit f5a42a4a3dc443fac50a19b1c5eba75871134098.

asd
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/framework/ui/uilayoutflexbox.cpp (2)

963-968: Manual save/restore of pendingUpdate could use RAII.

Lines 963-968 manually save and restore crossUnit.pendingUpdate, but this isn't exception-safe. If layoutFlex(*item.widget) throws, pendingUpdate won't be restored.

Consider using a small RAII helper for consistency with SizeUnitGuard:

♻️ Suggested fix
                 if (widthChanged || item.widget->getHeight() <= 0) {
-                    const bool origPending = crossUnit.pendingUpdate;
-                    crossUnit.pendingUpdate = false;
-                    layoutFlex(*item.widget);
-                    crossUnit.pendingUpdate = origPending;
+                    struct PendingGuard {
+                        bool& ref;
+                        bool orig;
+                        PendingGuard(bool& r) : ref(r), orig(r) { ref = false; }
+                        ~PendingGuard() { ref = orig; }
+                    } guard(crossUnit.pendingUpdate);
+                    layoutFlex(*item.widget);
                 }

Alternatively, rely on the assumption that layoutFlex does not throw.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uilayoutflexbox.cpp` around lines 963 - 968, The code
manually saves and restores crossUnit.pendingUpdate around
layoutFlex(*item.widget), which is not exception-safe; create and use a small
RAII helper (similar to SizeUnitGuard) that takes a reference to
crossUnit.pendingUpdate, stores the original value in its constructor, sets
pendingUpdate to false, and restores the original value in its destructor, then
replace the manual save/restore around layoutFlex in the block that currently
references crossUnit.pendingUpdate so the flag is always restored even if
layoutFlex throws.

587-594: State modification persists after restoring unit.

After the fit-content measurement, preferredCrossUnit.unit is restored, but pendingUpdate and version remain modified (true and 0). This intentionally triggers subsequent re-measurement, but could cause unexpected behavior if other code paths assume these fields are synchronized with unit.

The implementation appears intentional based on the comment at line 626-627 ("leave pendingUpdate=true and version=0 so subsequent passes pick up the measured size"), but ensure this doesn't conflict with caching logic elsewhere.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uilayoutflexbox.cpp` around lines 587 - 594,
preferredCrossUnit.pendingUpdate and preferredCrossUnit.version are being left
modified after temporarily switching preferredCrossUnit.unit to Unit::FitContent
for measurement, which can desync state; save the original pendingUpdate and
version values before setting pendingUpdate=true and version=0, call
child->updateSize(), then restore the original pendingUpdate and version after
restoring preferredCrossUnit.unit (while still keeping the temporary unit change
around the update call), i.e., capture originalPendingUpdate and
originalVersion, set pendingUpdate=true/version=0, call child->updateSize(),
restore preferredCrossUnit.unit, then restore pendingUpdate and version to the
saved originals to keep state consistent with unit.
src/framework/ui/uiwidgetbasestyle.cpp (2)

707-728: Redundant isAutoKeyword calls.

Lines 711-712 call isAutoKeyword(topStr) and isAutoKeyword(bottomStr) again despite already having topAuto and bottomAuto stored on lines 707 and 709.

♻️ Suggested fix
             const bool topAuto = isAutoKeyword(topStr);
             const bool rightAuto = isAutoKeyword(rightStr);
             const bool bottomAuto = isAutoKeyword(bottomStr);
             const bool leftAuto = isAutoKeyword(leftStr);
-            const int top = isAutoKeyword(topStr) ? 0 : stdext::to_number(topStr);
-            const int bottom = isAutoKeyword(bottomStr) ? 0 : stdext::to_number(bottomStr);
+            const int top = topAuto ? 0 : stdext::to_number(topStr);
+            const int bottom = bottomAuto ? 0 : stdext::to_number(bottomStr);
             const int right = rightAuto ? 0 : stdext::to_number(rightStr);
             const int left = leftAuto ? 0 : stdext::to_number(leftStr);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uiwidgetbasestyle.cpp` around lines 707 - 728, The code
redundantly calls isAutoKeyword(topStr) and isAutoKeyword(bottomStr) when
topAuto and bottomAuto are already computed; update the computation of top and
bottom to use the stored booleans (topAuto and bottomAuto) instead of calling
isAutoKeyword again (keep using rightAuto/leftAuto as-is), so replace the
expressions that compute top and bottom with ternaries that reference topAuto
and bottomAuto respectively while leaving the later uses of m_margin,
setMarginTopAuto/setMarginTop and setMarginBottomAuto/setMarginBottom unchanged.

642-650: Minor inconsistency in margin assignment pattern.

When auto is detected, the code directly assigns m_margin.top = 0 (or m_margin.bottom = 0), but when non-auto, it calls setMarginTop() / setMarginBottom(). If the setter methods have side effects (like clearing the auto flag, triggering validation, or updating layout), bypassing them could cause subtle issues.

Consider using the setter consistently:

         } else if (node->tag() == "margin-top") {
             auto value = node->value<std::string>();
             if (isAutoKeyword(value)) {
-                m_margin.top = 0;
+                setMarginTop(0);
                 setMarginTopAuto(true);
             } else {
                 setMarginTop(stdext::to_number(value));
             }

However, since setMarginTopAuto(true) already calls updateParentLayout(), the current approach may be intentional to avoid redundant layout updates. If so, this is acceptable.

Also applies to: 659-667

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uiwidgetbasestyle.cpp` around lines 642 - 650, The code
currently writes m_margin.top = 0 (and m_margin.bottom = 0) directly when
encountering the "auto" keyword, bypassing the setter methods; replace those
direct assignments with calls to the setters so side effects and validation run
consistently — e.g. call setMarginTop(0) then setMarginTopAuto(true) for the
"margin-top" branch and setMarginBottom(0) then setMarginBottomAuto(true) for
the "margin-bottom" branch (use the setMarginTop, setMarginTopAuto,
setMarginBottom and setMarginBottomAuto symbols to locate the logic), or if you
intentionally want to avoid duplicate layout updates keep only
setMarginTopAuto(true)/setMarginBottomAuto(true) and remove the direct
m_margin.* = 0 assignments for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/framework/ui/uilayoutflexbox.cpp`:
- Around line 195-229: queueDescendantVersionReset currently stores raw
UIWidget* in s_pendingDescendantVersionResetRootsByDepth which can dangle if
widgets are destroyed during layout; update the logic to avoid UB by either (A)
changing the container types s_pendingDescendantVersionResetRootsByDepth and
related s_pendingDescendantVersionResetLookupByDepth to store UIWidgetPtr (or
the project’s owning/shared widget smart pointer) so queued widgets are kept
alive, or (B) if ownership changes are unacceptable, ensure every use path
(queueDescendantVersionReset and
flushQueuedDescendantVersionResetsForCurrentDepth) validates pointers before
dereference by checking for nullptr or a widget.isDestroyed()/isAlive() API and
skipping/erasing destroyed entries; modify queueDescendantVersionReset to insert
UIWidgetPtr (or perform a defensive conversion) and adjust lookup operations to
use the same key type so lookup/erase behavior remains consistent.
- Around line 28-33: The three inline globals (s_flexDepth,
s_pendingDescendantVersionResetRootsByDepth,
s_pendingDescendantVersionResetLookupByDepth) are shared across threads and can
race; change them to thread-local storage so each thread has independent layout
state: replace "inline int s_flexDepth = 0;" with "thread_local int s_flexDepth
= 0;" and similarly make s_pendingDescendantVersionResetRootsByDepth and
s_pendingDescendantVersionResetLookupByDepth thread_local, ensuring they are
sized/initialized to MAX_FLEX_DEPTH where previously expected; keep
MAX_FLEX_DEPTH unchanged and update any code that assumes a single global (e.g.,
functions referencing these symbols during layoutFlex or re-entrance checks such
as m_inFlexLayout) to use the new thread_local variables.

---

Nitpick comments:
In `@src/framework/ui/uilayoutflexbox.cpp`:
- Around line 963-968: The code manually saves and restores
crossUnit.pendingUpdate around layoutFlex(*item.widget), which is not
exception-safe; create and use a small RAII helper (similar to SizeUnitGuard)
that takes a reference to crossUnit.pendingUpdate, stores the original value in
its constructor, sets pendingUpdate to false, and restores the original value in
its destructor, then replace the manual save/restore around layoutFlex in the
block that currently references crossUnit.pendingUpdate so the flag is always
restored even if layoutFlex throws.
- Around line 587-594: preferredCrossUnit.pendingUpdate and
preferredCrossUnit.version are being left modified after temporarily switching
preferredCrossUnit.unit to Unit::FitContent for measurement, which can desync
state; save the original pendingUpdate and version values before setting
pendingUpdate=true and version=0, call child->updateSize(), then restore the
original pendingUpdate and version after restoring preferredCrossUnit.unit
(while still keeping the temporary unit change around the update call), i.e.,
capture originalPendingUpdate and originalVersion, set
pendingUpdate=true/version=0, call child->updateSize(), restore
preferredCrossUnit.unit, then restore pendingUpdate and version to the saved
originals to keep state consistent with unit.

In `@src/framework/ui/uiwidgetbasestyle.cpp`:
- Around line 707-728: The code redundantly calls isAutoKeyword(topStr) and
isAutoKeyword(bottomStr) when topAuto and bottomAuto are already computed;
update the computation of top and bottom to use the stored booleans (topAuto and
bottomAuto) instead of calling isAutoKeyword again (keep using
rightAuto/leftAuto as-is), so replace the expressions that compute top and
bottom with ternaries that reference topAuto and bottomAuto respectively while
leaving the later uses of m_margin, setMarginTopAuto/setMarginTop and
setMarginBottomAuto/setMarginBottom unchanged.
- Around line 642-650: The code currently writes m_margin.top = 0 (and
m_margin.bottom = 0) directly when encountering the "auto" keyword, bypassing
the setter methods; replace those direct assignments with calls to the setters
so side effects and validation run consistently — e.g. call setMarginTop(0) then
setMarginTopAuto(true) for the "margin-top" branch and setMarginBottom(0) then
setMarginBottomAuto(true) for the "margin-bottom" branch (use the setMarginTop,
setMarginTopAuto, setMarginBottom and setMarginBottomAuto symbols to locate the
logic), or if you intentionally want to avoid duplicate layout updates keep only
setMarginTopAuto(true)/setMarginBottomAuto(true) and remove the direct
m_margin.* = 0 assignments for consistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 66028282-f531-4851-9f16-33b5fd753549

📥 Commits

Reviewing files that changed from the base of the PR and between 384928f and c62eafb.

📒 Files selected for processing (2)
  • src/framework/ui/uilayoutflexbox.cpp
  • src/framework/ui/uiwidgetbasestyle.cpp

@kokekanon
Copy link
Copy Markdown
Contributor Author

@coderabbitai help
review this pr

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 25, 2026

The PR description image shows the OTClient HTML/CSS module running a "Holy Grail Layout & Flexbox" demo — a three-column layout with a blue header, left sidebar navigation, main article content area, right pink sidebar ("Widgets"), and a footer with "Made with Flexbox." The tab strip shows "Current Demo" and "Flex Examples" tabs, with the examples dropdown set to 11-holy-grail-layout.html.

@kokekanon Sure! I'll kick off a full review of PR #1672. This is a substantial PR covering the core C++ flex layout engine, HTML widget processing, style/color parsing, Lua scrollbar logic, game module CSS/HTML updates, and 21 new flex documentation examples. I'll analyze all the changes thoroughly.

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

♻️ Duplicate comments (1)
docs/exampleHTML_flex/20-chat-layout-flex.html (1)

191-193: ⚠️ Potential issue | 🟠 Major

Composer input text is still invisible.

background-color and color are both #f1f5f9, so typed text disappears against the input fill. This was already flagged on the previous revision and is still present.

Suggested fix
   .input {
     flex: 1 0 360px;
     width: 360px;
     min-width: 300px;
     background-color: `#f1f5f9`;
-    color: `#f1f5f9`;
+    color: `#0f172a`;
     border: 1px solid `#cbd5e1`;
     border-radius: 8px;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/20-chat-layout-flex.html` around lines 191 - 193, The
composer input's text is invisible because the CSS rule sets both
background-color and color to `#f1f5f9`; update the rule that currently contains
"background-color: `#f1f5f9`; color: `#f1f5f9`;" (the composer input style in
20-chat-layout-flex.html) so the text color contrasts with the background (for
example change color to a dark value like `#0f172a` or `#111827`, or remove the
duplicate color declaration), ensuring the composer input text is readable.
🧹 Nitpick comments (10)
src/framework/util/color.cpp (1)

248-311: Consider reusing existing helper functions.

The lambdas clamp255, strip_spaces, split_commas, parse_byte_or_percent, parse_alpha_any, and hsl_to_rgb defined here duplicate the namespace-scope static functions at lines 160-223. Consider removing these lambdas and calling the existing helpers directly to reduce duplication.

This is a pre-existing issue and can be addressed separately from this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/util/color.cpp` around lines 248 - 311, The block defines local
lambdas clamp255, strip_spaces, split_commas, parse_byte_or_percent,
parse_alpha_any, and hsl_to_rgb which duplicate existing namespace-scope helper
functions; remove these local lambdas and replace their uses with calls to the
existing helper functions of the same logical purpose (use the existing
clamp255/strip_spaces/split_commas/parse_byte_or_percent/parse_alpha_any and the
namespace hsl_to_rgb helper), ensuring any captured variables or references are
adjusted (pass r,g,b by reference to the existing hsl_to_rgb) and that
includes/using directives make the helpers visible in this translation unit.
docs/exampleHTML_flex/15-equal-height-cards.html (1)

12-14: Remove duplicate display: block !important; declaration.

Same issue as other example files – lines 12 and 14 both declare display: block !important;.

🧹 Proposed fix
     display: block !important;
     --image-source: none;
-    display: block !important;
     width: 100% !important;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/15-equal-height-cards.html` around lines 12 - 14,
Remove the duplicate CSS declaration by keeping only one instance of "display:
block !important;" and deleting the redundant line so the block contains a
single "display: block !important;" alongside the "--image-source: none;"
declaration; locate the duplicate by searching for the "display: block
!important;" lines in the same rule (the ones surrounding the "--image-source:
none;" declaration) and remove the extra occurrence.
docs/exampleHTML_flex/08-margin-auto.html (1)

12-14: Remove duplicate display: block !important; declaration.

Lines 12 and 14 duplicate the same declaration.

🧹 Proposed fix
     display: block !important;
     --image-source: none;
-    display: block !important;
     width: 100% !important;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/08-margin-auto.html` around lines 12 - 14, The CSS
contains a duplicated declaration "display: block !important;" (appearing twice
in the same rule); remove the redundant occurrence so only a single "display:
block !important;" remains and keep the other declarations like "--image-source:
none;" untouched to avoid duplicate property definitions.
docs/exampleHTML_flex/14-responsive-sidebar.html (1)

12-14: Remove duplicate display: block !important; declaration.

Lines 12 and 14 both declare display: block !important;. Remove the duplicate.

🧹 Proposed fix
     display: block !important;
     --image-source: none;
-    display: block !important;
     width: 100% !important;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/14-responsive-sidebar.html` around lines 12 - 14,
Remove the duplicate CSS declaration "display: block !important;" so the block
only appears once; locate the repeated lines around the "--image-source: none;"
declaration in the responsive sidebar snippet and delete one of the two
identical "display: block !important;" entries to avoid redundancy.
docs/exampleHTML_flex/18-kanban-advanced.html (1)

12-14: Remove duplicate display: block !important; declaration.

Lines 12 and 14 both declare display: block !important;.

🧹 Proposed fix
     display: block !important;
     --image-source: none;
-    display: block !important;
     width: 100% !important;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/18-kanban-advanced.html` around lines 12 - 14, Remove
the duplicated CSS declaration: in the CSS block that currently contains two
identical "display: block !important;" lines (the block also sets
"--image-source: none;"), keep a single "display: block !important;" and delete
the redundant one so the rule contains only one display declaration.
modules/game_forge/game_forge.css (1)

400-407: Remove empty line before declaration.

Stylelint flags an unexpected empty line before display: flex. Remove the blank line at line 400 for consistent formatting.

🧹 Proposed fix
 .history-footer {
-
   display: flex;
   flex-direction: row;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_forge/game_forge.css` around lines 400 - 407, Remove the
unexpected blank line immediately before the "display: flex;" declaration in the
CSS rule block (the block that contains display: flex, flex-direction: row,
flex-wrap: nowrap, align-items: stretch, justify-content: flex-start, gap: 0) so
the declarations start directly after the opening brace; simply delete the empty
line preceding "display: flex" to satisfy stylelint and restore consistent
formatting.
docs/exampleHTML_flex/17-sticky-footer.html (1)

11-13: Remove duplicate display: block !important; declaration.

Lines 11 and 13 both declare display: block !important;.

🧹 Proposed fix
     display: block !important;
     --image-source: none;
-    display: block !important;
     width: 99% !important;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/17-sticky-footer.html` around lines 11 - 13, Remove the
duplicate CSS declaration "display: block !important;" so it only appears once;
in the block containing "--image-source: none" (the snippet showing two
identical "display: block !important;" lines), keep a single "display: block
!important;" (preferably the first) and delete the redundant one to avoid
repetition.
docs/exampleHTML_flex/10-nested-flex.html (1)

40-46: Keep structural layout rules out of the theme selector.

.example.purple currently owns the first demo's width/box-sizing/flex-column behavior, so changing only the color variant would also change layout semantics. This would be safer on .example itself or on a dedicated helper class for the card-row example.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/10-nested-flex.html` around lines 40 - 46, The CSS in
.example.purple contains structural layout rules (width, box-sizing, display,
flex-direction, align-items) and should not live on the color variant; move
those structural rules from .example.purple into the base .example (or into a
dedicated helper class like .card-row) and leave .example.purple to only define
color-related styles. Update references to .example.purple in markup if you
choose a helper class so the layout classes and color variant are applied
separately.
docs/exampleHTML_flex/11-holy-grail-layout.html (1)

23-28: Consider sizing this demo to the preview area instead of a fixed 500px box.

For a holy-grail example, the interesting part is the fill-available-height behavior. Hard-coding height: 500px makes the demo less representative and can clip or leave dead space depending on the sample pane size.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/11-holy-grail-layout.html` around lines 23 - 28, The
.page rule currently hard-codes height: 500px which prevents the demo from
sizing to the preview area; remove the fixed height and replace it with a
fill-available rule (e.g., use min-height: 100vh or min-height: 100%/height:
100% with the root/preview container given 100% height) so the .page element
will stretch to the available preview height and demonstrate the holy-grail
fill-available behavior. Reference: .page selector and its height property.
docs/exampleHTML_flex/03-align-items.html (1)

155-162: Make the baseline example visually stronger.

Both text items use the same font metrics, so this ends up looking close to bottom alignment. Giving one child a noticeably larger font size or line-height would make the baseline behavior much clearer.

Possible tweak
-      <div class="item1"><span>Small text</span></div>
-      <div class="item1"><span>Large text</span></div>
+      <div class="item1"><span style="font-size: 12px;">Small text</span></div>
+      <div class="item1"><span style="font-size: 22px;">Large text</span></div>
       <div class="item1"><img src="/data/images/clienticon.png" alt="img"
           style="border:1px solid `#777`; border-radius:4px;" /></div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/exampleHTML_flex/03-align-items.html` around lines 155 - 162, The
baseline example's alignment isn't clear because both children share the same
font metrics; update the .container baseline example so one of its children
(e.g., the second .item1 or the span inside it) has a noticeably larger
font-size and/or increased line-height (and keep the image item unchanged) to
make the baseline behavior visually obvious; locate the HTML block with the
container class "baseline" and modify the second .item1 (or its inner span) to
use larger typography so the baseline shift is visible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/exampleHTML_flex/07-flex-flow-shorthand.html`:
- Around line 84-115: Update the inline comments and visible heading text so the
shorthand labels and descriptions match the actual flex values exactly: correct
the comment annotations for .f-auto, .f-none, .f-1, .f-2, .f-3, and .f-0-200 to
reflect their true computed flex shorthand (e.g., "flex: auto", "flex: none",
"flex: 1" etc.), and fix the heading/description spacing/separators in the
example HTML so titles/readme text are properly spaced and punctuated; apply the
same corrections to the duplicate section indicated around lines 142-143.

In `@docs/exampleHTML_flex/09-row-gap-column-gap.html`:
- Around line 88-106: The stylesheet gap properties (gap, row-gap, column-gap)
are currently ignored by the UI style parser; update
src/framework/ui/uiwidgetbasestyle.cpp to parse and apply these properties by
adding handling in the style parsing path (e.g., extend parseStyleProperty or
the function that handles CSS property tokens) to set widget style members
rowGap, columnGap and gap, and ensure applyLayoutSpacing or the layout-measure
step consumes those members to compute spacing; add fallback logic so gap sets
both rowGap and columnGap when present and ensure numeric values (e.g., "16px")
are converted consistently with existing spacing units.

In `@docs/exampleHTML_flex/12-navbar-patterns.html`:
- Around line 40-68: The middle cluster drifts because .nav1 .links uses
margin-left:auto and margin-right:auto while .nav1 .cta adds a third auto margin
(margin-left:auto); remove the asymmetric auto margin on .nav1 .cta and either
(a) center .nav1 .links with margin: 0 auto and let .cta sit without auto
margins, or (b) convert the layout to a true flex spacer pattern by having .nav1
as display:flex with explicit left and right side wrappers (or spacer elements
with flex:1) so the centered .links stays centered; update the selectors .nav1
.links and .nav1 .cta accordingly to implement one of these fixes.

In `@docs/exampleHTML_flex/14-responsive-sidebar.html`:
- Around line 50-51: The comment claims a responsive "flex-direction change"
that isn't implemented; either implement it or correct the comment: add a media
query (e.g., `@media` (max-width: 600px)) that switches the layout by setting
.container { flex-direction: column; } and adjusting .sidebar and .main sizing
so the sidebar stacks above the main, or update the comments on the
.container/.sidebar/.main sections to state that the sidebar stays left and the
main grows (removing references to stacking/flex-direction change). Ensure edits
touch the .container, .sidebar, and .main selectors to keep behavior and docs
consistent.

In `@src/framework/ui/uilayoutflexbox.cpp`:
- Around line 804-809: Re-apply the single-line definite-cross-size override
after the step 7 recompute that recalculates line cross-sizes but before the
align-content logic runs: after the block that recomputes line heights from item
dimensions (the step 7 recompute) and immediately before the align-content
application, re-check the exact condition (!crossAuto && lines.size() == 1) and
set lines[0].crossSize to std::max(0.0, innerCrossSize) so the single-line
override is not later overwritten by the align-content handling.

In `@src/framework/ui/uiwidget.cpp`:
- Around line 1195-1203: The direct m_rect.translate(delta) fast path leaves
text hitbox cache m_rectToWord stale, breaking getTextByPos(); update the same
code path that currently calls w->m_rect.translate(delta) to also adjust cached
rects: either translate every Rect in w->m_rectToWord by the same delta or
clear/invalidate the cache so cacheRectToWord() will rebuild on next access;
target the block where w->m_rect is translated and modify m_rectToWord
accordingly to keep cache consistent with the moved widget.

In `@src/framework/ui/uiwidget.h`:
- Around line 821-829: The margin setters (setMargin, setMarginHorizontal,
setMarginVertical, setMarginTop/Right/Bottom/Left and the auto setters
setMarginTopAuto/setMarginBottomAuto) currently only call updateParentLayout();
they must also trigger the HTML flex invalidation path used for flex-item
placement. After the existing updateParentLayout() call in each of these
methods, call scheduleHtmlTask() (the same function used in layout invalidation
for HTML flex containers) so that layoutFlex() will be queued and flex
positioning is recomputed for margin-top/ bottom/ numeric changes on flex items.

In `@src/framework/ui/uiwidgethtml.cpp`:
- Around line 660-665: applyFitContentRecursive() currently special-cases only
row flex via parentIsRowFlex and uses breakLine(c->getDisplay()), which
mis-measures column flex containers and ignores gap; update the logic around
parentIsRowFlex/shouldBreak so that you detect any flex parent (use
isFlexContainer(w->getDisplay()) and w->getFlexDirection()) and: for
Row/RowReverse treat children as non-breaking flex items (shouldBreak = false),
for Column/ColumnReverse treat each child as stacked (shouldBreak = true)
instead of deferring to breakLine(c->getDisplay()); additionally, when
accumulating runWidth and totalHeight add the container’s gap spacing between
items (add gap for every item after the first) so fit-content measurement
includes gaps before layoutFlex() runs. Ensure references to
applyFitContentRecursive(), parentIsRowFlex (or renamed parentIsFlex),
shouldBreak, breakLine(c->getDisplay()), runWidth, totalHeight, and layoutFlex()
are updated accordingly.

In `@src/framework/ui/uiwidgettext.cpp`:
- Around line 646-649: The reset logic only updates this widget's
m_textWrapOptions so inherited parent options can persist; change these
assignments to modify the effective owner options (use getTextWrapOptions() or
the parent m_textWrapOptions reference) so removal of "overflow-wrap" or
"word-break" on the parent resets the child's effective options; specifically,
replace assignments to this->m_textWrapOptions.overflowWrapMode =
OverflowWrapMode::Normal and this->m_textWrapOptions.wordBreakMode =
WordBreakMode::Normal with updates to the owner returned by getTextWrapOptions()
(or m_parent->m_textWrapOptions) so updateText() sees the cleared defaults.

---

Duplicate comments:
In `@docs/exampleHTML_flex/20-chat-layout-flex.html`:
- Around line 191-193: The composer input's text is invisible because the CSS
rule sets both background-color and color to `#f1f5f9`; update the rule that
currently contains "background-color: `#f1f5f9`; color: `#f1f5f9`;" (the composer
input style in 20-chat-layout-flex.html) so the text color contrasts with the
background (for example change color to a dark value like `#0f172a` or `#111827`, or
remove the duplicate color declaration), ensuring the composer input text is
readable.

---

Nitpick comments:
In `@docs/exampleHTML_flex/03-align-items.html`:
- Around line 155-162: The baseline example's alignment isn't clear because both
children share the same font metrics; update the .container baseline example so
one of its children (e.g., the second .item1 or the span inside it) has a
noticeably larger font-size and/or increased line-height (and keep the image
item unchanged) to make the baseline behavior visually obvious; locate the HTML
block with the container class "baseline" and modify the second .item1 (or its
inner span) to use larger typography so the baseline shift is visible.

In `@docs/exampleHTML_flex/08-margin-auto.html`:
- Around line 12-14: The CSS contains a duplicated declaration "display: block
!important;" (appearing twice in the same rule); remove the redundant occurrence
so only a single "display: block !important;" remains and keep the other
declarations like "--image-source: none;" untouched to avoid duplicate property
definitions.

In `@docs/exampleHTML_flex/10-nested-flex.html`:
- Around line 40-46: The CSS in .example.purple contains structural layout rules
(width, box-sizing, display, flex-direction, align-items) and should not live on
the color variant; move those structural rules from .example.purple into the
base .example (or into a dedicated helper class like .card-row) and leave
.example.purple to only define color-related styles. Update references to
.example.purple in markup if you choose a helper class so the layout classes and
color variant are applied separately.

In `@docs/exampleHTML_flex/11-holy-grail-layout.html`:
- Around line 23-28: The .page rule currently hard-codes height: 500px which
prevents the demo from sizing to the preview area; remove the fixed height and
replace it with a fill-available rule (e.g., use min-height: 100vh or
min-height: 100%/height: 100% with the root/preview container given 100% height)
so the .page element will stretch to the available preview height and
demonstrate the holy-grail fill-available behavior. Reference: .page selector
and its height property.

In `@docs/exampleHTML_flex/14-responsive-sidebar.html`:
- Around line 12-14: Remove the duplicate CSS declaration "display: block
!important;" so the block only appears once; locate the repeated lines around
the "--image-source: none;" declaration in the responsive sidebar snippet and
delete one of the two identical "display: block !important;" entries to avoid
redundancy.

In `@docs/exampleHTML_flex/15-equal-height-cards.html`:
- Around line 12-14: Remove the duplicate CSS declaration by keeping only one
instance of "display: block !important;" and deleting the redundant line so the
block contains a single "display: block !important;" alongside the
"--image-source: none;" declaration; locate the duplicate by searching for the
"display: block !important;" lines in the same rule (the ones surrounding the
"--image-source: none;" declaration) and remove the extra occurrence.

In `@docs/exampleHTML_flex/17-sticky-footer.html`:
- Around line 11-13: Remove the duplicate CSS declaration "display: block
!important;" so it only appears once; in the block containing "--image-source:
none" (the snippet showing two identical "display: block !important;" lines),
keep a single "display: block !important;" (preferably the first) and delete the
redundant one to avoid repetition.

In `@docs/exampleHTML_flex/18-kanban-advanced.html`:
- Around line 12-14: Remove the duplicated CSS declaration: in the CSS block
that currently contains two identical "display: block !important;" lines (the
block also sets "--image-source: none;"), keep a single "display: block
!important;" and delete the redundant one so the rule contains only one display
declaration.

In `@modules/game_forge/game_forge.css`:
- Around line 400-407: Remove the unexpected blank line immediately before the
"display: flex;" declaration in the CSS rule block (the block that contains
display: flex, flex-direction: row, flex-wrap: nowrap, align-items: stretch,
justify-content: flex-start, gap: 0) so the declarations start directly after
the opening brace; simply delete the empty line preceding "display: flex" to
satisfy stylelint and restore consistent formatting.

In `@src/framework/util/color.cpp`:
- Around line 248-311: The block defines local lambdas clamp255, strip_spaces,
split_commas, parse_byte_or_percent, parse_alpha_any, and hsl_to_rgb which
duplicate existing namespace-scope helper functions; remove these local lambdas
and replace their uses with calls to the existing helper functions of the same
logical purpose (use the existing
clamp255/strip_spaces/split_commas/parse_byte_or_percent/parse_alpha_any and the
namespace hsl_to_rgb helper), ensuring any captured variables or references are
adjusted (pass r,g,b by reference to the existing hsl_to_rgb) and that
includes/using directives make the helpers visible in this translation unit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00e479a2-4a08-43d0-83d9-52fa754bf5cf

📥 Commits

Reviewing files that changed from the base of the PR and between 294896c and 7f31061.

📒 Files selected for processing (33)
  • docs/exampleHTML_flex/01-flex-direction.html
  • docs/exampleHTML_flex/02-justify-content.html
  • docs/exampleHTML_flex/03-align-items.html
  • docs/exampleHTML_flex/04-flex-wrap-gap.html
  • docs/exampleHTML_flex/05-flex-item-props.html
  • docs/exampleHTML_flex/06-align-content.html
  • docs/exampleHTML_flex/07-flex-flow-shorthand.html
  • docs/exampleHTML_flex/08-margin-auto.html
  • docs/exampleHTML_flex/09-row-gap-column-gap.html
  • docs/exampleHTML_flex/10-nested-flex.html
  • docs/exampleHTML_flex/11-holy-grail-layout.html
  • docs/exampleHTML_flex/12-navbar-patterns.html
  • docs/exampleHTML_flex/13-card-grid-wrap.html
  • docs/exampleHTML_flex/14-responsive-sidebar.html
  • docs/exampleHTML_flex/15-equal-height-cards.html
  • docs/exampleHTML_flex/16-center-both-axes.html
  • docs/exampleHTML_flex/17-sticky-footer.html
  • docs/exampleHTML_flex/18-kanban-advanced.html
  • docs/exampleHTML_flex/19-dashboard-shell.html
  • docs/exampleHTML_flex/20-chat-layout-flex.html
  • docs/exampleHTML_flex/21-master-detail-flex.html
  • modules/corelib/ui/uihtml.lua
  • modules/game_forge/game_forge.css
  • modules/game_htmlsample/htmlsample.css
  • modules/game_htmlsample/htmlsample.html
  • modules/game_htmlsample/htmlsample.lua
  • src/framework/ui/uilayoutflexbox.cpp
  • src/framework/ui/uiwidget.cpp
  • src/framework/ui/uiwidget.h
  • src/framework/ui/uiwidgetbasestyle.cpp
  • src/framework/ui/uiwidgethtml.cpp
  • src/framework/ui/uiwidgettext.cpp
  • src/framework/util/color.cpp

Comment on lines +821 to +829
void setMargin(const int margin) { m_margin.set(margin); m_marginTopAuto = m_marginBottomAuto = m_marginLeftAuto = m_marginRightAuto = false; updateParentLayout(); }
void setMarginHorizontal(const int margin) { m_margin.right = m_margin.left = margin; m_marginLeftAuto = m_marginRightAuto = false; updateParentLayout(); }
void setMarginVertical(const int margin) { m_margin.bottom = m_margin.top = margin; updateParentLayout(); }
void setMarginTop(const int margin) { m_margin.top = margin; updateParentLayout(); }
void setMarginVertical(const int margin) { m_margin.bottom = m_margin.top = margin; m_marginTopAuto = m_marginBottomAuto = false; updateParentLayout(); }
void setMarginTop(const int margin) { m_margin.top = margin; m_marginTopAuto = false; updateParentLayout(); }
void setMarginRight(const int margin) { m_margin.right = margin; m_marginRightAuto = false; updateParentLayout(); }
void setMarginBottom(const int margin) { m_margin.bottom = margin; updateParentLayout(); }
void setMarginBottom(const int margin) { m_margin.bottom = margin; m_marginBottomAuto = false; updateParentLayout(); }
void setMarginLeft(const int margin) { m_margin.left = margin; m_marginLeftAuto = false; updateParentLayout(); }
void setMarginTopAuto(bool v = true) { m_marginTopAuto = v; updateParentLayout(); }
void setMarginBottomAuto(bool v = true) { m_marginBottomAuto = v; updateParentLayout(); }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Margin setters still miss the HTML flex invalidation path.

These setters only call updateParentLayout(). In src/framework/ui/uiwidget.cpp, Lines 829-833 only queue the parent UILayout, but HTML flex containers are recomputed through scheduleHtmlTask() / layoutFlex(). A runtime margin-top:auto, margin-bottom:auto, or numeric margin change on a flex item can therefore keep stale placement until some unrelated HTML task happens to rerun layout.

🔧 Minimal fix pattern
 void setMarginTopAuto(bool v = true) {
     m_marginTopAuto = v;
     updateParentLayout();
+    if (isOnHtml()) {
+        scheduleHtmlTask(PropUpdateSize);
+        scheduleHtmlTask(PropApplyAnchorAlignment);
+    }
 }

Apply the same invalidation pattern to the other margin setters in this block.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uiwidget.h` around lines 821 - 829, The margin setters
(setMargin, setMarginHorizontal, setMarginVertical,
setMarginTop/Right/Bottom/Left and the auto setters
setMarginTopAuto/setMarginBottomAuto) currently only call updateParentLayout();
they must also trigger the HTML flex invalidation path used for flex-item
placement. After the existing updateParentLayout() call in each of these
methods, call scheduleHtmlTask() (the same function used in layout invalidation
for HTML flex containers) so that layoutFlex() will be queued and flex
positioning is recomputed for margin-top/ bottom/ numeric changes on flex items.

@kokekanon kokekanon marked this pull request as draft March 25, 2026 03:35
@kokekanon kokekanon marked this pull request as ready for review March 25, 2026 14:02
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/framework/ui/uiwidget.h (1)

821-831: ⚠️ Potential issue | 🟠 Major

margin-left/right:auto still misses the HTML flex invalidation path.

This block fixes the numeric and vertical auto-margin setters, but setMarginLeftAuto and setMarginRightAuto on Lines 830-831 still only call updateParentLayout(). For HTML flex parents that does not queue the flex HTML task, so toggling main-axis auto margins at runtime can leave the item in a stale position until some unrelated HTML update happens.

Suggested fix
-    void setMarginLeftAuto(bool v = true) { m_marginLeftAuto = v; updateParentLayout(); }
-    void setMarginRightAuto(bool v = true) { m_marginRightAuto = v; updateParentLayout(); }
+    void setMarginLeftAuto(bool v = true) { m_marginLeftAuto = v; updateParentLayout(); scheduleParentHtmlFlexLayout(); }
+    void setMarginRightAuto(bool v = true) { m_marginRightAuto = v; updateParentLayout(); scheduleParentHtmlFlexLayout(); }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uiwidget.h` around lines 821 - 831, setMarginLeftAuto and
setMarginRightAuto currently only call updateParentLayout(), missing the HTML
flex invalidation path; update both methods (setMarginLeftAuto(bool) and
setMarginRightAuto(bool)) to also call scheduleParentHtmlFlexLayout() after
updateParentLayout() so that toggling left/right auto margins triggers the HTML
flex re-layout for flex parents.
🧹 Nitpick comments (6)
modules/game_htmlsample/htmlsample.lua (4)

180-201: Inconsistent indentation in palette array.

The first element "red" has different indentation than the rest of the array elements, which affects readability.

✏️ Fix indentation
     local palette = {
-          "red",
-    "blue",
+        "red",
+        "blue",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_htmlsample/htmlsample.lua` around lines 180 - 201, The palette
table has inconsistent indentation for its first element ("red") which harms
readability; open the palette declaration (variable name "palette") and align
all string entries to the same indentation level (indent the "red" entry to
match the other lines) so every element in the table uses consistent spacing and
column alignment.

8-10: Defensive check could fail silently if modules.game_things is nil.

The function returns nil (falsy) when modules.game_things is nil, which works but is inconsistent with the explicit false pattern used elsewhere in this file (e.g., cycle event returns).

💡 Suggested improvement
 function HtmlSample:isThingsLoaded()
-    return modules.game_things and modules.game_things.isLoaded()
+    return modules.game_things ~= nil and modules.game_things.isLoaded()
 end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_htmlsample/htmlsample.lua` around lines 8 - 10,
HtmlSample:isThingsLoaded currently returns nil when modules.game_things is
missing; change it to explicitly return false in that case by checking
modules.game_things first and only calling modules.game_things.isLoaded() when
present, otherwise return false so the function consistently yields a boolean
(refer to HtmlSample:isThingsLoaded and modules.game_things.isLoaded).

49-61: Magic numbers for schedule delays reduce readability.

The delays 111, 1, and later 30, 80, 90 are unexplained. Consider extracting these as named constants or adding brief comments explaining why these specific values were chosen.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_htmlsample/htmlsample.lua` around lines 49 - 61, Replace magic
numeric delays used in scheduleEvent calls with named constants and/or brief
comments explaining their purpose; e.g., define descriptive constants like
EXAMPLES_RENDER_DELAY, RESPONSIVE_INIT_DELAY, RESPONSIVE_STEP_DELAY,
RESPONSIVE_START_DELAY and use them in the calls to self:scheduleEvent around
renderSelectedExample, updateResponsiveViewport, and startResponsiveDemo so the
intent of 111, 1, 30, 80, 90 is clear and maintainable.

144-169: Multiple deferred layout refreshes may be unnecessary overhead.

Scheduling refreshPreviewLayout three times at 1ms, 30ms, and 80ms suggests timing uncertainty. While this works, it indicates a potential underlying issue with layout timing. Consider whether a single deferred call with a longer delay would suffice, or investigate why multiple refreshes are needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_htmlsample/htmlsample.lua` around lines 144 - 169, The code
currently schedules refreshPreviewLayout three times (via self:scheduleEvent) at
1, 30 and 80 ms which is likely redundant; replace these multiple calls with a
single deferred refresh (choose a single reasonable delay, e.g. 30ms) or
implement a simple debounce/once-only scheduling around refreshPreviewLayout so
it only queues one future execution (check/track a boolean like refreshScheduled
or use existing scheduler token) and call
self:scheduleEvent(refreshPreviewLayout, <delay>) only when not already
scheduled; target the refreshPreviewLayout closure and the three
self:scheduleEvent(...) calls in this block to make the change.
modules/game_htmlsample/htmlsample.css (1)

127-141: Inconsistent gap unit usage: 0px vs unitless values elsewhere.

Line 131 uses gap: 0px while other properties in this file use unitless values (e.g., line 84 gap: 8px, line 132 width: 480). For a zero value, the unit is unnecessary. Consider being consistent with the codebase conventions.

✏️ Minor consistency fix
   flex-wrap: wrap;
   align-content: flex-start;
-  gap: 0px;
+  gap: 0;
   width: 480;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/game_htmlsample/htmlsample.css` around lines 127 - 141, The
stylesheet uses inconsistent unit notation for zero values: in
.responsiveViewport the property gap is set as "0px" while other numeric
properties in this file are unitless (e.g., width: 480) — change gap: 0px to
gap: 0 to match the project’s unitless convention for zero values and keep
formatting consistent within the .responsiveViewport rule (also review
.responsiveBox if similar unit issues exist).
src/framework/ui/uiwidget.h (1)

717-718: Avoid exposing a mutable reference to m_children.

Returning UIWidgetList& lets any caller bypass addChild/removeChild/moveChildToIndex, which can desync m_childrenById, m_childIndex, parent pointers, and layout bookkeeping. Since layoutFlex is already friended, I'd keep only the const accessor public.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/ui/uiwidget.h` around lines 717 - 718, The public non-const
accessor getChildrenRef() exposes m_children and allows external mutation that
will bypass invariants maintained by addChild/removeChild/moveChildToIndex and
corrupt m_childrenById, m_childIndex, parent pointers and layout bookkeeping;
remove or make the non-const UIWidgetList& getChildrenRef() inaccessible (e.g.,
delete it or move it to private/protected) and keep only the const UIWidgetList&
getChildrenRef() public, leaving layoutFlex (already friended) able to access
m_children when needed so all external modifications go through
addChild/removeChild/moveChildToIndex.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@modules/game_forge/game_forge.css`:
- Around line 399-406: The .history-footer rule block contains an extraneous
blank line that triggers Stylelint's declaration-empty-line-before rule; remove
the stray empty line inside the .history-footer CSS block so declarations follow
directly (no blank line) to satisfy the linter and keep the stylesheet
lint-clean.

In `@modules/game_htmlsample/htmlsample.css`:
- Around line 62-74: Stylelint is flagging the OTUI custom type selector
"window" used in selectors like ".examplePreview window" and ".examplePreview >
.exampleRoot" as unknown; update the project's Stylelint config
(.stylelintrc.json) to allow OTUI types by either adding a
"selector-type-allowed-list" that includes "window" (and other OTUI types like
"uicreature") or by adjusting "selector-no-unknown" to ignore custom type
selectors so these valid selectors aren't reported as errors.

In `@modules/game_htmlsample/htmlsample.lua`:
- Around line 87-96: The code sets self.selectedExampleFile = htmlFiles[1] and
handles the nil case, but the error message uses a hard-coded 'ejemplosFlex'
path; update the call to self:showExampleMessage to reference the actual path
variable self.exampleBasePath (e.g., self:showExampleMessage('No .html files
found in ' .. self.exampleBasePath)) so users see the correct directory, and
keep the existing nil check around self.selectedExampleFile and the calls to
combo:setCurrentOption, self.isExamplesTab and self:renderSelectedExample as
they are.

In `@src/framework/ui/uiwidget.cpp`:
- Around line 1156-1204: The fast-path currently treats any positionChanged as a
pure translation; narrow it so it only runs for pure translations by also
verifying the size hasn't changed (compare oldRect.size() vs clampedRect.size()
or check width/height equality) before computing delta and shifting descendants.
Update the condition that gates the translation block (the if using isOnHtml(),
m_inFlexLayout, m_parent->m_inFlexLayout, m_displayType and positionChanged) to
require no size change (e.g., && oldRect.size() == clampedRect.size()), so flex
layout/rewrapping still runs when the container is resized.

---

Duplicate comments:
In `@src/framework/ui/uiwidget.h`:
- Around line 821-831: setMarginLeftAuto and setMarginRightAuto currently only
call updateParentLayout(), missing the HTML flex invalidation path; update both
methods (setMarginLeftAuto(bool) and setMarginRightAuto(bool)) to also call
scheduleParentHtmlFlexLayout() after updateParentLayout() so that toggling
left/right auto margins triggers the HTML flex re-layout for flex parents.

---

Nitpick comments:
In `@modules/game_htmlsample/htmlsample.css`:
- Around line 127-141: The stylesheet uses inconsistent unit notation for zero
values: in .responsiveViewport the property gap is set as "0px" while other
numeric properties in this file are unitless (e.g., width: 480) — change gap:
0px to gap: 0 to match the project’s unitless convention for zero values and
keep formatting consistent within the .responsiveViewport rule (also review
.responsiveBox if similar unit issues exist).

In `@modules/game_htmlsample/htmlsample.lua`:
- Around line 180-201: The palette table has inconsistent indentation for its
first element ("red") which harms readability; open the palette declaration
(variable name "palette") and align all string entries to the same indentation
level (indent the "red" entry to match the other lines) so every element in the
table uses consistent spacing and column alignment.
- Around line 8-10: HtmlSample:isThingsLoaded currently returns nil when
modules.game_things is missing; change it to explicitly return false in that
case by checking modules.game_things first and only calling
modules.game_things.isLoaded() when present, otherwise return false so the
function consistently yields a boolean (refer to HtmlSample:isThingsLoaded and
modules.game_things.isLoaded).
- Around line 49-61: Replace magic numeric delays used in scheduleEvent calls
with named constants and/or brief comments explaining their purpose; e.g.,
define descriptive constants like EXAMPLES_RENDER_DELAY, RESPONSIVE_INIT_DELAY,
RESPONSIVE_STEP_DELAY, RESPONSIVE_START_DELAY and use them in the calls to
self:scheduleEvent around renderSelectedExample, updateResponsiveViewport, and
startResponsiveDemo so the intent of 111, 1, 30, 80, 90 is clear and
maintainable.
- Around line 144-169: The code currently schedules refreshPreviewLayout three
times (via self:scheduleEvent) at 1, 30 and 80 ms which is likely redundant;
replace these multiple calls with a single deferred refresh (choose a single
reasonable delay, e.g. 30ms) or implement a simple debounce/once-only scheduling
around refreshPreviewLayout so it only queues one future execution (check/track
a boolean like refreshScheduled or use existing scheduler token) and call
self:scheduleEvent(refreshPreviewLayout, <delay>) only when not already
scheduled; target the refreshPreviewLayout closure and the three
self:scheduleEvent(...) calls in this block to make the change.

In `@src/framework/ui/uiwidget.h`:
- Around line 717-718: The public non-const accessor getChildrenRef() exposes
m_children and allows external mutation that will bypass invariants maintained
by addChild/removeChild/moveChildToIndex and corrupt m_childrenById,
m_childIndex, parent pointers and layout bookkeeping; remove or make the
non-const UIWidgetList& getChildrenRef() inaccessible (e.g., delete it or move
it to private/protected) and keep only the const UIWidgetList& getChildrenRef()
public, leaving layoutFlex (already friended) able to access m_children when
needed so all external modifications go through
addChild/removeChild/moveChildToIndex.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2103533c-8796-4041-9e40-159908074a6c

📥 Commits

Reviewing files that changed from the base of the PR and between 7f31061 and dea4505.

📒 Files selected for processing (9)
  • modules/game_forge/game_forge.css
  • modules/game_htmlsample/htmlsample.css
  • modules/game_htmlsample/htmlsample.html
  • modules/game_htmlsample/htmlsample.lua
  • src/framework/ui/uilayoutflexbox.cpp
  • src/framework/ui/uiwidget.cpp
  • src/framework/ui/uiwidget.h
  • src/framework/ui/uiwidgethtml.cpp
  • src/framework/ui/uiwidgettext.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • modules/game_htmlsample/htmlsample.html
  • src/framework/ui/uilayoutflexbox.cpp
  • src/framework/ui/uiwidgethtml.cpp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant