Skip to content

Commit 652e073

Browse files
committed
fix: delete transcript records atomically with media files, bump 0.9.2
- Add deleteMediaRecords helper to remove media + transcript entries in one transaction; reuse in cleanupOldFiles, deleteMediaFile, and clearAllMediaFiles so orphaned transcripts are never left behind - Remove obsolete v3 migration block that wiped mediaTranscripts on store rehydration - Bump version to 0.9.2 and update CHANGELOG
1 parent e617fc2 commit 652e073

4 files changed

Lines changed: 63 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.9.2] - 2026-03-15
11+
12+
### Added
13+
- **Persistent player**:
14+
- Persistent media player that survives page navigation, preserving playback state across routes
15+
- Virtualized transcript list for smooth rendering of long transcripts without performance degradation
16+
17+
### Changed
18+
- **Transcript controls**:
19+
- Merged transcript action buttons directly into the header for a cleaner, more accessible layout
20+
- Replaced individual export buttons with a single dropdown menu to reduce toolbar clutter
21+
- **Storage cleanup**:
22+
- Transcript records are now deleted atomically alongside media files using a shared `deleteMediaRecords` helper
23+
- `clearAllMediaFiles` now clears both the media store and transcript store in a single transaction
24+
- Cleanup routine removes orphaned transcript records when old media files are purged
25+
26+
### Fixed
27+
- **AI settings**:
28+
- AI provider and model selections now initialize correctly from localStorage on first render
29+
- **Storage**:
30+
- Removed obsolete v3 migration block that incorrectly wiped `mediaTranscripts` on store rehydration
31+
1032
## [0.9.1] - 2026-03-14
1133

1234
### Added

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"loop-station",
2020
"media-player"
2121
],
22-
"version": "0.9.1",
22+
"version": "0.9.2",
2323
"type": "module",
2424
"scripts": {
2525
"dev": "vite",

src/stores/playerStore.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1816,10 +1816,6 @@ export const usePlayerStore = create<PlayerState & PlayerActions>()(
18161816
}
18171817
}
18181818

1819-
if (version < 3) {
1820-
delete persistedState.mediaTranscripts;
1821-
}
1822-
18231819
return persistedState;
18241820
},
18251821
onRehydrateStorage: () => (state, error) => {

src/utils/mediaStorage.ts

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,33 @@ const updateStorageMetadata = async (
143143
}
144144
};
145145

146+
const deleteMediaRecords = async (
147+
db: IDBDatabase,
148+
ids: string[]
149+
): Promise<void> => {
150+
if (ids.length === 0) {
151+
return;
152+
}
153+
154+
await new Promise<void>((resolve, reject) => {
155+
const transaction = db.transaction(
156+
[MEDIA_STORE, TRANSCRIPT_STORE],
157+
"readwrite"
158+
);
159+
const mediaStore = transaction.objectStore(MEDIA_STORE);
160+
const transcriptStore = transaction.objectStore(TRANSCRIPT_STORE);
161+
162+
transaction.oncomplete = () => resolve();
163+
transaction.onerror = () => reject(transaction.error);
164+
transaction.onabort = () => reject(transaction.error);
165+
166+
ids.forEach((id) => {
167+
mediaStore.delete(id);
168+
transcriptStore.delete(id);
169+
});
170+
});
171+
};
172+
146173
// Clean up old files if we exceed storage limits
147174
const cleanupOldFiles = async (
148175
maxTotalStorage = DEFAULT_MAX_TOTAL_STORAGE,
@@ -194,12 +221,7 @@ const cleanupOldFiles = async (
194221
}
195222

196223
if (filesToDelete.length > 0) {
197-
const transaction = db.transaction([MEDIA_STORE], "readwrite");
198-
const store = transaction.objectStore(MEDIA_STORE);
199-
200-
for (const id of filesToDelete) {
201-
store.delete(id);
202-
}
224+
await deleteMediaRecords(db, filesToDelete);
203225

204226
// Update metadata
205227
await updateStorageMetadata({
@@ -390,19 +412,7 @@ export const deleteMediaFile = async (id: string): Promise<void> => {
390412
}
391413

392414
// Delete file
393-
await new Promise<void>((resolve, reject) => {
394-
const transaction = db.transaction([MEDIA_STORE], "readwrite");
395-
const store = transaction.objectStore(MEDIA_STORE);
396-
const request = store.delete(id);
397-
398-
request.onsuccess = () => {
399-
resolve();
400-
};
401-
402-
request.onerror = () => {
403-
reject(request.error);
404-
};
405-
});
415+
await deleteMediaRecords(db, [id]);
406416

407417
// Update metadata
408418
const metadata = await getStorageMetadata();
@@ -422,17 +432,19 @@ export const clearAllMediaFiles = async (): Promise<void> => {
422432
const db = await initDB();
423433

424434
await new Promise<void>((resolve, reject) => {
425-
const transaction = db.transaction([MEDIA_STORE], "readwrite");
426-
const store = transaction.objectStore(MEDIA_STORE);
427-
const request = store.clear();
435+
const transaction = db.transaction(
436+
[MEDIA_STORE, TRANSCRIPT_STORE],
437+
"readwrite"
438+
);
439+
const mediaStore = transaction.objectStore(MEDIA_STORE);
440+
const transcriptStore = transaction.objectStore(TRANSCRIPT_STORE);
428441

429-
request.onsuccess = () => {
430-
resolve();
431-
};
442+
transaction.oncomplete = () => resolve();
443+
transaction.onerror = () => reject(transaction.error);
444+
transaction.onabort = () => reject(transaction.error);
432445

433-
request.onerror = () => {
434-
reject(request.error);
435-
};
446+
mediaStore.clear();
447+
transcriptStore.clear();
436448
});
437449

438450
// Reset metadata

0 commit comments

Comments
 (0)