From 506420e28ddaeab4570b5005da917bd332b8f3a4 Mon Sep 17 00:00:00 2001 From: WangEn Date: Fri, 18 Sep 2026 10:20:38 +0800 Subject: [PATCH 1/9] fix: reserve New Tab search fallback --- apps/extension/public/newtab-parity.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/extension/public/newtab-parity.js b/apps/extension/public/newtab-parity.js index a30d7e2..44a2b65 100644 --- a/apps/extension/public/newtab-parity.js +++ b/apps/extension/public/newtab-parity.js @@ -1,5 +1,5 @@ (() => { - const SOURCE_WEIGHT = { + const LAUNCHER_RESULT_LIMIT = 8;\n const SOURCE_WEIGHT = { tab: 500, workspace: 400, session: 350, @@ -92,8 +92,9 @@ if (!query) return []; const existing = originalBuildResults(rawQuery); - if (query.startsWith("@") || searchEngineCommand(query) || (existing.length && existing.every((result) => result.kind === "search-shortcut"))) { - return existing; + if (query.startsWith("@") || searchEngineCommand(query)) return existing; + if (existing.length && existing.every((result) => result.kind === "search-shortcut")) { + return existing.slice(0, LAUNCHER_RESULT_LIMIT); } const deduped = new Map(); @@ -114,9 +115,15 @@ if (result) deduped.set(`session:${session?.id || result.title}`, result); }); - return [...deduped.values()] - .sort((left, right) => right.score - left.score || String(left.title || "").localeCompare(String(right.title || ""))) - .slice(0, 10); + const ranked = [...deduped.values()] + .sort((left, right) => right.score - left.score || String(left.title || "").localeCompare(String(right.title || ""))); + const searchFallback = ranked.find((result) => result.kind === "search") || null; + if (!searchFallback) return ranked.slice(0, LAUNCHER_RESULT_LIMIT); + + return [ + ...ranked.filter((result) => result.kind !== "search").slice(0, LAUNCHER_RESULT_LIMIT - 1), + searchFallback, + ]; }; renderSearchResults = function parityRenderSearchResults() { From bb134250dc8eae1af0e9c440136415c586c403de Mon Sep 17 00:00:00 2001 From: WangEn Date: Fri, 18 Sep 2026 10:20:41 +0800 Subject: [PATCH 2/9] test: cover crowded launcher search fallback --- scripts/chromium-newtab-parity-smoke.mjs | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/scripts/chromium-newtab-parity-smoke.mjs b/scripts/chromium-newtab-parity-smoke.mjs index 821343a..6cfd947 100644 --- a/scripts/chromium-newtab-parity-smoke.mjs +++ b/scripts/chromium-newtab-parity-smoke.mjs @@ -135,6 +135,39 @@ try { "Shared Match Bookmark", ]); + await page.evaluate(() => { + const now = new Date().toISOString(); + snapshot = { + ...snapshot, + bookmarks: Array.from({ length: 12 }, (_, index) => ({ + id: `overflow-bookmark-${index + 1}`, + title: `Overflow Match Bookmark ${index + 1}`, + url: `https://overflow.example/${index + 1}`, + description: "Overflow Match fixture", + tags: [], + healthPolicy: "normal", + healthStatus: "unknown", + position: index, + createdAt: now, + updatedAt: now, + })), + }; + openTabs = []; + activeResult = 0; + elements.search.value = "Overflow Match"; + renderSearchResults(); + }); + + const overflowKinds = (await page.locator("#command-results .result-kind").allInnerTexts()) + .map((value) => value.toLocaleLowerCase()); + assert.equal(overflowKinds.length, 8, "New Tab should expose the same eight-result Launcher cap as Web."); + assert.deepEqual(overflowKinds.slice(0, 7), Array(7).fill("bookmark")); + assert.equal(overflowKinds[7], "search", "Default Search must remain visible even when local matches exceed the result limit."); + assert.match( + await page.locator("#command-results .result-row").last().innerText(), + /Search Google for “Overflow Match”/i, + ); + const search = page.locator("#search"); await search.fill("workspace-only-item"); const workspaceRow = page.locator("#command-results .result-row").filter({ hasText: "Shared Match Workspace" }).first(); @@ -147,6 +180,7 @@ try { assert.equal(await search.evaluate((element) => document.activeElement === element), false, "Escape on an empty Launcher should blur it on both Web and New Tab."); console.log("✓ New Tab canonical ordering is Open tab > Workspace > Session > Collection > Bookmark > Search"); + console.log("✓ New Tab keeps the eight-result Web Launcher cap and reserves the final slot for Search"); console.log("✓ New Tab exposes the same human-readable result type labels as Web Launcher"); console.log("✓ Workspace item title/URL content participates in New Tab Launcher search"); console.log("✓ Escape clears first and blurs the empty Launcher consistently"); From ce6fca249d00100b199dfcd8e2a4aaa5693ad62e Mon Sep 17 00:00:00 2001 From: WangEn Date: Fri, 18 Sep 2026 10:20:44 +0800 Subject: [PATCH 3/9] chore: bump extension package to 1.5.4 --- apps/extension/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/extension/package.json b/apps/extension/package.json index 34ef756..4cce4f3 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -1,6 +1,6 @@ { "name": "@dockmark/extension", - "version": "1.5.3", + "version": "1.5.4", "private": true, "type": "module", "scripts": { From f0169dc11f5f994744f384860cea7abcef15be33 Mon Sep 17 00:00:00 2001 From: WangEn Date: Fri, 18 Sep 2026 10:20:46 +0800 Subject: [PATCH 4/9] chore: bump extension manifest to 1.5.4 --- apps/extension/wxt.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/extension/wxt.config.ts b/apps/extension/wxt.config.ts index 1261d80..6e66f5f 100644 --- a/apps/extension/wxt.config.ts +++ b/apps/extension/wxt.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ modules: ["@wxt-dev/module-react"], manifest: ({ browser }) => ({ name: "Dockmark", - version: "1.5.3", + version: "1.5.4", description: "Browser bridge for Dockmark tabs, reviewed Capture Inbox, sessions, Smart Collection-aware local-first search, reviewed bookmark sync, local health checks and device pairing.", icons: { 16: "icons/dockmark-16.png", From 8c316a559df5dc9dba11fdf8c3e47fb9954e4094 Mon Sep 17 00:00:00 2001 From: WangEn Date: Fri, 18 Sep 2026 10:20:48 +0800 Subject: [PATCH 5/9] chore: publish extension 1.5.4 to Web distribution --- apps/web/src/extension-distribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/extension-distribution.ts b/apps/web/src/extension-distribution.ts index f3e02e1..9a3331a 100644 --- a/apps/web/src/extension-distribution.ts +++ b/apps/web/src/extension-distribution.ts @@ -1,4 +1,4 @@ -export const EXTENSION_RELEASE_VERSION = "1.5.3"; +export const EXTENSION_RELEASE_VERSION = "1.5.4"; // Fill these when browser-store listings are public. The UI will promote the // store action while keeping manual downloads visible. From 468c5f61f8e2b4b93707341a572dfd3caf9b599e Mon Sep 17 00:00:00 2001 From: WangEn Date: Fri, 18 Sep 2026 10:25:28 +0800 Subject: [PATCH 6/9] fix: restore New Tab parity script syntax From 1f8b5e8e9265aafc122ad82d3eb9c366da893e0a Mon Sep 17 00:00:00 2001 From: WangEn Date: Fri, 18 Sep 2026 10:25:43 +0800 Subject: [PATCH 7/9] fix: restore New Tab parity script syntax --- apps/extension/public/newtab-parity.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/extension/public/newtab-parity.js b/apps/extension/public/newtab-parity.js index 44a2b65..bfe3bbd 100644 --- a/apps/extension/public/newtab-parity.js +++ b/apps/extension/public/newtab-parity.js @@ -1,5 +1,6 @@ (() => { - const LAUNCHER_RESULT_LIMIT = 8;\n const SOURCE_WEIGHT = { + const LAUNCHER_RESULT_LIMIT = 8; + const SOURCE_WEIGHT = { tab: 500, workspace: 400, session: 350, From aff7b56a593077abf218ee83ca4ad57c838cfea3 Mon Sep 17 00:00:00 2001 From: WangEn Date: Fri, 18 Sep 2026 10:28:43 +0800 Subject: [PATCH 8/9] fix: reconstruct crowded New Tab search fallback --- apps/extension/public/newtab-parity.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/extension/public/newtab-parity.js b/apps/extension/public/newtab-parity.js index bfe3bbd..ff067da 100644 --- a/apps/extension/public/newtab-parity.js +++ b/apps/extension/public/newtab-parity.js @@ -65,6 +65,18 @@ }; } + function regularSearchFallback(query) { + const engine = preferredSearchEngine(); + const keyword = typeof engine?.keyword === "string" ? engine.keyword.trim() : ""; + return { + kind: "search", + title: `Search ${engine?.name || "the web"} for “${query}”`, + subtitle: keyword ? `Default · !${keyword}` : "Default search engine", + url: searchUrl(engine, query), + score: SOURCE_WEIGHT.search, + }; + } + function sessionResult(query, session, index) { const items = asArray(session?.items).filter((item) => isHttpUrl(item?.url)); const sourceDevice = typeof session?.sourceDevice === "string" ? session.sourceDevice.trim() : ""; @@ -118,12 +130,10 @@ const ranked = [...deduped.values()] .sort((left, right) => right.score - left.score || String(left.title || "").localeCompare(String(right.title || ""))); - const searchFallback = ranked.find((result) => result.kind === "search") || null; - if (!searchFallback) return ranked.slice(0, LAUNCHER_RESULT_LIMIT); return [ ...ranked.filter((result) => result.kind !== "search").slice(0, LAUNCHER_RESULT_LIMIT - 1), - searchFallback, + regularSearchFallback(query), ]; }; From d4f0a493dd0cc672dce9fe9d0bb9b48caeb1b5c8 Mon Sep 17 00:00:00 2001 From: WangEn Date: Fri, 18 Sep 2026 10:31:10 +0800 Subject: [PATCH 9/9] test: expect Web-aligned New Tab search fallback copy --- scripts/chromium-newtab-smoke.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/chromium-newtab-smoke.mjs b/scripts/chromium-newtab-smoke.mjs index 9d8c7e9..463c58f 100644 --- a/scripts/chromium-newtab-smoke.mjs +++ b/scripts/chromium-newtab-smoke.mjs @@ -327,7 +327,7 @@ try { assert.equal(await page.locator("#command-results .result-row").filter({ hasText: "Cached Example" }).count(), 0); await page.locator("#search").fill("offline query"); - await page.getByText("Search Secondary Search", { exact: true }).waitFor(); + await page.getByText("Search Secondary Search for “offline query”", { exact: true }).waitFor(); await page.locator("#search").fill("e offline query"); await page.getByText("Search Example Search", { exact: true }).waitFor();