From d9f07dcd63c6a94301da54babb13d638e78f6ff9 Mon Sep 17 00:00:00 2001 From: fs Date: Fri, 13 Mar 2026 22:21:37 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20web/=20quick=20wins=20=E2=80=94=20CSS=20?= =?UTF-8?q?cleanup,=20ARIA=20tabs,=20and=20file=20header=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CSS: - Remove duplicate li::before block in app-search.css - Fix typo: search-reuslt → search-result in app-search.css - Remove commented-out CSS rules in app-flash.css - Add descriptive header comment to all 27 CSS component files JS: - Complete WAI-ARIA Tabs pattern: generate IDs, add aria-controls on tab buttons and aria-labelledby on tab panels (app-tabs.js) - Add JSDoc header comments to ~33 JS files (core + components) - Add explanatory block comment to app-boot.js (why classic IIFE) Co-Authored-By: Claude Opus 4.6 --- web/css/components/app-active-filter-chips.css | 1 + web/css/components/app-badges.css | 1 + web/css/components/app-blockquote.css | 1 + web/css/components/app-brand.css | 1 + web/css/components/app-breadcrumb.css | 1 + web/css/components/app-buttons.css | 1 + web/css/components/app-confirm-dialog.css | 1 + web/css/components/app-dashboard-titlebar.css | 1 + web/css/components/app-details-card.css | 1 + web/css/components/app-details-titlebar.css | 1 + .../components/app-details-validation-summary.css | 1 + web/css/components/app-details.css | 1 + web/css/components/app-empty-state.css | 1 + web/css/components/app-filter-drawer.css | 1 + web/css/components/app-flash.css | 8 +------- web/css/components/app-form-info-tile.css | 1 + web/css/components/app-forms.css | 1 + web/css/components/app-list-table.css | 1 + web/css/components/app-list-tabs.css | 1 + web/css/components/app-list-titlebar.css | 1 + web/css/components/app-list-toolbar.css | 1 + web/css/components/app-page-copy.css | 2 +- web/css/components/app-page-editor.css | 1 + web/css/components/app-search.css | 15 ++------------- web/css/components/app-tabs.css | 1 + web/css/components/app-tile.css | 1 + web/css/components/app-tooltips.css | 1 + web/js/app-boot.js | 8 ++++++++ web/js/components/app-active-filter-chips.js | 3 +++ web/js/components/app-bulk-selection.js | 3 +++ web/js/components/app-color-default-toggle.js | 3 +++ web/js/components/app-confirm-actions.js | 3 +++ web/js/components/app-contrast-toggle.js | 3 +++ web/js/components/app-copy-badge.js | 3 +++ .../components/app-custom-field-options-toggle.js | 3 +++ web/js/components/app-details-state.js | 3 +++ web/js/components/app-filter-drawer.js | 3 +++ web/js/components/app-flash-auto-dismiss.js | 3 +++ web/js/components/app-fslightbox-refresh.js | 3 +++ web/js/components/app-global-search.js | 3 +++ web/js/components/app-multiselect-cascade.js | 3 +++ web/js/components/app-multiselect-init.js | 3 +++ web/js/components/app-nav-history.js | 3 +++ web/js/components/app-page-editor.js | 3 +++ web/js/components/app-password-hints.js | 3 +++ web/js/components/app-password-toggle.js | 3 +++ web/js/components/app-settings-telemetry.js | 3 +++ web/js/components/app-standard-detail-page.js | 3 +++ web/js/components/app-tabs.js | 14 ++++++++++++-- web/js/components/app-tenant-sso-toggle.js | 3 +++ web/js/components/app-tenant-switcher.js | 3 +++ web/js/components/app-theme-toggle.js | 3 +++ web/js/components/app-toggle-aside-panels.js | 3 +++ web/js/components/app-toggle-details-aside.js | 3 +++ web/js/components/app-toggle-sidebar.js | 3 +++ web/js/core/app-confirm-dialog.js | 3 +++ web/js/core/app-detail-page-factory.js | 3 +++ web/js/core/app-details-action-policy.js | 3 +++ web/js/core/app-details-open-state.js | 3 +++ web/js/core/app-dom.js | 3 +++ web/js/core/app-page-config.js | 3 +++ web/js/core/app-telemetry.js | 3 +++ 62 files changed, 147 insertions(+), 23 deletions(-) diff --git a/web/css/components/app-active-filter-chips.css b/web/css/components/app-active-filter-chips.css index bec8aba..23e938f 100644 --- a/web/css/components/app-active-filter-chips.css +++ b/web/css/components/app-active-filter-chips.css @@ -1,4 +1,5 @@ @layer components { + /* Active filter chips — removable pill indicators showing applied grid filters. */ .app-active-filters { margin: 0 0 0.875rem; } diff --git a/web/css/components/app-badges.css b/web/css/components/app-badges.css index 84e3d94..68cd4a5 100644 --- a/web/css/components/app-badges.css +++ b/web/css/components/app-badges.css @@ -1,4 +1,5 @@ @layer components { + /* Badge pills — inline status/role/tag indicators with color variants. */ .badge[role="button"], .badge { display: inline-flex; diff --git a/web/css/components/app-blockquote.css b/web/css/components/app-blockquote.css index 3ef40b9..1497d4b 100644 --- a/web/css/components/app-blockquote.css +++ b/web/css/components/app-blockquote.css @@ -1,4 +1,5 @@ @layer components { + /* Blockquote styling — left-bordered quote blocks with accent color. */ blockquote { --blockquote-bg: var(--app-blockquote-background-color); --blockquote-color: inherit; diff --git a/web/css/components/app-brand.css b/web/css/components/app-brand.css index 22d61f2..b7a7870 100644 --- a/web/css/components/app-brand.css +++ b/web/css/components/app-brand.css @@ -1,4 +1,5 @@ @layer components { + /* Branding — tenant logo display in sidebar and login. */ .brand { display: flex; align-items: center; diff --git a/web/css/components/app-breadcrumb.css b/web/css/components/app-breadcrumb.css index 3d367a1..3961740 100644 --- a/web/css/components/app-breadcrumb.css +++ b/web/css/components/app-breadcrumb.css @@ -1,4 +1,5 @@ @layer components { + /* Breadcrumb navigation — separator-delimited path links. */ .app-breadcrumb { margin-bottom: 5px; display: flex; diff --git a/web/css/components/app-buttons.css b/web/css/components/app-buttons.css index 3dd50b0..8dee541 100644 --- a/web/css/components/app-buttons.css +++ b/web/css/components/app-buttons.css @@ -1,4 +1,5 @@ @layer components { + /* Action button variants — success and danger colors with outline and focus states. */ .app-action-success:is(button, [type="submit"], [type="button"], [role="button"]) { --app-background-color: var(--app-action-success-background); --app-border-color: var(--app-action-success-border); diff --git a/web/css/components/app-confirm-dialog.css b/web/css/components/app-confirm-dialog.css index 1dad676..fee638f 100644 --- a/web/css/components/app-confirm-dialog.css +++ b/web/css/components/app-confirm-dialog.css @@ -1,4 +1,5 @@ @layer components { + /* Confirm dialog — modal overlay for destructive action confirmation with danger/warning variants. */ dialog[data-app-confirm-dialog] > article.app-confirm-dialog { max-width: 32rem; } diff --git a/web/css/components/app-dashboard-titlebar.css b/web/css/components/app-dashboard-titlebar.css index 399a8c8..66a4539 100644 --- a/web/css/components/app-dashboard-titlebar.css +++ b/web/css/components/app-dashboard-titlebar.css @@ -1,4 +1,5 @@ @layer components { + /* Dashboard title bar — welcome heading with tenant context. */ .app-dashboard-titlebar { display: flex; align-items: center; diff --git a/web/css/components/app-details-card.css b/web/css/components/app-details-card.css index 75c7b5f..c38265d 100644 --- a/web/css/components/app-details-card.css +++ b/web/css/components/app-details-card.css @@ -1,4 +1,5 @@ @layer components { + /* Detail cards — bordered content sections within detail pages. */ details.app-details-card { margin-bottom: calc(var(--app-spacing) * 0.75); border: 1px solid diff --git a/web/css/components/app-details-titlebar.css b/web/css/components/app-details-titlebar.css index 1ba2c50..0d13724 100644 --- a/web/css/components/app-details-titlebar.css +++ b/web/css/components/app-details-titlebar.css @@ -1,4 +1,5 @@ @layer components { + /* Detail page title bar — sticky save/close actions with unsaved-changes indicator. */ .app-details-titlebar { display: flex; align-items: center; diff --git a/web/css/components/app-details-validation-summary.css b/web/css/components/app-details-validation-summary.css index 11078a8..64d0a55 100644 --- a/web/css/components/app-details-validation-summary.css +++ b/web/css/components/app-details-validation-summary.css @@ -1,4 +1,5 @@ @layer components { + /* Validation summary — error list displayed at top of detail forms. */ .app-details-errors { padding-block-start: 1rem; } diff --git a/web/css/components/app-details.css b/web/css/components/app-details.css index 8c655c8..26e974f 100644 --- a/web/css/components/app-details.css +++ b/web/css/components/app-details.css @@ -1,4 +1,5 @@ @layer components { + /* Detail page layout — aside panel, card sections, sticky titlebar integration. */ .app-details-container aside { padding-top: var(--app-spacing); } diff --git a/web/css/components/app-empty-state.css b/web/css/components/app-empty-state.css index 38dd9e6..9d95219 100644 --- a/web/css/components/app-empty-state.css +++ b/web/css/components/app-empty-state.css @@ -1,4 +1,5 @@ @layer components { + /* Empty state placeholders — dashed border cards with icon, message, and optional action. */ .app-empty-state { border: 1px dashed var(--app-empty-border-color); border-radius: var(--app-border-radius); diff --git a/web/css/components/app-filter-drawer.css b/web/css/components/app-filter-drawer.css index 88f1871..145fa86 100644 --- a/web/css/components/app-filter-drawer.css +++ b/web/css/components/app-filter-drawer.css @@ -1,4 +1,5 @@ @layer components { + /* Filter drawer — slide-in overlay panel with apply/reset/close actions and focus trap. */ .app-visually-hidden { position: absolute !important; width: 1px !important; diff --git a/web/css/components/app-flash.css b/web/css/components/app-flash.css index cee95a5..a3a8cb1 100644 --- a/web/css/components/app-flash.css +++ b/web/css/components/app-flash.css @@ -1,14 +1,12 @@ @layer components { + /* Flash notices — success/warning/error/info feedback banners with auto-dismiss timer. */ .notice { --app-notice-border-color: var(--app-muted-border-color); --app-notice-background-color: var(--app-card-background-color); --app-notice-accent-color: var(--app-color); - /* border: var(--app-border-width) solid var(--app-notice-border-color); */ - /* border-radius: var(--app-border-radius); */ background: var(--app-notice-background-color); padding: calc(var(--app-spacing) * .5) calc(var(--app-spacing) * 1); padding-right: calc(var(--app-spacing) * .5); - /* margin-bottom: var(--app-spacing); */ display: flex; align-items: center; justify-content: space-between; @@ -113,10 +111,6 @@ margin: 0; } -/* .flash-stack:has(.notice) { - margin-top: var(--app-spacing); -} */ - .app-main-content .flash-stack { margin-bottom: var(--app-spacing); } diff --git a/web/css/components/app-form-info-tile.css b/web/css/components/app-form-info-tile.css index 1bb014f..6a3dce2 100644 --- a/web/css/components/app-form-info-tile.css +++ b/web/css/components/app-form-info-tile.css @@ -1,4 +1,5 @@ @layer components { + /* Form info tile — inline help/context card within form sections. */ .app-form-info-tiles-title { display: block; margin-bottom: calc(var(--app-spacing) * 0.55); diff --git a/web/css/components/app-forms.css b/web/css/components/app-forms.css index 91a35b5..4e343c0 100644 --- a/web/css/components/app-forms.css +++ b/web/css/components/app-forms.css @@ -1,4 +1,5 @@ @layer components { + /* Form element enhancements — validation hints, color inputs, checkbox/radio labels, disabled states. */ .form-hint { font-size: small; } diff --git a/web/css/components/app-list-table.css b/web/css/components/app-list-table.css index a9f8d36..746f9a6 100644 --- a/web/css/components/app-list-table.css +++ b/web/css/components/app-list-table.css @@ -1,4 +1,5 @@ @layer components { + /* List table tweaks — minor overrides for Grid.js table rendering. */ .app-list-table table { margin: 0; } diff --git a/web/css/components/app-list-tabs.css b/web/css/components/app-list-tabs.css index 0850858..f11f357 100644 --- a/web/css/components/app-list-tabs.css +++ b/web/css/components/app-list-tabs.css @@ -1,4 +1,5 @@ @layer components { + /* List page tab bar — horizontal filter tabs above data grids. */ .app-list-tabs { display: flex; flex-wrap: wrap; diff --git a/web/css/components/app-list-titlebar.css b/web/css/components/app-list-titlebar.css index 76e2b16..16f3cf5 100644 --- a/web/css/components/app-list-titlebar.css +++ b/web/css/components/app-list-titlebar.css @@ -1,4 +1,5 @@ @layer components { + /* List page title bar — heading, action buttons, and breadcrumb row. */ .app-list-titlebar { display: flex; align-items: center; diff --git a/web/css/components/app-list-toolbar.css b/web/css/components/app-list-toolbar.css index 62a41de..74ff3e2 100644 --- a/web/css/components/app-list-toolbar.css +++ b/web/css/components/app-list-toolbar.css @@ -1,4 +1,5 @@ @layer components { + /* List toolbar — page-size selector, filter toggle, and search controls above grids. */ .app-list-toolbar { display: flex; align-items: flex-end; diff --git a/web/css/components/app-page-copy.css b/web/css/components/app-page-copy.css index 3c44d53..23b3c2f 100644 --- a/web/css/components/app-page-copy.css +++ b/web/css/components/app-page-copy.css @@ -1,5 +1,5 @@ @layer components { - + /* Page copy block — rendered markdown content for CMS pages. */ .app-page-copy .app-field { min-width: 200px; diff --git a/web/css/components/app-page-editor.css b/web/css/components/app-page-editor.css index 852ad79..50b7209 100644 --- a/web/css/components/app-page-editor.css +++ b/web/css/components/app-page-editor.css @@ -1,4 +1,5 @@ @layer components { + /* Page editor — Editor.js integration styles for CMS content editing. */ .app-page-header { display: flex; align-items: center; diff --git a/web/css/components/app-search.css b/web/css/components/app-search.css index 19f345d..c60e26e 100644 --- a/web/css/components/app-search.css +++ b/web/css/components/app-search.css @@ -1,4 +1,5 @@ @layer components { + /* Global sidebar search — input, result list, preview rows, and search result cards. */ .app-search { position: relative; padding: 0 var(--app-spacing); @@ -83,18 +84,6 @@ ul.app-search-preview { .search-details-hidden ul.app-search-preview { display: none; } -ul.app-search-preview li::before { - content: ""; - position: absolute; - left: 17px; - bottom: 11px; - width: 8px; - height: 10px; - background: transparent; - border-left: 1px solid var(--app-border); - border-bottom: 1px solid var(--app-border); - border-bottom-left-radius: 6px; -} ul.app-search-preview li { position: relative; @@ -146,7 +135,7 @@ ul.app-search-preview:has(li) { align-items: flex-start; } -.search-reuslt-title-container { +.search-result-title-container { display: flex; align-items: center; gap: 10px; diff --git a/web/css/components/app-tabs.css b/web/css/components/app-tabs.css index a7a213f..8eded2e 100644 --- a/web/css/components/app-tabs.css +++ b/web/css/components/app-tabs.css @@ -1,4 +1,5 @@ @layer components { + /* Tab navigation — scrollable strip with chevron overflow, active/invalid states, no-JS fallback. */ .app-tabs { display: flex; flex-direction: column; diff --git a/web/css/components/app-tile.css b/web/css/components/app-tile.css index 560ceb0..185a0d5 100644 --- a/web/css/components/app-tile.css +++ b/web/css/components/app-tile.css @@ -1,4 +1,5 @@ @layer components { + /* Info tiles — key-value display cards used in detail page sidebars. */ .app-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); diff --git a/web/css/components/app-tooltips.css b/web/css/components/app-tooltips.css index 155c664..e3707f9 100644 --- a/web/css/components/app-tooltips.css +++ b/web/css/components/app-tooltips.css @@ -1,4 +1,5 @@ @layer components { + /* CSS-only tooltips — positioned via data-tooltip-pos with arrow indicator. */ [data-tooltip] { --app-card-background-color: inherit; position: relative; diff --git a/web/js/app-boot.js b/web/js/app-boot.js index 2480fb0..88b4b93 100644 --- a/web/js/app-boot.js +++ b/web/js/app-boot.js @@ -1,3 +1,11 @@ +/** + * Render-blocking boot script — runs synchronously before first paint. + * + * Intentionally a classic IIFE (not an ES module) so the browser executes it + * immediately while parsing . This prevents layout flicker by applying + * persisted UI state (sidebar collapsed, contrast mode) before any content + * is rendered. Uses `var` for broadest compatibility in the sync path. + */ (function () { var root = document.documentElement; root.classList.remove('no-js'); diff --git a/web/js/components/app-active-filter-chips.js b/web/js/components/app-active-filter-chips.js index eccda91..755eb0a 100644 --- a/web/js/components/app-active-filter-chips.js +++ b/web/js/components/app-active-filter-chips.js @@ -1,3 +1,6 @@ +/** + * Renders removable pill chips for currently active grid filters. + */ const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&', '<': '<', diff --git a/web/js/components/app-bulk-selection.js b/web/js/components/app-bulk-selection.js index 3d67393..9a0b1f4 100644 --- a/web/js/components/app-bulk-selection.js +++ b/web/js/components/app-bulk-selection.js @@ -1,3 +1,6 @@ +/** + * Shows/hides bulk action buttons based on Grid.js row selection state. + */ import { warnOnce } from '../core/app-dom.js'; export function bindBulkVisibility({ diff --git a/web/js/components/app-color-default-toggle.js b/web/js/components/app-color-default-toggle.js index e2ecf95..1a0291e 100644 --- a/web/js/components/app-color-default-toggle.js +++ b/web/js/components/app-color-default-toggle.js @@ -1,3 +1,6 @@ +/** + * Toggles color input between custom value and default/inherited color. + */ import { warnOnce } from '../core/app-dom.js'; const toggles = document.querySelectorAll('[data-color-default-toggle]'); diff --git a/web/js/components/app-confirm-actions.js b/web/js/components/app-confirm-actions.js index f448f91..7aecb5c 100644 --- a/web/js/components/app-confirm-actions.js +++ b/web/js/components/app-confirm-actions.js @@ -1,3 +1,6 @@ +/** + * Declarative confirm-before-action — intercepts clicks and submits on [data-confirm-message] elements. + */ import { confirmDialog, requestSubmitWithFallback } from '../core/app-confirm-dialog.js'; const CONFIRM_ATTR = 'data-confirm-message'; diff --git a/web/js/components/app-contrast-toggle.js b/web/js/components/app-contrast-toggle.js index 6429451..d0bf9d3 100644 --- a/web/js/components/app-contrast-toggle.js +++ b/web/js/components/app-contrast-toggle.js @@ -1,3 +1,6 @@ +/** + * High-contrast mode toggle with localStorage persistence. + */ import { requireEl } from '../core/app-dom.js'; const STORAGE_KEY = 'app-contrast'; diff --git a/web/js/components/app-copy-badge.js b/web/js/components/app-copy-badge.js index 8c56e73..4bc1b82 100644 --- a/web/js/components/app-copy-badge.js +++ b/web/js/components/app-copy-badge.js @@ -1,3 +1,6 @@ +/** + * Click-to-copy badge — copies text to clipboard with visual feedback. + */ const COPY_SELECTOR = '.badge[data-copy="true"]'; const COPIED_CLASS = 'is-copied'; diff --git a/web/js/components/app-custom-field-options-toggle.js b/web/js/components/app-custom-field-options-toggle.js index 97269dd..4ae5774 100644 --- a/web/js/components/app-custom-field-options-toggle.js +++ b/web/js/components/app-custom-field-options-toggle.js @@ -1,3 +1,6 @@ +/** + * Shows/hides custom field option list based on field type selection. + */ import { warnOnce } from '../core/app-dom.js'; const TYPES_WITH_OPTIONS = new Set(['select', 'multiselect']); diff --git a/web/js/components/app-details-state.js b/web/js/components/app-details-state.js index de146ae..96cec48 100644 --- a/web/js/components/app-details-state.js +++ b/web/js/components/app-details-state.js @@ -1,3 +1,6 @@ +/** + * Initializes detail page open-state persistence and action policy. + */ import { initPersistedDetailsGroup } from '../core/app-details-open-state.js'; const controllerRegistry = new WeakMap(); diff --git a/web/js/components/app-filter-drawer.js b/web/js/components/app-filter-drawer.js index 73b2b31..8c8f4de 100644 --- a/web/js/components/app-filter-drawer.js +++ b/web/js/components/app-filter-drawer.js @@ -1,3 +1,6 @@ +/** + * Slide-in filter drawer with focus trap, scroll lock, and apply/reset/discard lifecycle. + */ import { warnOnce } from '../core/app-dom.js'; export function initFilterDrawer(options = {}) { diff --git a/web/js/components/app-flash-auto-dismiss.js b/web/js/components/app-flash-auto-dismiss.js index 4e4f25b..1b144a6 100644 --- a/web/js/components/app-flash-auto-dismiss.js +++ b/web/js/components/app-flash-auto-dismiss.js @@ -1,3 +1,6 @@ +/** + * Auto-dismisses flash notices after their data-flash-timeout expires. + */ export function initFlashAutoDismiss(options = {}) { const { selector = '.flash-stack .notice[data-flash-timeout]', diff --git a/web/js/components/app-fslightbox-refresh.js b/web/js/components/app-fslightbox-refresh.js index bcb87a6..4253b22 100644 --- a/web/js/components/app-fslightbox-refresh.js +++ b/web/js/components/app-fslightbox-refresh.js @@ -1,3 +1,6 @@ +/** + * Triggers FSLightbox refresh after dynamic content changes. + */ const refreshFsLightbox = () => { if (typeof window.requestFsLightboxRefresh === 'function') { return window.requestFsLightboxRefresh(); diff --git a/web/js/components/app-global-search.js b/web/js/components/app-global-search.js index 6984520..83f0e75 100644 --- a/web/js/components/app-global-search.js +++ b/web/js/components/app-global-search.js @@ -1,3 +1,6 @@ +/** + * Global search sidebar — debounced fetch with live result counts and Ctrl+K shortcut. + */ import { requireEl, optionalEl, warnOnce } from '../core/app-dom.js'; const input = requireEl('#side-search', { module: 'global-search' }); diff --git a/web/js/components/app-multiselect-cascade.js b/web/js/components/app-multiselect-cascade.js index e457cee..bbea199 100644 --- a/web/js/components/app-multiselect-cascade.js +++ b/web/js/components/app-multiselect-cascade.js @@ -1,3 +1,6 @@ +/** + * Cascading multi-select — filters child options based on parent selection. + */ import { getMultiSelectInstance } from './app-multiselect-init.js'; import { warnOnce } from '../core/app-dom.js'; diff --git a/web/js/components/app-multiselect-init.js b/web/js/components/app-multiselect-init.js index a2ceaba..5f0b9f3 100644 --- a/web/js/components/app-multiselect-init.js +++ b/web/js/components/app-multiselect-init.js @@ -1,3 +1,6 @@ +/** + * Initializes MultiSelect.js instances from select[multiple] elements. + */ import { warnOnce } from '../core/app-dom.js'; let multiSelectLoader = null; diff --git a/web/js/components/app-nav-history.js b/web/js/components/app-nav-history.js index fbb78e0..f954b56 100644 --- a/web/js/components/app-nav-history.js +++ b/web/js/components/app-nav-history.js @@ -1,3 +1,6 @@ +/** + * Browser history integration — handles back/forward navigation state. + */ const updateHistoryButtons = () => { const back = document.querySelector('#global-back'); const forward = document.querySelector('#global-forward'); diff --git a/web/js/components/app-page-editor.js b/web/js/components/app-page-editor.js index fe48183..065b396 100644 --- a/web/js/components/app-page-editor.js +++ b/web/js/components/app-page-editor.js @@ -1,3 +1,6 @@ +/** + * Editor.js integration — initializes block editor for CMS page content. + */ import EditorJS from '../../vendor/editorjs/editorjs.mjs'; import { warnOnce } from '../core/app-dom.js'; diff --git a/web/js/components/app-password-hints.js b/web/js/components/app-password-hints.js index 3dfc5fd..5f92cb1 100644 --- a/web/js/components/app-password-hints.js +++ b/web/js/components/app-password-hints.js @@ -1,3 +1,6 @@ +/** + * Real-time password validation hints — checks length, uppercase, digit, special char. + */ import { warnOnce } from '../core/app-dom.js'; const rules = { diff --git a/web/js/components/app-password-toggle.js b/web/js/components/app-password-toggle.js index b80aae1..616053c 100644 --- a/web/js/components/app-password-toggle.js +++ b/web/js/components/app-password-toggle.js @@ -1,3 +1,6 @@ +/** + * Show/hide password toggle button injected next to password inputs. + */ export function initPasswordToggles() { const inputs = document.querySelectorAll('input[type="password"]'); if (!inputs.length) {return;} diff --git a/web/js/components/app-settings-telemetry.js b/web/js/components/app-settings-telemetry.js index 1bc1ba5..378c5e3 100644 --- a/web/js/components/app-settings-telemetry.js +++ b/web/js/components/app-settings-telemetry.js @@ -1,3 +1,6 @@ +/** + * Tracks settings page interactions for telemetry. + */ const initTelemetrySettings = () => { const toggle = document.querySelector('[data-telemetry-enabled-toggle]'); const samplingFieldset = document.querySelector('[data-telemetry-sampling-fieldset]'); diff --git a/web/js/components/app-standard-detail-page.js b/web/js/components/app-standard-detail-page.js index 09a52a9..07de982 100644 --- a/web/js/components/app-standard-detail-page.js +++ b/web/js/components/app-standard-detail-page.js @@ -1,3 +1,6 @@ +/** + * Wires up the standard detail page factory with titlebar and form state. + */ import { autoInitStandardDetailPages } from '../core/app-detail-page-factory.js'; export function initStandardDetailPages(root = document) { diff --git a/web/js/components/app-tabs.js b/web/js/components/app-tabs.js index 073377f..60ecb59 100644 --- a/web/js/components/app-tabs.js +++ b/web/js/components/app-tabs.js @@ -90,13 +90,18 @@ export function initTabs(root = document) { }; // ---------------------------------------------------------------- - // ARIA: role="tablist" on the nav, not the outer container + // ARIA: role, aria-controls, aria-labelledby (WAI-ARIA Tabs pattern) // ---------------------------------------------------------------- const nav = container.querySelector('.app-tabs-nav'); + const tabIdPrefix = container.id || `tabs-${Math.random().toString(36).slice(2, 8)}`; - // Initialize tabs tabs.forEach(tab => { + const tabName = tab.dataset.tab; + const tabId = `${tabIdPrefix}-tab-${tabName}`; + const panelId = `${tabIdPrefix}-panel-${tabName}`; tab.setAttribute('role', 'tab'); + tab.id = tabId; + tab.setAttribute('aria-controls', panelId); tab.addEventListener('click', (e) => { e.preventDefault(); activateTab(tab.dataset.tab); @@ -106,7 +111,12 @@ export function initTabs(root = document) { }); panels.forEach(panel => { + const panelName = panel.dataset.tabPanel; + const panelId = `${tabIdPrefix}-panel-${panelName}`; + const tabId = `${tabIdPrefix}-tab-${panelName}`; panel.setAttribute('role', 'tabpanel'); + panel.id = panelId; + panel.setAttribute('aria-labelledby', tabId); }); if (nav) { diff --git a/web/js/components/app-tenant-sso-toggle.js b/web/js/components/app-tenant-sso-toggle.js index aa1808e..bce6455 100644 --- a/web/js/components/app-tenant-sso-toggle.js +++ b/web/js/components/app-tenant-sso-toggle.js @@ -1,3 +1,6 @@ +/** + * Shows/hides SSO configuration fields based on tenant SSO toggle. + */ const roots = document.querySelectorAll('[data-tenant-sso-root]'); const syncControlState = (container, show) => { diff --git a/web/js/components/app-tenant-switcher.js b/web/js/components/app-tenant-switcher.js index 416e5af..106c725 100644 --- a/web/js/components/app-tenant-switcher.js +++ b/web/js/components/app-tenant-switcher.js @@ -1,3 +1,6 @@ +/** + * Tenant context switcher dropdown with logo display. + */ import { showAsyncFlash } from './app-async-flash.js'; import { optionalEl, warnOnce } from '../core/app-dom.js'; diff --git a/web/js/components/app-theme-toggle.js b/web/js/components/app-theme-toggle.js index 88075c3..a48ec4e 100644 --- a/web/js/components/app-theme-toggle.js +++ b/web/js/components/app-theme-toggle.js @@ -1,3 +1,6 @@ +/** + * Theme switcher — light/dark toggle and menu with server-side persistence. + */ import { optionalEl, warnOnce } from '../core/app-dom.js'; const setTheme = (theme) => { diff --git a/web/js/components/app-toggle-aside-panels.js b/web/js/components/app-toggle-aside-panels.js index ee99ff6..c9e3355 100644 --- a/web/js/components/app-toggle-aside-panels.js +++ b/web/js/components/app-toggle-aside-panels.js @@ -1,3 +1,6 @@ +/** + * Aside panel switcher — opens/closes named sidebar panels. + */ import { requireEl, warnOnce } from '../core/app-dom.js'; import { initPersistedDetailsGroup } from '../core/app-details-open-state.js'; diff --git a/web/js/components/app-toggle-details-aside.js b/web/js/components/app-toggle-details-aside.js index 9d1a031..f1344ce 100644 --- a/web/js/components/app-toggle-details-aside.js +++ b/web/js/components/app-toggle-details-aside.js @@ -1,3 +1,6 @@ +/** + * Toggles visibility of the detail page aside column. + */ import { optionalEl, warnOnce } from '../core/app-dom.js'; export function initDetailsAsideToggle(options = {}) { diff --git a/web/js/components/app-toggle-sidebar.js b/web/js/components/app-toggle-sidebar.js index f0b4cd8..d49d3f8 100644 --- a/web/js/components/app-toggle-sidebar.js +++ b/web/js/components/app-toggle-sidebar.js @@ -1,3 +1,6 @@ +/** + * Sidebar collapse/show toggle with localStorage persistence and Ctrl+B shortcut. + */ import { requireEl, warnOnce } from '../core/app-dom.js'; export function initSidebarToggle(options = {}) { diff --git a/web/js/core/app-confirm-dialog.js b/web/js/core/app-confirm-dialog.js index 66a901a..b9207a2 100644 --- a/web/js/core/app-confirm-dialog.js +++ b/web/js/core/app-confirm-dialog.js @@ -1,3 +1,6 @@ +/** + * Promise-based confirm dialog controller with variant inference, focus trap, and native fallback. + */ import { warnOnce } from './app-dom.js'; const VARIANTS = new Set(['default', 'warning', 'danger']); diff --git a/web/js/core/app-detail-page-factory.js b/web/js/core/app-detail-page-factory.js index 9874d45..2a57cd1 100644 --- a/web/js/core/app-detail-page-factory.js +++ b/web/js/core/app-detail-page-factory.js @@ -1,3 +1,6 @@ +/** + * Factory for standard detail pages — form dirty-tracking, unsaved indicator, validation routing, and keyboard save. + */ import { initDetailActionPolicy } from './app-details-action-policy.js'; import { confirmDialog } from './app-confirm-dialog.js'; diff --git a/web/js/core/app-details-action-policy.js b/web/js/core/app-details-action-policy.js index 667e1b7..55ed467 100644 --- a/web/js/core/app-details-action-policy.js +++ b/web/js/core/app-details-action-policy.js @@ -1,3 +1,6 @@ +/** + * Form submit policy for detail pages — confirm dialogs, double-submit prevention, and aria-busy. + */ import { confirmDialog, requestSubmitWithFallback } from './app-confirm-dialog.js'; const escapeAttr = (value) => { diff --git a/web/js/core/app-details-open-state.js b/web/js/core/app-details-open-state.js index 43ca39d..5d2f49a 100644 --- a/web/js/core/app-details-open-state.js +++ b/web/js/core/app-details-open-state.js @@ -1,3 +1,6 @@ +/** + * Persists open/closed state of details elements to localStorage by key. + */ const toElement = (value) => { if (value instanceof HTMLElement) { return value; diff --git a/web/js/core/app-dom.js b/web/js/core/app-dom.js index ea8244c..f0ce912 100644 --- a/web/js/core/app-dom.js +++ b/web/js/core/app-dom.js @@ -1,3 +1,6 @@ +/** + * DOM utility helpers — safe element lookup with missing-element warnings and telemetry. + */ import { telemetry } from './app-telemetry.js'; const warned = new Set(); diff --git a/web/js/core/app-page-config.js b/web/js/core/app-page-config.js index 648d6d9..d203d61 100644 --- a/web/js/core/app-page-config.js +++ b/web/js/core/app-page-config.js @@ -1,3 +1,6 @@ +/** + * Reads JSON configuration embedded in the page via script#page-config-{id} elements. + */ import { warnOnce } from './app-dom.js'; export function readPageConfig(configId, root = document) { diff --git a/web/js/core/app-telemetry.js b/web/js/core/app-telemetry.js index 2023c5b..e382ddd 100644 --- a/web/js/core/app-telemetry.js +++ b/web/js/core/app-telemetry.js @@ -1,3 +1,6 @@ +/** + * Frontend event telemetry — captures UI events with deduplication, sampling, and rate limiting. + */ const DEFAULT_SAMPLE_RATE = 0.2; const DEFAULT_DEDUPE_TTL_MS = 5 * 60 * 1000; const DEFAULT_MAX_EVENTS_PER_MINUTE = 20;