Commit Graph
9 Commits
Author SHA1 Message Date
samiandClaude Sonnet 5 2cb1959bff ext: no-download-logic ESLint gate in the extension-lint CI job
CLAUDE.md §3's rule was prose only for extension/ (GUI already has
gui_no_download_logic as a ctest). eslint.config.mjs adds a
no-restricted-syntax/no-restricted-globals rule banning fetch/XHR/Request,
ReadableStream.getReader, Range/Content-Range header construction, and
IndexedDB in src/**/*.ts. Verified red on a planted violation (fetch +
Range header + stream reader) and green on ordinary code; that check is
now a permanent regression test (tests/lint/no-download-logic.test.ts)
rather than a one-off manual run. Wired into the existing extension-lint
job in .github/workflows/ci.yml, ahead of web-ext lint.

Generated protocol code (src/shared/protocol/**) is excluded from lint
entirely — it must never be hand-edited, so flagging it as fixable would
be a lie.

Answers gui/docs/ext-requests-m1.md.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ed8KEmAW48v4YHdxLtqsMB
2026-09-11 12:25:19 +04:00
samiandClaude Sonnet 5 ec288db5ea ext: context-menus.ts — link/media menu items + grab-tab command
docs/05 §3: "Download with Velox" on a link (linkUrl) or a media element
(srcUrl) hands that one URL to download.add with the page as referrer; a
configurable command (Ctrl+Shift+U) does the same for the active tab. These
are explicit user requests, so they skip shouldCapture and the fail-open
path — a failure is surfaced to the user via notifications instead.

The page/selection "Download all links…" item waits on the content-script
link harvester (build-order step 7) and will be added there.

manifest: commands.velox-grab-current-tab. 9 tests. 110 total, lint clean.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
2026-09-10 15:41:54 +04:00
samiandClaude Sonnet 5 175185bbfc ext: capture/downloads-api.ts — downloads.onCreated safety net
Downloads that never reach the header hook (form POST results, service-worker
responses, clicks Firefox routes straight to its downloader) surface here.
If one looks like the daemon's, offer it FIRST and only cancel + erase
Firefox's copy on {action:"take"} — a failed or slow offer can never leave
the user with nothing. blob:/data: downloads are left to Firefox (the daemon
can't fetch a blob URL).

- offered-urls.ts: short-lived, bounded TTL set of URLs the header hook has
  already offered; the safety net checks it (via wasOffered) so nothing is
  double-handled. Hook gains an onOffered hook to populate it.
- background/index.ts: both paths share one offer(), getCookies, rules
  mirror, and OfferedUrls instance.

13 new tests incl. fail-open (offer rejects -> 'error', ignore ->
'offer_declined', cancel() throwing after take still returns 'taken').
101 tests green; web-ext lint clean.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
2026-09-10 15:39:40 +04:00
samiandClaude Sonnet 5 23407974d2 ext: capture/index.ts — blocking onHeadersReceived hook, fail-open
For a response shouldCapture() likes: gather cookies, offer to the daemon
under a hard 750 ms budget, and return {cancel:true} ONLY on an explicit
{action:"take"}. Every other outcome — shouldCapture says no, daemon down /
slow / erroring, cookies fail, anything throws — resolves to {} and Firefox
downloads normally.

Fail-open tests written first (tests/capture/hook.test.ts): offer() rejects,
offer() never settles (resolves within budget), timeout-shaped rejection,
getRules() throws, malformed details. Plus the happy paths and a check that
the capture.offer payload is well-formed from details + stash + headers.

background/index.ts: wire the stash + hook onto browser.webRequest, mirror
capture rules via capture.getRules on connect and on a capture.* settings
change; DEFAULT_CAPTURE_RULES (enabled:false) until the first mirror lands.

84 tests green; web-ext lint clean.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
2026-09-10 15:35:29 +04:00
samiandClaude Sonnet 5 05b650b8ec ext: capture/rules.ts — shouldCapture() decision table
The header-hook manager's core call: capture when any positive signal holds
and none of the vetoes do (docs/05 §2). Vetoes are absolute and checked
first — a monitored .zip on an excluded host is not captured.

Decision table written first (tests/capture/rules.test.ts, 22 rows); every
branch here exists to satisfy one. Covers the M1 DoD set: attachment,
monitored extension, monitored MIME, size threshold, excluded host (exact +
wildcard), HTML navigation, blob:/data: origin, bypass modifier, streaming
media (HLS MIME and resourceType 'media'), a page-issued range request, plus
non-GET, redirect status, sub-threshold, and large-but-renderable.

Pure function of (candidate, rules); rules are the daemon's, mirrored via
capture.getRules, so the decision never drifts from daemon policy.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
2026-09-10 13:55:05 +04:00
samiandClaude Sonnet 5 801bcaae12 ext: capture/headers.ts — request-header stash
onHeadersReceived (where shouldCapture runs) doesn't carry the headers the
browser actually sent; signed-URL and referrer-gated CDNs need them. Stash
from onBeforeSendHeaders keyed by requestId, read back at capture time.

This map sees every request, so it is bounded both ways — oldest-out past a
size cap (default 2048) and a 5-minute TTL, swept on a 60 s timer and on read
— and attach() clears an entry the moment its request completes or errors.

normalizeHeaders(): Array<{name,value}> -> lower-cased map, repeats joined
with ", ". 13 tests: eviction order, redirect re-put refresh, TTL expiry,
sweep, and the onBeforeSendHeaders/onCompleted/onErrorOccurred wiring.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
2026-09-10 13:52:07 +04:00
samiandClaude Sonnet 5 c5d596d93b ext: MV3 manifest + esbuild build; drop polyfill for Firefox's browser global
Firefox-only extension, so webextension-polyfill (a Chrome shim) is dead
weight and forces a bundler just to resolve one bare import. Use the native
`browser.*` global with @types/firefox-webext-browser instead.

  - manifest.json: MV3, event-page background (dist/background.js), the
    docs/05 §7 permission set (<all_urls> in host_permissions),
    strict_min_version 128.0, data_collection_permissions none.
  - scripts/build.mjs: esbuild bundle of src/background/index.ts -> dist/,
    esm, target firefox128. Wired to `prepare` so `npm ci` produces the
    bundle and CI's `web-ext lint` (which needs it to exist) passes with no
    added CI step. dist/ stays gitignored.
  - src/background/index.ts: event-page entry — brings the transport up,
    holds the shared reference. Capture surfaces attach here next.
  - transport/storage.ts, transport/index.ts: use the browser global.
  - tests/setup.ts: stub the browser global instead of mocking a module.

web-ext lint clean (0/0/0). typecheck clean. 38 tests still green.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
2026-09-10 13:50:23 +04:00
samiandClaude Sonnet 5 90e2580b05 ext: S1 native-messaging spike (ADR 0003) + WebSocket transport
Spike S1 — run on the target machine through real snap confinement
(apparmor snap.firefox.firefox enforced; web-ext's direct-exec of the inner
binary bypasses it, so runs were forced through `snap run firefox`):

  - manifest in ~/.mozilla/native-messaging-hosts/  -> WORKS; host launched
    unconfined with real $HOME and real $XDG_RUNTIME_DIR, bound a socket in
    the real /run/user/<uid>. Corroborated by the machine's 1Password host.
  - ~/snap/firefox/common/.mozilla/native-messaging-hosts/  -> not read
  - /usr/lib/mozilla/native-messaging-hosts/               -> not read
  - flatpak path                                           -> N/A (snap Firefox)

Decision: WebSocket stays the default; native messaging is an opportunistic
upgrade taken only when its handshake succeeds. docs/05 §4 corrected in this
commit to point the snap manifest at ~/.mozilla and mark /usr/lib as
deb/tarball-only. ADR carries a self-contained reproduction; the scratch
harness has been removed.

transport/ (build order item 1):
  - types.ts        VeloxTransport interface + error taxonomy
  - rpc.ts          JSON-RPC id correlation, per-call deadline, AbortSignal
  - backoff.ts      exponential backoff with jitter
  - discovery.ts    52000-52016 scan ordering (last-good port first)
  - websocket.ts    scan -> session.hello -> auto-pair (token in
                    storage.local) -> reconnect; -32001 fatal, refused/
                    rate-limited pairing latches needsPairing (no retry storm);
                    a mid-handshake drop aborts hello immediately
  - native.ts       connectNative(); distinguishes "not installed" (fatal,
                    lets the picker fall through) from a crash (reconnect)
  - index.ts        createTransport() runtime picker + persisted Options override

Toolchain: package.json / tsconfig (strict) / vitest; webextension-polyfill
mocked. 38 tests, incl. the WS suite against a real loopback ws server.
No manifest.json yet, so CI's extension-lint guard stays a no-op.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
2026-09-10 01:17:43 +04:00
samiandClaude Opus 5 8bb683b09d scaffold: project structure, wire contract, roadmap and agent briefs
Lays out Velox Download Manager (IDM-class download manager for Ubuntu
26.04) as a monorepo ready for parallel lane development. No implementation
code by design.

- docs/: architecture, roadmap M0-M7, IDM-parity GUI spec, engine design,
  Firefox extension spec, risks/spikes, packaging
- contracts/: wire-contract skeleton (JSON Schema + fixture templates) —
  the single synchronization point between lanes
- docs/agents/: one brief per lane (PROTO, CORE, DAEMON, GUI, EXT, PKG/QA)
  with owned directories, build order and definition of done
- CLAUDE.md: rules of engagement — lane ownership, layering, non-negotiables
- CMake scaffolding with dev/tsan/release/ci presets

Two environment findings shape the design: Firefox here is the Mozilla snap
(native-messaging risk, so the extension carries a loopback-WebSocket
fallback), and Wayland forbids passive clipboard monitoring (so clipboard
capture is explicit-action-first).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-09 18:21:11 +04:00