Continues the build order past Options/Scheduler/Speed Limiter/Batch/ Grabber/tray. - DropTargetWidget: frameless always-on-top drop target (docs/03-gui-spec.md §5), position persisted, accepts a dropped http(s) URL or link text and opens FileInfoDialog directly (skipping Add URL, since the URL is already known). Shown/hidden from general.showDropTarget, live via event.settings.changed, same pattern MainWindow already used for general.minimizeToTray. - Clipboard, explicit path #2 (docs/06-risks-and-spikes.md R2): GlobalShortcut wraps org.freedesktop.portal.GlobalShortcuts (CreateSession -> BindShortcuts -> Activated), triggering the same Add URL flow. Guarded end-to-end on `if(TARGET Qt6::DBus)` / VELOX_GUI_HAVE_DBUS so a build without the component degrades to "feature skipped," not broken (gui/docs/pkg-qa-requests-m1.md R4). Best-effort by design per the risk doc: fails silent, never advertised. Verified live against the real portal (a real Wayland session, not just offscreen): `CreateSession` refuses every caller with "An app id is required" — reproduced identically via a bare `busctl` call with no Qt involved at all, so this is the portal requiring a sandboxed caller identity, not something fixable from an unconfined process. Recorded as a partial Spike S2 answer in docs/06-risks-and-spikes.md: this explicit path likely doesn't work for Velox as a traditionally-packaged app on stock GNOME, only if/when it ships confined. Also fixed a real leak this verification caught: QDBusInterface's introspection cache reads as a LeakSanitizer leak the first time anything touches D-Bus (tst_rtl went red under ASan) — switched to QDBusMessage::createMethodCall, which needs no introspection. - Theming (docs/03-gui-spec.md §7): gui/resources/qss/{idm-like,dark}.qss, each with a documented palette block up top (QSS itself has no variable syntax), applied by ThemeManager and kept live via QStyleHints::colorSchemeChanged. util/Theme.hpp gives the handful of inline C++ styles (status dot, offline banner, the eleven identical error-label styles across dialogs) named constants instead of a twelfth copy of the same hex. - UiThreadWatchdog: the M1 DoD's 200 ms debug-build watchdog. A background std::thread pings the UI thread every 50 ms via a queued invokeMethod and warns once (not per-poll) if a ping goes unanswered past 200 ms; no QThread, no Qt event loop of its own, so the watchdog itself can never be what blocks the thread it watches. No-op in a release build. Proven both ways in tst_uithreadwatchdog: fires on a genuinely blocked UI thread (synchronous sleep, no processEvents) and stays silent on a responsive one. Full non-conformance suite (55 tests across every lane, `ctest -LE conformance`) passes clean at this point, including the whole gui label under ASan+UBSan. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NSCdCWFXBTSBBK3MzWtJiC
130 lines
6.7 KiB
Markdown
130 lines
6.7 KiB
Markdown
# 06 — Risks and spikes
|
|
|
|
Each spike is a **timeboxed M0/M1 investigation with a written answer in `docs/adr/`**.
|
|
Do not let any of these be discovered in M6.
|
|
|
|
---
|
|
|
|
## R1 — Snap-packaged Firefox blocks native messaging ⚠ HIGH
|
|
|
|
**Evidence gathered on this machine (2026-09-09):**
|
|
```
|
|
$ snap list firefox → firefox 154.0 mozilla**
|
|
$ snap connections firefox → home connected
|
|
network connected
|
|
network-bind connected
|
|
personal-files (dot-mozilla-firefox)
|
|
```
|
|
Ubuntu ships Firefox as a strictly-confined snap. Executing a native-messaging host binary
|
|
that lives outside the snap's confinement has a long history of breaking, and even when the
|
|
manifest is found, the host runs under the snap's constraints.
|
|
|
|
**Why it's already handled:** `network` and `network-bind` are connected, so a loopback
|
|
WebSocket to `127.0.0.1` is available regardless. The dual-transport design in
|
|
`docs/05-extension-spec.md` §4 is not belt-and-braces engineering for its own sake — it is
|
|
the direct consequence of this finding.
|
|
|
|
**Spike S1 (2 days, M0, lane EXT):** on a clean 26.04 VM, install a trivial native host
|
|
into each of the four manifest locations and record exactly which ones snap Firefox can
|
|
launch, and whether the launched process can reach `$XDG_RUNTIME_DIR`. Write the result to
|
|
`docs/adr/0003-native-messaging-under-snap.md`.
|
|
|
|
**Decision rule:** if native messaging works → prefer it, keep WS as fallback. If it does
|
|
not → WS becomes the primary path, `velox-nmhost` still ships for deb/flatpak/tarball
|
|
Firefox users, and the installer detects the snap and configures pairing automatically.
|
|
Either way, M2 ships. The *installer* must detect which Firefox is in use and say so.
|
|
|
|
---
|
|
|
|
## R2 — Wayland clipboard monitoring ⚠ HIGH (feature-shaping)
|
|
|
|
Ubuntu 26.04 defaults to GNOME on Wayland. A Wayland client **cannot** passively observe
|
|
clipboard changes made by other applications — that is a deliberate security property, not
|
|
a bug, and it is the mechanism IDM's clipboard capture relies on.
|
|
|
|
**Spike S2 (2 days, M1, lane GUI):** test, on this exact desktop, (a) whether Qt receives
|
|
`QClipboard::dataChanged` for copies made in another app, (b) whether Mutter exposes
|
|
`ext-data-control-v1` / `wlr-data-control`, (c) whether `org.freedesktop.portal.GlobalShortcuts`
|
|
gives a reliable "grab clipboard now" hotkey, (d) XWayland fallback behaviour.
|
|
|
|
**Ship-regardless design:** the extension context menu covers the browser case (where
|
|
almost all copied download links come from), the portal global shortcut covers explicit
|
|
capture, and the Add-URL dialog pre-fills from the clipboard when opened. Background
|
|
monitoring is a bonus if the spike says yes. **Do not let this block the release, and do
|
|
not promise it in the UI before S2 answers.**
|
|
|
|
**Spike S2, item (c) answered — verified live, not the full spike:** on this desktop
|
|
(GNOME/Mutter, Ubuntu 26.04, plain non-Flatpak/non-snap process), `CreateSession` on
|
|
`org.freedesktop.portal.GlobalShortcuts` refuses every caller with `"An app id is
|
|
required"` — reproduced two ways: `gui/src/clipboard/GlobalShortcut.cpp`'s real async
|
|
D-Bus call, and a bare `busctl --user call … CreateSession` from an interactive shell
|
|
(no Qt involved at all), both under a real Wayland session (`WAYLAND_DISPLAY` set), not
|
|
just offscreen. Because the second reproduction has no Qt/app-level identity to configure
|
|
at all and still fails identically, this looks like the portal requiring the caller's
|
|
*bus connection* to already carry a sandboxed app id (Flatpak/snap portal-confined) —
|
|
something no amount of `QGuiApplication::setDesktopFileName()` or similar can supply from
|
|
an unconfined process. **Practical read:** the global-shortcut explicit path likely does
|
|
not work at all for Velox as a traditionally-packaged (.deb/AppImage) app on stock
|
|
GNOME — only if/when it ships confined. The code is still in (best-effort, fails silent
|
|
exactly like this, never advertised — see the file's own header), since it costs nothing
|
|
and activates automatically the day that changes. Items (a) `QClipboard::dataChanged`
|
|
cross-app, (b) `wlr-data-control`, and (d) XWayland fallback are **still unanswered** —
|
|
this was one item of S2's four, not the full spike.
|
|
|
|
---
|
|
|
|
## R3 — AMO review friction 🟠 MEDIUM
|
|
|
|
`<all_urls>` + `webRequestBlocking` + `nativeMessaging` is a heavyweight permission set;
|
|
a download manager legitimately needs it, but reviews take longer and can bounce.
|
|
**Mitigation:** run `web-ext lint` in CI from day one, no remote code execution *at all*
|
|
(no CDN scripts, no `eval`), ship readable source with a build-reproduction script, write
|
|
the permission justification in M1 rather than at submission, and submit an early
|
|
unlisted build in M3 to shake out review problems while there's still time.
|
|
|
|
---
|
|
|
|
## R4 — Servers that lie about ranges 🟠 MEDIUM
|
|
|
|
`Accept-Ranges: bytes` present but `206` never delivered; ETags that change per request;
|
|
CDNs that 403 a second connection; signed URLs that expire mid-download.
|
|
**Mitigation:** resumability is *proven* by an actual 206 with a matching `Content-Range`
|
|
(`docs/04` §2); `tools/testserver` implements each of these as an explicit hostile mode and
|
|
CORE's DoD requires passing all of them; `download.refreshUrl` exists so the user can paste
|
|
a fresh signed URL into a running task.
|
|
|
|
---
|
|
|
|
## R5 — Qt 6 LGPL compliance 🟢 LOW but do it right
|
|
|
|
Dynamic linking against unmodified system Qt satisfies LGPLv3. **Do not** static-link Qt
|
|
into the AppImage without reading the terms; if the AppImage bundles Qt, bundle it as
|
|
shared objects and ship the relink information. Record in `docs/adr/0002-qt-licensing.md`.
|
|
|
|
---
|
|
|
|
## R6 — ffmpeg/libav licensing for the media grabber 🟢 LOW
|
|
|
|
Depend on the distro's ffmpeg rather than bundling; keep the muxer behind a runtime check
|
|
so the app degrades gracefully when ffmpeg is absent. Never bundle a GPL build into a
|
|
package whose licence conflicts.
|
|
|
|
---
|
|
|
|
## R7 — Contract drift between lanes 🟠 MEDIUM
|
|
|
|
The classic parallel-development failure: GUI and extension each "fix" the protocol in
|
|
their own tree and integration in M2 becomes a rewrite.
|
|
**Mitigation:** the entire `contracts/` discipline — single owner, generated code, golden
|
|
fixtures, conformance suite as a merge gate. If a lane finds the contract wrong, it opens
|
|
a `contracts/`-only PR; it does **not** work around it locally. This is the single most
|
|
important process rule in the project.
|
|
|
|
---
|
|
|
|
## R8 — Scope creep into a browser-agnostic product 🟢 LOW
|
|
|
|
Chrome/Chromium support means losing blocking `webRequest` and rebuilding capture on
|
|
`declarativeNetRequest` + `downloads.onDeterminingFilename`, which is a different design.
|
|
Ship Firefox 1.0 first. Revisit after M7 as its own project, not as an M3 side quest.
|