diff --git a/api/web/index.html b/api/web/index.html
index 0322af7..0291e44 100644
--- a/api/web/index.html
+++ b/api/web/index.html
@@ -1377,6 +1377,22 @@
}
.load-btn .arrow { color: var(--orange); margin-right: 4px; }
.load-btn:hover .arrow { color: var(--black); }
+ /* Per-row "remove from playlist" × — only rendered when the active
+ source is a regular playlist (see renderLibrary's reorderMode). */
+ .pl-rm-btn {
+ background: transparent;
+ border: 1px solid var(--wire);
+ color: var(--text3);
+ width: 22px; height: 22px; padding: 0;
+ margin-right: 6px;
+ vertical-align: middle;
+ font-family: inherit;
+ font-size: 12px;
+ line-height: 1;
+ cursor: pointer;
+ transition: all .12s ease;
+ }
+ .pl-rm-btn:hover { background: var(--red); color: var(--black); border-color: var(--red); }
.load-menu {
position: fixed;
@@ -5902,6 +5918,7 @@
+ ${activeRegularPlaylist() ? '' : ''}
@@ -5913,6 +5930,8 @@
bar.querySelector('#bulk-tag-add').onclick = e => openBulkTagPicker(e.currentTarget, 'add');
bar.querySelector('#bulk-tag-remove').onclick = e => openBulkTagPicker(e.currentTarget, 'remove');
bar.querySelector('#bulk-playlist').onclick = e => openBulkPlaylistPicker(e.currentTarget);
+ const plRemove = bar.querySelector('#bulk-pl-remove');
+ if (plRemove) plRemove.onclick = bulkRemoveFromPlaylist;
bar.querySelector('#bulk-export').onclick = () => {
const sel = Array.from(bulkSelectedIDs);
openExportModal('selection:' + sel.join(','), `${sel.length} SELECTED TRACK${sel.length === 1 ? '' : 'S'}`);
@@ -6279,6 +6298,9 @@
// row at once — now that the cap is gone every filtered row is rendered,
// so this matches the full filtered total shown in the stats chip.
const head2 = `
` + head + '
';
+ // Source-aware row extras: when viewing a regular playlist, rows can be
+ // drag-reordered and get a per-row × that removes them from the playlist.
+ const reorderMode = activeRegularPlaylist();
const body = visible.map(t => {
const cells = cols.map(c => `