diff --git a/cache-bust.json b/cache-bust.json
index c77c43c6..d032c795 100644
--- a/cache-bust.json
+++ b/cache-bust.json
@@ -1,5 +1,5 @@
{
- "version": "512de215e15f2828",
+ "version": "4571b2de4aa33cd0",
"generatedBy": "scripts/updateCacheBusting.mjs",
"assets": {
"assets/app-icon-16.png": "6645731d86da1071",
@@ -14,7 +14,7 @@
"assets/favicon-32.png": "c9b411f8fdc302f2",
"assets/favicon.svg": "8596af3b51a53409",
"backgroundNotificationServiceWorker.js": "2f8933df91d32a1a",
- "index.html": "9f9af1d24e2a3e90",
+ "index.html": "120ac11359474cd7",
"site.webmanifest": "7641f91a81ee63a2",
"src/appModules.js": "f4135fb45be489bb",
"src/components/date-picker/index.js": "c9a9363a3fdae5ee",
@@ -54,8 +54,9 @@
"src/core/formatting/valueFormatting.js": "3f1e24d4cdc46608",
"src/core/hydrationEta.js": "250ec4ee2633be0f",
"src/core/icons.js": "51f534abf118b7e1",
+ "src/core/libraryScopes.js": "9fd73251bf6e3fcc",
"src/core/mobileScrollLock.js": "d197a1dc4a683409",
- "src/core/mockQueryBackend.js": "a69b05dccb087495",
+ "src/core/mockQueryBackend.js": "9147b5dea4adbced",
"src/core/operatorSelectUtils.js": "4986a94dce50dcd9",
"src/core/queryErrorDetails.js": "5c26531918b7a2c8",
"src/core/queryExecution.js": "5fddde4c241c8699",
@@ -189,10 +190,10 @@
"src/lib/workbook-export/workbookXmlUtils.js": "2cc0fcdda2a1d411",
"src/lib/workbook-export/xlsxZipWriter.js": "5f4c1f6e98fbcb06",
"src/styles/api-settings.css": "c5f76b60fb8684bb",
- "src/styles/app.css": "09faabcfd35d38aa",
+ "src/styles/app.css": "410351104445b653",
"src/styles/base.css": "8a925806001990b6",
"src/styles/bib-compare.css": "144f2a1093e00dcf",
- "src/styles/controls.css": "5372b30b175cd1e7",
+ "src/styles/controls.css": "30d36ccc65181c21",
"src/styles/dashboard.css": "3d11ffe059411be7",
"src/styles/filters.css": "9e7297e65dd68a3c",
"src/styles/form-mode.css": "7f332bdcf58c2df0",
@@ -232,15 +233,15 @@
"src/ui/cliPairing.js": "972206be3731df5f",
"src/ui/controls/customDatePicker.js": "88e4eb3245ac256c",
"src/ui/controls/searchUI.js": "9d46afc995531905",
- "src/ui/controls/selectorControls.js": "bfcb8b0a6aba4bd4",
+ "src/ui/controls/selectorControls.js": "ee4bde178cbce906",
"src/ui/controls/selectorListPasteInput.js": "4e7f16d036dfd795",
"src/ui/controls/tableNameInput.js": "b6fc884c36961de3",
"src/ui/controls/virtualList.js": "bd7a8d24c7c48493",
- "src/ui/dashboard/kpiDashboard.js": "a30231379c3c6b6f",
+ "src/ui/dashboard/kpiDashboard.js": "6f73f5e9f2b62c49",
"src/ui/dashboard/kpiDashboardModel.js": "fa4c42b18c1245e5",
"src/ui/dashboard/kpiDashboardView.js": "82508c550975ada6",
"src/ui/dashboard/libraryDashboardExport.js": "b756c909cbadc7e9",
- "src/ui/dashboard/libraryDashboardModel.js": "ff0861a1889a9b32",
+ "src/ui/dashboard/libraryDashboardModel.js": "526c678143b0373e",
"src/ui/dashboard/libraryDashboardView.js": "f46f69224a55ebe3",
"src/ui/field-picker/buildableFieldPreview.js": "8abd486bb0a87fe2",
"src/ui/field-picker/fieldPicker.js": "526a1bf934f5dd74",
@@ -255,7 +256,7 @@
"src/ui/form-mode/formMode.js": "bac8e41c43689e9c",
"src/ui/form-mode/formModeCard.js": "ec0b92176b97872b",
"src/ui/form-mode/formModeCardBuilder.js": "f906542bfb990667",
- "src/ui/form-mode/formModeControls.js": "ce0cb516c6920165",
+ "src/ui/form-mode/formModeControls.js": "b84b20e082760f7d",
"src/ui/form-mode/formModeDateInput.js": "f08093e5d0548828",
"src/ui/form-mode/formModeFieldPicker.js": "a6825b92b2a9fbb6",
"src/ui/form-mode/formModePresentation.js": "caaa646c76cdf735",
diff --git a/index.html b/index.html
index 5982889c..56753a41 100644
--- a/index.html
+++ b/index.html
@@ -833,7 +833,7 @@
-
+
diff --git a/src/core/libraryScopes.js b/src/core/libraryScopes.js
new file mode 100644
index 00000000..a3d5426f
--- /dev/null
+++ b/src/core/libraryScopes.js
@@ -0,0 +1,21 @@
+const ALL_LIBRARY_SYSTEMS_LABEL = 'All library systems';
+
+function normalizeLibraryScopeOptions(options = []) {
+ return (Array.isArray(options) ? options : []).map(option => typeof option === 'string'
+ ? { value: option, label: option }
+ : { value: option.value ?? option.code, label: option.label ?? option.name ?? option.code });
+}
+
+function buildLibraryScopeGroups(systems = [], libraries = []) {
+ return [
+ { label: 'Library systems', options: normalizeLibraryScopeOptions(systems) },
+ { label: 'Item libraries', options: normalizeLibraryScopeOptions(libraries) }
+ ].filter(group => group.options.length > 0);
+}
+
+function systemCodeForLibraryScope(scope = '') {
+ const normalized = String(scope || '').replace(/^system:/u, '');
+ return normalized && normalized !== 'all' ? normalized.split('-')[0].toUpperCase() : '';
+}
+
+export { ALL_LIBRARY_SYSTEMS_LABEL, buildLibraryScopeGroups, systemCodeForLibraryScope };
diff --git a/src/core/mockQueryBackend.js b/src/core/mockQueryBackend.js
index b279f5dc..fbad2a67 100644
--- a/src/core/mockQueryBackend.js
+++ b/src/core/mockQueryBackend.js
@@ -104,7 +104,7 @@ function demoCalendarPeriods(now = new Date()) {
function buildDemoLibraryDashboard(payload = {}, data = {}) {
const library = payload.library || 'all';
const itemType = payload.item_type || 'all';
- const scopeFactor = library === 'all' ? 1 : 0.08;
+ const scopeFactor = library === 'all' ? 1 : library.startsWith('system:') ? 0.25 : 0.08;
const typeFactor = itemType === 'all' ? 1 : 0.22;
const factor = scopeFactor * typeFactor;
const reportingPeriod = String(payload.reporting_period || payload.active_window_days || 365);
@@ -141,11 +141,17 @@ function buildDemoLibraryDashboard(payload = {}, data = {}) {
const previousCheckouts = scaled(842110);
const previousRenewals = scaled(469220);
const filters = {
+ systems: [
+ { value: 'system:MSU', code: 'MSU', label: 'Mississippi State University' },
+ { value: 'system:MMRLS', code: 'MMRLS', label: 'Mid-Mississippi Regional Library System' },
+ { value: 'system:FRL', code: 'FRL', label: 'First Regional Library' },
+ { value: 'system:LILS', code: 'LILS', label: 'Lee-Itawamba Library System' }
+ ],
libraries: [
- { value: 'MSU', label: 'Mississippi State University' },
- { value: 'MMRLS', label: 'Mid-Mississippi Regional Library System' },
- { value: 'FRL', label: 'First Regional Library' },
- { value: 'LILS', label: 'Lee-Itawamba Library System' }
+ { value: 'MSU-MAIN', label: 'MSU Main Library' },
+ { value: 'MMRLS-CARTHAGE', label: 'MMRLS Carthage' },
+ { value: 'FRL-HERNANDO', label: 'First Regional Hernando' },
+ { value: 'LILS-TUPELO', label: 'Lee-Itawamba Tupelo' }
],
item_types: ['BOOK', 'EBOOK', 'DVD', 'AUDIOBOOK', 'KIT'],
calendar_periods: calendarPeriods,
@@ -157,7 +163,7 @@ function buildDemoLibraryDashboard(payload = {}, data = {}) {
sample_data: true,
scope: {
library,
- library_label: library === 'all' ? 'All MLP libraries' : (filters.libraries.find(entry => entry.value === library)?.label || library),
+ library_label: library === 'all' ? 'All MLP libraries' : ([...filters.systems, ...filters.libraries].find(entry => entry.value === library)?.label || library),
item_type: itemType,
item_type_label: itemType === 'all' ? 'All item types' : itemType,
active_window_days: Number(payload.active_window_days || 365),
diff --git a/src/styles/app.css b/src/styles/app.css
index ae27eb4a..6ca2a25d 100644
--- a/src/styles/app.css
+++ b/src/styles/app.css
@@ -5,7 +5,7 @@
@import url("./filters.css?v=9e7297e65dd68a3c");
@import url("./form-mode.css?v=7f332bdcf58c2df0");
@import url("./table.css?v=8bc187669db70079");
-@import url("./controls.css?v=5372b30b175cd1e7");
+@import url("./controls.css?v=30d36ccc65181c21");
@import url("./history.css?v=64598bfd565a29af");
@import url("./dashboard.css?v=3d11ffe059411be7");
@import url("./templates.css?v=1c0a1594bedc65b9");
diff --git a/src/styles/controls.css b/src/styles/controls.css
index 1cd6dafb..31adf8f8 100644
--- a/src/styles/controls.css
+++ b/src/styles/controls.css
@@ -523,6 +523,20 @@ input[type="search"]:-webkit-autofill,
transition: background-color var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
}
+.selector-all-option {
+ border: 0;
+ cursor: pointer;
+ font: inherit;
+ margin: 0 0 0.35rem;
+ text-align: left;
+ width: 100%;
+}
+
+.selector-all-option.is-selected {
+ box-shadow: inset 3px 0 0 var(--accent-color);
+ font-weight: 700;
+}
+
/* ==========================================
12. CUSTOM DATE PICKER
========================================== */
diff --git a/src/ui/controls/selectorControls.js b/src/ui/controls/selectorControls.js
index b77faccc..e86aad94 100644
--- a/src/ui/controls/selectorControls.js
+++ b/src/ui/controls/selectorControls.js
@@ -132,6 +132,8 @@ function createGroupedSelector(values, isMultiSelect, currentValues = [], option
const enableGrouping = options.enableGrouping !== false;
const groupSelectionLabel = String(options.groupSelectionLabel || '').trim();
const groupSelectionDescription = String(options.groupSelectionDescription || '').trim();
+ const allSelectionLabel = String(options.allSelectionLabel || '').trim();
+ const allSelectionDescription = String(options.allSelectionDescription || '').trim();
const containerId = Object.prototype.hasOwnProperty.call(options, 'containerId')
? options.containerId
: 'condition-select-container';
@@ -165,6 +167,21 @@ function createGroupedSelector(values, isMultiSelect, currentValues = [], option
initializeSearchInputs(searchWrapper);
container.appendChild(searchWrapper);
+ let allSelectionButton = null;
+ if (allSelectionLabel) {
+ allSelectionButton = document.createElement('button');
+ allSelectionButton.type = 'button';
+ allSelectionButton.className = 'group-header selector-all-option';
+ allSelectionButton.textContent = allSelectionLabel;
+ allSelectionButton.title = allSelectionDescription;
+ allSelectionButton.addEventListener('click', () => {
+ selectedValues.clear();
+ container.dispatchEvent(new Event('change', { bubbles: true }));
+ rebuildVisibleRows();
+ });
+ container.appendChild(allSelectionButton);
+ }
+
const optionsContainer = document.createElement('div');
optionsContainer.className = 'grouped-options-container grouped-options-container--virtualized';
container.appendChild(optionsContainer);
@@ -288,6 +305,11 @@ function createGroupedSelector(values, isMultiSelect, currentValues = [], option
const searchTerm = searchInput.value.toLowerCase().trim();
visibleRows = [];
optionIndex.clear();
+ if (allSelectionButton) {
+ const selected = selectedValues.size === 0;
+ allSelectionButton.classList.toggle('is-selected', selected);
+ allSelectionButton.setAttribute('aria-pressed', selected ? 'true' : 'false');
+ }
topLevelEntries
.slice()
diff --git a/src/ui/dashboard/kpiDashboard.js b/src/ui/dashboard/kpiDashboard.js
index d247c87d..338659dc 100644
--- a/src/ui/dashboard/kpiDashboard.js
+++ b/src/ui/dashboard/kpiDashboard.js
@@ -1,6 +1,7 @@
import { BackendApi } from '../../core/backendApi.js';
import { appServices } from '../../core/appServices.js';
import { getClientErrorMessage } from '../../core/clientErrorMessages.js';
+import { ALL_LIBRARY_SYSTEMS_LABEL, buildLibraryScopeGroups, systemCodeForLibraryScope } from '../../core/libraryScopes.js';
import { onDOMReady } from '../../core/domReady.js';
import { libraryDashboardHasData, normalizeLibraryDashboard } from './libraryDashboardModel.js';
import { downloadLibraryDashboardCsv } from './libraryDashboardExport.js';
@@ -46,9 +47,28 @@ function replaceOptions(select, baseLabel, options, selected) {
select.value = normalized.some(option => option.value === selected) ? selected : 'all';
}
+function replaceLibraryOptions(select, systems, libraries, selected) {
+ if (!select) return;
+ const groups = buildLibraryScopeGroups(systems, libraries);
+ const options = groups.flatMap(group => group.options);
+ const all = new Option(ALL_LIBRARY_SYSTEMS_LABEL, 'all');
+ select.replaceChildren(all, ...groups.map(group => {
+ const element = document.createElement('optgroup');
+ element.label = group.label;
+ element.append(...group.options.map(option => new Option(option.label, option.value)));
+ return element;
+ }));
+ select.value = options.some(option => option.value === selected) ? selected : 'all';
+}
+
function syncFilterOptions(elements) {
if (!libraryData) return;
- replaceOptions(elements.library, 'All MLP libraries', libraryData.filters.libraries, elements.library?.value || 'all');
+ replaceLibraryOptions(
+ elements.library,
+ libraryData.filters.systems,
+ libraryData.filters.libraries,
+ elements.library?.value || 'all'
+ );
replaceOptions(elements.itemType, 'All item types', libraryData.filters.itemTypes, elements.itemType?.value || 'all');
syncPeriodOptions(elements);
}
@@ -57,7 +77,7 @@ function syncPeriodOptions(elements) {
if (!elements.period || !libraryData) return;
const selected = elements.period.value || '365';
const library = elements.library?.value || 'all';
- const system = library === 'all' ? '' : String(library).split('-')[0].toUpperCase();
+ const system = systemCodeForLibraryScope(library);
const rolling = [
{ value: '90', label: 'Last 90 days' },
{ value: '365', label: 'Last 12 months' },
diff --git a/src/ui/dashboard/libraryDashboardModel.js b/src/ui/dashboard/libraryDashboardModel.js
index ffca4ae9..d6052f29 100644
--- a/src/ui/dashboard/libraryDashboardModel.js
+++ b/src/ui/dashboard/libraryDashboardModel.js
@@ -49,6 +49,7 @@ function normalizeLibraryDashboard(payload = {}) {
patronStateBreakdown: normalizeSeries(data.patron_state_breakdown),
opportunities: Array.isArray(data.opportunities) ? data.opportunities : [],
filters: {
+ systems: Array.isArray(data.filters?.systems) ? data.filters.systems : [],
libraries: Array.isArray(data.filters?.libraries) ? data.filters.libraries : [],
itemTypes: Array.isArray(data.filters?.item_types) ? data.filters.item_types : [],
calendarPeriods: Array.isArray(data.filters?.calendar_periods) ? data.filters.calendar_periods : [],
diff --git a/src/ui/form-mode/formModeControls.js b/src/ui/form-mode/formModeControls.js
index 06fa8a9b..ac30216f 100644
--- a/src/ui/form-mode/formModeControls.js
+++ b/src/ui/form-mode/formModeControls.js
@@ -8,6 +8,7 @@ import { SelectorControls } from '../controls/selectorControls.js';
import { fieldDefs, getFieldFilterOperators, isFieldBackendFilterable } from '../../features/filters/fieldDefs.js';
import { CustomDatePicker } from '../controls/customDatePicker.js';
import { createFormModeDateInputState } from './formModeDateInput.js';
+import { ALL_LIBRARY_SYSTEMS_LABEL } from '../../core/libraryScopes.js';
function parseFieldOptions(fieldDef, inputSpec, normalizeOperatorForField) {
const source = Array.isArray(inputSpec.options) && inputSpec.options.length > 0
@@ -382,6 +383,8 @@ import { createFormModeDateInputState } from './formModeDateInput.js';
if (SelectorControls.createGroupedSelector) {
const selector = SelectorControls.createGroupedSelector(values, isMultiSelect, initialValues, {
enableGrouping: shouldGroupValues && hasDashes,
+ allSelectionLabel: fieldDef?.allSelectionLabel || fieldDef?.all_selection_label || (shouldGroupValues ? ALL_LIBRARY_SYSTEMS_LABEL : ''),
+ allSelectionDescription: fieldDef?.allSelectionDescription || fieldDef?.all_selection_description || '',
groupSelectionLabel: fieldDef?.groupSelectionLabel || fieldDef?.group_selection_label || '',
groupSelectionDescription: fieldDef?.groupSelectionDescription || fieldDef?.group_selection_description || '',
containerId: null
@@ -389,7 +392,7 @@ import { createFormModeDateInputState } from './formModeDateInput.js';
return createPopupListControl(
selector,
inputSpec.label || (fieldDef && fieldDef.name) || 'Select values',
- inputSpec.placeholder || (isMultiSelect ? 'Click to select values…' : 'Click to select a value…')
+ inputSpec.placeholder || fieldDef?.allSelectionLabel || fieldDef?.all_selection_label || (isMultiSelect ? 'Click to select values…' : 'Click to select a value…')
);
}
diff --git a/tests/browser/browserSmoke.mjs b/tests/browser/browserSmoke.mjs
index e68c7d7f..7862beb9 100644
--- a/tests/browser/browserSmoke.mjs
+++ b/tests/browser/browserSmoke.mjs
@@ -520,6 +520,7 @@ async function runSmokeTest() {
chartCount: panel.querySelectorAll('.kpi-chart-card').length,
opportunityRows: panel.querySelectorAll('.kpi-opportunity-table tbody tr').length,
libraryOptions: Array.from(panel.querySelectorAll('#kpi-dashboard-library option')).map(option => option.value),
+ libraryGroups: Array.from(panel.querySelectorAll('#kpi-dashboard-library optgroup')).map(group => group.label),
exportVisible: !panel.querySelector('#kpi-dashboard-export')?.classList.contains('hidden'),
comparisonText: panel.querySelector('.kpi-card')?.textContent || '',
selectedTab: panel.querySelector('[data-kpi-view][aria-selected="true"]')?.dataset.kpiView || ''
@@ -530,7 +531,10 @@ async function runSmokeTest() {
|| dashboardState.cardValues[2] !== '2,813,442'
|| dashboardState.chartCount !== 6
|| dashboardState.opportunityRows !== 1
- || !dashboardState.libraryOptions.includes('MSU')
+ || !dashboardState.libraryOptions.includes('system:MSU')
+ || !dashboardState.libraryOptions.includes('MSU-MAIN')
+ || !dashboardState.libraryGroups.includes('Library systems')
+ || !dashboardState.libraryGroups.includes('Item libraries')
|| !dashboardState.exportVisible
|| !/up 38,119/iu.test(dashboardState.comparisonText)
|| dashboardState.selectedTab !== 'overview'
@@ -554,7 +558,7 @@ async function runSmokeTest() {
}
await page.locator('#kpi-dashboard-window').selectOption('cy:2026');
await page.waitForFunction(() => document.querySelector('#kpi-dashboard-content .kpi-card')?.textContent?.includes('Calendar Year 2026 to date'));
- await page.locator('#kpi-dashboard-library').selectOption('MSU');
+ await page.locator('#kpi-dashboard-library').selectOption('system:MSU');
await page.waitForFunction(() => Array.from(document.querySelectorAll('#kpi-dashboard-window option')).some(option => option.value === 'fy:MSU:2027'));
if (!await page.locator('#kpi-dashboard-window optgroup[label="Fiscal years"]').count()) {
throw new Error('Dashboard should expose fiscal years as a distinct reporting-period group after choosing a system.');
diff --git a/tests/browser/support/browserSmokeSupport.mjs b/tests/browser/support/browserSmokeSupport.mjs
index 85d73644..b7818fd9 100644
--- a/tests/browser/support/browserSmokeSupport.mjs
+++ b/tests/browser/support/browserSmokeSupport.mjs
@@ -293,7 +293,7 @@ function buildDefaultQueryApiResponse(payload) {
body: JSON.stringify({
schema_version: 1,
generated_at: new Date().toISOString(),
- scope: { library: payload.library || 'all', library_label: payload.library === 'MSU' ? 'Mississippi State University' : 'All MLP libraries', item_type_label: 'All item types', reporting_period: payload.reporting_period || '365' },
+ scope: { library: payload.library || 'all', library_label: payload.library === 'system:MSU' ? 'Mississippi State University' : 'All MLP libraries', item_type_label: 'All item types', reporting_period: payload.reporting_period || '365' },
circulation: { checkouts: 880229, renewals: 487605, previous_checkouts: 842110, previous_renewals: 469220, checkout_change: 38119, renewal_change: 18385, checkout_change_rate: 0.045, renewal_change_rate: 0.039, comparison_available: true, comparison_coverage_complete: true, period_label: payload.reporting_period === 'fy:MSU:2027' ? 'FY 2027 to date (Jul 1, 2026–Aug 21, 2026)' : payload.reporting_period === 'cy:2026' ? 'Calendar Year 2026 to date' : 'Recent 365 days', renewal_share: 0.356, holds: 121843, holds_per_100_items: 4.3 },
collection: { items: 2813442, titles: 1601291, lifetime_checkouts: 12844308, lifetime_renewals: 5160244, in_house_uses: 843108, used_recently: 947835, recent_use_rate: 0.337, never_used: 1023995, never_used_rate: 0.364, checkouts_per_item: 4.6, total_value: 42850300, price_coverage: 0.71 },
patrons: { total: 618420, active: 183804, active_rate: 0.297, new: 42640, with_charges: 74482, with_holds: 18814, expiring_soon: 29711 },
@@ -309,7 +309,7 @@ function buildDefaultQueryApiResponse(payload) {
patron_city_breakdown: [{ label: 'Tupelo, MS', patrons: 49220 }, { label: 'Other / unknown', patrons: 420700 }],
patron_state_breakdown: [{ label: 'Mississippi', patrons: 582400 }, { label: 'Other / unknown', patrons: 36020 }],
opportunities: [{ label: 'Older items with no recorded use', count: 618220, detail: 'Created more than five years ago with zero lifetime checkouts.' }],
- filters: { libraries: [{ value: 'MSU', label: 'Mississippi State University' }], item_types: ['BOOK', 'EBOOK'], calendar_periods: [{ value: 'cy:2026', label: 'Calendar Year 2026 to date' }, { value: 'cy:2025', label: 'Calendar Year 2025' }], fiscal_periods_by_system: { MSU: [{ value: 'fy:MSU:2027', label: 'FY 2027 to date (Jul 1, 2026–Aug 21, 2026)', date_span: 'Jul 1, 2026–Aug 21, 2026' }, { value: 'fy:MSU:2026', label: 'FY 2026 (Jul 1, 2025–Jun 30, 2026)', date_span: 'Jul 1, 2025–Jun 30, 2026' }] } },
+ filters: { systems: [{ value: 'system:MSU', code: 'MSU', label: 'Mississippi State University' }], libraries: [{ value: 'MSU-MAIN', label: 'MSU Main Library' }], item_types: ['BOOK', 'EBOOK'], calendar_periods: [{ value: 'cy:2026', label: 'Calendar Year 2026 to date' }, { value: 'cy:2025', label: 'Calendar Year 2025' }], fiscal_periods_by_system: { MSU: [{ value: 'fy:MSU:2027', label: 'FY 2027 to date (Jul 1, 2026–Aug 21, 2026)', date_span: 'Jul 1, 2026–Aug 21, 2026' }, { value: 'fy:MSU:2026', label: 'FY 2026 (Jul 1, 2025–Jun 30, 2026)', date_span: 'Jul 1, 2025–Jun 30, 2026' }] } },
privacy: { suppression_threshold: 10 },
sources: [{ label: 'Current item snapshot', detail: 'Aggregated test data.' }]
}),
diff --git a/tests/unit/core/mockQueryBackendLogic.mjs b/tests/unit/core/mockQueryBackendLogic.mjs
index e46119c5..61cfb80d 100644
--- a/tests/unit/core/mockQueryBackendLogic.mjs
+++ b/tests/unit/core/mockQueryBackendLogic.mjs
@@ -75,13 +75,13 @@ test('demo dashboard status includes clearly marked sample operational activity'
test('demo library dashboard demonstrates comparisons, fiscal periods, and privacy-safe geography', async () => {
const initialResponse = await handleDemoQueryRequest({
- body: JSON.stringify({ action: 'library_dashboard', library: 'MSU' }),
+ body: JSON.stringify({ action: 'library_dashboard', library: 'system:MSU' }),
headers: authHeaders
});
const initial = await initialResponse.json();
const reportingPeriod = initial.filters.fiscal_periods_by_system.MSU[0].value;
const response = await handleDemoQueryRequest({
- body: JSON.stringify({ action: 'library_dashboard', library: 'MSU', reporting_period: reportingPeriod }),
+ body: JSON.stringify({ action: 'library_dashboard', library: 'system:MSU', reporting_period: reportingPeriod }),
headers: authHeaders
});
const payload = await response.json();
@@ -89,6 +89,8 @@ test('demo library dashboard demonstrates comparisons, fiscal periods, and priva
assert.equal(payload.scope.reporting_period, reportingPeriod);
assert.equal(payload.circulation.comparison_available, true);
assert.equal(payload.circulation.fiscal_system, 'MSU');
+ assert.equal(payload.scope.library, 'system:MSU');
+ assert.ok(payload.filters.systems.some(system => system.value === 'system:MSU'));
assert.match(payload.circulation.period_label, /^FY \d{4}.*\([A-Z][a-z]{2} \d{1,2}, \d{4}–[A-Z][a-z]{2} \d{1,2}, \d{4}\)$/u);
assert.match(initial.filters.fiscal_periods_by_system.MSU[0].date_span, /^[A-Z][a-z]{2} \d{1,2}, \d{4}–[A-Z][a-z]{2} \d{1,2}, \d{4}$/u);
assert.ok(payload.filters.fiscal_periods_by_system.MSU.length >= 3);
diff --git a/tests/unit/ui/libraryDashboardModelLogic.mjs b/tests/unit/ui/libraryDashboardModelLogic.mjs
index 5c5ee2dc..ba09eca5 100644
--- a/tests/unit/ui/libraryDashboardModelLogic.mjs
+++ b/tests/unit/ui/libraryDashboardModelLogic.mjs
@@ -10,6 +10,7 @@ test('library dashboard normalizes aggregate groups and filter metadata', () =>
patrons: { total: 32 },
library_breakdown: [{ label: 'Main', checkouts: '30' }],
filters: {
+ systems: [{ value: 'system:MAIN', label: 'Main System' }],
libraries: [{ value: 'MAIN', label: 'Main' }],
item_types: ['BOOK'],
calendar_periods: [{ value: 'cy:2026', label: 'Calendar Year 2026 to date' }]
@@ -21,6 +22,7 @@ test('library dashboard normalizes aggregate groups and filter metadata', () =>
assert.equal(dashboard.circulation.coverage_complete, true);
assert.equal(dashboard.libraryBreakdown[0].checkouts, 30);
assert.equal(dashboard.filters.libraries[0].value, 'MAIN');
+ assert.equal(dashboard.filters.systems[0].value, 'system:MAIN');
assert.equal(dashboard.filters.calendarPeriods[0].value, 'cy:2026');
assert.deepEqual(dashboard.availability, { circulation: true, collection: true, patrons: true });
assert.equal(libraryDashboardHasData(dashboard), true);