The extension buttons sat in registration order with no way to change
it, hide one, or switch an add-on off without opening Settings. Buttons
are now draggable (drop side follows the pointer, same feedback as tabs
and bookmark chips) and the order is persisted per profile. Right-click
opens a native menu: open/close the panel, move left/right, hide from
the toolbar, turn the add-on off, and Manage extensions; the dock's own
right-click offers Show hidden. Main owns the prefs (dockOrder,
dockHidden) and the actions, so the chrome only renders. Add-on
rediscovery now pushes a fresh dock state to the chrome, which it never
did before — turning an add-on off or installing one from a page
updates the toolbar at once.
Opening a PDF cost four clicks across two screens: the dock icon, a dropdown
with a single entry in it, an editor with an empty drop zone, and finally the
Open button that produced a file browser. Three of those were the program
asking the user to confirm what they had already said by clicking a PDF icon.
The dock item now raises the file browser itself. The dialog is native and
raised from the add-on's Node side, because a file:// page cannot open one
without a user gesture of its own and a freshly-opened tab has none to spend.
The chosen file goes through the same scratch handoff a right-clicked link
already used, so the editor opens with the document in it rather than with an
invitation to find one. Cancelling still lands in the empty editor, which is
where drag-and-drop works and is probably still where someone who changed
their mind about the file wants to be.
Second click removed in the chrome: a toolbar menu holding exactly one item is
not a menu, it is a button wearing a dropdown, so it now dispatches directly.
Falls through to the popup if the renderer has no addonMenuSelect.
Not yet exercised end to end — TheseusNavigator/node_modules is empty while
another session reinstalls it, so the CDP suites cannot boot Electron. Both
files parse; run scratchpad/verify-pdf-editor/drive.mjs once the tree is back.
The Tor chip was the widest control in the toolbar and competed with
the address bar for space. It now sits on the favorites row beside the
Aegis chip, under the Theseus menu, at the same chip height — the
network-level switch next to the wallet, both always visible. State
tints (off / connecting / on) are unchanged.
Aegis sat among the extension buttons and drifted as add-ons were
installed or removed. First-class Silent Mode components (manifest
category "plugin") now get a labelled chip at the right end of the
favorites row, directly below the Theseus menu — always visible, never
collapsed into the extensions overflow. The add-on host tags each
sidebar panel and toolbar menu with the category so the chrome can
split the two docks; the extensions dock keeps its behaviour.
Tabs keep a readable minimum width (76px). Once they overflow, the row
scrolls: the earliest tabs slide out on the left and an arrow at each end
moves the row by 60% of its width, disabled at its end of travel and
hidden while everything fits. The wheel scrolls the row too, the selected
tab is brought into view when the selection changes (never while the user
is scrolling), and the + button stays outside the row. Moves are immediate
rather than animated: the chrome view has no smooth scrolling, and frame
callbacks stop while the window is occluded, which stranded a frame-driven
slide at its start.
With many tabs open, the last ones slid under the minimise / maximise /
close overlay: the strip reserved that space as padding, and padding does
not stop overflowing flex items. The tabs now live in their own row that
clips at its own edge, shrink down to icon + close before anything is
hidden, and the row scrolls sideways with the wheel once they hit that
minimum, keeping the selected tab in view. The + button sits outside the
row so it stays reachable no matter how many tabs are open.
Holding Back or Forward for 450 ms (or right-clicking it) pops a native
menu of the tab's history entries in that direction — nearest first, up to
15, titled with the page title and host — and picking one jumps straight
to it. A hold swallows the click that would otherwise fire on release, so
a long press never also goes back one page.
- Address bar: the chrome view keeps document.activeElement on the URL
input after the user clicks into the page (focus moved to the tab's own
view), and the "don't clobber typed text" guard then froze the bar until
something blurred the field. The guard now requires real focus
(document.hasFocus()), and the field is blurred when the view loses focus.
- Tab strip: rebuilt with innerHTML on every tabs event, which recreated
every favicon <img> — a blink on the other tabs whenever one tab loaded,
reloaded or changed title — and dropped drag state mid-gesture. Elements
are now keyed by tab id (chips by group colour), updated in place, and
moved into order; the strip is never rebuilt.
- Settings › Extensions links to theseus.x/extensions. That page now has a
card per bundled add-on with the current signed version, tarball and
hash read from each add-on's updates.json at load (it still claimed
Screenshot 0.2.4 while the channel serves 0.6.5).
Three tab-strip changes from use:
- A tab opened from a link (target=_blank, middle-click, the context menu,
Duplicate) now goes right after the tab it came from — and after any
siblings that tab already opened — instead of at the end of the strip.
The + button, session restore and add-on requests still append.
- The selected tab gets an accent stripe and outline on top of its brighter
fill; with a dozen same-size tabs the fill alone was easy to lose. A
grouped tab keeps its group colour on the stripe.
- On Windows the tab row is the title bar: the native frame is hidden, the
minimise/maximise/close buttons are drawn as an overlay over the chrome
(colours follow the theme), the row is a drag region with every control
in it opted out, and 140px (or the overlay's real width when the API is
exposed) is kept clear on the right. The page gains the old title bar's
height. Other platforms keep the native frame.
A typed or pasted path such as D:\Dev\x\page.html has no dotted host, so
the URL-vs-search heuristic handed it to the search engine. Paths (drive,
UNC, file://, and absolute/~ on POSIX) now load as file:// URLs before the
heuristic runs. Local-file tabs keep their file:// URL in the address bar
(normally suppressed because our own home/error pages are file://), show a
"Local file" badge, and hide the registry button since no name resolution
is involved. A missing file lands on the error page with a matching badge.
- Per-tab page zoom on Chrome's ladder (25–500 %) via setZoomFactor, so
Chromium keys it per host: every tab on a site shares the level and it
persists across navigations and restarts. Ctrl +/=/numpad+ in,
Ctrl -/numpad- out, Ctrl 0 reset, Ctrl+wheel via zoom-changed. A
percentage chip appears in the address bar when a tab isn't at 100 %;
clicking it resets. Settings and add-on tabs never zoom.
- Address bar / search bar drag ratio floor lowered from 30 % to 20 %,
so the split runs 80/20 to 20/80 (pixel floors still apply).
- The collapsed extension-dock button and its dropdown printed a data:
URI icon as text ("data:image/svg+xml…"). One addonIconHtml() renderer
now serves the dock buttons, the collapsed button and the dropdown.
- Link-status pill: it measured its own width inside a view already
capped at 100 px, so it could never grow and long hrefs were cut short.
An off-screen twin now reports the natural width; main caps it to the
tab area (never under the sidebar) and the pill ellipsises past that.
- Address bar at narrow widths: the URL input's intrinsic minimum width
pushed the registry chips and the star out past the bar. #url now has
min-width: 0 and the trailing controls are fixed-size flex items.
- The BCDN/ICANN segmented chips are replaced by one Ariadne's Thread
icon (spiral + tail) at the end of the bar: acid when served from BCDN,
blue for ICANN, caret when the name exists on both. Click opens a
native menu (registry-menu-popup): switch registry, remember per name /
per TLD, forget choices, collision policy, and a jump to the Plug-ins
settings section. Reuses the existing switch / remember / policy paths
(collision-switch body extracted to switchRegistry, open-settings to
openSettingsTab). preload's openSettings now forwards a section slug.
Editor:
- Crop tool restored — drag to select, marquee sits with a dashed acid
border and a dimmed backdrop for the area you'll discard, then the
topbar shows Apply crop / Cancel. Applying trims #base to the rect,
resets undo (dimensions changed), and drops back into the select tool.
Enter / Esc keyboard shortcuts while a crop is pending.
- Blur / mosaic redaction tool back — drag a rectangle, editor
downsamples that region of #base to ~12-block granularity and paints
the blocks back nearest-neighbour. Commits directly (no confirm step).
- Sidebar-window controls (Back, name, Copy, Save, Sound, Maximize)
reflow: Back + name on the left, Copy + Save + Sound + Maximize on
the right so the "put the sidebar back to normal size" affordance
lives where users expect it. Toolbar's drawing tools stay centred.
- Back arrow icon swapped from a chevron to a proper flat arrow
(line + arrowhead), matching the new browser back/forward glyphs.
Sounds — modeled on Firefox Screenshots' feedback rather than beeps:
- Shutter is now a real photoshoot click: two mirror-slaps built from a
band-passed noise burst (metallic ping) plus a very short square-wave
thud each. Sounds like a camera, not a beep.
- Copy is a two-chirp "printer feed" — filtered noise burst on top of a
sine chirp per beat, staccato ascending pair. Same shape Firefox Easy
Screenshot uses for "copied to clipboard".
- Save keeps its ascending triad; Discard keeps its descending pair;
new small ascending pair for Apply crop.
Chrome:
- Browser Back / Forward chevrons (M10 3 L5 8 L10 13 — two segments
meeting at a point, no shaft) replaced with straight-arrow glyphs
(line + arrowhead). Reads as a navigation arrow, not an angle bracket.
Bundled but not shipped separately — parent session OTA-signs and pushes.
Toolbar drag handle now clamps both bars to at least 30 % of the
.urlsearch budget (URL: 30 %–70 %, search fills the rest). The
existing absolute mins (URL 220 px, search bumped from 140 → 180 px
so the 'Search' placeholder always fits) still apply — the tighter of
absolute vs 30 %-of-container wins at any width. .urlsearch also gets
margin-right: 10 px so the search bar has visible breathing room from
the trailing dock (Downloads / extensions / ⛓ Theseus).
Bookmark brand-case now uses a canonical map for multi-word Silent Mode
names so all-caps sources come out correctly cased: SILENTMODE.X →
SilentMode.X, silentmode.x → SilentMode.X, coinspectrum.x →
CoinSpectrum.X. Single-word brands (Theseus, Sirius, Deviant, Aegis,
Ariadne, Argus, Hermes, Prometheus, Hephaestus, Helios, Atlas,
Katalogos, Game, Poutakidis, Syskypo) are in the same map for
consistency. Unknown names fall back to Title-case (foo.x → Foo.X)
— the ALL-CAPS preserve rule is gone, so GAME.X → Game.X now,
matching the user's ask.
Bookmark chip labels now normalise the .x TLD family to <Name>.X on
render:
theseus.x -> Theseus.X
deviant.x -> Deviant.X
Sirius.x -> Sirius.X
foo-bar.x -> Foo-bar.X
Names that were already ALL-CAPS keep their form so the visual weight
carries through:
GAME.X -> GAME.X (unchanged)
SILENTMODE.X -> SILENTMODE.X (unchanged)
Non-.x titles are untouched (CoinSpectrum, navigate.st, etc.). The
transformation runs after the descriptor trim, so titles like
'theseus.x — the browser…' also come out 'Theseus.X'.
Two visible fixes from the same 2026-09-09 screenshot:
Right-click a tab was building a DOM menu and then growing the chrome
view height so it would fit under the tabstrip. That opened a
visible gap between the toolbar and the tab body while the menu was
up. Now the tab context menu goes through a new IPC
"tab-context-menu-popup" (main.js) that pops an OS-native Menu at
the click point, floating above every WebContentsView — no layout
change, no gap. Preload exposes tabContextMenuPopup(id, {x, y});
chrome.html's tab contextmenu handler now calls it directly and the
DOM openTabContextMenu / openGroupSubmenu / growChromeForMenu path
is bypassed for tabs. (The bookmark bar's own ctxmenu still uses
the DOM path — its short 2-3-row menus don't grow chrome enough
to be visible.)
Bookmark chips were too small (130px max-width, 11px text, 22px row).
Now every chip is a fixed 150px × 28px cell so the row reads as a
uniform grid, 12.5px text, 14px favicon. Labels drop the descriptor:
"GAME.X — Bitcoin Cash game platform" renders as "GAME.X". The
trimmer splits on the first em-dash / en-dash / hyphen that's
surrounded by whitespace, so single-word titles and hyphenated
compound names ("Foo-Bar" with no spaces) come through intact. Full
title still shows on hover.
The two independent drag handles (right edge of URL bar, left edge of
search bar) are replaced with ONE handle sitting between them, wrapped
in a new .urlsearch flex container that owns the URL-bar-plus-search-bar
budget between the nav buttons on the left and the trailing dock on the
right.
Zero-sum semantics: dragging the handle right grows the URL bar and
shrinks the search bar; dragging left inverts. Nothing else in the
toolbar moves — nav buttons, Downloads, extension dock and the
⛓ Theseus logo all sit outside .urlsearch's flex parent.
When the window shrinks below the responsive breakpoint the search
bar (and the drag handle) hide as before, and the URL bar grows to
fill the .urlsearch budget on its own (added flex: 1 1 auto in the
data-responsive rules for .urlwrap).
Verified static rendering: url=700px → search=157px, url=300px →
search=539px, url=default → 60/40 split, narrow window → search
hidden and url fills.
Old settings (urlBarSize / searchBoxSize / searchBoxWidthPx) are
ignored by chrome.html but kept in main's SETTINGS_DEFAULTS so an
older chrome.html could still read them on a downgrade. Only
urlBarWidthPx is used going forward.
Same 0.3.31 version, new binary. Rebuilds the shipped 0.3.31 with the
salvageable content from the reverted 0.3.32-0.3.34 track:
chrome.html
- light-mode chrome strip: --bg #e6e8ec, inactive tab #f2f4f7,
active tab #ffffff. Fixes the "tabs disappear into the light
Windows title bar" report.
- bookmark chips shrunk: 130px max-width, 11px text, 12px favicon,
22px row (was 26). ~40% more chips fit in the same width.
- bookmark chips draggable with the tab-strip's left/right-half
drop convention; new .dropbefore/.dropafter accent.
- light-mode .tor + .logo + .upchip chips: from illegible white-
on-#253A49 (at 12-13px) to #eef1f5 with #253A49 ink. Both readable
now. .tor.connecting/.on keep amber/purple hue in light fills.
main.js
- will-download update handler now streams the saved setup .exe
through crypto.createHash("sha256"), compares to the manifest's
updateAvailable.setupHash before marking ready. Rejects and
deletes the file on mismatch or on empty manifest hash. Test C
in the previous session proved this catches truncated payloads
Electron reports as "completed" (a real class of failure the
Ariadne addon updater has always guarded against here).
- new bookmark-move IPC: splices the list, no-ops on self-drop
or missing entry.
preload.js
- moveBookmark(fromUrl, targetUrl, place) exposed for chrome.
Deliberately NOT changed: install-update-now still spawns setup with
["/S"] alone. The 0.3.32 --updated /S --force-run change was proven
in the previous session's real-install E2E to not address the actual
"browser vanished on D:\Program Files install" symptom — every flag
combination (/S alone, --updated /S --force-run, /S /currentuser,
/S /D=<install>) exits 0 without upgrading anything on that specific
install path. That's a separate open bug; not touched here.
Version stays 0.3.31 — this is a binary rewrite of 0.3.31, not a new
release. Existing 0.3.31 installs won't see an update chip (version
compare returns false), which is intentional given the auto-update
path is still broken for non-default install locations.
Bundled revert of six commits:
9bacdcb Ship 0.3.34
aef7864 0.3.34 UI (grey chrome, DnD bookmarks, chip colors)
c2da8bb Ship 0.3.33
9b92224 fix(updater) verify SHA-256
4e932e0 Ship 0.3.32
4e695c4 fix(updater) --updated flag
Reason: the real-install E2E test (turn N-1) showed silent update
against the user's custom D:\Program Files\Theseus Navigator location
returns exit 0 without upgrading anything — every flag combination
tried (/S alone, --updated /S --force-run, /S /currentuser,
/S /D=<install>) exits successfully and no files change. The 0.3.32
flag fix and 0.3.33 hash-verify fix were defensive improvements but
did not address the actual "browser vanished" symptom, and 0.3.34
inherits the same broken silent-update path so its UI improvements
never reach anyone still on 0.3.33+ with a non-default install path.
Rolling manifest back to 0.3.31 so the update chip stops appearing on
installs that can't complete the update. Users on the default
%LOCALAPPDATA%\Programs\theseus-navigator location are unaffected —
0.3.31 is still their canonical shipped version.
package.json reset to 0.3.31 explicitly (git revert stopped one bump
short on the merged diff).
Screenshot 0.3.0 (8ca5ac3) preserved — unrelated parallel work.
Chrome strip: light-mode --bg/--surface/--surface2/--active retuned to a
soft neutral grey. Flat #ffffff tabs disappeared into light Windows
title bars and the top strip read as one bright rectangle; now the
tab row sits on #e6e8ec, inactive tabs are #f2f4f7, active tab is
white so the current one still pops.
Bookmarks: chips down to 130px max-width, 11px text, 12px favicon,
tighter padding, 22px row (was 26). Fits ~40% more saved pages in
the same width. Chips are now draggable — dragstart/dragover/drop
mirror the tab strip's reorder pattern, dropping before/after by
which half of the target chip the pointer is on. IPC bookmark-move
splices the array; a missing entry or self-drop is a no-op.
Tor + Theseus (⛓ logo) chips in light mode: white-on-#253A49 was
below legible at 12-13px on real light Windows themes. Both now use
#eef1f5 with #253A49 ink and a subtle border — same footprint, dark
letters read cleanly. .tor.connecting/.on keep amber/purple hue but
in light-appropriate fills with dark text. .upchip picks up the
same treatment for consistency.
Two follow-ups from the on-device test.
Two Aegis addons showing up (bchwallet + aegis):
- migrateAegisRename previously only ran when addons/aegis/ didn't exist,
which meant any bchwallet copy the signed OTA update endpoint reinstalls
after the first migration stays there forever, and AddonHost loads both
as separate wallets. Rewritten to always retire addons/bchwallet/ when
it's present, regardless of whether aegis/ is already installed. The
storage-copy (bchwallet.json → aegis.json) still only runs the first
time so a downgrade doesn't clobber fresh 0.4+ state.
- Also flushes any stray addons/siawallet/ that comes back the same way.
Sidebar dock and Extensions list icons showed the raw 🛡 emoji:
- chrome.html's dock-button renderer and settings.html's extensions-list
renderer now accept `data:image/svg+xml…` values for manifest.icon and
render them as <img> instead of text. Emoji strings still render as
before.
- aegis addon.json's icon is now the exact hex-aspis mark from
aegis.x/brand/favicon.svg (URL-encoded inline). Version bumped to
0.4.1 so seedBundledAddons reseeds the new addon.json on next launch.
Standard browser Ctrl+F support:
- main.js: Ctrl+F caught in the shared before-input-event handler (same
place as F12 / reload shortcuts) fires 'find-open' to chrome.
Two new IPC handlers proxy to activeTab().view.webContents:
find-in-page(query, {forward, findNext, matchCase})
find-stop
Each tab's webContents listens for 'found-in-page' and forwards
{activeMatchOrdinal, matches, finalUpdate} back to chrome via
'find-result' — only when it's the active tab so the bar doesn't
update from a background tab's stale match count.
- preload.js: exposes onFindOpen / findInPage / findStop / onFindResult.
- chrome.html: adds a .findbar strip below the bookmarks bar (part of
chrome-view height like .tordisc / .bcnrbar, so syncHeight picks it
up). Input + "N of M" counter + prev/next/close buttons. Enter jumps
next, Shift+Enter previous, Esc closes. Typing runs findNext:false
(fresh search); pressing Enter runs findNext:true (walk matches).
No-match state paints the input border red.
Two spots where the light-mode swap didn't take effect because the
color was hardcoded to a dark-mode pastel yellow:
- .btn (used by 'Check for updates' and the engine 'Add' button) had
color:#eaffb0 — pale yellow tinted on top of the acid tint, invisible
on a white ground. Route through var(--acid) so light mode picks up
#0AC18E (BCH teal) with proper contrast.
- .brand ('⛓ Theseus' in Settings sidebar) was var(--acid). In light
mode that's #0AC18E on #f6f8fb — ~2.7:1 contrast. Swap to #253A49
(BCH dark navy) in the light-media block so it matches the Theseus
toolbar chip treatment and reads as the primary UI accent.
Same #eaffb0 sweep applied to the two chrome.html spots that used it
(upchip download button, bcnrbar open button). Dark mode unchanged.
Two follow-ups on the light-mode acid work:
1) Every hardcoded #d6ff3d and rgba(214,255,61,X) in the browser
chrome and every addon panel now goes through var(--acid), so the
light-mode BCH-teal (#0AC18E) takes effect everywhere — not just
where var(--acid) was already used. Hex-with-alpha (#d6ff3d55 etc.)
converts to color-mix(); rgba() converts to rgb(from var(--acid)…)
for the same alpha with the current --acid hue. Chromium 128+
supports both. Files touched: chrome / settings / error / home /
approval / bchwallet / siawallet / screenshot (html + css).
Screenshot editor.js's #d6ff3d stays — that's the drawing colour
swatch, not UI chrome.
2) The Theseus button (.logo) and update chip (.upchip) become dark
BCH-navy chips (#253A49 background, #F8FDFF text) in light mode.
Previously the .logo hardcoded #d6ff3d text on a bright-acid tint —
invisible on a light toolbar. The dark chip stands out and gives
the light theme a distinct accent using the BCH secondary from
whybitcoincash.com's palette.
Two related visibility fixes:
1) Light-mode --acid → #0AC18E (Bitcoin Cash brand primary, from
whybitcoincash.com's palette per user). Direct swap from #088A66
(darkened variant) to the on-brand primary. Applied across chrome /
settings / error / home / approval / messages / bchwallet /
siawallet / screenshot editor. Dark mode's #d6ff3d is unchanged.
2) User-Agent no longer includes 'theseus-navigator/<ver>' or
'Electron/<ver>' tokens. Cloudflare's WAF was returning HTTP 503
'Service Unavailable' to any request carrying those (verified
directly against whybitcoincash.com — same URL, same headers, only
the UA differed; plain Chrome UA got 200, Theseus UA got 503).
Strip both tokens via a stockChromeUA() helper called from
applyAcceptLanguage(), which whenReady already invokes at boot.
Standard practice: Brave, Vivaldi, Slack all do the same.
Verified via CDP: navigator.userAgent now reports
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/130.0.6723.191 Safari/537.36
— indistinguishable from stock Chrome.
Prior light-mode --acid was #3a5c00 (dark olive-green) — legible but
off-brand. The Bitcoin Cash brand primary is #0AC18E (a teal-leaning
green already used in bchwallet's --bch variable). Darken it a step to
#088A66 for AA text contrast on white (~5:1) while staying in the BCH
family — the light-mode accent now reads as "Bitcoin Cash green,
darkened for legibility" instead of an arbitrary olive.
Applied across every chrome page + addon panel that carries the light
override (chrome / settings / error / home / approval / messages /
bchwallet / siawallet / screenshot editor). Dark mode's #d6ff3d
untouched.
The extension row was stacking into a column when the user dragged the
URL bar wide enough to squeeze the dock's slot. Two things broken:
1) #extbuttons could wrap: added flex-wrap:nowrap + overflow:hidden so
the buttons never break to a new line. Same for .extdock's own
flex container (nowrap + min-width:0 so it can flex-shrink to zero).
2) Collapse was width-based on .bar only — a window wide enough for
level 0 wouldn't collapse the dock even when the URL-bar override
left extdock with 0 px. Added a second signal:
data-extcollapse="1" fires when .bar's contents overflow their slot
(bar.scrollWidth > bar.clientWidth). Applies alongside the level-3
collapse; either path shows .extmore instead of the row.
Hysteresis to avoid RO loop: while EXPANDED, cache the row's natural
scrollWidth. While COLLAPSED, re-expand only when summing every other
.bar child leaves at least (naturalWidth + 8 px slack) of room. Both
directions verified across URL widths 400/800/1200/1500/reset — the
row expands / collapses at the right thresholds with no oscillation.
Two fixes bundled:
1) Right-side items (download, extension dock, Theseus button) no longer
slide leftward when the URL bar is capped. Added margin-right: auto
to .urlwrap under every capped state (data-urlsize=medium/compact
and the new data-urlwidth override) so the leftover flex space sits
AFTER the URL bar, keeping the right group pinned to the right edge.
Verified: logo's gap from bar's right edge stays at 10px across
default / urlBarSize=compact / urlBarWidthPx=500 / reset.
2) Drag handles for live resize. A 6-px col-resize strip sits on the
trailing edge of .urlwrap and the leading edge of .searchbox; a
pointerdown/move/up dance updates the width live via a CSS custom
property and persists to settings on release. Two new keys:
- urlBarWidthPx (0-1800, 0 = follow size preset)
- searchBoxWidthPx (0-800, 0 = follow size preset)
When either is non-zero, the corresponding CSS override wins over
the discrete size preset. Preload gets setSetting so the drag can
push the persistent value from chrome.
Visual affordance: handles are transparent by default, gain a faint
acid tint on hover and while dragging.
Previous #4d7300 (0.3.10) was still too light against actual white
backgrounds — several tint fills (rgba(214,255,61,X)) and unpatched
addon panels were making the effective color feel bright green. Two
fixes bundled:
1) Bump --acid in every top-level page's light-media block from
#4d7300 to #3a5c00 — same hue, ~7:1 contrast on #ffffff (was ~5.5:1).
2) Add the missing light-media --acid override to the addon panels
that were still resolving to #d6ff3d: bchwallet/panel.html,
siawallet/panel.html, and screenshot/editor.css (was #b4e024, now
#3a5c00 to match).
Dark mode unchanged. Tint fills (rgba backgrounds at low alpha) still
stay as-is — at 8–15% opacity the specific hue barely matters and the
darker foreground now dominates.
At responsive level 3 the dock folds into a single button; that button
was a generic 🧩 puzzle piece. Swap it for the first registered
extension's own icon (📝, ₿, Ⓢ, whatever ships first in the panels
list), with the puzzle piece staying as the empty-state fallback if
no addons are registered yet. Clicking still opens the same dropdown
of all installed extensions, so no functional change — just the
button reads as "the extension you're most likely to want" instead of
a generic marker.
A ResizeObserver on .bar sets data-responsive to one of four levels
based on width, and CSS reacts:
0 (>=1050px) wide — everything visible (default)
1 ( >=820px) tight — auto-hide the search box, URL min-width drops
2 ( >=620px) narrow — Theseus button collapses to just the gear
3 ( <) xnarrow — extension dock collapses to a single 🧩 button
that opens a dropdown listing every registered
panel (icon + name), click to open the sidebar
Puzzle dropdown reuses the same open/close pattern as the existing
bookmarks menu — click outside to dismiss, click a row to open (or
close if it's already the active panel). User's explicit
Settings > Address bar size / Search box size still win when the
toolbar is wide enough for them; responsive collapse only forces
extra hides at the tighter widths.
Verified on a running instance across 5 widths (1200/900/700/500/400):
each breakpoint flips exactly the elements it should.
#d6ff3d on a #ffffff / #f6f8fb background sits at ~1.3:1 contrast — the
acid green went almost invisible any time the user flipped Settings >
Theme to light. Override --acid to #4d7300 in every chrome page's
prefers-color-scheme: light block. Same hue family, ~5.5:1 contrast
on white, still reads as the same brand color.
Also: chrome.html was missing --acid: #d6ff3d in :root entirely (every
site used var(--acid, #d6ff3d) fallbacks). Adding the real declaration
means the light override can actually take effect.
Files touched: chrome.html, home.html, settings.html, error.html,
approval.html, messages.html. downloads.html is a dark-only overlay
(hardcoded), collision.html already had a proper light-mode --bcdn.
Tint fills (rgba(214,255,61,X) at low alpha) stay as-is — the specific
hue barely matters through 8% opacity.
Verified live via CDP: getComputedStyle(--acid) returned #d6ff3d in
dark, #4d7300 after cfg.set('theme','light').
Setup cc676e62057b42c1e1221f0e9b69a55806bab90259bbbb4e6eb64701ab3d420a
Portable 1ab811f7edba74220b452763ee0f3329ba91d84c834d3e38be6426de0b04f1c7
Two changes since 0.3.8:
f1d117e - .extbtn was display:grid, which is block-level, so multiple
extension buttons inside #extbuttons stacked vertically. Switch to
inline-grid — same icon-centering, no forced line break between
siblings. Verified on a running instance: all 4 dock buttons at the
same y-coordinate.
(this commit) - Remove the static Aegis ₿ placeholder button and its
handler. Redundant now that the real bchwallet addon ships in the box
and registers its own dock entry. Also flip the dock-hidden logic to
disappear entirely on a fresh install with no add-ons registered, so
the toolbar doesn't carry an empty slot for users who never install
one.
Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.9.
.extbtn had display:grid, which is block-level by default. Multiple
buttons inside #extbuttons stacked vertically because each was a block.
Switch to inline-grid — same centering behaviour for the icon inside
the button, no forced line break between siblings. Verified on a
running instance: all 4 buttons render at the same y-coordinate with
strictly increasing x.
Two new settings under Settings > Appearance > Toolbar:
urlBarSize: wide (default) | medium | compact
searchBoxSize: hidden | compact | normal (default) | wide
Applied as data-attrs on the .bar element in chrome.html; CSS switches
the flex-basis of .urlwrap and the width of .searchbox. min-width on
.urlwrap guards against squeezing the URL invisible.
Settings-set now broadcasts settings-update to the chrome renderer, so
resizing takes effect live without a relaunch. Same channel is exposed
for future chrome-side settings.
Motivation: the extension dock grew a per-addon button per install, and
the URL bar (flex:1) had been eating all the remaining space. Users who
want more room for extensions can now shrink or hide the search box and
cap the URL bar width.
Replaces the single sidebarbtn with a dock that renders one button per
registered addon sidebar-panel. Each button shows the panel's icon
(the emoji from its manifest) and opens the sidebar on that panel.
Clicking the currently-active button collapses the sidebar; clicking a
different one swaps the visible panel. Notepad (📝) and Silent Mode
Relay (🌐) appear automatically because they already register panels.
Also lands a static Aegis Wallet placeholder button (₿) next to the
live ones, styled as .soon so it reads as coming-soon. It'll come out
once the addon registers a real panel.
Preload adds openSidebar(panelId) and closeSidebar() wrappers around
the existing sidebar-open / sidebar-close IPC handlers.
Every .tab now carries a native title="<title>\n<url>" so hovering the
tab shows the page's own title and the URL (the way Chrome/Firefox do
it). Uses the url already present in the per-tab payload emitTabs sends;
no extra IPC.
Two bugs the user hit:
1) Edit title silently did nothing. window.prompt is disabled in
Electron BrowserView contexts, so prompt() returned null and the
round-trip remove-then-re-add never fired. Built an in-chrome
modal (.promptmask/.promptbox) that resolves to the trimmed
string on OK/Enter or null on Cancel/Escape/mask-click, and
swapped the ctx-menu Edit action to use it. Added a real
bookmark-update IPC (title + optional favicon merge) so the edit
no longer round-trips through remove/add — same URL, just the
title changes.
2) Bookmarks had no favicon. The bookmark data model was {title,url}
only, and the row template had no icon slot. Extended:
- Renderer tracks current.favicon from onTabs's active tab.
- Star click and ctx "Add current page" pass favicon along.
- main persists a favicon field (capped 2KB) and emits it.
- Row template shows the favicon (14×14) with an onerror
fallback so a broken data:/URL doesn't leave a broken glyph.
- Backfill: when a currently-open URL matches a stale bookmark
without favicon, the render pass patches it once via
updateBookmark. Stops itself the next render.
Older bookmarks.json files stay valid — no schema migration needed;
missing favicon just renders the empty placeholder slot.
The 750ms self-heal (b9d70e4) was a bandage. Real root cause: body had
height:100vh, so document.body.scrollHeight always equalled the chrome
view's current height (which IS the viewport). Once a menu grew chrome
via growChromeForMenu, syncHeight measured the inflated body and re-sent
the same big number — CHROME_H never shrank back.
Fix: drop height:100vh so body sizes to its content. scrollHeight becomes
the natural chrome height, and menu-close paths correctly report a value
smaller than the current CHROME_H. The self-heal stays in as insurance
for any future overlay that bypasses closeAllMenus.
Bar layout unchanged — every bar has an intrinsic height (.tabs 34px,
.bookmarks 26px, .bar auto), so body flow reflects them naturally.
overflow:hidden stays to clip any pathological overflow.
The onTabs / onNav re-sync catches the common menu-close path, but a menu
that gets orphaned by a drag interruption, a focus flip, or a submenu
race can leave the chrome view taller than the natural body-scrollHeight
until the next tabs/nav event fires. Symptom: a huge blank strip between
the address bar and the page that grows and doesn't shrink until the tab
changes.
Fix: a 750ms interval that re-runs syncHeight whenever no .ctxmenu /
.grouppop is present. setChromeHeight in main.js only re-layouts when
the value actually changes, so this is a cheap no-op in the steady state.
Holding the ship: will bundle with the next batch per the slower-cadence
policy. Users on 0.3.5 keep the existing gap fix; this one lands with
the next release.
Setup 935d637ae19ea821f7e89b0f9a802b4e774b6d1ae3254a70f3e5f17f89424177
Portable bb6bea253d6f32f86e8fdd152cf4c969cf311c991329d5e94fce31215ecf7f00
Chrome-view height gap: any tabs event that fires while no menu/popover
is open now normalises the chrome-view height via syncHeight(). A leaked
menu-close path could previously leave the strip inflated; the next
tabs update guarantees it shrinks back to the natural body-scrollHeight.
Tab-group drag-and-drop: group chips are now valid drop targets in the
same drag session as tab reorder. Dragging any tab onto a chip and
dropping assigns that tab to the chip's group (which also auto-clusters
it via the existing tab-group handler in main). Chip highlights acid
green while a valid drop hovers.
Collapsed group vertical popover: click a collapsed group chip and a
floating panel opens below it, listing every tab in the group. Each
row shows the favicon + title + a ✕ to close that tab. Clicking a row
switches to the tab AND expands the group so the newly-active tab
appears in the strip (tabGroupToggle). Popover closes on outside click
or Escape; the outside-click filter also ignores clicks inside .gchip
so opening the popover doesn't immediately close it.
growChromeForMenu() and closeAllMenus() now include .grouppop in their
overlay queries so the popover contributes to chrome-view sizing and
gets cleaned up alongside the ctxmenus.
Deployed: scp + sia-upload of both trees. Verified LIVE 0.3.5.
Setup 3d6d3d6aeacf482284707f50d129315d07a593e09b5d68af852abe5aa0ed4c92
Portable 6c8c5ef212bfccb377e17404e83a18cf2f09771d0eb3e0ec86a85587efdcfc9e
Three address-bar suggestion fixes.
Per-row ✕ delete on hover. Clicks on the X call address-forget instead
of address-pick; the row disappears optimistically in the picker and
main drops the entry from history + persists. Sender-URL gated to the
picker's own file:// origin.
Enter with a highlighted suggestion now navigates to THAT url. The
URL input handler tracks a pickerHasCursor flag that flips true on
ArrowDown/ArrowUp and false on any input; Enter with cursor forwards
to the picker's own submit path via addressCursor("enter") — before
this fix, Enter always ran goURL against the typed letters, which
submitted them as a web search instead of opening the selected url.
Address bar reliably shows the picked URL. onAddressPicked now arms
an overrideUrlBarUntil = now+1500ms flag; the onTabs handler treats
that window as "force write the url", bypassing the focus-guard that
was leaving the bar blank when blur() hadn't landed yet.
Deployed: scp + sia-upload of both trees, verified LIVE 0.3.4.
Setup e016d66543976b7a4df5ac16033319d59cb0f418e54d7e838ef16de274525a83
Portable b9881a62c39ecfe22644611f5aa16a48f32f97f9ec5751b5888ccbd870338c3e
Three 0.3.2 misses fixed:
Tab context menu was clipped by the chrome view's fixed height — the
menu rendered with position:fixed inside a WebContentsView whose
bottom edge stops at CHROME_H, so items below the tab strip weren't
visible. New growChromeForMenu() measures every open menu's bounding
box and calls T.setChromeHeight(needed) so main.js grows the chrome
view to fit; closeAllMenus() calls syncHeight() to restore.
Bookmarks bar was hidden on every tab except Home, so Add-to-
Bookmarks had no on-screen feedback. Now always visible.
Tab groups now actually cluster. Assigning a color to a tab moves it
adjacent to its group-mates in the tabs array (main-side splice, no
render-only trick). The chrome renderer inserts a colored group chip
before the first tab of each group; clicking the chip toggles a
per-group collapsed state (in-memory, resets on relaunch). Collapsed
groups hide their member tabs and the chip shows the member count.
Expanded groups keep the per-tab top color stripe from 0.3.2 so the
cluster reads as one visual block.
Deployed: scp + sia-upload, verified LIVE 0.3.3 on dl.silentmode.st.
Setup 09331b2fd9ccf136e2183b7cd85354cfd56e2ed50260b7aadeed63c7ea450251
Portable 21752d0fc85fb39ec1e65192920461e9ae395a22d9a68abd27f12e638d0fdd07
Right-click a tab: floating context menu with Reload, Duplicate, Group
(submenu: None / Red / Orange / Yellow / Green / Cyan / Blue / Purple),
Add to Bookmarks, Mute (also Unmute; 🔇 shows next to the title when
muted), Close. Menus close on outside click or Escape.
Group state is per-tab. A grouped tab shows a colored dot before the
title and a matching 2-px accent stripe on the top edge, so a cluster
of same-group tabs reads visually. Palette is drawn from existing
provenance colors (err/warn/acid/srv/sia/blue).
Backend IPCs are all tab-scoped (not "active tab"): tab-reload,
tab-duplicate, tab-mute (toggle or explicit boolean), tab-group,
tab-bookmark. emitTabs payload gains muted, group, and url so the
menu can read current state.
Installer wizard branding: 164×314 sidebar BMP with the compass mark
centered + "Theseus / NAVIGATOR" wordmark under it, plus a 150×57
top-strip header with a mini compass on the right. Sharp can't write
BMP directly (only png/webp/etc), so nsis/make-icons.mjs renders raw
RGB via sharp and wraps it in a hand-rolled 24-bit uncompressed BMP
header. Uninstaller reuses the same sidebar.
Silent-install fix: nsis/installer.nsh's AriadnePageCreate now checks
IfSilent BEFORE touching nsDialogs::Create. In /S mode the flag is
zeroed and the function returns cleanly, so the installer no longer
hangs waiting for a page it will never draw. This is why 0.3.2 needed
two builds — the first hung on /S install; the fixed hash is the one
that ships.
Deployed: scp + sia-upload of both trees. Verified VPS hash matches
local 09331b2f. Fresh /S install to D:\Program Files\Theseus Navigator\
placed 0.3.2 with the correct HKCU Uninstall registry entry.
Setup fe59105d2e99a41b7000caeb86601a8e1675846d193e92204034669f5b368d60
Portable 1b6eda55b53894cf9889548116c7b6100888fb160edc84cc1592bb79f9d95b53
The update flow no longer asks the user to click Download. When
checkForUpdate detects a newer version, autoDownloadUpdate() kicks off
session.defaultSession.downloadURL against the setup URL immediately.
will-download recognises the update URL and routes the file to a
fixed %TEMP% path (bypassing the visible downloads panel entirely),
streams updateDownloadReceived/Total into the chip via
emitUpdateAvailable, and flips updateDownloadState to "ready" when
the transfer finishes.
Chip states:
idle first render before the fetch starts — clickable to
trigger the manual download (kept as a fallback).
downloading "↓ 42% — 0.3.2" — no click, just progress.
ready "✓ Install 0.3.2 & restart" — one click.
failed fall back to the pre-0.3.1 explicit-download click.
install-update-now IPC: spawns the cached setup with /S (detached,
stdio ignored), then app.quit() 400ms later so the installer can
overwrite the running exe. Our nsis/installer.nsh detects an existing
Ariadne install via the HKLM registry and skips its Ariadne prompt on
upgrades, so the /S run is fully unattended.
The one-click flow eliminates two long-standing sources of confusion:
- "Download opens a different browser" — Theseus's default session
fetches the installer itself, not a URL handoff to shell.
- "Update requires multiple wizard clicks" — /S skips them.
Extensions aren't touched by this. The framework lives in
addons-host.js + sidebar-preload.js; add-ons themselves live in
%APPDATA%\Theseus Navigator\addons\<id>\ and are a separate layer.
New extensions ship by drop-a-folder, no browser release required.
Deployed: scp + sia-upload, verified 200 + 0.3.1 in the manifest.
Setup 972f6209639122f32f032d5f2f9fc5a4808e0d4a810f88ae38ec9a1275ac52ac
Portable a9771f054ec36b9aff6ddee958cecf7e84cdacd4d7932aa8385c445aab4d29be
User-visible rename: the Settings tab and its labels say "Extensions"
now instead of "Add-ons". Internal identifiers (disabledAddons, the
addons/ folder, IPC channels, capability strings) stay put — code
churn wasn't worth it, and users only see the user-facing text.
Draggable sidebar. sidebar-preload.js now injects a 5px grip strip
along the LEFT edge of every panel document. mousedown+mousemove
streams delta-x px to main via sidebar-drag IPC; main clamps to
[200, 800] and debounces a save to settings.sidebarWidth. Width is
restored on next launch. The default is still 340. Faint acid-green
highlight on hover so the affordance is discoverable.
New extension capability: session-proxy. An extension whose addon.json
declares "session-proxy" gets api.setSessionProxy(rules) which routes
to session.defaultSession.setProxy — the same primitive Tor already
uses under the hood. Rules can be a string ("socks5://host:port") or
an object matching Electron's setProxy shape; null clears. The
capability is opt-in: an extension without the declaration gets an
error if it tries to call setSessionProxy. This is the framework
surface a private 3-VPS relay extension would build on (extension
folder stays on the operator's disk only; nothing about it appears in
the public build).
Deployed: scp installers + manifest + tools/ + releases/ pages to
VPS, sia-upload of both trees, verified HEAD 200 and manifest 0.2.2.
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.
Files:
- addons-host.js Loader + api.registerSidebarPanel() + per-
addon storage on <userData>/addons-data/.
Kept at the CommonJS-scoped top level (lib/
is ESM-scoped via its own package.json).
- sidebar-preload.js Runs in every sidebar panel. Exposes
window.silentmode.storage.{get,set,all} +
onVisibility. Main-side handlers derive the
add-on id from the sender file:// URL, so a
panel can only touch its own store.
- bundled-addons/notepad/ Reference add-on: addon.json, index.js,
note.html. Autosaving textarea with char /
word count.
main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
(SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
views by the sidebar width when visible. First registered panel
wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
sidebar-state, addons-list / addons-set-enabled / addons-reveal /
addons-open-dir / addons-reload, and origin-gated
addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
the loader honours them without a restart (discoverAndActivate
runs again on toggle).
chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.
settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.
package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.
Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.
Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
Address-picker fix:
Picking a suggestion from the address dropdown loaded the URL but
left the address bar showing the 3-4 letters the user had typed.
Root cause: onTabs's focus guard
if (document.activeElement !== $("url")) $("url").value = d.url
skipped its write while the URL input still held DOM focus, and
clicking a sibling WebContentsView doesn't always deliver the blur
to the chrome renderer in time. Fix: address-pick sends an explicit
address-picked IPC to the chrome, which force-blurs and writes the
full picked URL before the tabs event arrives.
Reload keys:
None of the standard reload accelerators worked (Menu.setApplicationMenu(null)
drops Chromium's default menu accelerators, and hard-reload was never
wired at any layer). Now:
F5 / Ctrl+R -> soft reload
Ctrl+F5 / Ctrl+Shift+R -> hard reload (reloadIgnoringCache)
Shift-click on the toolbar reload button -> hard reload
Same before-input-event hook that carries Ctrl+Shift+M; always targets
the active tab regardless of which view got the key, and skips the
Settings tab. Toolbar tooltip updated to advertise Shift-click.
Merges a parallel session's work with the multi-source BNS story from 0.0.7.
The dApp side (parallel session)
--------------------------------
* bcnr-preload.js — installs `window.bcnr` on every page via contextBridge.
Read-only surface: resolveName(name), isRegistered(name), getBcnrTlds(),
getRecordVersion(name), plus getPermissionOrigin() for diagnostics. All
Promises; a missing name returns null (not throw). No signing, no wallet
unlock — that surface is designed but deliberately out of scope for 0.0.8
(see TheseusNavigator/DESIGN-integrated-wallet.md).
* bcnr-origin.js — pure function that computes the eTLD+1 permission origin
for a URL. ICANN suffixes via `psl` (same PSL Chromium uses, handles
.co.uk / .github.io / etc); BNS names key off the on-chain TLD list so
foo.wallet becomes a public suffix as soon as `wallet` appears there.
Match browser cookie / MetaMask semantics: a grant on pay.merchant.com
covers account.merchant.com but not evil.com.
* dev/bcnr-selftest.js, dev/origin-selftest.mjs — self-tests, no I/O.
* main.js wires bcnr-preload.js into session.defaultSession.setPreloads() so
it runs BEFORE per-WebContentsView preloads; adds bcnr:* IPC handlers.
* preload.js + chrome.html — small hooks so the shell picks up window.bcnr
the same way regular content does.
* package.json — psl dep, bcnr-preload.js/bcnr-origin.js in `files`.
Also included
-------------
* AriadneResolver/mobile/.../UpdateCheck.java — in-app update-check for the
Android app; already active in the shipped 0.11 APK (build.ps1 -Recurse
picked it up), formalising the source now.
* TheseusNavigator/snapshots/bns-name-snapshot.json — refreshed bundled
starter (73 beacon txs, root c37b8596…c54e414ba).
* Site pages + manifest updated to point at 0.0.8.
TheseusNavigator-Setup-0.0.8.exe 95.4 MB
21939743eafdfe8742a6b7c4b987bd2782384d7bc41289cb80a7e08019dc9f02
TheseusNavigator-0.0.8-portable.exe 92.7 MB
2aa429fe39dc0fa4ac040fc6d6eb31b0f890c8a83175c49fcb50f052c480d39d
Favicons:
- Each tab stores a favicon URL; wired via wc.on('page-favicon-updated')
to Electron's first-emitted icon URL. Included in the emitTabs
payload; the chrome renderer paints an <img class="fav"> in the
same slot the loading spinner uses (spinner while loading, favicon
once page-favicon-updated fires). No proactive clear on navigation
- mainstream browsers keep the old icon until the new one arrives,
which avoids a flash on every subpage click.
Cursor:
- .tab { cursor: default } instead of the previous 'grab'. Grab looked
like the page was always in the middle of a drag. .tab.dragging
keeps 'grabbing', so the grab hand only appears when the user
actually picks a tab up to reorder it.