Skip to content

Commit 7b35642

Browse files
Harden fresh-install empty state and API fallback handling
1 parent ec14449 commit 7b35642

5 files changed

Lines changed: 60 additions & 4 deletions

File tree

13.9 MB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1e59e3efe1562eb99d5595657dc9a39caf08d46d377cb0f03fea6f8e3ab00fe2 folderview.plus-2026.03.08.05.txz

folderview.plus.plg

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
66
<!ENTITY launch "Settings/FolderViewPlus">
77
<!ENTITY plugdir "/usr/local/emhttp/plugins/&name;">
88
<!ENTITY pluginURL "https://raw.githubusercontent.com/&github;/main/folderview.plus.plg">
9-
<!ENTITY version "2026.03.08.04">
10-
<!ENTITY md5 "2e71f08fbeb23ce32587c25abd680b71">
9+
<!ENTITY version "2026.03.08.05">
10+
<!ENTITY md5 "0a80435be4ea2e2e2b4a03c3ebf1d8da">
1111
]>
1212

1313
<PLUGIN name="&name;" author="&author;" version="&version;" launch="&launch;" pluginURL="&pluginURL;" icon="folder-icon.png" support="https://github.com/alexphillips-dev/FolderView-Plus/issues" min="7.0.0">
1414
<CHANGES>
1515

16+
###2026.03.08.05
17+
- Bug fix: hardened first-install behavior when no prior folder JSON data exists.
18+
- Reliability: sanitize error-shaped API responses before rendering folder/state tables.
19+
- UX: improved empty-state messages for fresh installs and hide-empty-folder scenarios.
20+
- Regression guard: added tests for first-run API fallback and empty-state handling.
21+
22+
1623
###2026.03.08.04
1724
- Bug fix: resolved a first-install regression that could render the Settings page blank.
1825
- Reliability: added startup safeguards so basic Docker/VM sections stay visible even if initial API calls fail.

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4600,8 +4600,35 @@ const maybeShowUpdateNotesPanel = async () => {
46004600
});
46014601
};
46024602

4603-
const fetchFolders = async (type) => apiGetJson(`/plugins/folderview.plus/server/read.php?type=${type}`);
4604-
const fetchTypeInfo = async (type) => apiGetJson(`/plugins/folderview.plus/server/read_info.php?type=${type}`);
4603+
const sanitizeTypeMapResponse = (response) => {
4604+
if (!response || typeof response !== 'object' || Array.isArray(response)) {
4605+
return {};
4606+
}
4607+
if (response.ok === false && typeof response.error === 'string') {
4608+
return {};
4609+
}
4610+
return response;
4611+
};
4612+
4613+
const sanitizeTypeInfoMap = (value) => {
4614+
const source = sanitizeTypeMapResponse(value);
4615+
const output = {};
4616+
for (const [name, item] of Object.entries(source)) {
4617+
if (typeof name !== 'string' || !name.trim()) {
4618+
continue;
4619+
}
4620+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
4621+
continue;
4622+
}
4623+
output[name] = item;
4624+
}
4625+
return output;
4626+
};
4627+
4628+
const fetchFolders = async (type) => (
4629+
utils.normalizeFolderMap(sanitizeTypeMapResponse(await apiGetJson(`/plugins/folderview.plus/server/read.php?type=${type}`)))
4630+
);
4631+
const fetchTypeInfo = async (type) => sanitizeTypeInfoMap(await apiGetJson(`/plugins/folderview.plus/server/read_info.php?type=${type}`));
46054632

46064633
const fetchBackups = async (type) => {
46074634
const resolvedType = normalizeManagedType(type);
@@ -5628,6 +5655,7 @@ const offerUndoAction = async (type, backup, actionLabel) => {
56285655
const buildRowsHtml = (type, folders, memberSnapshot = {}, hideEmptyFolders = false, healthMetrics = null, statusContext = null) => {
56295656
const isDockerType = type === 'docker';
56305657
const TABLE_COLUMN_COUNT = 10;
5658+
const folderCount = Object.keys(folders || {}).length;
56315659
const dockerHealthPrefs = isDockerType ? normalizeHealthPrefs('docker') : null;
56325660
const statusPrefs = normalizeStatusPrefs(type);
56335661
const rows = [];
@@ -5945,6 +5973,15 @@ const buildRowsHtml = (type, folders, memberSnapshot = {}, hideEmptyFolders = fa
59455973
const clearButton = showClearFilters
59465974
? `<button type="button" class="folder-empty-clear-filter" onclick="clearFolderTableFilters('${type}')">Clear filters</button>`
59475975
: '';
5976+
if (folderCount <= 0 && !showClearFilters) {
5977+
const firstRunMessage = isDockerType
5978+
? 'No Docker folders yet. Create your first folder in the Docker tab or import an export file.'
5979+
: 'No VM folders yet. Create your first folder in the VMs tab or import an export file.';
5980+
return `<tr><td colspan="${TABLE_COLUMN_COUNT}" class="folder-empty-cell">${firstRunMessage}</td></tr>`;
5981+
}
5982+
if (folderCount > 0 && hideEmptyFolders && !showClearFilters) {
5983+
return `<tr><td colspan="${TABLE_COLUMN_COUNT}" class="folder-empty-cell">All folders are currently hidden by "Hide empty folders".</td></tr>`;
5984+
}
59485985
return `<tr><td colspan="${TABLE_COLUMN_COUNT}" class="folder-empty-cell">No folders match current filters${filterSuffix}. ${clearButton}</td></tr>`;
59495986
}
59505987
return rows.join('');

tests/settings-bindings.test.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,14 @@ test('fresh install guard keeps basic Docker/VM sections visible on startup fail
8888
assert.match(script, /visibleKeys\.add\(section\.key\);/);
8989
assert.match(script, /showError\('Initial data load failed', error\);/);
9090
});
91+
92+
test('fresh install fallback sanitizes error-shaped API payloads and shows empty-state guidance', () => {
93+
assert.match(script, /const sanitizeTypeMapResponse = \(response\) =>/);
94+
assert.match(script, /if \(response\.ok === false && typeof response\.error === 'string'\) \{/);
95+
assert.match(script, /const sanitizeTypeInfoMap = \(value\) =>/);
96+
assert.match(script, /const fetchFolders = async \(type\) =>/);
97+
assert.match(script, /const fetchTypeInfo = async \(type\) =>/);
98+
assert.match(script, /No Docker folders yet\./);
99+
assert.match(script, /No VM folders yet\./);
100+
assert.match(script, /All folders are currently hidden by "Hide empty folders"\./);
101+
});

0 commit comments

Comments
 (0)