diff --git a/scripts/lib/gallery.mjs b/scripts/lib/gallery.mjs
index c8e0497..f30320e 100644
--- a/scripts/lib/gallery.mjs
+++ b/scripts/lib/gallery.mjs
@@ -12,6 +12,7 @@ const SHELL = readTemplate('gallery.html');
const CARD = readTemplate('card.html');
const HTML_ESCAPES = { '&': '&', '<': '<', '>': '>', '"': '"' };
const SCHEME_LABELS = { light: 'Light', dark: 'Dark', both: 'Light & Dark' };
+const NAME_COLLATOR = new Intl.Collator('en-US', { numeric: true, sensitivity: 'accent' });
// GitHub-style alerts (https://docs.github.com/get-started/writing-on-github). Octicon paths.
const ALERTS = {
@@ -223,13 +224,35 @@ function renderNav(populated) {
return ``;
}
+function compareEntries(lhs, rhs) {
+ const featuredOrder = Number(rhs.featured === true) - Number(lhs.featured === true);
+ if (featuredOrder !== 0) {
+ return featuredOrder;
+ }
+
+ const dateValue = (entry) => {
+ const value = Date.parse(entry.latest.date ?? entry.addedDate ?? '');
+ return Number.isNaN(value) ? Number.NEGATIVE_INFINITY : value;
+ };
+
+ const lhsDate = dateValue(lhs);
+ const rhsDate = dateValue(rhs);
+ if (lhsDate !== rhsDate) {
+ return rhsDate - lhsDate;
+ }
+
+ const nameOrder = NAME_COLLATOR.compare(lhs.name, rhs.name);
+ if (nameOrder !== 0) {
+ return nameOrder;
+ }
+
+ return lhs.id < rhs.id ? -1 : Number(lhs.id > rhs.id);
+}
+
export function renderGallery(index) {
const groups = SECTIONS.map((section) => ({
section,
- items: index.extensions.filter(section.isMatch).sort((lhs, rhs) => {
- // Featured entries float to the top, then sort alphabetically
- return ((rhs.featured === true) - (lhs.featured === true)) || lhs.id.localeCompare(rhs.id);
- }),
+ items: index.extensions.filter(section.isMatch).sort(compareEntries),
}));
const populated = groups.filter(({ items }) => items.length > 0);
diff --git a/test/gallery.test.mjs b/test/gallery.test.mjs
index 192bac1..ffc2ccf 100644
--- a/test/gallery.test.mjs
+++ b/test/gallery.test.mjs
@@ -22,3 +22,48 @@ test('version label tooltip combines release date and notes', () => {
assert.match(html, /v1\.0\.0<\/span>/);
});
+
+test('gallery uses the app Discover order', () => {
+ const entry = (id, fields = {}) => ({
+ id,
+ name: fields.name ?? id,
+ description: 'Description',
+ author: 'Author',
+ homepage: 'https://example.com',
+ category: fields.category ?? 'extension',
+ addedDate: fields.addedDate,
+ featured: fields.featured,
+ latest: {
+ version: '1.0.0',
+ date: fields.releaseDate,
+ },
+ });
+
+ const html = renderGallery({
+ generatedAt: '2026-08-18T02:00:00Z',
+ extensions: [
+ entry('theme', { category: 'theme', featured: true, releaseDate: '2026-08-18T02:00:00Z' }),
+ entry('theme-10', { name: 'Theme 10', category: 'theme' }),
+ entry('same-z', { name: 'Same' }),
+ entry('newer', { addedDate: '2026-08-10T00:00:00Z' }),
+ entry('featured', { featured: true, addedDate: '2026-08-01T00:00:00Z' }),
+ entry('theme-2', { name: 'Theme 2', category: 'theme' }),
+ entry('updated', { addedDate: '2026-07-01T00:00:00Z', releaseDate: '2026-08-12T00:00:00Z' }),
+ entry('same-a', { name: 'Same' }),
+ entry('older', { featured: false, addedDate: '2026-08-01T00:00:00Z' }),
+ ],
+ });
+
+ const ids = [...html.matchAll(//g)].map((match) => match[1]);
+ assert.deepEqual(ids, [
+ 'featured',
+ 'updated',
+ 'newer',
+ 'older',
+ 'same-a',
+ 'same-z',
+ 'theme',
+ 'theme-2',
+ 'theme-10',
+ ]);
+});