Add ov-empty CSS class to .ov-scroll-area when an overview list has no
items — disables overflow scrolling and uses flexbox to vertically and
horizontally center the empty message. Class is removed automatically
when data is present. Keeps the normal scrollable layout intact for
populated lists.
The Root CA card and Install CA modal incorrectly stated the CA only
covers *.hole.sail domains. Updated both to accurately describe that
the CA covers all virtual host domains using any private TLD
(e.g. myapp.hole.sail, api.my.internal).
Add a minimal sw.js with an empty fetch handler — required by Chrome
for the PWA install prompt to appear. Register it at the bottom of
dashboard.html with a silent catch. Add "scope": "." to the manifest.
Register sw.js in web_accessible_resources so Chrome can fetch it from
the extension origin.
- Fix icon purpose from "any maskable" to "any" to avoid incorrect
padding on some platforms
- Add explicit id field matching start_url to stabilise the App ID
- Add display_override with window-controls-overlay for native title bar
- Register web+holesail:// protocol handler so installed app can be
launched directly from custom protocol links
Add wide (1024x515) and narrow (390x844) screenshots to the web app
manifest so Chrome displays the richer install UI with a preview on
both desktop and mobile. Register screenshots directory in
web_accessible_resources so Chrome can fetch the images from the
extension origin.
Replace all extension icons (16, 32, 48, 128, 192, 512) with versions
derived from the new 512x512 brand icon for consistency. Add 192x192
and 512x512 entries to manifest.webmanifest — Chrome requires both
sizes to show the "Install page as app" option in the browser menu.
Mark the 512px icon as maskable for OS launcher compatibility.
Add manifest.webmanifest to the dashboard so Chrome can install it as
a standalone app via the browser menu. Register the manifest in
web_accessible_resources and link it from dashboard.html. Add a
dedicated PWA install section to docs/INSTALLATION.md covering install
steps, uninstall, and the requirement that the extension remains
installed.
- Default latencyPingEnabled to false so badges are hidden until opted in
- Fix toggleLatencyPing sync condition from !== false to === true to match
the new default correctly
- Default tunnelAutoReconnect to true so tunnels reconnect automatically
out of the box
- Hide latency badges in virtual hosts and service tunnels tables when
latency ping is disabled
Track a _settingsDirty flag that is set whenever the user interacts with
a settings toggle or number input. updateSettingsUI() skips the sync while
dirty so the 2-second refresh cycle cannot revert in-progress edits.
The flag is cleared after a successful save or reset.
- Snapshot and restore latency badge values across innerHTML re-renders
so badges never flash back to "…ms" on the 2-second state refresh cycle
- Add CSS transition (color 0.4s ease) and in-flight opacity dim (.pinging)
to .latency-badge for smooth color changes
- Decouple latency pinging from the state refresh cycle — pings now run on
their own independent interval instead of being triggered by refresh()
- Add two new settings under a "Latency Ping" section in Settings:
- Enable Latency Ping toggle (latencyPingEnabled, default: true)
- Ping Interval in seconds (latencyPingIntervalMs, default: 5s)
- Restart ping interval immediately on settings save so changes take effect
without a page reload
Replace the inline result div with a dedicated modal that displays
structured lookup data (host, port, protocol, private) returned by
Holesail.lookup(). Shows a loading state while the request is in
flight, and renders clear error/no-record states when the key is
not found or the lookup fails.
CRITICAL:
- certificate-authority.js: declare `regenerated` variable in installRootCA Windows path to prevent ReferenceError crash
HIGH:
- virtual-hosts.js/service-tunnels.js: call hs.removeAllListeners() in catch blocks to prevent stale listeners on failed Holesail instances
- https-proxy.js: destroy rawSocket in TLS error handler to prevent file descriptor exhaustion
- message-router.js (native): move setEventEmitter() to module-level init instead of re-calling on every message
- ssh-manager.js: add error handler to WS server to prevent unhandled error crashes
- init.js: store setInterval ID and clear on beforeunload to prevent interval accumulation
- logs.js: store and remove chrome.runtime.onMessage listener on beforeunload; add duplicate-call guard
- events.js: move pending++ before async sendMessage call to fix SSH/RDP-only import showing "Nothing to import"
- ssh.js: store resizeTimer on activeSshSession and clear in disconnectSsh; fix auto-reconnect race with _sshConnecting lock
- native-messaging.js: track retry timer IDs in array and cancel all on disconnect
- rdp.js: reuse single offscreen canvas per session instead of allocating per bitmap
MEDIUM:
- virtual-hosts.js/service-tunnels.js: clear existing.reconnectTimer before replacing tunnel entries
- message-router.js (native): destroy pingTunnel socket on error path; clear 15s fallback timer via finally()
- startup.js: wrap setImmediate body in try/finally to always resolve proxiesReadyPromise
- https-proxy.js: fix pre-connect upstream error handler to avoid writing raw HTTP into piped TLS stream; move HOP_BY_HOP to module-level constant
- connect-proxy.js: destroy upstreamSocket on clientSocket close; track and destroy active sockets in stop()
- ssh-manager.js: call cancelPasswordWatch on WS disconnect during password collection
- rdp-manager.js: remove dead remotePort variable; add error handlers to both WS servers
- backup-manager.js: log cleanupStaging errors and non-zero exit codes
- rdp.js: null out ws callbacks before closing in disconnectRdp; disconnect MutationObserver on beforeunload
- proxy-ca.js: prune stale entries from validationResults Map in renderValidatorTable
- refresh.js: deduplicate in-flight pings per port via Set
- messaging.js: read chrome.runtime.lastError in sendToNative callback
- servers.js (dashboard): add null check for $('serverEditId') element
LOW:
- port-allocator.js: add dedup check before pushing to tunnelPortFreeList
- servers.js (native): add error listener to server-mode Holesail instances
- virtual-hosts.js: remove dead prevReconnectDelay variable
- tab-lifecycle.js: change swarmRefCount fallback from || 1 to || 0 to prevent premature swarm destroy
- ssh.js/rdp.js: disconnect MutationObservers on beforeunload
- Change readyTimeoutMs default from 0 to 30000 in both state files
- Register Alt+Shift+H keyboard shortcut via manifest _execute_action command
- Add severity filter buttons (All/Info/Warn/Error) and Download .txt to Logs page; background logs.js now tags entries with proper level field
- Fire browser notifications on tunnelError events (notifyOnTunnelError setting)
- Add exponential-backoff auto-reconnect for virtual hosts and service tunnels (tunnelAutoReconnect setting, 5s–120s backoff)
- Add backupIntervalHours setting and scheduled auto-backup timer in message-router.js
- Add TCP connect latency badges to Virtual Hosts and Service Tunnels tables via new pingTunnel native message
- Add bytesIn/bytesOut/requests counters to https-proxy.js; expose in Overview status bar via getState
- Add Export/Import connection configs (JSON, no CA key) in Settings
- Add autoReconnect flag and exponential-backoff reconnect to SSH connection cards
- Add light theme CSS variables and theme toggle in Settings (persisted to localStorage)
- Add checkbox column and bulk Stop/Remove actions to Virtual Hosts, Service Tunnels, and Servers tables
- Add Peer Lookup UI card on Overview page using existing lookup message handler
Split monolithic dashboard.js (2753 lines) into 18 focused modules
under dashboard/{core,data,ui,pages}/ with refresh.js and events.js
as orchestrators. Extracted ~900-line inline <style> into dashboard.css
and moved dashboard.html to dashboard/dashboard.html.
Split background.js (609 lines) into background/{logs,state,proxy,
native-messaging,tab-lifecycle,message-router}.js with a thin entry
point using importScripts().
Deleted dead files: wrong-domain.js (duplicate of inline script).
Updated manifest.json web_accessible_resources for new paths.
Updated docs/ARCHITECTURE.md to reflect the new file structure.
No functionality changed. No build step introduced.