Docs

Browser URL Suggestions — Delivery Notes

Delivery summary for URL-bar history suggestions. For the decision record — global-vs-per-window scope, the frecency formula, the DuckDuckGo switch — read ADR-0021: URL-bar history suggestions; this note says what shipped and where.

Status

Implemented in tranquil-browser only; manually verified 2026-08-04. Not yet committed.

PieceState
lib/history-suggestions.js — index, frecency scoring, typed store, DDG fetch, row renderer✅ built
URL-bar source rewired: history → favorites → DuckDuckGo, two-phase, deduped✅ built
Typed-URL recording (Enter navigations + suggestion picks, bp.typedUrls)✅ built
Custom dropdown rows (icon + title + dimmed URL, theme-agnostic)✅ built
HAR-replay tabs excluded from history + typed recording✅ built
Bing osjson.aspx removed✅ gone

What shipped

Typing two or more characters in a browser tab’s URL bar now suggests previously visited pages — matched case-insensitively against both the URL (scheme/www.-stripped) and the page title — ranked by frecency (visit count weighted by recency buckets: today ×100, ≤7 d ×70, ≤30 d ×50, older ×30; 90-day scan horizon) with an additive boost (400 + 200·min(n, 8)) for URLs the user explicitly typed or picked. History rows lead the dropdown (clock icon, title, dimmed URL), then matching favorites (star), then DuckDuckGo search completions (magnifier) filling to ten, all deduplicated by normalized destination so http/https/www. variants collapse to one row.

History is aggregated across every window’s store (bp.history.<windowSessionId>): per-window session isolation keeps cookie/login jars apart, not browsing recall — the Firefox-containers trade-off. A brand-new isolated window gets useful suggestions from the first keystroke.

Search completions moved from the legacy plain-http Bing endpoint (undocumented, no error handler, silent failures) to https://ac.duckduckgo.com/ac/?type=list — the same endpoint and [term, [completions]] shape the start page already uses — fetched over Node https in the host renderer, always resolving (offline just means history/favorites only).

Where everything lives

All in tranquil-browser; no core, theme, or third-party changes.

lib/history-suggestions.js (new)

  • normalizeUrl / isSearchUrl / isSuggestibleUri — canonical scheme-less form for matching, dedupe, and display; DDG-navigation detection; https?/file + length gate (keeps blob:, about:, giant data: URIs out of suggestions).
  • buildIndex / getIndex / invalidate — walks every bp.history.* key newest-first with early-exit at 90 days / 5 000 entries per key, merges per URI (visit weights, last-day, title from bp.title, typed record), 30 s in-module cache.
  • historySuggestions — substring match + frecency sort + variant collapse.
  • localSuggestions — phase 1: history rows, then favorites (full {label, value} objects now).
  • appendSearchSuggestions — phase 2: DDG completions deduped against phase 1 by destination.
  • recordTypedUrl — upserts bp.typedUrls ({norm: {n, last}}, 200-entry cap, oldest-by-use eviction) via the same mutate-then-set pattern addHistory uses; no-ops for searches and internal pages.
  • fetchSearchSuggestions — host-renderer twin of the preload’s suggest (Node https, 4 s timeout, always resolves).
  • renderItem — the jquery-ui _renderItem body: <li><a class="tb-suggestion …"> with inline-SVG icon, .text()-inserted title (titles are untrusted page content), dimmed normalized URL.

lib/tranquil-browser-view.js

  • Autocomplete source (in initialize) — two-phase: synchronous localSuggestions response, then fetchSearchSuggestions().then(…) re-responds with completions appended. The Bing jQ.ajax block is deleted.
  • select handler — records the pick as a typed navigation, then goToUrl as before.
  • Attach block — installs renderItem on the widget instance (base1.data("ui-autocomplete")), and invalidates the index on URL-bar focus so each session starts fresh against other windows’ writes.
  • recordTypedNavigation (new method, near suggestionTarget) — HAR-replay guard + recordTypedUrl.
  • Enter handler — records the final resolved URL as typed just before navigating (search values filter themselves out downstream).
  • addHistory — now returns early for HAR-replay tabs (model.opt.isHarReplay): replaying an archive isn’t browsing. Side effect: replay visits also stop appearing on the history page — intentional; move the guard if archival listings are ever wanted.

styles/tranquil-browser.less

  • Existing .ui-autocomplete block gains max-width: 620px; overflow: hidden (jquery-ui’s _resizeMenu sets an inline width; the cap makes long titles ellipsize instead of growing the menu).
  • New global .tb-suggestion* rules — flex rows, ellipsizing title/URL spans, opacity-dimmed secondary text and icons. Deliberately color-free: the menu attaches to <body> (outside the theme-scoped element), and the business/classic themes already restyle body .ui-autocomplete globally, so rows inherit each theme’s anchor color untouched.

Gotchas worth remembering

  1. History date fields are locale strings, not timestamps (new Date().toString() in addHistory). All recency math uses the "YYYYMMDD" day-bucket keys; never parse entry dates.
  2. jquery-ui 1.10’s _normalize short-circuits on the first item: if items[0] has label + value, the whole array passes through un-normalized. History objects lead the array now, so every suggestion in every res() call must be a full object — favorites were bare strings before and only worked by coming first.
  3. The two-phase res() pattern is legitimate: the widget’s _response proxy re-renders per call and its requestIndex guard drops replies for superseded terms — no stale-response handling needed in our code. (Each extra call decrements the widget’s pending counter below zero; harmless, only feeds an unstyled loading class.)
  4. .data("ui-autocomplete") must go through the widget’s own jQuery (jQ = require("jquery")), not space-pen’s $ — separate jQuery copies keep separate data expandos, and the wrong one returns undefined silently. 1.10 has no .autocomplete("instance").
  5. Menu rows must be <li><a>…</a></li> in 1.10 — an anchor-less <li> is treated as a divider (unfocusable). menu.refresh adds ui-menu-item/ui-corner-all classes itself.
  6. jStorage get() returns live references into its in-memory cache: the index walk is strictly read-only, and writers follow addHistory’s mutate-then-set idiom. Cross-window freshness is automatic (jStorage’s storage-event observer), so the 30 s cache + focus invalidation exist purely to avoid re-aggregating per keystroke.
  7. Dropdown icons are inline SVGs, not <img src>: the menu lives on <body> outside the theme scope, and currentColor only follows the themed anchor color when the SVG is inline. History/favorite icons are read from the same resources/icons/*.svg files the toolbar uses; the magnifier matches the find bar’s.
  8. Enter and row-selection can’t double-fire: with a menu row active, the widget’s ENTER keydown preventDefault()s, which suppresses the input’s keypress handler — so recording typed URLs in both paths counts each navigation exactly once.

Manual verification checklist

  1. Type 2+ chars of a visited site → history rows first (clock, title, dimmed URL), then favorites, then DDG completions filling in a beat later.
  2. Arrows fill the input with the destination URL; Enter on a highlighted row navigates once; Esc restores the typed text; mouse click navigates.
  3. example.com + Enter, then type exam → it outranks equally-visited pages; window.bp.js.get('bp.typedUrls') shows it; a plain search (foo bar + Enter) adds nothing.
  4. Second window: visit a unique site there → first window suggests it after re-focusing its URL bar (cross-window aggregation).
  5. Delete a day/entry on tranquil-browser://history → gone from suggestions after refocus.
  6. Offline: dropdown still shows history/favorites; no console errors.
  7. Blank tab: bar empty; nothing tranquil-browser:// is suggested or recorded.
  8. Dropdown legible in business dark + light (rows inherit the themes’ menu colors).
  9. Open a .har archive: replay navigations no longer append to the history page.