Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ Linux phones (Librem 5 / PinePhone / Ubuntu Touch). See

## What you get today

- **De-googled new tab** — TronBrowser page, **Kagi** as the default search
(no Google in the omnibox; NeoSearch, Xprivo, and DuckDuckGo available as
alternatives), quick links.
- **De-googled new tab** — TronBrowser page, **[pit.world](https://pit.world)**
as the default search in both the address bar and the new tab (no logs, no
cookies, resolves Moshpit names; DuckDuckGo, Kagi, NeoSearch and Xprivo
available with `tron search <engine>`), quick links.
- **RSS reader** on the new tab — seeded from your OPML; add/remove feeds and
**import/export OPML** in Settings.
- **AI sidebar (bring your own keys)** — Anthropic, OpenAI, Google/Gemini,
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/extensions/ai-sidebar/newtab.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
<button type="button" id="mode-web" class="mode active" aria-selected="true">Web</button>
<button type="button" id="mode-ai" class="mode" aria-selected="false">AI</button>
</div>
<input id="q" type="text" placeholder="Search Kagi…" autocomplete="off" autofocus />
<input id="q" type="text" placeholder="Search pit.world…" autocomplete="off" autofocus />
</form>

<nav class="links">
<a href="https://pit.world">pit.world</a>
<a href="https://kagi.com">Kagi</a>
<a href="https://neosearch.org">NeoSearch</a>
<a href="https://www.xprivo.com">Xprivo</a>
Expand Down
12 changes: 7 additions & 5 deletions apps/desktop/extensions/ai-sidebar/newtab.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ const DEFAULT_TICKERS = 'SPY, AAPL, NVDA, BTC-USD';
const DEFAULT_LEAGUES = 'nfl, nba';
const splitList = (s) => String(s || '').split(',').map((x) => x.trim()).filter(Boolean);

// --- Search: Web (DuckDuckGo by default) or AI (sidebar) ---
// --- Search: Web (pit.world by default) or AI (sidebar) ---
// This is the NEW-TAB box only. The address bar's engine is a browser-level
// setting the extension cannot reach; `tron search <engine>` sets that one.
// Default is DuckDuckGo because it answers without an account — Kagi is
// subscription-only past its trial, so it cannot be the out-of-box default.
// Default is pit.world: it answers without an account, keeps no logs or cookies,
// and resolves Moshpit names. Kagi is subscription-only past its trial, so it
// cannot be the out-of-box default.
const SEARCH_ENGINES = {
pit: { name: 'pit.world', url: 'https://pit.world/search?q=' },
kagi: { name: 'Kagi', url: 'https://kagi.com/search?q=' },
neosearch: { name: 'NeoSearch', url: 'https://neosearch.org/?q=' },
xprivo: { name: 'Xprivo', url: 'https://www.xprivo.com/search/?q=' },
Expand All @@ -42,15 +44,15 @@ const TOR_SEARCH_ENGINES = {
excavator: { name: 'Excavator', url: 'http://2fd6cemt4gmccflhm6imvdfvli3nf7zn6rfrwpsy7uhxrgbypvwf5fad.onion/?q=' },
};
let searchMode = 'web';
let searchEngine = 'ddg';
let searchEngine = 'pit';
let torSearchEngine = 'ahmia';
let torEnabled = false;

// Resolve the engine to use right now: the Tor default while the onion toggle
// is on, otherwise the clearnet default.
function activeEngine() {
if (torEnabled) return TOR_SEARCH_ENGINES[torSearchEngine] || TOR_SEARCH_ENGINES.ahmia;
return SEARCH_ENGINES[searchEngine] || SEARCH_ENGINES.ddg;
return SEARCH_ENGINES[searchEngine] || SEARCH_ENGINES.pit;
}

function setMode(mode) {
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/extensions/ai-sidebar/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ <h2>Search</h2>
<code>chrome://settings/search</code>.</p>
<label for="searchEngine">New-tab search engine (clearnet)</label>
<select id="searchEngine">
<option value="ddg">DuckDuckGo (default)</option>
<option value="pit">pit.world (default, private, Moshpit names)</option>
<option value="ddg">DuckDuckGo</option>
<option value="kagi">Kagi (needs a Kagi subscription)</option>
<option value="neosearch">NeoSearch (private)</option>
<option value="xprivo">Xprivo (private)</option>
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/extensions/ai-sidebar/settings-sections.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function mountSettingsSections({ store, el, flash }) {
const cur = await store.get(['feeds', 'tickers', 'leagues', 'searchEngine', 'torSearchEngine']);

// Populate current values (on every mount, e.g. after a cloud pull).
if (el('searchEngine')) el('searchEngine').value = cur.searchEngine || 'ddg';
if (el('searchEngine')) el('searchEngine').value = cur.searchEngine || 'pit';
if (el('torSearchEngine')) el('torSearchEngine').value = cur.torSearchEngine || 'ahmia';
if (el('tickers')) el('tickers').value = cur.tickers ?? '';
if (el('leagues')) el('leagues').value = cur.leagues ?? '';
Expand Down
12 changes: 8 additions & 4 deletions apps/desktop/launcher/tronbrowser
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ fi
# enter and stalls typing when the endpoint is slow or answers 401.
search_engine_spec() {
case "$1" in
pit) echo "pit.world|pit.world|https://pit.world/search?q={searchTerms}" ;;
ddg) echo "DuckDuckGo|duckduckgo.com|https://duckduckgo.com/?q={searchTerms}" ;;
kagi) echo "Kagi|kagi.com|https://kagi.com/search?q={searchTerms}" ;;
neosearch) echo "NeoSearch|neosearch.org|https://neosearch.org/?q={searchTerms}" ;;
Expand All @@ -474,14 +475,17 @@ search_engine_spec() {

# What the user asked for (`tron search <engine>`), or a default that works
# without an account. Kagi is subscription-only past its trial, so defaulting
# the omnibox to it leaves a fresh install unable to search at all.
# the omnibox to it leaves a fresh install unable to search at all. The default
# is pit.world (no logs, no cookies, and it resolves Moshpit names, which pairs
# with the Pit toggle). A profile still on our previous default (ddg) moves to
# it on the next launch; an explicit `tron search ddg` stays ddg.
SEARCH_WANT="$(cat "$DATA/search-engine" 2>/dev/null || true)"
SEARCH_WANT="$(printf '%s' "$SEARCH_WANT" | tr -d '[:space:]')"
SEARCH_EXPLICIT=1
if [ -z "$SEARCH_WANT" ]; then SEARCH_WANT="ddg"; SEARCH_EXPLICIT=0; fi
if [ -z "$SEARCH_WANT" ]; then SEARCH_WANT="pit"; SEARCH_EXPLICIT=0; fi
if ! search_engine_spec "$SEARCH_WANT" >/dev/null 2>&1; then
echo "TronBrowser: unknown search engine '$SEARCH_WANT' — using ddg. See 'tron search'." >&2
SEARCH_WANT="ddg"; SEARCH_EXPLICIT=0
echo "TronBrowser: unknown search engine '$SEARCH_WANT' — using pit. See 'tron search'." >&2
SEARCH_WANT="pit"; SEARCH_EXPLICIT=0
fi

# Which engine we last set. Profiles from before this file existed recorded it
Expand Down
16 changes: 14 additions & 2 deletions apps/desktop/test/launcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ describe('omnibox search engine', () => {
// Kagi is subscription-only past its trial, so defaulting to it left a new
// install unable to search at all — it just landed on a login wall.
const { home } = run([]);
expect(dse(home).url).toBe('https://duckduckgo.com/?q={searchTerms}');
expect(dse(home).url).toBe('https://pit.world/search?q={searchTerms}');
});

it('never sets a suggestions_url', () => {
Expand All @@ -181,14 +181,26 @@ describe('omnibox search engine', () => {
const home = seed({ want: 'notanengine' });
const { stderr } = run([], { home });
expect(stderr).toContain("unknown search engine 'notanengine'");
expect(dse(home).url).toBe('https://duckduckgo.com/?q={searchTerms}');
expect(dse(home).url).toBe('https://pit.world/search?q={searchTerms}');
});

it('repairs a profile it had previously pinned to Kagi', () => {
// The actual bug: everyone who installed before this had Kagi written into
// their profile by us, and nothing moved them off it.
const home = seed({ url: KAGI_URL, legacyKagi: true });
run([], { home });
expect(dse(home).url).toBe('https://pit.world/search?q={searchTerms}');
});

it('moves a profile on our previous default (DuckDuckGo) to pit.world', () => {
const home = seed({ url: 'https://duckduckgo.com/?q={searchTerms}', marker: 'ddg' });
run([], { home });
expect(dse(home).url).toBe('https://pit.world/search?q={searchTerms}');
});

it('keeps DuckDuckGo for someone who chose it with `tron search ddg`', () => {
const home = seed({ url: 'https://duckduckgo.com/?q={searchTerms}', marker: 'ddg', want: 'ddg' });
run([], { home });
expect(dse(home).url).toBe('https://duckduckgo.com/?q={searchTerms}');
});

Expand Down
8 changes: 4 additions & 4 deletions apps/mobile/src/lib/navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,25 @@ describe('normalizeUrl', () => {

it('searches ordinary text', () => {
expect(normalizeUrl('privacy first browser')).toBe(
'https://duckduckgo.com/?q=privacy%20first%20browser',
'https://pit.world/search?q=privacy%20first%20browser',
);
});

it('searches unsupported schemes instead of loading them', () => {
expect(normalizeUrl('javascript:alert(1)')).toBe(
'https://duckduckgo.com/?q=javascript%3Aalert(1)',
'https://pit.world/search?q=javascript%3Aalert(1)',
);
});

it('does not treat domain-looking text with spaces as a URL', () => {
expect(normalizeUrl('example.com malicious suffix')).toBe(
'https://duckduckgo.com/?q=example.com%20malicious%20suffix',
'https://pit.world/search?q=example.com%20malicious%20suffix',
);
});

it('searches with a no-account engine, not Kagi', () => {
// Kagi needs a subscription after its trial; a fresh install must be able
// to search out of the box, matching the desktop DuckDuckGo default.
// to search out of the box, matching the desktop pit.world default.
expect(normalizeUrl('some query')).not.toContain('kagi.com');
});
});
Expand Down
8 changes: 4 additions & 4 deletions apps/mobile/src/lib/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ const DOMAIN_OR_IP =
/^(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}|(?:\d{1,3}\.){3}\d{1,3}|localhost)(?::\d{1,5})?(?:[/?#][^\s]*)?$/i;

function searchUrl(query: string): string {
// DuckDuckGo answers without an account. Kagi is subscription-only past its
// trial, so defaulting to it left a fresh install with a broken search box —
// the same out-of-box failure the desktop launcher already corrects.
return `https://duckduckgo.com/?q=${encodeURIComponent(query)}`;
// pit.world answers without an account and keeps no logs or cookies. Kagi is
// subscription-only past its trial, so defaulting to it left a fresh install
// with a broken search box; the desktop launcher uses pit.world too.
return `https://pit.world/search?q=${encodeURIComponent(query)}`;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/test/browser-event-order.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ describe.each(['android', 'ios'] as const)('browser event ordering on %s', platf
n.props.pointerEvents === 'none', 'failed native page');
expect(page.props.accessibilityElementsHidden).toBe(true);
await fire(input(root), 'onSubmitEditing');
expect(theWebView().props.source?.uri).toBe('https://duckduckgo.com/?q=unfinished%20query');
expect(theWebView().props.source?.uri).toBe('https://pit.world/search?q=unfinished%20query');
expect(textContents(root)).not.toContain(ERROR);
expect(button(root, 'Back').props.disabled).toBe(true);
expect(webViewRegistry()).toHaveLength(2);
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/test/browser-recovery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe.each(['android', 'ios'] as const)('browser recovery on %s', platform =>
await navigate(SECOND);
expect(input(root).props.value).toBe('my unfinished search');
await fire(input(root), 'onSubmitEditing');
expect(theWebView().props.source?.uri).toBe('https://duckduckgo.com/?q=my%20unfinished%20search');
expect(theWebView().props.source?.uri).toBe('https://pit.world/search?q=my%20unfinished%20search');
});

it('restores the current page address when an edit is dismissed without submitting', async () => {
Expand Down
10 changes: 5 additions & 5 deletions apps/mobile/test/browser-search.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Search through the real BrowserScreen: address-bar input must reach the
* actual WebView `source` as a no-account DuckDuckGo query — the same
* actual WebView `source` as a no-account pit.world query — the same
* correction the desktop launcher already ships — never as Kagi and never as a
* raw unsupported scheme.
*/
Expand All @@ -26,14 +26,14 @@ describe('BrowserScreen search', () => {
expect(theWebView().props.source).toEqual({ uri: HOME });
});

it('sends plain text to DuckDuckGo, which needs no account', async () => {
it('sends plain text to pit.world, which needs no account', async () => {
const renderer = await renderScreen(<BrowserScreen />);
await submitAddress(renderer.root, 'privacy first browser');
expect(theWebView().props.source).toEqual({
uri: 'https://duckduckgo.com/?q=privacy%20first%20browser',
uri: 'https://pit.world/search?q=privacy%20first%20browser',
});
expect(addressInput(renderer.root).props.value).toBe(
'https://duckduckgo.com/?q=privacy%20first%20browser',
'https://pit.world/search?q=privacy%20first%20browser',
);
});

Expand All @@ -47,7 +47,7 @@ describe('BrowserScreen search', () => {
const renderer = await renderScreen(<BrowserScreen />);
await submitAddress(renderer.root, 'javascript:alert(1)');
expect(theWebView().props.source).toEqual({
uri: 'https://duckduckgo.com/?q=javascript%3Aalert(1)',
uri: 'https://pit.world/search?q=javascript%3Aalert(1)',
});
});
});
6 changes: 3 additions & 3 deletions apps/web/public/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -446,17 +446,17 @@ case "${1:-}" in
# only the launcher can write it there — an MV3 extension has no API for this
# on Linux — so this records the choice and the next launch applies it. The
# new-tab box has its own separate picker in TronBrowser Settings.
_engines="ddg kagi neosearch xprivo oxiverse"
_engines="pit ddg kagi neosearch xprivo oxiverse"
_dirs="${TRONBROWSER_DATA:-$HOME/.tronbrowser}"
if [ -d "$HOME/TronBrowser" ]; then _dirs="$_dirs $HOME/TronBrowser"; fi
_want="${2:-}"
if [ -z "$_want" ]; then
for _d in $_dirs; do
_cur="$(cat "$_d/search-engine" 2>/dev/null || true)"
echo "Address bar: ${_cur:-ddg (default)} [$_d]"
echo "Address bar: ${_cur:-pit (default, pit.world)} [$_d]"
done
echo "Available: $_engines"
echo "Set with: tron search ddg"
echo "Set with: tron search pit (or ddg, kagi, ...)"
echo "(The new-tab search box is set separately in TronBrowser Settings.)"
exit 0
fi
Expand Down
3 changes: 2 additions & 1 deletion apps/web/public/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ <h2>Search</h2>
<code>chrome://settings/search</code>.</p>
<label for="searchEngine">New-tab search engine (clearnet)</label>
<select id="searchEngine">
<option value="ddg">DuckDuckGo (default)</option>
<option value="pit">pit.world (default, private, Moshpit names)</option>
<option value="ddg">DuckDuckGo</option>
<option value="kagi">Kagi (needs a Kagi subscription)</option>
<option value="neosearch">NeoSearch (private)</option>
<option value="xprivo">Xprivo (private)</option>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/test/install-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('tron search', () => {
const h = home();
const { status, out } = search(h);
expect(status).toBe(0);
expect(out).toContain('ddg');
expect(out).toContain('pit (default, pit.world)');
});

it('records the chosen engine where the launcher looks for it', () => {
Expand Down
Loading