Skip to content
Merged
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "webbrain",
"version": "25.8.5",
"version": "25.8.6",
"description": "Open-source AI browser agent — chat with pages, automate tasks, multi-provider LLM support.",
"private": true,
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion src/chrome/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# WebBrain Chrome/Edge Extension — Architecture

> Version 25.8.5 · Manifest V3 · Service Worker background
> Version 25.8.6 · Manifest V3 · Service Worker background

## High-Level Overview

Expand Down
2 changes: 1 addition & 1 deletion src/chrome/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "WebBrain",
"version": "25.8.5",
"version": "25.8.6",
"description": "Open-source AI browser agent — chat with pages, automate tasks, multi-provider LLM support.",
"permissions": [
"sidePanel",
Expand Down
2 changes: 1 addition & 1 deletion src/chrome/src/ui/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { ADDITIONAL_PROVIDER_UI } from '../providers/provider-catalog.js';

// Version shown in the subtitle. Kept here so it only needs one update per
// release; the subtitle string itself is translated.
const EXT_VERSION = '25.8.5';
const EXT_VERSION = '25.8.6';

const providersContainer = document.getElementById('providers');
const displaySettings = document.getElementById('display-settings');
Expand Down
40 changes: 40 additions & 0 deletions src/chrome/src/ui/sidepanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,14 @@ const activityText = document.getElementById('activity-text');
const modeAskBtn = document.getElementById('btn-mode-ask');
const modeActBtn = document.getElementById('btn-mode-act');
const modeDevBtn = document.getElementById('btn-mode-dev');
const modeToggleEl = document.getElementById('mode-toggle');
const modeToggleHighlight = (() => {
const el = document.createElement('div');
el.className = 'mode-toggle-highlight instant';
el.setAttribute('aria-hidden', 'true');
modeToggleEl?.prepend(el);
return el;
})();
const actWarning = document.getElementById('act-warning');
const inputArea = document.getElementById('input-area');
const slashCommandMenuEl = document.getElementById('slash-command-menu');
Expand Down Expand Up @@ -9571,6 +9579,14 @@ async function handleGlobalKeydown(e) {

// --- Mode Toggle ---

function positionModeHighlight(btn, { instant = false } = {}) {
if (!modeToggleHighlight || !btn) return;
if (instant) modeToggleHighlight.classList.add('instant');
modeToggleHighlight.style.width = `${btn.offsetWidth}px`;
modeToggleHighlight.style.transform = `translate3d(${btn.offsetLeft}px, 0, 0)`;
if (instant) requestAnimationFrame(() => modeToggleHighlight.classList.remove('instant'));
}

function setMode(mode) {
if (mode !== 'ask' && mode !== 'act' && mode !== 'dev') mode = 'ask';
const previousMode = agentMode;
Expand All @@ -9589,6 +9605,12 @@ function setMode(mode) {
modeDevBtn?.classList.toggle('active', mode === 'dev');
modeDevBtn?.classList.toggle('act', mode === 'dev');
inputArea.classList.toggle('act-mode', mode !== 'ask');

// Slide the highlight pill to the active button
const activeBtn = mode === 'ask' ? modeAskBtn : mode === 'act' ? modeActBtn : modeDevBtn;
modeToggleHighlight?.classList.toggle('act', mode !== 'ask');
positionModeHighlight(activeBtn);

updateActWarning();
resetInputPlaceholderRotation();
}
Expand Down Expand Up @@ -9627,6 +9649,24 @@ modeDevBtn?.addEventListener('click', async () => {
await ensureDevMode();
});

// Set initial highlight position without animation
requestAnimationFrame(() => positionModeHighlight(modeAskBtn, { instant: true }));

// Recalculate highlight when the toggle resizes (e.g. side panel drag)
if (modeToggleEl) {
new ResizeObserver(() => {
const activeBtn = modeToggleEl.querySelector('.mode-btn.active');
if (activeBtn) positionModeHighlight(activeBtn, { instant: true });
}).observe(modeToggleEl);
}

// Reposition highlight when document direction changes (RTL locale switch)
new MutationObserver(() => {
requestAnimationFrame(() => {
const activeBtn = modeToggleEl?.querySelector('.mode-btn.active');
if (activeBtn) positionModeHighlight(activeBtn, { instant: true });
});
}).observe(document.documentElement, { attributes: true, attributeFilter: ['dir'] });

// --- Stop / Abort ---

Expand Down
37 changes: 34 additions & 3 deletions src/chrome/styles/sidepanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -2112,10 +2112,43 @@ body {
border: 1px solid var(--border);
border-radius: 8px;
padding: 2px;
position: relative;
}

.mode-toggle-highlight {
position: absolute;
top: 2px;
left: 0;
z-index: 0;
height: calc(100% - 4px);
border-radius: 6px;
background: var(--accent-dim);
pointer-events: none;
will-change: transform, width;
transition:
transform 170ms cubic-bezier(0.22, 1, 0.36, 1),
width 170ms cubic-bezier(0.22, 1, 0.36, 1),
background 170ms ease;
}

.mode-toggle-highlight.act {
background: rgba(255, 152, 0, 0.12);
}

.mode-toggle-highlight.instant {
transition: none;
}

@media (prefers-reduced-motion: reduce) {
.mode-toggle-highlight {
transition: none;
}
}

.mode-btn {
flex: 1;
position: relative;
z-index: 1;
background: none;
border: none;
color: var(--text-muted);
Expand All @@ -2124,7 +2157,7 @@ body {
padding: 5px 0;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
transition: color 0.2s;
text-align: center;
}

Expand All @@ -2133,12 +2166,10 @@ body {
}

.mode-btn.active {
background: var(--accent-dim);
color: var(--accent);
}

.mode-btn.active.act {
background: rgba(255, 152, 0, 0.12);
color: var(--warning);
}

Expand Down
2 changes: 1 addition & 1 deletion src/firefox/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# WebBrain Firefox Extension — Architecture

> Version 25.8.5 · Manifest V2 · Background Page
> Version 25.8.6 · Manifest V2 · Background Page

## How Firefox Differs from Chrome

Expand Down
2 changes: 1 addition & 1 deletion src/firefox/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "WebBrain",
"version": "25.8.5",
"version": "25.8.6",
"description": "Open-source AI browser agent — chat with pages, automate tasks, multi-provider LLM support.",
"permissions": [
"activeTab",
Expand Down
2 changes: 1 addition & 1 deletion src/firefox/src/ui/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { ADDITIONAL_PROVIDER_UI } from '../providers/provider-catalog.js';

// Version shown in the subtitle. Kept here so it only needs one update per
// release; the subtitle string itself is translated.
const EXT_VERSION = '25.8.5';
const EXT_VERSION = '25.8.6';

const providersContainer = document.getElementById('providers');
const displaySettings = document.getElementById('display-settings');
Expand Down
40 changes: 40 additions & 0 deletions src/firefox/src/ui/sidepanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,14 @@ const activityText = document.getElementById('activity-text');
const modeAskBtn = document.getElementById('btn-mode-ask');
const modeActBtn = document.getElementById('btn-mode-act');
const modeDevBtn = document.getElementById('btn-mode-dev');
const modeToggleEl = document.getElementById('mode-toggle');
const modeToggleHighlight = (() => {
const el = document.createElement('div');
el.className = 'mode-toggle-highlight instant';
el.setAttribute('aria-hidden', 'true');
modeToggleEl?.prepend(el);
return el;
})();
const actWarning = document.getElementById('act-warning');
const inputArea = document.getElementById('input-area');
const slashCommandMenuEl = document.getElementById('slash-command-menu');
Expand Down Expand Up @@ -9141,6 +9149,14 @@ async function sendToBackground(action, data = {}) {

// --- Mode Toggle ---

function positionModeHighlight(btn, { instant = false } = {}) {
if (!modeToggleHighlight || !btn) return;
if (instant) modeToggleHighlight.classList.add('instant');
modeToggleHighlight.style.width = `${btn.offsetWidth}px`;
modeToggleHighlight.style.transform = `translate3d(${btn.offsetLeft}px, 0, 0)`;
if (instant) requestAnimationFrame(() => modeToggleHighlight.classList.remove('instant'));
}

function setMode(mode) {
if (mode !== 'ask' && mode !== 'act' && mode !== 'dev') mode = 'ask';
agentMode = mode;
Expand All @@ -9152,6 +9168,12 @@ function setMode(mode) {
modeDevBtn?.classList.toggle('active', mode === 'dev');
modeDevBtn?.classList.toggle('act', mode === 'dev');
inputArea.classList.toggle('act-mode', mode !== 'ask');

// Slide the highlight pill to the active button
const activeBtn = mode === 'ask' ? modeAskBtn : mode === 'act' ? modeActBtn : modeDevBtn;
modeToggleHighlight?.classList.toggle('act', mode !== 'ask');
positionModeHighlight(activeBtn);

updateActWarning();
resetInputPlaceholderRotation();
}
Expand Down Expand Up @@ -9190,6 +9212,24 @@ modeDevBtn?.addEventListener('click', async () => {
await ensureDevMode();
});

// Set initial highlight position without animation
requestAnimationFrame(() => positionModeHighlight(modeAskBtn, { instant: true }));

// Recalculate highlight when the toggle resizes (e.g. side panel drag)
if (modeToggleEl) {
new ResizeObserver(() => {
const activeBtn = modeToggleEl.querySelector('.mode-btn.active');
if (activeBtn) positionModeHighlight(activeBtn, { instant: true });
}).observe(modeToggleEl);
}

// Reposition highlight when document direction changes (RTL locale switch)
new MutationObserver(() => {
requestAnimationFrame(() => {
const activeBtn = modeToggleEl?.querySelector('.mode-btn.active');
if (activeBtn) positionModeHighlight(activeBtn, { instant: true });
});
}).observe(document.documentElement, { attributes: true, attributeFilter: ['dir'] });

// --- Stop / Abort ---

Expand Down
37 changes: 34 additions & 3 deletions src/firefox/styles/sidepanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -1983,10 +1983,43 @@ body {
border: 1px solid var(--border);
border-radius: 8px;
padding: 2px;
position: relative;
}

.mode-toggle-highlight {
position: absolute;
top: 2px;
left: 0;
z-index: 0;
height: calc(100% - 4px);
border-radius: 6px;
background: var(--accent-dim);
pointer-events: none;
will-change: transform, width;
transition:
transform 170ms cubic-bezier(0.22, 1, 0.36, 1),
width 170ms cubic-bezier(0.22, 1, 0.36, 1),
background 170ms ease;
}

.mode-toggle-highlight.act {
background: rgba(255, 152, 0, 0.12);
}

.mode-toggle-highlight.instant {
transition: none;
}

@media (prefers-reduced-motion: reduce) {
.mode-toggle-highlight {
transition: none;
}
}

.mode-btn {
flex: 1;
position: relative;
z-index: 1;
background: none;
border: none;
color: var(--text-muted);
Expand All @@ -1995,7 +2028,7 @@ body {
padding: 5px 0;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
transition: color 0.2s;
text-align: center;
}

Expand All @@ -2004,12 +2037,10 @@ body {
}

.mode-btn.active {
background: var(--accent-dim);
color: var(--accent);
}

.mode-btn.active.act {
background: rgba(255, 152, 0, 0.12);
color: var(--warning);
}

Expand Down
Loading