Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions .claude/skills/coding-standards/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Write a comment only when the code cannot say it itself. Before adding one, chec
- repeating what a guard clause or an early return already shows
- "what changed", "added for issue N", "new in 2.4" — that is git history

Match the surrounding density rather than your own taste: the JS modules run one short lowercase line per top-level function and few inside bodies. `filter.js` sits near 6% comment lines — a new block far above the file's ratio is a signal to cut, not a new house style. Do not restyle existing comments while you are there.
Match the surrounding density rather than your own taste: the JS modules run one short lowercase line per top-level function and few inside bodies. `filter/pipeline.js` sits near 6% comment lines — a new block far above the file's ratio is a signal to cut, not a new house style. Do not restyle existing comments while you are there.

Robot `[Documentation]` is API documentation, not a comment: it renders in `log.html`. Write it for a keyword whose contract or trap is invisible at the call site; skip it when the name already says everything (`Open Compare Page`).

Expand All @@ -41,7 +41,8 @@ Robot `[Documentation]` is API documentation, not a comment: it renders in `log.

## CSS

- Files: `css/base.css`, `colors.css`, `components.css`, `dark.css` — concatenated in sorted order at generation time.
- Files: `css/base.css`, `colors.css`, `components/01-…08-*.css`, `dark.css` — concatenated in **path order** at generation time,
so the numbered prefixes inside `css/components/` are the cascade order. Renaming one reorders the stylesheet.
- Reuse existing Bootstrap/DataTables class conventions, keep selectors shallow, prefer CSS variables for theme values.

## Docs
Expand Down Expand Up @@ -79,11 +80,27 @@ ES `import`/`export` syntax in source, but the Python `DependencyProcessor` stri
```
main.js startup entry; imports and calls all setup functions
common.js shared utilities (format_duration, add_alert, path helpers, …)
filter.js filter pipeline + filter profiles
localstorage.js settings persistence, merge_deep / merge_view / merge_layout
layout.js GridStack setup, customize/save layout, undo/redo snapshots
menu.js tab/page switching (update_menu)
eventlisteners.js wires every modal/filter/settings listener on load
filter/
pipeline.js filter_data + every apply_*/filter_* run filter, setup_filtered_data_and_filters
section_selects.js the per-section suite/test/tag/keyword/compare selects
modal_options.js fills the filter modal's option lists (runs, tags, dates, metadata, versions, custom)
suite_path.js suite path navigator and its run/data filters
availability.js per-option run counts and greying out (issue #296)
controls.js filter indicators, checkbox handlers, every clear_* function
profiles.js filter profile capture/compare/apply/storage/edit mode
profile_merge.js merge_two_profiles ("largest horizon" rules)
eventlisteners/
filter_modal.js wires the filter modal
merge_profiles.js wires the merge-profiles dialogue
settings_modal.js wires the settings modal (toggles, theme colours, branding, JSON config)
confirm_modal.js confirm_action promise wrapper
section_filters.js wires the per-section filters and the overview switches
graph_view_buttons.js fullscreen, graph type buttons, show/hide, per-graph toggles
collapsables.js collapse icons
overview_listeners.js run card version links and the overview "sort by" selects
statwidgets.js custom stat widgets (CRUD, render, modal)
linkwidgets.js custom link widgets (same pattern)
customsections.js user-defined dashboard sections
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/dashboard-graphs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ Steps:
3. **`js/graph_creation/<section>.js`** — `build_<snake_key>_config()` calling `get_graph_config(type, data, title, xTitle, yTitle)`, then the one-liners `create_*` (`create_chart(id, build_fn)`) and `update_*` (`update_chart`). Respect `settings.graphTypes.<key>GraphType` for each view option and `inFullscreen && inFullscreenGraph.includes(key)` for larger limits. Export both.
4. **`js/graph_creation/all.js`** — import and call `create_*` in `create_dashboard_graphs()` and `update_*` in `update_dashboard_graphs()` inside the right section block.
5. **`js/variables/information.js`** — add `key` to `graphKeys` (generates Fullscreen/Close/Move/Show/Hide tooltips) and one `"<key>Graph<View>": "…"` tooltip per view option.
6. **`js/variables/settings.js`** — nothing for plain graphs (types and show/hide lists are derived). Only add a `settings.switch.*` entry if the graph gets its own toggle (e.g. `ignoreSkips`), wired in `eventlisteners.js` and persisted via `set_local_storage_item`.
6. **`js/variables/settings.js`** — nothing for plain graphs (types and show/hide lists are derived). Only add a `settings.switch.*` entry if the graph gets its own toggle (e.g. `ignoreSkips`), wired in `eventlisteners/graph_view_buttons.js` and persisted via `set_local_storage_item`.
7. **Template** — nothing; sections already exist (`#runStatisticsSection` … `#runDataHidden`). Compare/Table graphs are the exception and have their own markup patterns in `graphmetadata.js` (`_tableHtml`).
8. **CSS** — only for a new `viewOptionClassMap` icon class (`css/components.css`).
8. **CSS** — only for a new `viewOptionClassMap` icon class (`css/components/06-graphs.css`).
9. **Tests** — reference screenshot for the section changes: regenerate `dashboard_output/<section>/base<Section>Section.png` in Docker; add a JS unit test for the `graph_data` function.
10. **Docs** — row in the section table of `docs/graphs-tables.md` (Graph Name / Views / Views Description / Notes).

Expand Down
12 changes: 6 additions & 6 deletions .claude/skills/filtering-and-settings/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The dashboard front-end has three tightly coupled systems:
2. **Filtering** — a multi-stage pipeline that produces `filteredRuns/Suites/Tests/Keywords` from the raw decoded data
3. **Layout** — GridStack-based drag-and-drop positioning + section ordering, also persisted in `settings`

Key files: `js/variables/settings.js`, `js/variables/globals.js`, `js/filter.js`, `js/localstorage.js`, `js/eventlisteners.js`, `js/layout.js`
Key files: `js/variables/settings.js`, `js/variables/globals.js`, `js/filter/` (8 modules), `js/localstorage.js`, `js/eventlisteners/` (8 modules), `js/layout.js`

---

Expand Down Expand Up @@ -58,7 +58,7 @@ All graph-level toggle switches (the controls directly on individual graphs, not

- `update_switch_local_storage(key, state, firstLoad)` — for checkbox switches (overview toggles, suite paths); on `firstLoad=true`, reads from `settings` and sets the DOM element
- Direct `set_local_storage_item("switch.*", value)` calls — for graph-specific switches like `ignoreSkips`, `onlyLastRunSuite`, `heatmapStatus`, etc.
- Data-driven initialization loops in `eventlisteners.js` — arrays of `[elementId, settingsKey]` pairs that restore checkbox/select states on load and wire change listeners that persist to localStorage
- Data-driven initialization loops in `eventlisteners/graph_view_buttons.js` — arrays of `[elementId, settingsKey]` pairs that restore checkbox/select states on load and wire change listeners that persist to localStorage

### Deep Merge Behavior (`merge_deep`)

Expand All @@ -80,7 +80,7 @@ In `localstorage.js`: if the placeholder string was not replaced (i.e. the strin

---

## Filtering Pipeline (`js/filter.js`)
## Filtering Pipeline (`js/filter/pipeline.js`)

`setup_filtered_data_and_filters()` is called when the filter modal closes, when navigating from the overview page, and on initial load. Stages, in order:

Expand Down Expand Up @@ -129,7 +129,7 @@ settings.filterProfiles = {
```
A profile need not contain all keys — only the keys that were checked when the profile was saved.

### Key functions (`filter.js`)
### Key functions (`js/filter/`)
| Function | What it does |
|---|---|
| `capture_current_filters()` | Reads all filter DOM controls → plain profile object |
Expand Down Expand Up @@ -178,7 +178,7 @@ Key mutable globals used across modules:

| Global | Description |
|---|---|
| `filteredRuns`, `filteredSuites`, `filteredTests`, `filteredKeywords` | Current filtered data arrays (reassigned by `filter.js` after each filter pass) |
| `filteredRuns`, `filteredSuites`, `filteredTests`, `filteredKeywords` | Current filtered data arrays (reassigned by `filter/pipeline.js` after each filter pass) |
| `filteredAmount` | Total count before amount-slicing (shown as "showing X of N runs") |
| `gridUnified`, `gridRun`, `gridSuite`, `gridTest`, `gridKeyword`, `gridCompare` | GridStack instances per section |
| `gridEditMode` | Boolean: layout editor is active |
Expand Down Expand Up @@ -221,7 +221,7 @@ In normal mode, hidden graphs are rendered to a `#[section]DataHidden` container

---

## Event Wiring (`js/eventlisteners.js`)
## Event Wiring (`js/eventlisteners/`)

`setup_filter_modal()` — wires all filter modal controls. The Bootstrap `hidden.bs.modal` event triggers `setup_filtered_data_and_filters()` + `update_dashboard_graphs()` (with a loading overlay and double `requestAnimationFrame` to let the DOM settle before rendering).

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/js-bundling/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ This block replaces `<!-- placeholder_javascript -->` in `templates/dashboard.ht

## How CSS Is Bundled

`DependencyProcessor._inline_css_files()` reads all `.css` files under `robotframework_dashboard/css/` in sorted order (`base.css`, `colors.css`, `components.css`, `dark.css`) and concatenates them into a single `<style>` block that replaces `<!-- placeholder_css -->`.
`DependencyProcessor._inline_css_files()` reads all `.css` files under `robotframework_dashboard/css/` recursively in **path order** (`base.css`, `colors.css`, `components/01-cards.css` … `components/08-section-filters-modal.css`, `dark.css`) and concatenates them into a single `<style>` block that replaces `<!-- placeholder_css -->`.

---

Expand Down
8 changes: 4 additions & 4 deletions .claude/skills/js-features/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Must contain:

**Import the new module from `layout.js`** — the bundler discovers it via the import graph from `main.js`.

### Part 2 — CSS (`css/components.css`)
### Part 2 — CSS (`css/components/03-widgets.css`)

Required CSS classes:

Expand Down Expand Up @@ -167,7 +167,7 @@ Some keys only exist in localStorage (not in the `settings` defaults object). Th
| `layouts` | `layout.js` |
| `libraries` | keyword library toggles |
| `theme` | theme switcher |
| `filterProfiles` | `filter.js` |
| `filterProfiles` | `filter/profiles.js` |
| `statWidgets` | `statwidgets.js` |
| `linkWidgets` | `linkwidgets.js` |
| `customSections` | `customsections.js` |
Expand All @@ -193,7 +193,7 @@ The `STAT_WIDGET_COLORS` and `STAT_WIDGET_BG_COLORS` arrays from `js/variables/s
import { STAT_WIDGET_COLORS, STAT_WIDGET_BG_COLORS } from './variables/statwidgetdefs.js';
```

The color CSS classes they reference (`.white-text`, `.blue-text`, `.blue-bg`, etc.) are defined in `css/components.css` and `css/base.css`.
The color CSS classes they reference (`.white-text`, `.blue-text`, `.blue-bg`, etc.) are defined in `css/components/03-widgets.css`, `css/components/04-status-colors.css` and `css/base.css`.

---

Expand All @@ -216,5 +216,5 @@ Do not use an `<a>` tag as the outermost clickable wrapper for GridStack items
| `js/<name>widgets.js` | **Create** — full widget module |
| `js/layout.js` | Import + add to snapshots + render calls + setup call |
| `js/localstorage.js` | (1) Add key to `merge_deep` whitelist AND (2) add `data-gs-id` prefix to `merge_layout` allowlist — **both are required** or positions reset on refresh |
| `css/components.css` | Add all widget CSS classes |
| `css/components/03-widgets.css` | Add all widget CSS classes |
| `templates/dashboard.html` | Add modal HTML |
11 changes: 10 additions & 1 deletion .claude/skills/server-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ description: "The optional FastAPI server (--server): every REST endpoint, HTTP

## Overview

The server is an optional FastAPI application started with `robotdashboard --server` (or `-s host:port:user:pass`). It wraps a persistent `RobotDashboard` instance and exposes REST endpoints for managing outputs and serving the dashboard. Implementation is in `robotframework_dashboard/server.py`.
The server is an optional FastAPI application started with `robotdashboard --server` (or `-s host:port:user:pass`). It wraps a persistent `RobotDashboard` instance and exposes REST endpoints for managing outputs and serving the dashboard. Implementation is split across four modules:

| File | Holds |
|---|---|
| `server.py` | `ApiServer`: app + auth setup, the HTML serving routes (`/`, `/admin`, `/log`, `/refresh-dashboard`, catch-all), `run()` |
| `server_models.py` | The Pydantic models (`AddOutput`, `RemoveOutputs`, `AddLog`, …), their `*_model_config` OpenAPI examples and `model_examples()` |
| `server_routes_outputs.py` | `register_output_routes(server, authenticate)` — `/get-outputs`, `/add-outputs`, `/add-output-file`, `/remove-outputs` |
| `server_routes_logs.py` | `register_log_routes(server, authenticate)` — `/get-logs`, `/add-log`, `/add-log-file`, `/remove-log` |

The route modules are plain functions that take the `ApiServer` instance (they read `server.robotdashboard`, `server.log_dir`, `server.no_autoupdate`) and the `authenticate` dependency built in `_setup_routes`. A new endpoint goes in the matching route module; a new model goes in `server_models.py`. `server.py` re-exports the models, so `from robotframework_dashboard.server import ResponseMessage` keeps working.

---

Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/testing/javascript-unit-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ tests/javascript/
| File | What it covers |
|---|---|
| `common.test.js` | `format_duration`, `strip_tz_suffix`, `get_next_folder_level`, `compare_to_average`, case converters, `parse_run_start`, `format_relative_time`, `format_run_start_exact`, `transform_file_path`, `combine_paths`, `debounce` |
| `filter.test.js` | Re-implemented pure logic from `filter.js` (`sort_wall_clock`, `remove_milliseconds`, `remove_timezones`, `filter_data`) plus the exported `convert_timezone` — most of `filter.js` reads the DOM and is covered by robot tests instead |
| `filter.test.js` | Re-implemented pure logic from `filter/pipeline.js` (`sort_wall_clock`, `remove_milliseconds`, `remove_timezones`, `filter_data`) plus the exported `convert_timezone` — most of `filter/` reads the DOM and is covered by robot tests instead |
| `localstorage.test.js` | `merge_deep`, `set_nested_setting`, `merge_view_section_or_graph` and related merge helpers |
| `log.test.js` | `open_log_file` (regression for issue #311) |
| `graph_data/helpers.test.js` | `convert_timeline_data` |
Expand All @@ -73,7 +73,7 @@ Only **pure functions** (no DOM access, no Chart.js instances, no DataTables) sh

| Classification | Examples |
|---|---|
| **Pure / testable** | `common.js`, `filter.js`, `localstorage.js`, `graph_data/tooltip_helpers.js`, `graph_data/failed.js`, `graph_data/flaky.js`, `graph_data/donut.js`, `graph_data/messages.js`, `graph_data/time_consuming.js`, `graph_data/graph_config.js`, `graph_data/helpers.js` |
| **Pure / testable** | `common.js`, `filter/pipeline.js`, `filter/availability.js`, `localstorage.js`, `graph_data/tooltip_helpers.js`, `graph_data/failed.js`, `graph_data/flaky.js`, `graph_data/donut.js`, `graph_data/messages.js`, `graph_data/time_consuming.js`, `graph_data/graph_config.js`, `graph_data/helpers.js` |
| **DOM-dependent / not testable** | `graph_data/statistics.js`, `graph_data/duration.js`, `graph_data/heatmap.js`, `graph_data/duration_deviation.js`, `graph_creation/*.js`, `js/main.js`, `js/admin_page/*.js` |

If a module has **some** pure functions and **some** DOM-dependent functions, test only the pure ones. Do not attempt to mock `document`, `window.Chart`, or DataTables.
Expand Down
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,17 @@ Optional `--server` mode hosts the same pipeline behind FastAPI (upload endpoint
| `robotframework_dashboard/abstractdb.py` | `AbstractDatabaseProcessor` for custom backends (`--databaseclass`) |
| `robotframework_dashboard/dashboard.py` | `DashboardGenerator` — template rendering |
| `robotframework_dashboard/dependencies.py` | `DependencyProcessor` — JS/CSS inlining, CDN/offline switching |
| `robotframework_dashboard/server.py` | FastAPI server |
| `robotframework_dashboard/server.py` | `ApiServer` — app setup, auth, the HTML serving routes (`/`, `/admin`, `/log`, catch-all) |
| `robotframework_dashboard/server_models.py` | Pydantic request/response models + their OpenAPI examples |
| `robotframework_dashboard/server_routes_outputs.py` / `server_routes_logs.py` | The `/…-output(s)` and `/…-log(s)` endpoints, registered onto the app by `_setup_routes` |
| `robotframework_dashboard/robotdashboardlistener.py` | Robot listener that uploads results to the server |
| `robotframework_dashboard/js/main.js` | Browser startup entry; everything is reached via its import graph |
| `robotframework_dashboard/templates/dashboard.html`, `admin.html` | Templates with string placeholders (not Jinja) |

Frontend source: `robotframework_dashboard/js/` and `css/`. **There is no Node bundler** for the dashboard — Python does the bundling. `package.json` exists only for the VitePress docs site.

The two largest front-end concerns live in directories, not single files: `js/filter/` (pipeline, modal option lists, suite path, option availability, controls, profiles) and `js/eventlisteners/` (one module per modal or listener group). `css/components/` is numbered because path order is cascade order. The `coding-standards` skill has the per-file map.

---

## Hard rules
Expand Down
9 changes: 9 additions & 0 deletions robotframework_dashboard/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ body {
padding-right: 0px !important;
}

/* Bootstrap locks the page behind a modal with overflow:hidden on <body>, but the dashboard's
scrollbar sits on <html> (.html-scroll above), so the page keeps scrolling without this.
scrollbar-gutter keeps the scrollbar width reserved, otherwise the layout jumps sideways
every time a modal opens. */
html:has(body.modal-open) {
overflow: hidden;
scrollbar-gutter: stable;
}

h4,
h5,
h6 {
Expand Down
Loading