ADR-0021: URL-bar history suggestions
Status: Active · Date: 2026-08-04
References
- Browser URL Suggestions — Delivery Notes — what shipped, where it lives, and the gotchas
- ADR-0005: Per-window session isolation — the isolation boundary this decision deliberately does not extend to history
- ADR-0002: A full Chromium browser, not a VS Code-style preview pane — why the URL bar is expected to behave like a real browser’s
- ADR-0019: Configurable browser User-Agent — the previous pass over the same toolbar
Context
Typing in a browser tab’s URL bar pops a jQuery-UI autocomplete[^1] fed by two sources: locally
saved favorites (bp.fav) and Bing’s legacy OpenSearch endpoint (http://api.bing.com/osjson.aspx — undocumented, plain http, and with no error handler, so when it fails it fails silently). What it
never suggested was the user’s own browsing: pages already visited, addresses already typed.
The data was already there. Every navigation is recorded by addHistory into a per-window jStorage
key — bp.history.<windowSessionId> — as day-buckets of {date, uri} entries, with page titles in
a parallel bp.title map. These stores back the tranquil-browser://history page, but the URL-bar
autocomplete never read them.
The wrinkle is per-window session isolation: every
window is its own persisted cookie/login jar (persist:tb-window-<id>), so “which history feeds
which window’s suggestions?” is a real product question, not just plumbing. Two facts shape the
answer. First, the visit logs live in the host renderer’s localStorage, which all windows share
— isolation partitions the guest webviews, not the host — so aggregation is technically trivial.
Second, there is precedent: Firefox Multi-Account Containers separates cookies and site storage per
container while history and address-bar suggestions stay shared[^2] — isolation protects login
state, not recall.
Decision
Suggest previously visited pages in the URL bar, ranked by frecency, aggregated across all
windows’ history stores — with a boost for URLs the user explicitly typed — above DuckDuckGo search
suggestions. All in tranquil-browser; a new lib/history-suggestions.js module owns the logic:
- Global history scope. The suggestion index merges every
bp.history.*key, joined withbp.title. Window isolation continues to mean “separate logins,” never “separate recall” — a brand-new window suggests URLs typed anywhere, immediately. - Frecency ranking[^3] with a typed boost. Each visit contributes a recency-bucketed weight
(today ×100, ≤7 days ×70, ≤30 days ×50, older ×30; buckets older than 90 days are not scanned);
matching is case-insensitive substring over the normalized URL + title, with a ×2 multiplier
when the term prefixes the URL. URLs the user deliberately navigated to — Enter on a URL-ish
value or picking a concrete suggestion row — accrue an additive boost (
400 + 200·min(n, 8)) from a new cappedbp.typedUrlsstore, so a once-typed address decisively outranks incidentally visited pages, while a heavily revisited page can still win. Plain searches never count as typed. - One merged dropdown, three row kinds. History rows (clock icon, title + dimmed URL) first,
then favorites (star), then search completions (magnifier) filling to ten total, deduplicated by
normalized destination so http/https/
www.variants collapse and a search row never duplicates a history row. - DuckDuckGo replaces Bing for search completions: the
ac.duckduckgo.com/ac/?type=listOpenSearch-suggestions endpoint[^4] the start page already uses, fetched over Nodehttpsin the host renderer (CORS-exempt, always resolves — failure degrades to “history and favorites only”).
Options considered
- Option A: History scope — global across windows (chosen) vs. per-window strict vs. hybrid (global, own-window boosted). Per-window recall would make every new window start amnesiac and would pretend the host-side stores are isolated when they aren’t. The Firefox-containers precedent[^2] matches user expectations: separate logins, shared memory. The hybrid was deferred — the per-window keys are preserved, so an own-window boost is a one-line scoring tweak later if wanted.
- Option B: Data source — reuse the existing jStorage visit logs (chosen) vs. a new SQLite/StateStore visit database. The day-bucketed jStorage log already captures every visit, syncs across windows via storage events, and is what the history page reads and deletes from — reusing it means deletions instantly stop feeding suggestions, with no migration and no second source of truth. A real database becomes worth it only if history outgrows the 90-day / capped scan this design bounds itself to.
- Option C: Ranking — frecency buckets + typed boost (chosen) vs. recency-only match vs. Chrome-style learned scoring. Recency-only surfaces whatever was open five minutes ago regardless of relevance; a learned model is machinery this scale doesn’t need. Bucketed frecency is deterministic, explainable, and computable in one linear pass.
- Option D: Search completions — DuckDuckGo over Node https (chosen) vs. keep Bing vs. drop search suggestions. The Bing endpoint is an undocumented plain-http legacy API that fails
silently today; DuckDuckGo is already the browser’s search engine and the start page’s suggestion
source, so this also collapses two providers into one. Node
httpsin the host sidesteps CORS the same way the start page’s preload does.
Consequences
- The URL bar finally recalls where you’ve been — matched on both URL and title, ranked by how often and how recently, with typed addresses first in line. A fresh window is immediately useful.
- Cross-window visibility is now intentional and documented: any window can surface a URL
visited in any other window. If a “private window” mode ever ships, it must opt out at the recording end (
addHistory/recordTypedNavigation), not by filtering suggestions. - Deleting history works end-to-end for free: the history page edits the same stores the index reads, so removed entries stop being suggested at the next index rebuild (≤30 s TTL, refreshed on every URL-bar focus).
- HAR-replay tabs no longer pollute history — replaying an archive is not browsing intent, so they’re excluded from both the visit log and the typed store. (Their entries also stop appearing on the history page; if archival visits should be listed there, that guard moves.)
- Bounded cost, bounded store: scans cap at 90 days / 5 000 entries per window key; the typed store caps at 200 URLs with oldest-by-use eviction; the in-memory index rebuilds lazily.
- Re-evaluate if history volume outgrows the localStorage-backed log (→ Option B’s database), or if per-window privacy expectations change (→ Option A’s hybrid or a private-window mode).
[^1]: jQuery UI 1.10 Autocomplete — the source(request, response) contract the two-phase merge is built on. https://api.jqueryui.com/1.10/autocomplete/ [^2]: Firefox Multi-Account Containers — containers separate cookies and site storage per container, while history stays shared across them. https://support.mozilla.org/en-US/kb/containers [^3]: Mozilla Places “frecency” — ranking visited pages by a combination of visit frequency and recency-bucketed weights. https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Places/Frecency_algorithm [^4]: OpenSearch Suggestions extension — the ["term", ["completion", …]] JSON shape DuckDuckGo’s ac endpoint returns. https://github.com/dewitt/opensearch/blob/master/mediawiki/Specifications/OpenSearch/Extensions/Suggestions/1.1/Draft%201.wiki