Skip to content

refactor: split the largest source files, fix modal scroll lock and popup placement (#353, #354) - #355

Merged
timdegroot1996 merged 2 commits into
mainfrom
refactor/split-large-files
Sep 25, 2026
Merged

timdegroot1996 merged 2 commits into
mainfrom
refactor/split-large-files

Conversation

@timdegroot1996

Copy link
Copy Markdown
Collaborator

Fixes #353
Fixes #354

Problem

#353 — four files had grown large enough that any change touching them meant reading the whole file: js/filter.js (1922 lines), js/eventlisteners.js (1564), server.py (908) and css/components.css (854). Each was doing several unrelated jobs, so the size was not inherent — every one had clean internal seams.

#354 — two visual defects around modals. The page scrolled behind an open modal instead of staying put, and long information popups near the bottom of the filter modal were cut off by the bottom of the window.

Root cause

  • Modal scroll: Bootstrap locks the page with overflow: hidden on <body>, but the dashboard's scrollbar is on <html> (class="html-scroll" in the template, .html-scroll { overflow-y: scroll } in base.css). <html> is the scroll container, so the body-level lock had nothing to lock.
  • Popups: createTooltip() in js/information.js places the popup below its icon, then measures it and shifts it left or right to stay inside the window. There was no equivalent vertical correction.

Fix

Splits (#353)

Was Lines Now
server.py 908 server.py (211) + server_models.py (289) + server_routes_outputs.py (275) + server_routes_logs.py (193)
js/eventlisteners.js 1564 js/eventlisteners/ — 8 modules, 25–386 lines
js/filter.js 1922 js/filter/ — 8 modules, 107–472 lines
css/components.css 854 css/components/ — 8 files, 30–247 lines
  • server.py — the first 295 lines were OpenAPI example dicts and Pydantic models with no logic, and the routes were one 520-line _setup_routes. The route modules are plain register_*_routes(server, authenticate) functions taking the ApiServer instance; server.py re-exports the models so from robotframework_dashboard.server import ResponseMessage keeps working.
  • js/eventlisteners.js — one module per modal or listener group: filter_modal, merge_profiles, settings_modal, confirm_modal, section_filters, graph_view_buttons, collapsables, overview_listeners. The merge-profiles dialogue wiring moved out of setup_filter_modal into setup_merge_profiles_modal().
  • js/filter.js — split along its existing concerns: pipeline, section_selects, modal_options, suite_path, availability, controls, profiles, profile_merge.
  • css/components.css — _inline_css_files() concatenates in path order, and that order is the cascade. A subdirectory keeps the files in the exact slot components.css held between colors.css and dark.css, and the numbered prefixes keep their order within it. The first file carries a comment saying renaming one reorders the stylesheet.

Three imports that only ever resolved because the bundler flattens every module into one scope are fixed along the way: filteredAmount was imported from variables/globals.js instead of variables/data.js, six setup_*_in_select functions were used without being imported, and set_filter_show_current_version was imported from graph_creation/overview.js, which does not export it. Two are deliberately left as bundle globals, because importing them would create a source-level cycle back into the modules that import filter: update_filters_button_indicator and clear_project_filter.

arguments.py, the modals in templates/dashboard.html and the large test files were considered and deliberately left alone.

Fixes (#354)

  • css/base.css — html:has(body.modal-open) { overflow: hidden; scrollbar-gutter: stable }. The gutter keeps the scrollbar width reserved, otherwise the layout jumps sideways every time a modal opens. :has() is already used elsewhere in the stylesheet. The admin page is unaffected: its <html> has no html-scroll, so Bootstrap's own lock works there.
  • js/information.js — after the existing measurement, flip the popup above its icon when it would overflow the bottom, pinned to the top edge when the popup is taller than the window. The sides were already handled; the top only becomes reachable after a flip, which the clamp covers.

CLAUDE.md and the coding-standards, js-bundling, js-features, filtering-and-settings, dashboard-graphs, server-api and testing skills carry the new file maps, so the splits do not just move the search cost around.

Tests

Tier Result
Python 406 passed, 100% coverage on all four server modules
JS 337 passed
Robot (Docker) 119 tests, 119 passed, 0 failed, single pass, no reruns

Two regression tests, each verified to fail on main and pass here:

  • 03_dashboard.robot — The Page Behind An Open Modal Does Not Scroll: opens the database modal, sends a wheel event, asserts window.scrollY is unchanged. The database modal is short enough not to scroll itself, so the wheel reaches the page whenever the lock is missing. On main: 15 passed, 1 failed (page scrolls 0 → 900).
  • 06_filters.robot — Information Popups In The Filter Modal Stay Inside The Window: hovers every information icon in the filter modal and asserts each popup is fully inside the viewport. Shrinks the window to 1600x800 first, since the suite's 2000px-high viewport is too tall for any popup to overflow. On main: 29 passed, 1 failed (filterAmountInformation 616–905 and filterSuitePathsInformation 660–881, both past 800).

The refactor is behaviour-preserving, checked rather than assumed:

  • The bundled <script> is line-for-line identical to the one built from main, except the extracted setup_merge_profiles_modal() wrapper, its call, and one corrected comment.
  • The bundled <style> is identical except the /* === filename === */ section markers, so the cascade is unchanged.
  • All 67 JS modules' imports resolve to a real export of the target module.

Notes

No user-visible behaviour changes from the refactor, so no docs/ updates; the CHANGELOG is written at release time. example/robot_dashboard.html and example/robot_results.db are release-time artifacts and are deliberately not regenerated here, although the CSS fix will show up in them at the next release.

One trap worth recording for local runs: a stale gitignored build/ directory makes setuptools ship both the old filter.js and the new filter/ in the wheel, which puts two const dashboardPages in the bundle and kills every browser test with a SyntaxError. Run rm -rf build before pip install . after files move. Related: single-suite robot runs skip the __init__.robot teardown, so robotdashboard_shared.html survives and is reused by the next run.

🤖 Generated with Claude Code

timdegroot1996 and others added 2 commits September 25, 2026 23:17
filter.js (1922), eventlisteners.js (1564), server.py (908) and
components.css (854) each did several unrelated jobs, so every change
touching them meant reading the whole file.

- server.py -> server.py (app, auth, HTML routes) + server_models.py +
  server_routes_outputs.py + server_routes_logs.py. The route modules are
  register_*_routes(server, authenticate) functions; server.py re-exports
  the models so existing imports keep working.
- js/eventlisteners.js -> js/eventlisteners/, one module per modal or
  listener group. The merge-profiles wiring moved out of setup_filter_modal
  into setup_merge_profiles_modal().
- js/filter.js -> js/filter/: pipeline, section_selects, modal_options,
  suite_path, availability, controls, profiles, profile_merge.
- css/components.css -> css/components/. CSS is concatenated in path
  order, which is the cascade order, so the subdirectory keeps the files
  between colors.css and dark.css and the numbered prefixes keep their
  order within it.

Also fixes three imports that only ever resolved because the bundler
flattens every module into one scope: filteredAmount came from the wrong
module, six setup_*_in_select functions were used without being imported,
and set_filter_show_current_version was imported from a module that does
not export it.

Behaviour is unchanged: the bundled <script> is identical to the one built
from main apart from the extracted function and its call, and the bundled
<style> is identical apart from the section marker comments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…354)

The dashboard's scrollbar sits on <html> (.html-scroll), but Bootstrap
locks the page by setting overflow:hidden on <body>, so the lock never
applied and the page scrolled behind every open modal. Lock <html> when
body carries .modal-open, with scrollbar-gutter so the layout does not
jump sideways when the scrollbar goes away.

createTooltip() corrected only horizontal overflow, so long information
popups low in the filter modal ran past the bottom of the window. Flip
them above their icon when they would overflow, pinned to the top edge
when the popup is taller than the window.

Both get a robot test, each checked to fail before the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@timdegroot1996
timdegroot1996 merged commit 04d893b into main Sep 25, 2026
3 checks passed
@timdegroot1996 timdegroot1996 mentioned this pull request Sep 26, 2026
@timdegroot1996
timdegroot1996 deleted the refactor/split-large-files branch September 26, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant