()
| 186 | } |
| 187 | |
| 188 | async function loadState() { |
| 189 | let changed = false; |
| 190 | try { |
| 191 | const data = await storeGet(STORAGE_KEY, null); |
| 192 | if (data) { |
| 193 | if ((data.v ?? 1) >= STORAGE_VERSION) { |
| 194 | folders = data.folders ?? []; |
| 195 | tracks = data.tracks ?? {}; |
| 196 | // Migrate old timestamp-based "Unsorted" folder to reserved ID. |
| 197 | const oldUnsorted = folders.find((f) => f.id !== TRASH_ID && f.name === "Unsorted" && f.id !== "f-unsorted"); |
| 198 | if (oldUnsorted) { oldUnsorted.id = "f-unsorted"; changed = true; } |
| 199 | // Ensure all folders have parentId field. |
| 200 | for (const f of folders) { |
| 201 | if (!Object.prototype.hasOwnProperty.call(f, "parentId")) { f.parentId = null; changed = true; } |
| 202 | } |
| 203 | // Drop title-less entries left over from before metadata persistence. |
| 204 | const noTitle = Object.keys(tracks).filter((id) => !tracks[id].title); |
| 205 | if (noTitle.length) { |
| 206 | const toRemove = new Set(noTitle); |
| 207 | noTitle.forEach((id) => delete tracks[id]); |
| 208 | folders.forEach((f) => { f.items = f.items.filter((id) => !toRemove.has(id)); }); |
| 209 | changed = true; |
| 210 | } |
| 211 | } |
| 212 | // else: stale version → start fresh |
| 213 | } |
| 214 | } catch (e) { console.warn("[catalog] failed to load state:", e); } |
| 215 | |
| 216 | try { |
| 217 | const arr = await storeGet(DELETED_JOBS_KEY, []); |
| 218 | if (Array.isArray(arr)) _deletedJobIds = new Set(arr); |
| 219 | } catch (e) { console.warn("[catalog] failed to load deleted jobs", e); } |
| 220 | |
| 221 | ensureTrash(); |
| 222 | for (const folder of folders) { |
| 223 | if (folder.id !== TRASH_ID) { |
| 224 | const nextColor = normalizeFolderColor(folder.color); |
| 225 | if (folder.color !== nextColor) { |
| 226 | folder.color = nextColor; |
| 227 | changed = true; |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | // Remove trash refs whose track data is missing (orphaned), but don't auto-empty. |
| 232 | const trashFolder = folders.find((f) => f.id === TRASH_ID); |
| 233 | if (trashFolder) { |
| 234 | const before = trashFolder.items.length; |
| 235 | trashFolder.items = trashFolder.items.filter((id) => tracks[id]); |
| 236 | if (trashFolder.items.length !== before) changed = true; |
| 237 | } |
| 238 | if (changed) saveState(); |
| 239 | } |
| 240 | |
| 241 | function saveState() { |
| 242 | ensureTrash(); |
no test coverage detected