Skip to content

Commit c5092b1

Browse files
committed
Fix settings UX contrast and unify header/icon behavior
1 parent 44fa71d commit c5092b1

9 files changed

Lines changed: 199 additions & 41 deletions

File tree

background/service-worker.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ function getDashboardUrl() {
4545
return chrome.runtime.getURL("ui/dashboard.html");
4646
}
4747

48+
function getSettingsUrl() {
49+
return chrome.runtime.getURL("ui/settings.html");
50+
}
51+
4852
function isTrackableTab(tab) {
4953
if (!tab || typeof tab.id !== "number" || typeof tab.windowId !== "number") {
5054
return false;
@@ -549,6 +553,22 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
549553
return false;
550554
}
551555

556+
if (message?.type === "open-settings") {
557+
if (message.surface === "full" && typeof _sender?.tab?.id === "number") {
558+
chrome.tabs
559+
.update(_sender.tab.id, { url: getSettingsUrl() })
560+
.then(() => sendResponse({ ok: true, mode: "same_tab" }))
561+
.catch((error) => sendResponse({ ok: false, error: String(error) }));
562+
return true;
563+
}
564+
565+
chrome.runtime
566+
.openOptionsPage()
567+
.then(() => sendResponse({ ok: true, mode: "options_page" }))
568+
.catch((error) => sendResponse({ ok: false, error: String(error) }));
569+
return true;
570+
}
571+
552572
if (message?.type === "open-side-panel") {
553573
openPanelForAllWindows()
554574
.then((opened) => {

project-history.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ Chronological execution log:
167167
- switched native side-panel behavior to `openPanelOnActionClick: true`
168168
- extended smoke assertions to require side-panel API availability and configured action-click behavior
169169

170+
31. Fixed post-release UI consistency and settings workflow regressions:
171+
- replaced synthetic hero badge with app icon asset on dashboard/panel/settings headers
172+
- corrected dashboard tagline copy to present tense ("Surface what matters")
173+
- fixed dark-mode settings dropdown contrast (readable select + option colors)
174+
- changed full-dashboard Settings action to open settings in the same tab via runtime message routing
175+
- extended automated coverage:
176+
- e2e test for theme select contrast in dark/light
177+
- smoke assertions for same-tab settings routing and cross-page theme sync
178+
170179
## 4. What Were The Decisions That We Took?
171180

172181
### Product/Architecture Decisions

scripts/extension-smoke-test.mjs

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ async function run() {
2525

2626
const extensionId = new URL(serviceWorker.url()).host;
2727
const dashboardUrl = `chrome-extension://${extensionId}/ui/dashboard.html`;
28-
const settingsUrl = `chrome-extension://${extensionId}/ui/settings.html`;
2928
const panelUrl = `chrome-extension://${extensionId}/ui/panel.html`;
3029

3130
const dashboardPage = await context.newPage();
@@ -45,9 +44,51 @@ async function run() {
4544
await panelPage.goto(panelUrl, { waitUntil: "domcontentloaded" });
4645
const panelViewCount = await panelPage.locator('[data-view]').count();
4746

48-
const settingsPage = await context.newPage();
49-
await settingsPage.goto(settingsUrl, { waitUntil: "domcontentloaded" });
50-
const settingsHeading = await settingsPage.textContent("h1");
47+
await dashboardPage.click("#theme-toggle");
48+
await dashboardPage.waitForTimeout(250);
49+
const dashboardThemeAfterToggle = await dashboardPage.getAttribute("body", "data-theme");
50+
51+
await dashboardPage.click("#open-settings");
52+
await dashboardPage.waitForURL((url) => url.toString().endsWith("/ui/settings.html"), { timeout: 5_000 });
53+
const settingsHeading = await dashboardPage.textContent("h1");
54+
const settingsOpenedInCurrentTab = dashboardPage.url().endsWith("/ui/settings.html");
55+
const settingsTheme = await dashboardPage.getAttribute("body", "data-theme");
56+
const settingsThemeValue = await dashboardPage.inputValue("#theme");
57+
const themeSelectContrast = await dashboardPage.evaluate(() => {
58+
const select = document.querySelector("#theme");
59+
if (!select) {
60+
return 0;
61+
}
62+
63+
const parseRgb = (value) => {
64+
const matches = String(value).match(/\d+(\.\d+)?/g);
65+
if (!matches || matches.length < 3) {
66+
return [0, 0, 0];
67+
}
68+
return matches.slice(0, 3).map((part) => Number(part));
69+
};
70+
71+
const luminance = ([r, g, b]) => {
72+
const toLinear = (channel) => {
73+
const value = channel / 255;
74+
if (value <= 0.03928) {
75+
return value / 12.92;
76+
}
77+
return ((value + 0.055) / 1.055) ** 2.4;
78+
};
79+
80+
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
81+
};
82+
83+
const styles = window.getComputedStyle(select);
84+
const fg = parseRgb(styles.color);
85+
const bg = parseRgb(styles.backgroundColor);
86+
const l1 = luminance(fg);
87+
const l2 = luminance(bg);
88+
const lighter = Math.max(l1, l2);
89+
const darker = Math.min(l1, l2);
90+
return Number(((lighter + 0.05) / (darker + 0.05)).toFixed(2));
91+
});
5192

5293
const result = {
5394
extensionId,
@@ -61,7 +102,12 @@ async function run() {
61102
paused: status?.paused,
62103
sidePanelApiAvailable: status?.sidePanelApiAvailable,
63104
openPanelOnActionClick: status?.openPanelOnActionClick,
64-
settingsHeading
105+
settingsHeading,
106+
settingsOpenedInCurrentTab,
107+
dashboardThemeAfterToggle,
108+
settingsTheme,
109+
settingsThemeValue,
110+
themeSelectContrast
65111
};
66112

67113
console.log(JSON.stringify(result, null, 2));
@@ -76,6 +122,11 @@ async function run() {
76122
result.runtimeStatusOk !== true ||
77123
result.sidePanelApiAvailable !== true ||
78124
result.openPanelOnActionClick !== true ||
125+
result.settingsOpenedInCurrentTab !== true ||
126+
result.dashboardThemeAfterToggle !== "light" ||
127+
result.settingsTheme !== "light" ||
128+
result.settingsThemeValue !== "light" ||
129+
Number(result.themeSelectContrast || 0) < 4.5 ||
79130
result.settingsHeading !== "Settings"
80131
) {
81132
process.exitCode = 1;

tests/e2e/dashboard.spec.js

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ test("all tabs view includes never-focused and focuses existing tab", async ({ p
196196
expect(calls.some((item) => item.api === "tabs.update")).toBeTruthy();
197197
});
198198

199-
test("most recent view handles navigation actions and settings", async ({ page }) => {
199+
test("most recent view handles navigation actions and routes settings through runtime", async ({ page }) => {
200200
const now = Date.now();
201201
const activities = [
202202
makeActivity({
@@ -227,6 +227,62 @@ test("most recent view handles navigation actions and settings", async ({ page }
227227
expect(
228228
calls.some((item) => item.api === "tabs.create" && item.options?.url === "https://example.com/closed")
229229
).toBeTruthy();
230-
expect(calls.some((item) => item.api === "runtime.openOptionsPage")).toBeTruthy();
230+
expect(
231+
calls.some(
232+
(item) =>
233+
item.api === "runtime.sendMessage" &&
234+
item.message?.type === "open-settings" &&
235+
item.message?.surface === "full"
236+
)
237+
).toBeTruthy();
231238
expect(calls.some((item) => item.api === "runtime.sendMessage" && item.message?.type === "open-side-panel")).toBeTruthy();
232239
});
240+
241+
test("settings theme select maintains readable contrast in dark and light modes", async ({ page }) => {
242+
await page.goto("/ui/settings.html");
243+
244+
const contrastRatios = await page.evaluate(() => {
245+
const parseRgb = (value) => {
246+
const matches = String(value).match(/\d+(\.\d+)?/g);
247+
if (!matches || matches.length < 3) {
248+
return [0, 0, 0];
249+
}
250+
return matches.slice(0, 3).map((part) => Number(part));
251+
};
252+
253+
const luminance = ([r, g, b]) => {
254+
const toLinear = (channel) => {
255+
const value = channel / 255;
256+
if (value <= 0.03928) {
257+
return value / 12.92;
258+
}
259+
return ((value + 0.055) / 1.055) ** 2.4;
260+
};
261+
262+
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
263+
};
264+
265+
const getRatio = () => {
266+
const select = document.querySelector("#theme");
267+
const styles = window.getComputedStyle(select);
268+
const fg = parseRgb(styles.color);
269+
const bg = parseRgb(styles.backgroundColor);
270+
const l1 = luminance(fg);
271+
const l2 = luminance(bg);
272+
const lighter = Math.max(l1, l2);
273+
const darker = Math.min(l1, l2);
274+
return (lighter + 0.05) / (darker + 0.05);
275+
};
276+
277+
const darkRatio = getRatio();
278+
const themeSelect = document.querySelector("#theme");
279+
themeSelect.value = "light";
280+
themeSelect.dispatchEvent(new Event("change", { bubbles: true }));
281+
const lightRatio = getRatio();
282+
283+
return { darkRatio, lightRatio };
284+
});
285+
286+
expect(contrastRatios.darkRatio).toBeGreaterThanOrEqual(4.5);
287+
expect(contrastRatios.lightRatio).toBeGreaterThanOrEqual(4.5);
288+
});

ui/activity.css

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -151,29 +151,16 @@ body[data-surface="panel"] .hero {
151151
.brand-mark {
152152
width: 42px;
153153
height: 42px;
154-
position: relative;
155154
border-radius: 50%;
156-
background: radial-gradient(circle at 35% 35%, var(--accent), var(--accent-strong));
157-
box-shadow: 0 0 0 3px var(--accent-soft), 0 10px 20px rgba(0, 0, 0, 0.24);
158-
}
159-
160-
.brand-mark .ring {
161-
position: absolute;
162-
inset: -5px;
163-
border-radius: 50%;
164-
border: 1px solid rgba(255, 255, 255, 0.35);
165-
}
166-
167-
.brand-mark .ring-b {
168-
inset: -10px;
169-
opacity: 0.55;
155+
display: grid;
156+
place-items: center;
170157
}
171158

172-
.brand-mark .pulse {
173-
position: absolute;
174-
inset: 14px;
175-
border-radius: 50%;
176-
background: rgba(2, 7, 23, 0.45);
159+
.brand-mark img {
160+
width: 42px;
161+
height: 42px;
162+
display: block;
163+
filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.3));
177164
}
178165

179166
h1 {

ui/activity.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,10 @@ function bindEvents() {
283283
});
284284

285285
openSettingsButton?.addEventListener("click", () => {
286-
chrome.runtime.openOptionsPage();
286+
const surface = bodyElement.dataset.surface === "panel" ? "panel" : "full";
287+
chrome.runtime.sendMessage({ type: "open-settings", surface }).catch((error) => {
288+
console.error("Open settings failed", error);
289+
});
287290
});
288291

289292
expandDashboardButton?.addEventListener("click", async () => {

ui/dashboard.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@
1212
<header class="hero">
1313
<div class="brand">
1414
<div class="brand-mark" aria-hidden="true">
15-
<span class="ring ring-a"></span>
16-
<span class="ring ring-b"></span>
17-
<span class="pulse"></span>
15+
<img src="../assets/icons/icon-48.png" alt="" />
1816
</div>
1917
<div>
2018
<h1>Chrome Activity Reader</h1>
21-
<p class="subtitle">Track everything. Surface what mattered.</p>
19+
<p class="subtitle">Track everything. Surface what matters.</p>
2220
</div>
2321
</div>
2422
<div class="hero-actions">

ui/panel.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
<header class="hero">
1313
<div class="brand">
1414
<div class="brand-mark" aria-hidden="true">
15-
<span class="ring ring-a"></span>
16-
<span class="ring ring-b"></span>
17-
<span class="pulse"></span>
15+
<img src="../assets/icons/icon-48.png" alt="" />
1816
</div>
1917
<div>
2018
<h1>Activity Reader</h1>

ui/settings.html

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,53 @@
3939
border: 1px solid var(--border);
4040
padding: 9px 12px;
4141
color: var(--ink);
42-
background: rgba(0, 0, 0, 0.12);
4342
font: inherit;
4443
}
4544

46-
body[data-theme="light"] textarea,
47-
body[data-theme="light"] select {
45+
textarea {
46+
background: rgba(0, 0, 0, 0.12);
47+
}
48+
49+
select {
50+
appearance: none;
51+
padding-right: 34px;
52+
background-repeat: no-repeat;
53+
background-position:
54+
calc(100% - 17px) calc(50% - 2px),
55+
calc(100% - 12px) calc(50% - 2px);
56+
background-size: 5px 5px, 5px 5px;
57+
}
58+
59+
body[data-theme="dark"] select {
60+
color: #ecf2ff;
61+
background-color: #121f3f;
62+
background-image:
63+
linear-gradient(45deg, transparent 50%, #97a6cb 50%),
64+
linear-gradient(135deg, #97a6cb 50%, transparent 50%);
65+
}
66+
67+
body[data-theme="dark"] select option {
68+
color: #ecf2ff;
69+
background: #121f3f;
70+
}
71+
72+
body[data-theme="light"] textarea {
4873
background: rgba(255, 255, 255, 0.86);
4974
}
5075

76+
body[data-theme="light"] select {
77+
color: #0d1531;
78+
background-color: #ffffff;
79+
background-image:
80+
linear-gradient(45deg, transparent 50%, #4f5a7f 50%),
81+
linear-gradient(135deg, #4f5a7f 50%, transparent 50%);
82+
}
83+
84+
body[data-theme="light"] select option {
85+
color: #0d1531;
86+
background: #ffffff;
87+
}
88+
5189
textarea {
5290
min-height: 140px;
5391
resize: vertical;
@@ -77,9 +115,7 @@
77115
<header class="hero">
78116
<div class="brand">
79117
<div class="brand-mark" aria-hidden="true">
80-
<span class="ring ring-a"></span>
81-
<span class="ring ring-b"></span>
82-
<span class="pulse"></span>
118+
<img src="../assets/icons/icon-48.png" alt="" />
83119
</div>
84120
<div>
85121
<h1>Settings</h1>

0 commit comments

Comments
 (0)