# Oasis Dashboard JS Patterns

> Dashboard JavaScript patterns for OASIS. Use when editing oasis/static/js/dashboard/*.js, dashboard-app.js, dashboard CSS/templates, or any frontend/backend contract (progress, stats, filters, report modal, audit comparison, assistant chat UI).

- Skill: `psyray/oasis-dashboard-js-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add psyray/oasis-dashboard-js-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/psyray/oasis-dashboard-js-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: psyray (https://skillmd.com/u/psyray)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/psyray/oasis-dashboard-js-patterns

---


# OASIS Dashboard JS Patterns

- **No duplicated logic**: **Never** repeat the same DOM, fetch, formatting, or state-handling logic in multiple files. Extract to the appropriate module (`utils`, `api`, etc.) and import—same rule as Python: one canonical behavior, **zero** parallel copies.
- Keep dashboard code modular by concern (`bootstrap`, `api`, `filters`, `interactions`, `modal`, `views`, `utils`, **`assistant`**).
- **Assistant**: Chat UI and client logic live in `assistant.js` with shared constants in `assistant-constants.js`; HTTP calls go through `api.js` (same pattern as other dashboard APIs—no duplicated `fetch` wiring in `modal.js`). Changing assistant request/response shapes requires updating both JS and `oasis/web.py` handlers together.
- Extend the shared `DashboardApp` namespace instead of introducing parallel globals.
- Centralize formatting, display helpers, and repeated request/response handling in utility modules; adding a second inline copy of an existing pattern is **not** acceptable—extend the shared helper instead.
- When fixing UI bugs, update both interaction scripts and matching templates if the issue spans behavior and markup.
- Prefer compatibility-safe fixes that preserve existing dashboard data contracts (`report` fields and optional defaults).
- Vulnerability stats come from **`stats` on `format: "json"`** rows (`total_findings`, `high_risk`, etc.). Reload must pass **`force=1`** to both `/api/stats` and `/api/reports` when refreshing the filesystem-backed index. The same applies to **`/api/progress`** when forcing a refresh of progress from the index (use `DashboardApp.fetchProgress(true)` or equivalent so the request includes `force=1`).
- **Scan progress (REST + Socket.IO)**: Poll `GET /api/progress` and map the JSON through `DashboardApp.applyProgressPayload` into `DashboardApp.progressState`—do not scatter ad-hoc progress field reads across views. For realtime updates, listen for the Socket.IO event `scan_progress` (server emits the same shape as the REST payload). If `window.__OASIS_DASHBOARD__.realtimeEnabled === false` (set from the server in `oasis/templates/dashboard.html`), skip opening the socket and rely on REST only.
- **Stale progress guard**: `applyProgressPayload` ignores an incoming payload when `updated_at` is lexicographically older than the current `progressState.updated_at` (valid for UTC ISO-8601 strings from `oasis.report.progress_timestamp_iso()`). If the server timestamp format changes, update **both** Python and this guard (or switch both sides to a comparable numeric epoch).
- **Shared progress helpers** (in `api.js`, not duplicated in `views.js`): `DashboardApp.normalizeProgressNumber`, `DashboardApp.htmlProgressPhaseLabelWithStatus`, and `DashboardApp.PhaseRowStatus` (frozen object, wire strings aligned with `oasis.enums.PhaseRowStatus`).
- **Progress visibility scope**: Dashboard progress UI keeps only high-level summary phases (embeddings/scan/deep/graph pipeline). Low-level per-file/per-vulnerability rows (e.g. adaptive subphases) must stay hidden from dashboard payload rendering.
- **UI surfaces**: Reuse existing progress-related styles in `oasis/static/css/dashboard.css` before adding new class names.
- Modal preview: **`json`** uses `/api/report-json/...`; **`md`** uses `/api/report-content/...` only for legacy reports (no sibling `json/<stem>.json`).
- **Model filtering UX**: Model-tag filtering is multi-select; persist selection state on the card dataset (`selectedModels`), reuse shared model helpers from `utils.js`, and keep date chips + audit comparison table filtered consistently.
- **Audit comparison contract**: Audit card comparison rows depend on parsed `audit_metrics` from `/api/reports`; prefer **`json/audit_report.json`** metrics when the sibling file exists (fallback: Markdown parsing). When changing metrics keys (`count`, `avg_score`, `median_score`, `max_score`, `min_score`, `high`, `medium`, `low`), update `web.py` extraction and dashboard table rendering together. Keep **`audit-report-paths.js`** / `auditReportJsonSiblingPath` aligned with Python **`json_sibling_for_format_artifact`** (`audit_report` stem).
- **Severity filter**: Tier-band severity filtering must stay aligned with **`/api/reports`** / **`/api/stats`** query params; stats payloads expose **`severity_finding_totals`** (not the legacy **`severities`** key). Update `filters.js`, `views.js`, `api.js`, and `web.py` together.
- **Filtered previews**: Wrap preview and metadata fetches with **`DashboardApp.urlWithActiveFilters`** (or equivalent) so **`/api/report-json`**, **`/api/report-html`**, **`/api/report-content`** requests cannot bypass the active dashboard filter set—server-side guards in **`web.py`** are authoritative.
- **Theme**: **`bootstrap.js`** defines **`THEME_CHANGE_EVENT`** (`oasis:theme-change`), **`getDashboardChartThemeColors`**, and theme persistence helpers. Chart.js dashboards (**`views.js`**, **`executive-preview.js`**) must refresh axis/grid colors when the theme changes—subscribe to the shared event; do not duplicate theme palettes ad hoc.

## Report modal architecture (canonical — all report types)

**Goal**: Every report opened in `#report-modal` (vulnerability JSON preview, executive MD/HTML, audit MD/HTML, future canonical JSON types) follows **one** UX/code architecture: compact body, **section navigation** (TOC buttons or equivalent with **stable** anchor IDs), **optional Chart.js** summaries fed by structured data or a small read-only meta endpoint, and **one** assistant integration path with **mode variants** (e.g. file/chunk/finding selectors only for vulnerability JSON; scan-wide vs single-report context for executive / audit) — avoid parallel modal pipelines per report kind.

- **CSS**: Scope under `#report-modal-content`; extend [`oasis/static/css/report_preview.css`](oasis/static/css/report_preview.css) and reuse shared classes (`report-toc`, chart wrappers, section spacing) before adding unrelated class names in [`dashboard.css`](oasis/static/css/dashboard.css).
- **JS**: Report “kind” detection and one-shot setup (TOC wiring, chart init, which assistant variant to show) live in **one** initializer invoked from [`modal.js`](oasis/static/js/dashboard/modal.js) (`_finalizeReportModalView` path) — do not scatter stem/`report_type` checks across unrelated modules.
- **Server**: HTML preview composition and meta endpoints belong in [`oasis/web.py`](oasis/web.py) with parsing/helpers under [`oasis/helpers/`](oasis/helpers/) so executive, audit, and vulnerability stay DRY unless a genuinely different document shape requires a separate template.
- **Assistant**: Any new request field or UI mode is implemented in [`assistant.js`](oasis/static/js/dashboard/assistant.js), [`api.js`](oasis/static/js/dashboard/api.js), and [`web.py`](oasis/web.py) together; copy strings and labels go through [`assistant-constants.js`](oasis/static/js/dashboard/assistant-constants.js).

**Audit (and future modals)** should adopt this same skeleton when brought in line — reuse TOC/chart/assistant patterns rather than cloning a second modal stack.

