Skip to content

Implement Responsive Client-Side Autoscaling#90

Open
Lightning11wins wants to merge 106 commits intomasterfrom
apos_autoscale9-slim5
Open

Implement Responsive Client-Side Autoscaling#90
Lightning11wins wants to merge 106 commits intomasterfrom
apos_autoscale9-slim5

Conversation

@Lightning11wins
Copy link
Contributor

After extracting changes into #87, #88, #89, and removing a few minor/unnecessary changes; this PR is hopefully slightly more manageable.

GitHub Relationships:

This PR includes several TODOs:

  • 3 TODOs for Israel to handle after Update Duplicate Detection #77 is merged into master.
  • 4 TODOs for Greg (although other reviewers are welcome to handle these if they have the requisite knowledge), in the following files: ht_render.c, htdrv_page.c, htdrv_tab.c, and htdrv_table.c.

There are several known issues with this code:

  • Table row details sometimes don't appear.
    • e.g. Broken left menus on the Kardia home page.
  • Code that breaks responsive layout.
    • widget/pane: resize action (test apps/kardia/modules/payroll/pay_form.app)
    • widget/image: offset & scale actions (not used anywhere)
    • widget/objcanvas: add_osrc_object()
  • ht_get_parent_w/h__INTERNAL() & wgtrGetContainerWidth/Height() should be merged.
    • They appear to give almost identical results, except when checking a top-level widget. In this case, ht_get_parent_h__INTERNAL() (mine) gives a height 2px taller.
  • Flexibility calculations that depend on page size cause breakage since the client assumes widget flexibility scale is constant.
    • This would be very hard to fix! The best solution I can think of so far is to rewrite the entire layout system to convert px -> % on the server, then do all layout client-side. However, I'm not sure this system could handle flexibility values at all.

…yout, button, editbox, html, image, label, scrollpane, textbutton, & treeview.
Fixes components, dropdowns, and images.
Renames flex variables to be more clear.
Send parent_w and parent_h to client.
Add tab-height support to apos.c.
Add design support to apos.c.
Add IsDesign to the WgtrClientInfo struct.

Fix spelling mistakes.
Clean up.
Add an error message when cxsecVerifySymbol_n() fails.
Improve an existing error message when htr_internal_WriteWgtrProperty() fails to write a property of an unknown type.
Set Centrallix event listener to be explicitly non-passive, fixing a console error when later code assumes that calling preventDefault() is allowed.
Fix a bug in qprintf() that caused % and & characters inside conditional printing areas to always print, regardless of the condition.
Improve documentation for qpfPrintf_va_internal() and qpf_grow_fn_t().
Clean up.
Add shortcut functions: ht_flex_x(), ht_flex_y(), ht_flex_w(), ht_flex_h().
Rename fl_scale_x (was total_fl_x).
Rename fl_scale_y (was total_fl_y).
Rename fl_scale_w (was total_fl_w).
Rename fl_scale_h (was total_fl_h).
Rename fl_parent_w (was parent_w).
Rename fl_parent_h (was parent_h).
Remove ht_flex_format_all and ht_flex_all().
Improve usage of new feature in previously updated widgets.

Fix spelling mistakes.
Clean up.
Clean up some apps.
Add Math.clamp() and Math.isBetween().
Add getParentSize(), getParentW(), and getParentH().
Refactor getRelativeX/Y/W/H() to call the new getRelative().
Refactor setRelativeX/Y/W/H() to call the new setRelative().
Add fast_setRelativeX/Y().
Add setResponsiveX/Y/W/H() using a new shared setResponsive().
Add responsiveness to moveTo() with the new functions.

Fix style guide mistakes.
Clean up.
Add support for more edge cases with undefined values to wgtrGetServerProperty().
Add the Log action (and docs).
Add the ReloadPage action (and docs).
Improve documentation for the Alert widget.
Add htr_action_point() to ht_render.js to handle the point action responsively.
Refactor pane and window widgets to use the htr_action_point().
Improve documentation and code clarity for htutil_point().
Improve documentation for the point action.
Update al_reflow() to create responsive CSS using the setResponsive() functions.
Improve documentation for al_childresize().
Optimize and clean up inefficient code.
Rewrite C & JS code to use responsive styling.
Fix a bug that allowed multiple tabs to be selected at once.

Fix spelling errors.
Clean up.
Remove the moveable attribute.
Simplify clock widget event code.
Clean up cl_get_time().
Add tab_w and tab_h parameters to tab_features.app to fix broken left and right tab locations.
Remove unnecessary button widget from autoscale_test.app.
Add dragging cursor styles for window widgets.
Add cursor styles for the checkbox, radiobutton, scrollpane, treeview, and window widgets.
Update existing cursor styles for the button, datetime, dropdown, imagebutton, tab, and textbutton widgets.
Add the .dt_dropdown CSS class to make styling the datetime dropdown easier.
Update table columns to use col-resize instead of move.
Clean up cursor CSS styles to follow consistent code styling.
…atives.

Rewrite getters and setters in the ClipObject polyfill to replace __defineGetter__() and __defineSetter__().
Rewrite getters in the datetime, dropdown, and radiobutton widgets to replace __defineGetter__().
Rewrite pg_area() constructor to replace __defineGetter__().
Define a base when parsing relative properties to fix edge cases.
Remove unused debug setters in the table widget.
Remove unused debug code.
…aster.

Replace calls to fast_setRelativeX/Y() with calls to setRelativeX/Y().
Remove obsolete workaround code.
Remove a completed todo.
Improve a comment.
Fix windows failing to release when the mouseup event comes from outside the web page.
Update responsive event listener to use a pattern more similar to other resize code.
Add .titlebar to window object.
Add global wn_windows array that stores all windows.
@greptile-apps
Copy link

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR implements client-side responsive autoscaling for the Centrallix application server. It introduces flexible layout calculations in apos.c (new loc_fl/my_fl flex weights propagated through grid lines), serialises these weights as new fl_scale_*/fl_parent_* widget properties to the browser via ht_render.c, and consumes them client-side in the revamped ht_geom_dom1html.js geometry layer. Accompanying this is a large modernisation sweep across nearly all HTML widget drivers (removing language="javascript", switching to single-quoted HTML attributes, adopting responsive CSS helpers via ht_flex_format) and significant rewrites of the scrollpane and tab JS drivers.

Key findings from this review:

  • Critical logic bug in htdrv_tab.c: wgtrGetPropertyValue is called with POD(&page_type) (a char[32]) as its out-parameter, but the result is never actually read — the subsequent condition checks type, which is a separate char* that is always NULL. This causes page_type to be unconditionally set to "static", silently disabling "dynamic" tab-page support. The same mistake appears at both tab-rendering loops (lines 469 and 607).
  • JavaScript Window.clipped reference in ht_geom_dom1html.js uses capital-W Window (the constructor, not the instance), making Array.from(Window.clipped) throw a TypeError whenever the debug helper getClipped() is invoked.
  • Malformed HTML in htdrv_scrollpane.c: C string-literal concatenation of "<table" and "border='0' " produces "<tableborder='0' ..." (missing space) in the Netscape DOM branch, rendering the sp%POSarea table's attributes invisible to browsers.
  • strcpy rule violations across htdrv_tab.c, htdrv_table.c, and htdrv_scrollpane.c — all newly added sites should use strtcpy() instead.
  • goto error pattern not followed in the non-trivial httabRender function in htdrv_tab.c.
  • Several known issues are pre-acknowledged by the author in the PR description (broken left menus, widget/pane resize, config_buf TODO) and are expected to be addressed in follow-up work.

Confidence Score: 2/5

  • Not safe to merge in current state — a critical logic regression silently disables dynamic tab-page support.
  • The page_type always-"static" bug in htdrv_tab.c is a silent regression that breaks existing "dynamic" tab-page functionality. Combined with the Window.clipped TypeError in JS, the malformed HTML in scrollpane, the multiple strcpy rule violations, and the author's own list of known breakages (broken left menus, pane resize, etc.), the PR has too many open issues to merge safely. The core autoscaling machinery in apos.c/wgtr.c looks structurally sound, but the widget driver layer has several defects introduced during the refactor.
  • centrallix/htmlgen/htdrv_tab.c (critical page_type bug, missing goto error, many strcpy calls), centrallix-os/sys/js/ht_geom_dom1html.js (Window.clipped), and centrallix/htmlgen/htdrv_scrollpane.c (malformed HTML).

Important Files Changed

Filename Overview
centrallix/htmlgen/htdrv_tab.c Major refactor of the tab widget renderer; introduces a critical logic bug where page_type is always set to "static" (breaking dynamic tabs), multiple new strcpy violations, absence of goto error pattern, and a config_buf memory management TODO.
centrallix/htmlgen/htdrv_scrollpane.c Large rewrite of the scrollpane renderer; adopts goto err error handling correctly, but has a missing space in "<table" string concatenation in the Netscape DOM branch producing malformed HTML, and a renamed driver name strcpy.
centrallix-os/sys/js/ht_geom_dom1html.js Adds responsive geometry helpers (setResponsive*, getParentW/H, getRelativeW/H); introduces Window.clipped (capital W) bug in the debug getClipped() function which will throw a TypeError at runtime.
centrallix/wgtr/apos.c Extensive comment additions, refactored aposSpaceOutLines to track loc_fl/my_fl flex weights per grid line, and aposSnapWidgetsToGrid extended to write fl_scale_* properties; logic follows existing patterns with thExcessiveRecursion guards preserved.
centrallix/htmlgen/ht_render.c Adds serialization of new fl_scale_* and fl_parent_* widget properties to the client WGTR, modernises HTML output (removes language="javascript", adds charset, lang), fixes bug in htrRender ordering of ns and build_wgtr_ init calls, extends DATA_T_DOUBLE/DATETIME/INTVEC/STRINGVEC property serialization.
centrallix/htmlgen/htdrv_table.c Significant refactor: splits the large tbld_init() call into multiple readable sub-calls, renames tbld%POSpane to tbld%POSbase, adds responsive flex CSS for layout; two new strcpy violations introduced.
centrallix/wgtr/wgtr.c Adds fl_scale_* and fl_parent_* fields to wgtrNewNode initialisation and wgtrGetPropertyType/wgtrGetPropertyValue dispatch; clean, low-risk changes with explicit zero-initialisation.
centrallix-os/sys/js/htdrv_scrollpane.js Complete rewrite of the scrollpane JS; replaces global mutable vars with scoped state, adds wheel/keyboard scrolling, uses a ResizeObserver to keep the scroll thumb in sync; generally well-structured.
centrallix/include/wgtr.h Adds fl_scale_x/y/w/h (double) and fl_parent_w/h (int) fields to WgtrNode for responsive layout calculations, and IsDesign flag to WgtrClientInfo; straightforward header change.
centrallix/include/apos.h Adds loc_fl/my_fl float fields to AposLine, updates aposSnapWidgetsToGrid signature, and adds several readability macros (APOS_IS_BORDER, APOS_VERTICAL, isScrollpane, etc.).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Server: wgtrVerify()"] --> B["aposAutoPositionWidgetTree()"]
    B --> C["aposBuildGrid() — draw lines & sections"]
    C --> D["aposSetFlexibilities() — compute fl_x/y/w/h"]
    D --> E["aposSpaceOutLines() — distribute space\n✦ NEW: saves loc_fl / my_fl per line"]
    E --> F["aposSnapWidgetsToGrid() — snap widgets to lines\n✦ NEW: writes fl_scale_x/y/w/h to each WgtrNode"]
    F --> G["htr_internal_BuildClientWgtr_r()\n✦ NEW: serialises fl_scale_* + fl_parent_* to JS"]
    G --> H["Browser receives HTML + WGTR JSON"]
    H --> I["startup_() → build_wgtr_()"]
    I --> J["setResponsiveX/Y/W/H() in ht_geom_dom1html.js\n✦ NEW: converts server px → % using fl_scale_*"]
    J --> K["window resize event"]
    K --> L["Recalculate all element positions/sizes\nusing stored fl_scale weights"]
    L --> J
Loading

Last reviewed commit: c53a8f4

Comment on lines 1596 to 1600
node->fl_y = fly;
node->fl_width = flwidth;
node->fl_height = flheight;
node->fl_parent_h = node->fl_parent_w = -1;
node->ObjSession = s;
Copy link

Choose a reason for hiding this comment

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

fl_scale_* fields not initialized in wgtrNewNode

fl_scale_x, fl_scale_y, fl_scale_w, and fl_scale_h are not explicitly set to 0.0 here. fl_parent_w/fl_parent_h received explicit initialisation (= -1) in this same commit, but the fl_scale_* fields did not. If nmMalloc returns unzeroed memory, aposSnapWidgetsToGrid will += random garbage into these fields, silently producing wrong flexibility values for every widget.

Add:

node->fl_scale_x = node->fl_scale_y = node->fl_scale_w = node->fl_scale_h = 0.0;

alongside the other new field initialisations.

Copy link
Contributor Author

@Lightning11wins Lightning11wins Mar 6, 2026

Choose a reason for hiding this comment

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

@gbeeley I think Greptile is just wrong here. 1585 calls memset(node, 0, sizeof(WgtrNode));, so this data should already be 0. Am I missing something?

I'm still going to make this explicit because A) I think it makes the code more clear and B) the compiler will probably prune this line when optimization is turned on, but I don't think this is really necessary.

@greptile-apps
Copy link

greptile-apps bot commented Mar 4, 2026

Additional Comments (1)

centrallix/htmlgen/ht_render.c
Missing thExcessiveRecursion() guard and NULL parent check in recursive helpers

ht_get_parent_w__INTERNAL and ht_get_parent_h__INTERNAL both recurse upward through the widget tree without:

  1. A thExcessiveRecursion() check — required for all deeply recursive functions to prevent stack exhaustion.
  2. A NULL guard on parent — if every ancestor has a negative width/height, the recursion will walk past the root node (Root->Parent == NULL) and dereference a null pointer.
int
ht_get_parent_w__INTERNAL(pWgtrNode widget)
    {
    if (thExcessiveRecursion())
        {
        mssError(1, "HTRND", "Resource exhaustion in ht_get_parent_w__INTERNAL");
        return 0;
        }
    const int cached_value = widget->fl_parent_w;
    if (cached_value != -1) return cached_value;
    const pWgtrNode parent = widget->Parent;
    if (!parent) return widget->fl_parent_w = 0;
    const int parentWidth = parent->width;
    return widget->fl_parent_w = (parentWidth >= 0)
        ? parentWidth - (parent->left + parent->right)
        : ht_get_parent_w__INTERNAL(parent);
    }

Apply the same fix to ht_get_parent_h__INTERNAL.

Context Used: Rule from dashboard - What: Use thExcessiveRecursion() checks in any deeply recursive functions.

Why: This prevents sta... (source)

Fix critical invalid HTML generation for pgstat.
Add null guards and stack exhaustion checks to ht_get_parent_w/h__INTERNAL().
Add explicit initialization for fl_scale_* fields.
Fix quotes breaking CSS background-color.
Fix incorrect CSS styling.
Replace Math.clamp() & Math.isBetween() debug versions with their development versions.
Improve naming in thExcessiveRecursion().
Clean up.
@Lightning11wins
Copy link
Contributor Author

@greptileai Can you rereview this and let me know if you have any other comments? Did I resolve your issues?

Add copyright notice to htdrv_button.c.
Fix incorrect line wrap in copyright notice for net_http_conn.c.
@Lightning11wins
Copy link
Contributor Author

@greptileai Can you rereview this and let me know if you have any other comments? Did I resolve your issues?

Comment on lines +469 to +470
if (type == NULL || strcmp(type, "dynamic") != 0) strcpy(page_type, "static");

Copy link

Choose a reason for hiding this comment

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

type is always NULL here — dynamic tab pages silently broken

wgtrGetPropertyValue(tab, "type", ..., POD(&page_type)) stores the result into page_type (a char[32]), but the immediately following condition checks type, which is a separate char* variable initialized to NULL at the top of the function and never assigned in this code path.

Because type == NULL is always true, strcpy(page_type, "static") is unconditionally executed regardless of what the tab page's "type" property says. This silently breaks "dynamic" tab pages for every caller.

The old code (pre-PR) correctly used POD(&type) as the out-parameter and checked the return value:

if(wgtrGetPropertyValue(tabpage_obj,"type",DATA_T_STRING,POD(&type)) != 0)
    strcpy(page_type,"static");
else if(!strcmp(type,"static") || !strcmp(type,"dynamic"))
    strcpy(page_type,type);
else
    strcpy(page_type,"static");

The same bug appears again at line 607 for the second tab-page rendering loop. Suggested fix for both sites:

if (wgtrGetPropertyValue(tab, "type", DATA_T_STRING, POD(&type)) != 0 ||
    (strcmp(type, "static") != 0 && strcmp(type, "dynamic") != 0))
    strtcpy(page_type, "static", sizeof(page_type));
else
    strtcpy(page_type, type, sizeof(page_type));

Comment on lines +51 to +52
.from(Window.clipped)
.filter(id=>id)
Copy link

Choose a reason for hiding this comment

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

Window.clipped (capital W) is undefined — Array.from will throw

Window (capital W) is the Window interface/constructor object, not the current window instance. Window.clipped will be undefined, causing Array.from(undefined) to throw TypeError: undefined is not iterable whenever getClipped() is invoked.

Suggested change
.from(Window.clipped)
.filter(id=>id)
.from(window.clipped)

Comment on lines +361 to +369
"<table"
"border='0' "
"cellpadding='0' "
"cellspacing='0' "
"width='%POSpx' "
"height='%POSpx' "
"><tr><td>",
id,
w - 2,
Copy link

Choose a reason for hiding this comment

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

Missing space after <table produces malformed HTML

C adjacent-string-literal concatenation merges "<table" and "border='0' " into "<tableborder='0' ...", which is invalid HTML. All attributes will be silently ignored by browsers (or worse, cause parse errors) in the Netscape DOM branch.

Suggested change
"<table"
"border='0' "
"cellpadding='0' "
"cellspacing='0' "
"width='%POSpx' "
"height='%POSpx' "
"><tr><td>",
id,
w - 2,
htrAddBodyItem_va(s,
"<div ID=\"sp%POSarea\">"
"<table "
"border='0' "
"cellpadding='0' "
"cellspacing='0' "
"width='%POSpx' "
"height='%POSpx' "
"><tr><td>",
id,
w - 2,
h - 2
);

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

Labels

ai-review Request AI review of this PR enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Autoscaling/responsive widgets using CSS calc()

1 participant