Bundle of UX + feature work. Split from packaging by intent so the diff
is reviewable; the next Theseus rebuild ships it.
Features
- Download tracker (new): session.on("will-download") → per-item state
{id, filename, url, mime, total, received, state, savePath, startedAt}
with updated/done event handlers. New downloadsPop WebContentsView
loads downloads.html (new file) + downloads-preload.js (new file);
panel positioned under a new #downloads toolbar button between search
and Tor. Full IPC: downloads-get, toggle/close/resize-downloads,
download-open/show/cancel/clear, downloads-clear-all. In-memory only —
cross-session persistence is a future addition. Button badge shows
active count + spin/done/err color.
- Search engines split by kind + tier:
* kind: "search" | "llm" — separate headers in picker + settings
("Search with" / "Ask an AI"). Empty sections hidden.
* tier: "catalog" | "extra" — Settings now has THREE panes behind the
"+ Add search engine" button: curated catalog, wider discoverable
bank filtered by a live search input, custom URL form.
* DEFAULT_ENABLED unchanged (5 major engines).
* Custom user-added engines carry tier="custom" (never in catalog/extra
panes).
- 9 tier="extra" engines added (all non-login ?q=): Marginalia, Stract,
Yep, Presearch, MetaGer, Qwant, Swisscows, Naver, Baidu. Same rot rule
as LLMs: if one starts bouncing to a login gate, drop it.
Bug fixes
- Loadbar collapses to 0px when idle (was reserving a permanent 2px
strip below the address bar). .loadbar {height:0} + .loadbar.on
{height:2px} + 120ms transition.
- Native <select> popup theme sync via :root { color-scheme: dark } +
@media(prefers-color-scheme: light). nativeTheme.themeSource already
drives prefers-color-scheme, so the OS popup color follows the app
theme automatically (fixed light popup on dark app / vice versa).
- .ctl layout flipped to flex-direction: row with flex-wrap so
anti-fingerprint mode + value fields fit side-by-side.
Settings restructure
- General section: Startup group at the top ("Open previous windows and
tabs" toggle), then Appearance below with three visual THEME CARDS
(System / Light / Dark) — small mock-browser previews per theme,
Firefox-style, active card gets a blue ring. System pipes through to
nativeTheme.themeSource = "system".
- Search promoted to a top-level sidebar item between General and
Naming. Search-engine controls moved out of General into Search.
- Search section: enabled list shows only enabled engines, grouped by
kind, drag-reorder within a kind. "+ Add search engine" opens the
catalog/extras/custom-URL panel.
Search engine catalog trims (already flagged in prior work)
- Removed ChatGPT / Claude / You.com (login-gated ?q=).
- Removed SearXNG (federated; every single-instance default rots).
Docs
- TheseusNavigator/PENDING.md and GOTCHAS.md born with this work
(see the HANDOFF.md commit for the convention).
- PENDING.md's own "session: 2026-08-02:theseus-ux-polish" group will be
emptied after this ship lands.
Preview harness
- _preview.html + _settings-preview.html stubs updated with kind + tier
+ downloads seed + tier="extra" samples so the preview reflects reality.
Both files are gitignored — local only.
Coordination
- Parallel session's collision-policy work (chrome.html registry chips,
popover switcher, Naming section, in-tab collision prompt) already
landed in commits 256079d/42b340f/b0d6375/78dddda. This commit adds
cleanly on top.
59 lines
3.1 KiB
HTML
59 lines
3.1 KiB
HTML
<!doctype html>
|
|
<html><head><meta charset="utf-8">
|
|
<style>
|
|
:root { color-scheme: light dark; font-family: system-ui, sans-serif; }
|
|
html, body { margin: 0; background: transparent; }
|
|
.menu { background: #1c222c; border: 1px solid #ffffff26; border-radius: 10px; box-shadow: 0 12px 34px #000c; overflow: hidden; color: #e7eaf1; }
|
|
.hdr { font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: #8b98a9; padding: 10px 14px 6px; }
|
|
.item { display: flex; align-items: center; gap: 10px; padding: 8px 14px; cursor: pointer; font-size: 13px; }
|
|
.item:hover { background: #ffffff12; }
|
|
.item .ic { width: 18px; height: 18px; flex: none; display: grid; place-items: center; }
|
|
.item .ic img { width: 16px; height: 16px; border-radius: 3px; }
|
|
.item .ic .em { font-size: 14px; }
|
|
.item .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.item .chk { color: #4fd1a5; font-size: 12px; }
|
|
.sep { height: 1px; background: #ffffff14; margin: 6px 0; }
|
|
.foot .item { color: #b9c2d0; }
|
|
@media (prefers-color-scheme: light) {
|
|
.menu { background: #ffffff; border-color: rgba(0,0,0,.15); color: #1a1f28; }
|
|
.hdr { color: #7b8494; } .item:hover { background: rgba(0,0,0,.05); } .sep { background: rgba(0,0,0,.10); } .foot .item { color: #3c4453; }
|
|
}
|
|
</style></head>
|
|
<body>
|
|
<div class="menu">
|
|
<div id="groups"></div>
|
|
<div class="sep"></div>
|
|
<div class="foot">
|
|
<div class="item" id="settings"><span class="ic"><span class="em">⚙️</span></span><span class="nm">Search settings…</span></div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
const $ = (id) => document.getElementById(id);
|
|
const esc = (s) => String(s || "").replace(/</g, "<");
|
|
function report() { requestAnimationFrame(() => { try { window.picker.resize(document.querySelector(".menu").offsetHeight); } catch (e) {} }); }
|
|
const KINDS = [
|
|
{ key: "search", label: "Search with" },
|
|
{ key: "llm", label: "Ask an AI" },
|
|
];
|
|
const row = (e, current) => {
|
|
const ic = e.favicon
|
|
? `<img src="${esc(e.favicon)}" onerror="this.replaceWith(Object.assign(document.createElement('span'),{className:'em',textContent:'${(e.sym||'🔍')}'}))">`
|
|
: `<span class="em">${e.sym || "🔍"}</span>`;
|
|
return `<div class="item" data-id="${esc(e.id)}"><span class="ic">${ic}</span><span class="nm">${esc(e.name)}</span>${e.id === current ? '<span class="chk">✓</span>' : ""}</div>`;
|
|
};
|
|
window.picker.onEngines((d) => {
|
|
const engines = d.engines || [];
|
|
const groups = $("groups");
|
|
// Preserve engineOrder within each kind (engines already come sorted).
|
|
const sections = KINDS.map(({ key, label }) => {
|
|
const rows = engines.filter((e) => (e.kind || "search") === key).map((e) => row(e, d.current)).join("");
|
|
if (!rows) return ""; // hide a section with no engines
|
|
return `<div class="hdr">${label}</div>${rows}`;
|
|
}).join("");
|
|
groups.innerHTML = sections;
|
|
groups.querySelectorAll(".item").forEach((el) => el.onclick = () => window.picker.pick(el.dataset.id));
|
|
report();
|
|
});
|
|
$("settings").onclick = () => window.picker.openSettings();
|
|
</script>
|
|
</body></html>
|