()
| 250 | // ─── CRUD ───────────────────────────────────────────────── |
| 251 | |
| 252 | function _addSection() { |
| 253 | const defW = _duration * DEFAULT_WIDTH_FRAC; |
| 254 | const sorted = [..._sections].sort((a, b) => a.start - b.start); |
| 255 | |
| 256 | // Find first gap ≥ defW |
| 257 | let start = 0; |
| 258 | for (const s of sorted) { |
| 259 | if (s.start - start >= defW) break; |
| 260 | start = Math.max(start, s.end); |
| 261 | } |
| 262 | |
| 263 | // Clamp and verify room |
| 264 | start = Math.min(start, _duration - MIN_SEC); |
| 265 | if (start < 0) return; |
| 266 | const end = Math.min(start + defW, _duration); |
| 267 | if (end - start < MIN_SEC) return; |
| 268 | |
| 269 | // Verify no overlap |
| 270 | if (_sections.some((s) => start < s.end && end > s.start)) return; |
| 271 | |
| 272 | const color = _nextColor(); |
| 273 | const section = { id: _nextId(), name: "Section", start, end, color }; |
| 274 | _sections.push(section); |
| 275 | _render(); |
| 276 | _scheduleSave(); |
| 277 | |
| 278 | // Open rename immediately |
| 279 | const el = _container?.querySelector(`[data-id="${section.id}"]`); |
| 280 | if (el) _openRename(section.id, el.querySelector(".section-label")); |
| 281 | } |
| 282 | |
| 283 | function _deleteSection(id) { |
| 284 | _sections = _sections.filter((s) => s.id !== id); |
no test coverage detected