From e1de7a6a9f6a12e4e36964fba9e3504627161bad Mon Sep 17 00:00:00 2001 From: Harsh Singh Date: Sat, 10 Oct 2026 20:13:15 +0530 Subject: [PATCH] Match a version question to the newest release for the product it names bestNote counted every shared word, repeats included, so "What is the latest version of the desktop app?" was matched on words like "the" and "app" and came back with a release twelve builds old. A firmware or phone app question was handed a desktop release, because the last fifteen releases are all desktop builds. It now keeps to notes for the product the question names, ignores the words every version question shares, counts a shared word once and breaks ties by the newest release. --- releases.js | 39 ++++++++++++++++++++++++++++----- test/releases.test.js | 51 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/releases.js b/releases.js index b6a1b3e..76bee48 100644 --- a/releases.js +++ b/releases.js @@ -23,18 +23,45 @@ function clip(text) { return plain.length > 280 ? `${plain.slice(0, 280)}…` : plain; } +// Words a version question shares with every release note. They do not say which release is meant. +const GENERIC = new Set( + ('the and for with from that this what whats which where when how are was can does did you your any there have has get ' + + 'new newest latest current version versions release releases note notes update updates fix fixed fixes bug bugs ' + + 'app apps omi download downloads install installer firmware desktop mac macos macbook windows computer phone ' + + 'mobile android ios iphone ipad testflight').split(' ') +); + +// The one product a text names, or '' when it names none or more than one. +function productOf(text) { + const s = String(text || ''); + const named = []; + if (/\bfirmware\b/i.test(s)) named.push('firmware'); + if (/\b(?:desktop|mac(?!\s+address)|macos|macbook|windows|computer)\b/i.test(s)) named.push('desktop'); + if (/\b(?:phone|mobile|android|ios|iphone|ipad|testflight)\b/i.test(s)) named.push('phone'); + return named.length === 1 ? named[0] : ''; +} + +function publishedTime(note) { + const time = Date.parse(note?.publishedAt || ''); + return Number.isFinite(time) ? time : 0; +} + function bestNote(question, notes) { - const wanted = new Set(words(question)); + const product = productOf(question); + const eligible = (notes || []).filter((note) => !product || productOf(`${note.name} ${note.tag}`) === product); + const wanted = new Set(words(question).filter((word) => !GENERIC.has(word))); let best = null; - let score = 0; - for (const note of notes || []) { - const shared = words(`${note.name} ${note.body}`).filter((word) => wanted.has(word)).length; - if (shared > score) { + let score = -1; + for (const note of eligible) { + const shared = new Set(words(`${note.name} ${note.body}`).filter((word) => wanted.has(word))).size; + // Lists arrive newest first, so on a tie the earlier note stays unless a later one is dated newer. + if (shared > score || (shared === score && publishedTime(note) > publishedTime(best))) { best = note; score = shared; } } - if (!best || score < 1) return ''; + // A question about one change needs a note that mentions it. A plain "latest version" takes the newest. + if (!best || (wanted.size && score < 1)) return ''; const releaseUrl = best.tag ? `https://github.com/BasedHardware/omi/releases/tag/${encodeURIComponent(best.tag)}` : ''; diff --git a/test/releases.test.js b/test/releases.test.js index 2713d97..9470010 100644 --- a/test/releases.test.js +++ b/test/releases.test.js @@ -34,3 +34,54 @@ test('a charging question does not pull a release note', async () => { assert.equal(text, ''); assert.equal(called, false); }); + +const desktopFeed = [ + { + tag_name: 'v0.12.464+12464-macos', + name: 'Omi Desktop v0.12.464 (candidate)', + body: "## OMI Desktop v0.12.464\n\n### What's New\n- Bug fixes and improvements", + published_at: '2026-10-10T12:00:00Z', + }, + { + tag_name: 'v0.12.463+12463-macos', + name: 'Omi Desktop v0.12.463 (candidate)', + body: "## OMI Desktop v0.12.463\n\n### What's New\n- Settings now say plainly what the app does with the latest version of your data", + published_at: '2026-10-10T09:00:00Z', + }, + { + tag_name: 'v0.12.452+12452-macos', + name: 'Omi Desktop v0.12.452 (candidate)', + body: "## OMI Desktop v0.12.452\n\n### What's New\n- Fixed chat failing with a generic error for BYOK users in the desktop app", + published_at: '2026-10-08T12:00:00Z', + }, +]; + +function releaseFor(question, feed = desktopFeed) { + resetReleaseCache(); + return matchingRelease(question, { fetchImpl: async () => ({ ok: true, json: async () => feed }), store: null }); +} + +test('a latest-version question gets the newest release, not the one that shares the most words', async () => { + for (const question of [ + 'What is the latest version of the desktop app?', + 'Where do I download the desktop app, which version?', + "What's new in the desktop app?", + ]) { + assert.match(await releaseFor(question), /v0\.12\.464/, question); + } +}); + +test('a firmware or phone app question is not handed a desktop release note', async () => { + for (const question of [ + 'Is there a firmware update for my Omi?', + "What's new in the latest version of the phone app?", + 'Any new version of the Android app?', + ]) { + assert.equal(await releaseFor(question), '', question); + } +}); + +test('a question about one fix still gets the release that mentions it', async () => { + assert.match(await releaseFor('Which desktop version fixed the BYOK chat error?'), /v0\.12\.452/); + assert.equal(await releaseFor('Which desktop version fixed the calendar sync?'), ''); +});