fix: activity log now records from injector + dynamic icon colors

Activity log fix:
- Injector now writes directly to storage.local in addition to
  sending runtime messages to the background worker. This fixes
  the issue where MV3 service worker sleep caused messages to be
  silently dropped.

Dynamic icon colors:
- Black "SS" = active, normal
- Blue "SS" = reveal mode on
- Red "SS" = Silent Send disabled
- Icons generated via OffscreenCanvas in the service worker
- Updates on every settings change and keyboard shortcut toggle

Also adds keyboard shortcuts section to Options page showing
current bindings and how to customize them per browser.

https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
This commit is contained in:
Claude
2026-03-26 02:37:04 +00:00
parent e7880b3b1a
commit a844c94f13
3 changed files with 124 additions and 8 deletions
+78 -7
View File
@@ -176,10 +176,10 @@ api.commands.onCommand.addListener(async (command) => {
settings.revealMode = !settings.revealMode;
await Storage.saveSettings({ revealMode: settings.revealMode });
// Broadcast to all tabs
broadcastSettings(settings);
await updateIcon(settings);
// Show brief badge text
// Flash badge text briefly
const [tab] = await api.tabs.query({ active: true, currentWindow: true });
if (tab) {
api.action.setBadgeText({ text: settings.revealMode ? 'EYE' : '', tabId: tab.id });
@@ -188,7 +188,6 @@ api.commands.onCommand.addListener(async (command) => {
tabId: tab.id,
});
if (!settings.revealMode) {
// Restore normal badge after a moment
setTimeout(() => updateBadge(tab.id), 1500);
}
}
@@ -199,8 +198,9 @@ api.commands.onCommand.addListener(async (command) => {
await Storage.saveSettings({ enabled: settings.enabled });
broadcastSettings(settings);
await updateIcon(settings);
// Flash badge
// Flash badge text briefly
const [tab] = await api.tabs.query({ active: true, currentWindow: true });
if (tab) {
api.action.setBadgeText({ text: settings.enabled ? 'ON' : 'OFF', tabId: tab.id });
@@ -230,7 +230,78 @@ async function broadcastSettings(settings) {
}
}
// --- Set initial badge state ---
api.runtime.onInstalled.addListener(() => {
api.action.setBadgeBackgroundColor({ color: '#6b7280' });
// --- Dynamic Icon Colors ---
// Green = active, Blue = reveal mode, Red = disabled, Gray = unconfigured
function generateIcon(color, size) {
const canvas = new OffscreenCanvas(size, size);
const ctx = canvas.getContext('2d');
// Rounded rect background
const r = size * 0.19;
ctx.beginPath();
ctx.moveTo(r, 0);
ctx.lineTo(size - r, 0);
ctx.quadraticCurveTo(size, 0, size, r);
ctx.lineTo(size, size - r);
ctx.quadraticCurveTo(size, size, size - r, size);
ctx.lineTo(r, size);
ctx.quadraticCurveTo(0, size, 0, size - r);
ctx.lineTo(0, r);
ctx.quadraticCurveTo(0, 0, r, 0);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
// "SS" text
ctx.fillStyle = '#ffffff';
ctx.font = `bold ${size * 0.44}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('SS', size / 2, size / 2 + size * 0.03);
return ctx.getImageData(0, 0, size, size);
}
async function updateIcon(settings) {
let color;
if (!settings.enabled) {
color = '#dc2626'; // red — disabled
} else if (settings.revealMode) {
color = '#1d4ed8'; // blue — reveal mode
} else {
color = '#111111'; // dark — normal active
}
try {
const imageData = {
16: generateIcon(color, 16),
32: generateIcon(color, 32),
48: generateIcon(color, 48),
};
await api.action.setIcon({ imageData });
} catch (e) {
// OffscreenCanvas may not be available in all contexts
}
}
// Update icon when settings change
api.storage.onChanged.addListener(async (changes) => {
if (changes.ss_settings) {
const settings = { ...(changes.ss_settings.newValue || {}) };
await updateIcon(settings);
}
});
// --- Set initial state ---
api.runtime.onInstalled.addListener(async () => {
api.action.setBadgeBackgroundColor({ color: '#6b7280' });
const settings = await Storage.getSettings();
await updateIcon(settings);
});
// Also set icon on startup (service worker wake)
(async () => {
const settings = await Storage.getSettings();
await updateIcon(settings);
})();
+23 -1
View File
@@ -38,14 +38,36 @@
script.onload = () => script.remove();
// Listen for substitution events from the page script
window.addEventListener('message', (event) => {
window.addEventListener('message', async (event) => {
if (event.source !== window) return;
if (event.data?.type === 'ss:substitution-performed') {
// Try to notify background for badge update
api.runtime.sendMessage({
type: 'substitution:performed',
count: event.data.count,
replacements: event.data.replacements,
}).catch(() => {});
// Also log directly from the injector (content script world)
// in case the background worker is asleep
const replacements = event.data.replacements || [];
for (const r of replacements) {
const log = (await api.storage.local.get('ss_activity_log')).ss_activity_log || [];
log.unshift({
id: crypto.randomUUID(),
timestamp: Date.now(),
type: 'substitution',
direction: 'outbound',
original: r.original,
replaced: r.replaced,
category: r.category || 'general',
pattern: r.pattern || '',
url: location.href,
});
// Trim
if (log.length > 200) log.length = 200;
await api.storage.local.set({ ss_activity_log: log });
}
}
});
+23
View File
@@ -12,6 +12,29 @@
<p class="subtitle">Manage your privacy substitution mappings</p>
</header>
<section class="section">
<h2>Keyboard Shortcuts</h2>
<div class="setting-row">
<div>
<label>Toggle Reveal Mode</label>
<p class="setting-desc">Show your real data in AI responses</p>
</div>
<code style="font-size:12px;background:#f3f4f6;padding:4px 8px;border-radius:4px">Alt+Shift+R</code>
</div>
<div class="setting-row">
<div>
<label>Toggle Silent Send on/off</label>
<p class="setting-desc">Quickly enable or disable all substitution</p>
</div>
<code style="font-size:12px;background:#f3f4f6;padding:4px 8px;border-radius:4px">Alt+Shift+S</code>
</div>
<p class="section-desc" style="margin-top:8px;margin-bottom:0">
To change these shortcuts:<br>
<strong>Chrome:</strong> <code>chrome://extensions/shortcuts</code><br>
<strong>Firefox:</strong> <code>about:addons</code> → gear icon → Manage Extension Shortcuts
</p>
</section>
<section class="section">
<h2>Settings</h2>
<div class="setting-row">