The v3.3.103 log (real 2464-job, 4-hoster batch ramping to 95 concurrent) confirmed
the statSync fix held (batch-start main spike 336→231ms with 10× more jobs) and the
whole 90s ramp to 95 active was pristine (event-loop mean ~11ms, fps=32,
longtasks=0). The one residual: rapidly clicking tabs DURING the 95-active upload
produced 210-221ms renderer long-tasks (proc=0ms → layout/paint, not JS).
Cause: the Recent-uploads panel rendered every sessionFilesData row (up to 2000)
into the DOM non-virtualized — the exact analog of the History table before it was
virtualized in v3.3.102. Switching to that view laid out ~2000 rows (~210ms on the
RDP VM).
Fix — virtualize renderRecentUploadsPanel, mirroring the queue/History pattern:
- The tbody gets only the visible rows plus top/bottom spacer <tr> sized from
VIRTUAL_ROW_HEIGHT. A rAF-coalesced scroll handler and a ResizeObserver on
.recent-files-table-wrap re-render the visible window (the ResizeObserver also
serves as the show-trigger when the hidden panel gains size). _recentWorking holds
the sorted set. The insertAdjacentHTML append-only fast path is dropped — a
~40-row window re-render is cheap, so every render just re-renders the window; on
prepend (date desc) the scroll position is preserved (scrollTop=0 at top, else
+= added*ROW_HEIGHT).
- Selection stays correct: _buildRecentRowHtml already stamps the selected class
from selectedRecentIds.has(row.order) per row, so an off-screen-selected row
renders selected when scrolled into view; selectedRecentIds remains the source of
truth and shift-select already reads the sort cache, not the DOM.
- styles.css gives .recent-file-row a fixed 28px height so the virtualization math
is exact (the table already had table-layout:fixed, so no column-jump fix needed).
- _renderRecentVirtualRows returns early when there are no rows, so it never wipes
the empty-state message.
Verified with Playwright at 2000 rows (bounded container): view-show layout drops
from ~118ms to ~2.4ms, the DOM stays at 29-39 rows, scrolling maps to the correct
rows, the scrollbar height is exact, an off-screen-selected row renders with the
selected class, and the row height is exactly 28. Every large table (Queue,
History, Recent) is now virtualized.
407 tests pass; clean Electron boot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v3.3.101's gate removed the get-history parse on tab switch, but the log still
showed tab clicks at ~216ms with a ~197ms renderer long-task and NO get-history
(proc=0ms → pure browser layout, not JS). Cause: `.view{display:none}` ->
`.active{display:flex}` and the History table built up to 2000 non-virtualized
<tr>, so making the view visible laid out 2000 rows (~197ms on the RDP VM). The
queue was already virtualized; the History table was the only large non-virtual
one.
Measured the options with Playwright on the real DOM (this machine; the user's VM
is ~1.7x slower):
- current 2000 rows (auto layout): 118ms
- content-visibility + table-layout:fixed: 117ms (no help — rows still laid out)
- cap to 300 rows: 16ms, but rejected: History rows are per-file-per-hoster, so a
single 1280-file batch is ~3840 rows and a small cap would hide a recent batch's
links
- virtualize (40 visible of 2000): 2.3ms
Fix — virtualize renderHistoryTable, mirroring the queue's _renderVirtualRows:
- The header is always rendered; tbody#historyBody gets only the visible rows plus
top/bottom spacer <tr> sized from VIRTUAL_ROW_HEIGHT. A rAF-coalesced scroll
handler and a ResizeObserver on #historyContainer re-render the visible window;
the ResizeObserver also serves as the show-trigger (a hidden 0x0 container that
gains size on tab activation re-renders at the correct height). Sorting resets
scrollTop and re-renders; the copy-link / sort-header click delegation is
unchanged. _historyWorking holds the sorted working set.
- styles.css: .history-table gets table-layout:fixed plus scoped column widths so
columns do not jump as rows scroll in and out. This is scoped to .history-table
and does not touch the .col-* classes the queue shares.
Verified end-to-end with Playwright at 6000 rows: show cost 1.1ms (was 118ms), DOM
stays 32-42 rows, scroll maps to the right rows (top/middle/bottom), scrollbar
height exact, column widths stable, rows update on scroll. All rows remain
scrollable (no UX loss); the show cost drops ~100x.
407 tests pass; clean Electron boot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three changes, investigated and adversarially reviewed via multi-agent workflow.
1. Queue maximize bug: the virtual-scrolled upload queue derived its visible-row
window from #queueContainer.clientHeight but only recomputed it on 'scroll'.
Maximizing the window enlarges the container without a scroll event, so rows
below the old viewport stayed blank until you scrolled. Fix: a ResizeObserver
on #queueContainer reusing the existing rAF-coalesced _onQueueScroll. Strictly
stronger than a window 'resize' listener — it also covers the hidden->visible
view-switch case (container 0 -> real height). No feedback loop (container box
is flex-sized, not content-sized); early-returns for <200-row queues.
2. Einstellungen tab restructured from 4 dense stacked collapsible panels into
horizontal sub-tabs: Allgemein / Automatik / Logs & Diagnose / Fernsteuerung /
Backup. All sub-pages render into the DOM at once (only the active one is
shown), so every element id stays present and saveSettings keeps working. The
cluttered "Allgemein" block is split across Allgemein + Automatik + Logs.
Per-hoster settings already moved to Accounts in v3.3.69.
3. saveSettings hardened to be null-safe (elTxt/elChk/elInt helpers): when a
settings element is absent from the DOM it now keeps the current config value
instead of collapsing to a default. Behavior is identical when elements are
present; this is insurance against a future dropped id silently overwriting a
real setting (e.g. webhook URL, folder-monitor hoster pre-selection).
Verified: 43 sub-tab ids unique, all 26 saveSettings ids present, lint clean,
boot clean, 284 tests green, 0 confirmed defects from a 3-lens adversarial review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The settings section was nested inside the collapsible cards body, so you had to
expand a hoster group first before you could open its upload settings. Moved the
section out to the group level (sibling of the cards body), so the
"Upload-Einstellungen" toggle is always visible directly under each hoster header
— collapsed or not — for every hoster. Styled as a full-width row matching the
group header. Group-cards toggle and the settings toggle are now fully independent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Declutters the Einstellungen tab: the per-hoster panels (Retries, Max Speed,
Parallel Uploads, Restart-below, Interval, Max Size, log-to-file) are no longer
listed there. Each hoster group in the Accounts tab now has a collapsible
"Upload-Einstellungen" section with exactly those controls, so everything about
a hoster lives in one place. The Einstellungen tab shows a short pointer to where
they moved.
Per-hoster edits in Accounts save via a dedicated lightweight handler
(scheduleHosterSettingsSave → saveHosterSettings) that touches only
hosterSettings — it deliberately does NOT route through the global saveSettings,
so tweaking a hoster slider can't re-run the folder-monitor start/stop or clobber
global fields. The global saveSettings still reads .hs-input[data-hoster] wherever
they live, and global inputs (which have no data-hoster) are unaffected. The new
inputs drop the .settings-autosave class and bind via delegation on the persistent
accounts container so they survive re-renders.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a custom HTML menu bar above the tab bar, replicating the Real-Debrid
Downloader's look: click-to-open triggers, hover-switch between open menus,
click-outside / Escape to close, right-flyout submenus.
- Datei: Dateien hinzufügen, Ordner hinzufügen, Sicherung submenu
(Export/Import backup), Neustart, Beenden.
- Einstellungen: "Einstellungen öffnen" (jumps to the settings tab) plus an
inline live grid with the max-parallel-uploads spinner and a speed-limit
checkbox + MB/s spinner, both two-way-synced with the Settings tab fields
via saveGlobalSettings (parallelUploadCount / globalMaxSpeedKbs).
- Hilfe: Log-Ordner öffnen, Diagnose-Paket exportieren, Suche Aktualisierungen.
Wiring reuses existing handlers (addFiles/addFolder buttons, doBackupExport/
Import, openLogFolder, createSupportBundle, checkForUpdate) and two new tiny
IPC handlers app:restart (app.relaunch+quit) / app:quit. initMenuBar() is
wrapped in try/catch in setupListeners so a menu fault can never break core
app init. CSS maps the downloader's menu styles onto this app's theme vars.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a bottom-right button under the accounts list that expands every hoster
group at once (and collapses them again — the label flips based on current
state). Reuses the existing _hosterGroupOpenMemory so the per-group open/closed
state stays consistent with manual header clicks. The footer is hidden when no
accounts exist. Pure DOM toggle, no re-render.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Upload-Verlauf tab took 30s+ to open on large, never-cleared histories.
Measured on the live config: 18.9 MB config / 41 batches / 53,403 rendered rows.
Two independent problems, fixed together:
1. Render side (the 30s killer): renderHistoryTable built ALL rows into one
innerHTML. Now the build is capped to the newest 2,000 rows (HISTORY_RENDER_CAP)
after slicing the chronological tail, then sorted for display. A notice shows
"Zeige neueste 2000 von N" so nothing looks deleted; full history stays on disk
and "Verlauf exportieren" still emits everything (export reads loadHistory()).
2. Storage side: history grew unbounded because appendHistory only ever pushed.
New globalSettings.historyRetention ('all' default, non-destructive) with
policies: 7d / 30d / 90d (time-based) and 1000 / 100 (newest-uploads-based).
appendHistory now prunes after each batch; a new prune-history IPC re-applies
the policy immediately when the user changes it, gated behind a confirm() that
shows the exact removal count (dry-run first).
Prune model keeps whole batches (atomic): count policies accumulate rendered rows
from the newest batch backward and always keep >= the newest batch even if it
alone exceeds the target; time policies keep batches whose timestamp is missing
or unparseable (old entries have none) so ambiguous data is never silently dropped.
New retention dropdown lives in the Verlauf header. 9 unit tests cover the prune
edge cases incl. a realistic 41-batch fixture. Full suite: 272 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User reported two coupled issues in the accounts panel:
- "VOE Upload: CSRF-Token nicht gefunden. Bist du eingeloggt?" fires
intermittently across multiple VOE accounts when "Accounts prüfen" runs.
Each retry "fixes" one and breaks another — classic anti-bot burst response.
- The flat badge strip becomes unreadable with many accounts; user wants
collapsible per-hoster groups with "N/M" headers and green/red indicators,
click to expand to per-account detail.
DISCRIMINATOR CHECK (cheap before serializing): grep'd lib/voe-upload.js for
module-level state — none. Each new VoeUploader() carries its own cookie Map.
Burst-throttle on VOE's side is the only plausible root cause.
CONCURRENCY FIX in main.js runHosterHealthCheck:
- Group checks by hoster, run each hoster's group SEQUENTIALLY, groups in
parallel (Promise.all of sequential runners). Cross-hoster parallelism
preserved; intra-hoster bursts eliminated.
- Result array preserves input order via a result-index map.
- Hardening per review: dedup duplicate {hoster, accountId} entries before
grouping (no wasted API calls if a caller ever sends duplicates), and entries
missing accountId now return a clean "Account-ID fehlt" error instead of
silently calling per-hoster checker with null config.
- Validate-credentials and checkSingleAccount paths unchanged (single-check
payloads run the same way regardless).
- Latency trade-off acknowledged: 5 VOE accounts ~5x faster path → up to 25s
for that hoster's column. That's the cost for reliability; the user's
alternative was 0/5 working on burst-failed runs.
UI FIX in renderer:
- New _buildAccountHosterGroupHtml emits a collapsible per-hoster group
reusing the existing .hoster-panel-header / .panel-arrow CSS pattern.
- Header shows "VOE 4/5" (ok-count / total-accounts), a green/red/amber/gray
status dot, plus pills for "N deaktiviert" and "N Fehler".
- Default: auto-expand any hoster with errors, checking, or unchecked
accounts; collapse all-green.
- Open-state memory tracks user clicks. Per review: also tracks errorsAtClose
snapshot so a NEW failure since the user's close forces re-expand once.
Prevents the "I closed it once and now silent failures hide forever" risk.
- Single-card updates also refresh the parent group's header counter via
_refreshHosterGroupHeader.
- Flat badge strip in renderHealthCheckResults is now a no-op stub — the
per-hoster headers carry the same info, less duplication.
Three-lens review (workflow wch4p9ee9): concurrency PASS_WITH_NOTES, ui-state
PASS_WITH_NOTES, comment-policy PASS (zero new // or /* */ comments).
Latent concerns from review applied as hardenings.
210/210 tests green, lint clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The new "Links in Log schreiben" control reuses class .hs-input for
the autosave bind to pick it up — but .hs-input also carries the
text-input styling (flex:1, padding, background, border, max-width:
300px). Applied to a checkbox that produced a stretched, padded,
filled box instead of a normal tick box.
Add an .hs-input[type="checkbox"] override that resets flex/size/
padding/background/border so it renders as a plain 16×16 checkbox
beside its label, consistent with the other settings checkboxes.
Caught during the post-feature side-effect sweep (advisor flagged the
grid layout as the one thing self-checks couldn't cover). 147/147
tests still green.
The .queue-row rule had transition: background 0.15s applied
unconditionally. Every status flip (queued → getting-server →
uploading → done) on every visible row therefore animated for 150 ms,
and with 30+ rows changing state in close succession the compositor
ran overlapping tweens that ate paint time during heavy upload bursts.
Move the transition into the :hover rule. Hover-enter and hover-leave
keep their smooth fade — that's where transitions actually help the
user. Status changes now snap to the new background colour instantly,
which is what the queue table really wants: it conveys progress, not
animation.
No JS change. 97/97 tests still green.
Adds a red 'Alle entfernen' button next to the 'Zuletzt erzeugte
Upload-Links' label that clears all entries from the recent files
panel after confirmation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drag the right edge of any queue column header to resize it. Cursor
changes to col-resize on hover. Widths are saved to localStorage and
restored on next launch.
- Resizer handles in all 8 queue table columns
- Resize state visible via dragging class + body cursor override
- Min width 40px, no max (table can scroll horizontally)
- Click on resizer doesn't trigger column sort
- Persisted across sessions via localStorage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Multiple accounts per hoster with drag-sortable priority (primary + fallbacks)
- Separate account types: Web Login and API selectable per hoster
- Account fallback: after all retries fail, automatically switches to next fallback account
- Fix: Byse health check returning [Fehler] OK when API responds with msg "OK"
- Fix: retry during active upload sets status to "Wartet" instead of "Bereit"
- Config migration from single-object to multi-account array format
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Done/Error counters now use sessionFilesData (survives removeFromQueueOnDone)
- Uploaded/Total bytes tracked via session accumulators (never decrease)
- Errors no longer shown in Files list (stay in queue for retry)
- Right-click context menu: "hoster abbrechen" cancels all jobs for a hoster
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Toggle button on each account card to activate/deactivate hosters
- Disabled accounts are greyed out and excluded from upload selection
- Credentials are preserved when deactivated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Small always-on-top drop target window (toggle in Settings > Allgemein)
- Files dropped on it get added to the queue with hoster modal
- Auto-shows on app start if previously enabled
- Column headers now in English (Filename, Uploaded/Size, Progress)
- Statusbar labels in English (Connections, Total)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Full-window drop overlay with large "+" icon when dragging files over the app
- Works from any tab, not just the upload view
- Added colons to all statusbar labels for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Ordnerüberwachung panel: proper section layout matching Allgemein style
- Checkbox rows: compact spacing, checkbox before label via CSS order
- Upload inputs: consistent width, stacked vertically
- Backup section: moved to collapsible panel in settings
- Allgemein panel: collapsible
- Context menu: hidden when queue is empty
- Folder monitor badge: instant update on checkbox/path change
- Separator between system and hoster panels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Sticky tab bar: stays fixed at top when scrolling settings/history
- Context menu improvements:
- Click on empty queue area deselects all selected jobs
- Dynamic labels with selection count (e.g. "Links kopieren (3)")
- Singular/plural for single selection ("Link kopieren" vs "Links kopieren")
- "Alle entfernen" to clear entire queue
- Reorganized menu items into logical groups with separators
- Instant retry: "Erneut versuchen" now immediately starts uploading
the selected files instead of just resetting status to preview
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AES-256-GCM + PBKDF2 encrypted config backup (.mhu files).
Export/import all accounts, settings, and history.
Pre-import safety backup of current config.
Password modal with confirmation for export.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Click/Ctrl+Click/Shift+Click to select rows in Files panel
- Ctrl+A to select all, Delete to remove selected
- Right-click context menu with "Copy links" and "Remove"
- Double-click to copy single link
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Stats tab in recent panel (queue counts, sizes, speed, ETA, run time)
- Aborted jobs persist across restart (saved as queued)
- Doodstream: throttle support, better error messages with HTTP status
- Recent panel tab switching (Files / Stats)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Global speed throttle (shared across all uploads)
- Settings grouped into sections (Uploads, Verhalten, Log)
- Abort all resets jobs to queued (restartable without reupload)
- fileuploader.log writes immediately per upload
- Staggered interval per hoster (not parallel sleep)
- Recent files panel resizable via drag handle
- History hides aborted entries
- Done jobs removed from queue immediately when setting active
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use webUtils.getPathForFile (Electron 33+) for reliable file paths
- Use Set for O(1) dedup on large file drops
- Fix flex layout so Files panel stays visible with many queue items
- Fix virtual scrolling viewport height and range cache reset
- Add Ctrl+A (select all), Delete (remove selected) keyboard shortcuts
- Fix Shift+Click range selection to work with virtual scrolling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Queue table limited to 50% height with scrollbar, links panel below
- Upload processes files sequentially (file1 all hosters, then file2, etc.)
- Queue state persists immediately after adding files (not debounced)
- Add beforeunload handler to flush pending queue state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add byse.sx health check via API upload/server endpoint
- Virtual scrolling for queue table (>200 rows renders only visible rows)
- O(1) job lookups via index Maps instead of O(n) array.find()
- Event delegation on queue tbody instead of per-row listeners
- Async config writes to avoid blocking main process
- Increase persist debounce to 3s during uploads (was 250ms)
- Reduce debug logging to state changes only
- Move save button to bottom-right in settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New "Accounts" tab for managing hoster credentials separately from
upload settings. Accounts can be added, edited, and deleted via modal
dialogs. Login credentials are automatically verified on save, showing
status (Bereit/Fehler) in the account list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add max redirect depth (10) to Vidmoly _fetch to prevent stack overflow
- Drain undici response body on redirect to prevent connection leaks
- Fix installUpdate unhandled promise rejection in main.js
- Fix context menu submenu viewport overflow with flip-left CSS
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add FIFO semaphore for per-hoster concurrency control
- Add token-bucket speed limiter with abort signal support
- Rewrite upload-manager with retry loop, speed monitoring, and rich progress events
- Add per-hoster settings: retries, max speed, parallel count, restart below speed, time interval, max size
- Add context menu with shutdown-after-finish (sleep/shutdown/restart), always-on-top
- Add z-o-o-m-style queue table with 8 columns, status-colored rows, progress bars
- Add debounced queue rendering with scroll position preservation
- Add statusbar with global speed, total bytes, elapsed time
- Fix speedMonitor interval leak on error and scoping bug
- Fix throttle not respecting abort signal during cancellation
- Fix combined signal listener cleanup
- Bump version to 1.1.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>