Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions releases.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`
: '';
Expand Down
51 changes: 51 additions & 0 deletions test/releases.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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?'), '');
});