fix: selector overflow for 10+ items, drag-drop status guard, filename claim set for parallel safety
- Queue selector uses min-width instead of fixed width for double-digit numbers - Drag-start handler validates item is still pending before allowing drag - ensureUniqueFilename uses in-memory claim set to prevent TOCTOU race Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6c47c63fa8
commit
cf9d7b8334
14
src/main.ts
14
src/main.ts
@ -685,20 +685,26 @@ function formatDurationDashed(seconds: number): string {
|
|||||||
return `${h.toString().padStart(2, '0')}-${m.toString().padStart(2, '0')}-${s.toString().padStart(2, '0')}`;
|
return `${h.toString().padStart(2, '0')}-${m.toString().padStart(2, '0')}-${s.toString().padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const claimedFilenames = new Set<string>();
|
||||||
|
|
||||||
function ensureUniqueFilename(filePath: string): string {
|
function ensureUniqueFilename(filePath: string): string {
|
||||||
if (!fs.existsSync(filePath)) return filePath;
|
|
||||||
const dir = path.dirname(filePath);
|
const dir = path.dirname(filePath);
|
||||||
const ext = path.extname(filePath);
|
const ext = path.extname(filePath);
|
||||||
const base = path.basename(filePath, ext);
|
const base = path.basename(filePath, ext);
|
||||||
let counter = 1;
|
|
||||||
let candidate = filePath;
|
let candidate = filePath;
|
||||||
while (fs.existsSync(candidate)) {
|
let counter = 0;
|
||||||
candidate = path.join(dir, `${base}_${counter}${ext}`);
|
while (fs.existsSync(candidate) || claimedFilenames.has(candidate)) {
|
||||||
counter++;
|
counter++;
|
||||||
|
candidate = path.join(dir, `${base}_${counter}${ext}`);
|
||||||
}
|
}
|
||||||
|
claimedFilenames.add(candidate);
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function releaseClaimedFilename(filePath: string): void {
|
||||||
|
claimedFilenames.delete(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
function sanitizeFilenamePart(input: string, fallback = 'unnamed'): string {
|
function sanitizeFilenamePart(input: string, fallback = 'unnamed'): string {
|
||||||
const cleaned = (input || '')
|
const cleaned = (input || '')
|
||||||
.replace(/[<>:"|?*\x00-\x1f]/g, '_')
|
.replace(/[<>:"|?*\x00-\x1f]/g, '_')
|
||||||
|
|||||||
@ -216,6 +216,15 @@ function initQueueDragDrop(): void {
|
|||||||
list.addEventListener('dragstart', (e: DragEvent) => {
|
list.addEventListener('dragstart', (e: DragEvent) => {
|
||||||
const el = (e.target as HTMLElement).closest('.queue-item') as HTMLElement;
|
const el = (e.target as HTMLElement).closest('.queue-item') as HTMLElement;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
// Prevent dragging items that are no longer pending (race window between status change and re-render)
|
||||||
|
const itemId = el.dataset.id;
|
||||||
|
if (itemId) {
|
||||||
|
const item = queue.find(i => i.id === itemId);
|
||||||
|
if (!item || item.status !== 'pending') {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
draggedQueueItemId = el.dataset.id || null;
|
draggedQueueItemId = el.dataset.id || null;
|
||||||
el.classList.add('dragging');
|
el.classList.add('dragging');
|
||||||
if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move';
|
if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move';
|
||||||
|
|||||||
@ -358,8 +358,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.queue-selector {
|
.queue-selector {
|
||||||
width: 22px;
|
min-width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
padding: 0 3px;
|
||||||
border: 2px solid var(--text-secondary);
|
border: 2px solid var(--text-secondary);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -367,7 +368,7 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--bg-primary);
|
color: var(--bg-primary);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user