openvibers/openvibe.shoes Commits

Open.Shoes by name; Home and Drops draw their icons (#47)

OP OpenVibers committed 10 days ago · 3797d653b4 · 22 files, +66 −44

Home asked the navbar for fa-shoe-prints and Drops for fa-calendar-days, neither of which the shared navbar draws
(Font Awesome is not loaded), so both links showed an empty slot. Home is the house, Drops the clock, and
test/nav-icons.test.js checks every link's icon against the navbar's own set.

Changes

ddiff --git a/public/css/shoes.css b/public/css/shoes.css
iindex 6748dd8..50f748e 100644
---- a/public/css/shoes.css
++++ b/public/css/shoes.css
@@@ -3,7 +3,7 @@
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 12px; top: 12px; z-index: 99; padding: 8px 12px; border-radius: 10px; background: var(--bg-card, #0f1729); color: inherit; }
--/* OpenVibe.Shoes — the small set of page pieces the kit does not draw (tables, forms, the price chart).
++/* Open.Shoes — the small set of page pieces the kit does not draw (tables, forms, the price chart).
The room's look and every card, rail and scene come from kit.css, room.css and showcase.css. */
.sh-wrap { width: min(1180px, 100% - 32px); margin: 0 auto; }
.sh-page { padding: 28px 0 64px; }
ddiff --git a/server/app.js b/server/app.js
iindex eeedabc..e8d16e5 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -1,7 +1,7 @@
'use strict';
/**
-- * OpenVibe.Shoes — Express app factory; server/index.js listens, tests build their own instance with a temp
++ * Open.Shoes — Express app factory; server/index.js listens, tests build their own instance with a temp
* database and a mock Network.
*
* /, /shoes, /shoe/:slug, /drops, /track, /watches, /developers the pages (http/pages.js)
@@@ -112,7 +112,7 @@ async function createApp(opts = {}) {
app.use(cookieParser());
// ── Machine endpoints ───────────────────────────────────
-- app.get('/api/health', (_req, res) => res.json({ status: 'ok', service: 'openvibe-shoes', name: 'OpenVibe.Shoes', version: VERSION }));
++ app.get('/api/health', (_req, res) => res.json({ status: 'ok', service: 'openvibe-shoes', name: 'Open.Shoes', version: VERSION }));
release.mount(app, { registry: metrics.registry });
const readiness = createServiceReadiness({ s, config, release: release.release, valkey });
app.get('/api/ready', readiness.handler);
@@@ -123,7 +123,7 @@ async function createApp(opts = {}) {
// ── Sign-in (OAuth2 + PKCE client of OpenVibe.Network) ──
app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, standardHeaders: true, legacyHeaders: false }));
app.use('/auth', sso.routes());
-- { const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'shoes', service: 'shoes', host: 'open.shoes', name: 'OpenVibe.Shoes', profile: 'ugc' })); }
++ { const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'shoes', service: 'shoes', host: 'open.shoes', name: 'Open.Shoes', profile: 'ugc' })); }
// ── OpenVibe.Events → this service (loopback only) ──────
// Account export/deletion; the consumer reads the raw body itself, so it is mounted before any body parser.
@@@ -159,7 +159,7 @@ async function createApp(opts = {}) {
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, _next) => {
-- log.error('[OpenVibe.Shoes]', err && err.message ? err.message.slice(0, 300) : err);
++ log.error('[Open.Shoes]', err && err.message ? err.message.slice(0, 300) : err);
if (res.headersSent) return;
res.set('Cache-Control', cache.htmlHeaders({ private: true }));
if (err && err.type === 'entity.parse.failed') return contracts.http.sendProblem(res, 400, 'request.malformed_json', { ctx: req.ov });
ddiff --git a/server/auth/keys.js b/server/auth/keys.js
iindex 0293b2c..a6913f4 100644
---- a/server/auth/keys.js
++++ b/server/auth/keys.js
@@@ -34,7 +34,7 @@ function createKeyStore({ config, fetchImpl = globalThis.fetch, log = console })
const ks = await client.keys();
const first = ks && ks[0];
mirror = first ? { pem: first.key.export({ type: 'spki', format: 'pem' }), count: ks.length } : null;
-- } catch (err) { log.warn(`[OpenVibe.Shoes] JWKS mirror refresh failed: ${err && err.message || err}`); mirror = null; }
++ } catch (err) { log.warn(`[Open.Shoes] JWKS mirror refresh failed: ${err && err.message || err}`); mirror = null; }
}
async function ensure() { await refreshMirror(); }
function get() { return mirror ? mirror.pem : null; }
@@@ -50,7 +50,7 @@ function createKeyStore({ config, fetchImpl = globalThis.fetch, log = console })
} catch (err) {
await refreshMirror();
const code = err && err.code;
-- log.warn(`[OpenVibe.Shoes] user token rejected (${code || 'error'}):`, (err && err.message) || err);
++ log.warn(`[Open.Shoes] user token rejected (${code || 'error'}):`, (err && err.message) || err);
return { ok: false, reason: publicReason(err), code, expired: code === 'token.expired' };
}
}
@@@ -63,7 +63,7 @@ function createKeyStore({ config, fetchImpl = globalThis.fetch, log = console })
} catch (err) {
await refreshMirror();
const code = err && err.code;
-- log.warn(`[OpenVibe.Shoes] app token rejected (${code || 'error'}):`, (err && err.message) || err);
++ log.warn(`[Open.Shoes] app token rejected (${code || 'error'}):`, (err && err.message) || err);
return { ok: false, reason: publicReason(err), code };
}
}
ddiff --git a/server/auth/sso.js b/server/auth/sso.js
iindex 9bc9a71..c1cc994 100644
---- a/server/auth/sso.js
++++ b/server/auth/sso.js
@@@ -62,7 +62,7 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
// The one module. The site's own values, its own old cookies, and the audience it always verified with.
const sso = createSsoClient({
site: 'shoes',
-- siteName: 'OpenVibe.Shoes',
++ siteName: 'Open.Shoes',
baseUrl: config.baseUrl,
clientId: config.oauth.clientId,
clientSecret: config.oauth.clientSecret,
@@@ -144,7 +144,7 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
req.viewer = viewerFrom(fresh.claims, fresh.token);
}
} catch (err) {
-- log.warn('[OpenVibe.Shoes] session refresh failed:', (err && err.message) || err);
++ log.warn('[Open.Shoes] session refresh failed:', (err && err.message) || err);
}
return next();
});
@@@ -197,7 +197,7 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
const q = req.query.next ? `?next=${encodeURIComponent(nextOf(req.query.next))}` : '';
res.type('html').send('<!doctype html><meta charset="utf-8"><title>Sign out</title><meta name="viewport" content="width=device-width,initial-scale=1">'
+ `<form method="post" action="/auth/logout${q}" style="font:16px system-ui;margin:3em auto;max-width:24em;text-align:center">`
-- + '<p>Sign out of OpenVibe.Shoes?</p><button type="submit">Sign out</button> <a href="/">Cancel</a></form>');
++ + '<p>Sign out of Open.Shoes?</p><button type="submit">Sign out</button> <a href="/">Cancel</a></form>');
});
r.post('/logout', (req, res, next) => (sameOriginRequest(req) ? next() : res.status(403).type('text/plain').send('Sign-out must come from this site.')));
r.all('/logout', async (req, res) => {
ddiff --git a/server/config.js b/server/config.js
iindex f7c9f2b..3ed4766 100644
---- a/server/config.js
++++ b/server/config.js
@@@ -1,7 +1,7 @@
'use strict';
/**
-- * OpenVibe.Shoes configuration (open.shoes). Every value comes from the environment (production:
++ * Open.Shoes configuration (open.shoes). Every value comes from the environment (production:
* /etc/openvibe/shoes.env, see .env.example). Only environment variable NAMES appear in code and docs; secrets are
* never logged.
*
ddiff --git a/server/db.js b/server/db.js
iindex 5c107dd..71ec3a6 100644
---- a/server/db.js
++++ b/server/db.js
@@@ -1,7 +1,7 @@
'use strict';
/**
-- * OpenVibe.Shoes's own PostgreSQL database (ADR-035): the schema is migrations/NNNN_*.sql, applied at boot.
++ * Open.Shoes's own PostgreSQL database (ADR-035): the schema is migrations/NNNN_*.sql, applied at boot.
* Nothing written here is ever a secret. An offer stores the store's own URL, the price and the time it was
* checked; a watch stores a person's subject id, a threshold and the public URL they pasted.
*/
@@@ -17,7 +17,7 @@ async function openDb(config, { log = console, registry } = {}) {
if (!config.db.url) {
if (config.isProduction) throw new Error('DATABASE_URL is not set: production serves from PostgreSQL (OpenVibe.Host roles/data add-service.sh today)');
const dir = config.db.pgliteDir || DEV_PGLITE;
-- log.warn(`[OpenVibe.Shoes] DATABASE_URL unset: embedded PGlite database in ${dir} (development only, one process)`);
++ log.warn(`[Open.Shoes] DATABASE_URL unset: embedded PGlite database in ${dir} (development only, one process)`);
fs.mkdirSync(dir, { recursive: true });
const db = createDb({ pglite: dir, service: 'shoes', registry, log });
await db.migrate({ dir: MIGRATIONS, log });
ddiff --git a/server/http/api.js b/server/http/api.js
iindex 554e27b..d77703f 100644
---- a/server/http/api.js
++++ b/server/http/api.js
@@@ -1,7 +1,7 @@
'use strict';
/**
-- * /api/v1 — OpenVibe.Shoes' JSON API.
++ * /api/v1 — Open.Shoes' JSON API.
*
* GET /shoes?q&brand&size&condition&store&sort&page&limit public search, with facets
* GET /shoes/:slug offers, own history, size curve, cited facts
ddiff --git a/server/http/discovery.js b/server/http/discovery.js
iindex 1bb9f5c..0f63f3d 100644
---- a/server/http/discovery.js
++++ b/server/http/discovery.js
@@@ -11,9 +11,9 @@ const seo = require('openvibe-shared/seo');
const cache = require('openvibe-shared/cache-policy');
const { asyncRouter } = require('./router');
--const SITE_NAME = 'OpenVibe.Shoes';
++const SITE_NAME = 'Open.Shoes';
const HOST = 'open.shoes';
--const DESCRIPTION = 'OpenVibe.Shoes is every shoe, every market, the right price: one page per model with every store\'s all-in price, a price history from Shoes\' own daily snapshots, the size-price curve and a watch for your size.';
++const DESCRIPTION = 'Open.Shoes is every shoe, every market, the right price: one page per model with every store\'s all-in price, a price history from Shoes\' own daily snapshots, the size-price curve and a watch for your size.';
const DISALLOW = ['/auth/', '/api/', '/internal', '/metrics', '/watches', '/track'];
function siteUpdated() {
@@@ -54,7 +54,7 @@ function productJsonLd(config, model, offers, low) {
function publicPages(models = []) {
const pages = [
-- { path: '/', changefreq: 'hourly', priority: 1.0, title: 'OpenVibe.Shoes home', text: 'Every shoe, every market, the right price: all-in prices, the price history and the size-price curve.' },
++ { path: '/', changefreq: 'hourly', priority: 1.0, title: 'Open.Shoes home', text: 'Every shoe, every market, the right price: all-in prices, the price history and the size-price curve.' },
{ path: '/shoes', changefreq: 'hourly', priority: 0.9, title: 'Every shoe', text: 'Search every model by brand, size and condition; every offer is all-in with its store and the time it was checked.' },
{ path: '/drops', changefreq: 'daily', priority: 0.8, title: 'The drops calendar', text: 'Official shoe release dates, each cited to the brand\'s or store\'s own release page, with a reminder on OpenVibe.Today.' },
{ path: '/developers', changefreq: 'weekly', priority: 0.6, title: 'The Shoes API', text: 'The open JSON API for shoes, offers, history, curves, drops and watches, and the OpenAPI document.' },
ddiff --git a/server/http/openapi.js b/server/http/openapi.js
iindex 213d4b7..111fdbf 100644
---- a/server/http/openapi.js
++++ b/server/http/openapi.js
@@@ -25,7 +25,7 @@ function openapi(config = {}) {
return {
openapi: '3.1.0',
info: {
-- title: 'OpenVibe.Shoes API',
++ title: 'Open.Shoes API',
version: '0.1.0',
summary: 'Every shoe, every market, the right price.',
description: 'Shoes aggregates official store offers into one all-in price per variant, keeps its own daily price snapshots, and exposes the drops calendar and a person\'s watches. Public reads take no key. There are no affiliate links and no Marketplace scraping.',
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 0ce982b..b8345eb 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -59,7 +59,7 @@ function createPageRoutes(ctx) {
const body = views.home({ facets, dropping, dropsToday, popular, curveModel, curve, sceneDrop, jobs, now });
return page(req, res, {
title: null,
-- description: 'OpenVibe.Shoes: every shoe, every market, the right price. A page per model with every store\'s all-in price, Shoes\' own price history, the size-price curve and a watch for your size.',
++ description: 'Open.Shoes: every shoe, every market, the right price. A page per model with every store\'s all-in price, Shoes\' own price history, the size-price curve and a watch for your size.',
body,
active: '/',
index: true,
@@@ -83,7 +83,7 @@ function createPageRoutes(ctx) {
]);
return page(req, res, {
title: q ? `Shoes matching “${q}”` : 'Every shoe',
-- description: 'Search every shoe in OpenVibe.Shoes by brand, model, size and condition; every price is all-in with its store and the time it was checked.',
++ description: 'Search every shoe in Open.Shoes by brand, model, size and condition; every price is all-in with its store and the time it was checked.',
body: views.shoesIndex({ models, facets, query: { ...filters, sort }, page: pageNo, total }),
active: '/shoes',
robots: q || brand || size || condition || store ? 'noindex, follow' : 'index, follow',
ddiff --git a/server/http/principal.js b/server/http/principal.js
iindex 40ad7d2..4428afa 100644
---- a/server/http/principal.js
++++ b/server/http/principal.js
@@@ -84,7 +84,7 @@ function createPrincipal({ config, keys }) {
}
function require_(cap) {
-- if (!CAPABILITIES.includes(cap)) throw new Error(`requireCapability(${cap}): not one of OpenVibe.Shoes's capabilities`);
++ if (!CAPABILITIES.includes(cap)) throw new Error(`requireCapability(${cap}): not one of Open.Shoes's capabilities`);
return function guard(req, res, next) {
if (req.principal.kind === 'anonymous') return http.sendProblem(res, 401, 'token.required', { detail: 'Sign in at open.shoes, or send a person\'s Network token as a Bearer.', ctx: req.ov });
if (req.principal.kind === 'user') {
ddiff --git a/server/index.js b/server/index.js
iindex aa8015e..c390480 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -1,7 +1,7 @@
'use strict';
/**
-- * OpenVibe.Shoes — process entry. `node server/index.js`
++ * Open.Shoes — process entry. `node server/index.js`
* Listens on PORT (5180) behind nginx (deploy/).
*/
const { createApp } = require('./app');
@@@ -15,7 +15,7 @@ const { startSubscriptions } = require('openvibe-sdk/account-data');
*/
function createLifecycle({ server, ctx, exit, signals, timers = [], extra = [] }) {
return gracefulStop({
-- name: 'OpenVibe.Shoes', server, deadlineExitCode: 0, exit, signals, deadlineMs: 10_000,
++ name: 'Open.Shoes', server, deadlineExitCode: 0, exit, signals, deadlineMs: 10_000,
close: [
() => { for (const t of timers) clearInterval(t); },
() => ctx.keys.client.stop(),
@@@ -33,7 +33,7 @@ async function start() {
const { config } = ctx;
const server = app.listen(config.port, config.host, () => {
-- console.log(`[OpenVibe.Shoes] ${config.nodeEnv} on http://${config.host}:${config.port} → ${config.baseUrl} (db ${ctx.s.db.store})`);
++ console.log(`[Open.Shoes] ${config.nodeEnv} on http://${config.host}:${config.port} → ${config.baseUrl} (db ${ctx.s.db.store})`);
// Warm the Network signing key immediately: a service nobody has signed in to yet must not read degraded
// only because the first JWKS fetch had not happened.
ctx.keys.ensure().catch(() => {});
@@@ -66,7 +66,7 @@ async function start() {
}
if (require.main === module) {
-- start().catch((err) => { console.error('[OpenVibe.Shoes] failed to start:', err); process.exit(1); });
++ start().catch((err) => { console.error('[Open.Shoes] failed to start:', err); process.exit(1); });
}
module.exports = { start, createLifecycle };
ddiff --git a/server/jobs/drops.js b/server/jobs/drops.js
iindex e77beea..3d3b66e 100644
---- a/server/jobs/drops.js
++++ b/server/jobs/drops.js
@@@ -26,7 +26,7 @@ function createDropsJob({ config, catalogue, upstreams, log = console }) {
try {
releases = await upstreams.releaseCalendar(source);
} catch (err) {
-- log.warn(`[OpenVibe.Shoes] drops source ${source.url} failed: ${err.message}`);
++ log.warn(`[Open.Shoes] drops source ${source.url} failed: ${err.message}`);
continue;
}
counts.sources++;
@@@ -38,7 +38,7 @@ function createDropsJob({ config, catalogue, upstreams, log = console }) {
}
}
await catalogue.finishJobRun(runId, { ok: true, counts });
-- log.log(`[OpenVibe.Shoes] drops: ${counts.drops} releases from ${counts.sources} source(s)`);
++ log.log(`[Open.Shoes] drops: ${counts.drops} releases from ${counts.sources} source(s)`);
return counts;
} catch (err) {
await catalogue.finishJobRun(runId, { ok: false, counts, error: err.message });
@@@ -51,7 +51,7 @@ function createDropsJob({ config, catalogue, upstreams, log = console }) {
const tick = () => {
if (running) return;
running = true;
-- runOnce().catch((err) => log.warn('[OpenVibe.Shoes] drops job failed:', err.message)).finally(() => { running = false; });
++ runOnce().catch((err) => log.warn('[Open.Shoes] drops job failed:', err.message)).finally(() => { running = false; });
};
timer = setInterval(tick, config.jobs.dropsIntervalMs);
timer.unref();
ddiff --git a/server/jobs/ebay.js b/server/jobs/ebay.js
iindex d6618dc..e5f27ad 100644
---- a/server/jobs/ebay.js
++++ b/server/jobs/ebay.js
@@@ -38,7 +38,7 @@ function createEbayJob({ config, catalogue, upstreams, log = console }) {
else counts.skipped++;
}
await catalogue.finishJobRun(runId, { ok: true, counts });
-- log.log(`[OpenVibe.Shoes] ebay: ${counts.offers} offers from ${counts.items} items`);
++ log.log(`[Open.Shoes] ebay: ${counts.offers} offers from ${counts.items} items`);
return { configured: true, ...counts };
} catch (err) {
await catalogue.finishJobRun(runId, { ok: false, counts, error: err.message });
@@@ -51,7 +51,7 @@ function createEbayJob({ config, catalogue, upstreams, log = console }) {
const tick = () => {
if (running) return;
running = true;
-- runOnce().catch((err) => log.warn('[OpenVibe.Shoes] ebay job failed:', err.message)).finally(() => { running = false; });
++ runOnce().catch((err) => log.warn('[Open.Shoes] ebay job failed:', err.message)).finally(() => { running = false; });
};
timer = setInterval(tick, config.jobs.ebayIntervalMs);
timer.unref();
ddiff --git a/server/jobs/offers.js b/server/jobs/offers.js
iindex f35bdf6..fa852f2 100644
---- a/server/jobs/offers.js
++++ b/server/jobs/offers.js
@@@ -82,7 +82,7 @@ function createOffersJob({ config, catalogue, upstreams, log = console }) {
if (page < total && config.sources.requestGapMs) await upstreams.sleep(config.sources.requestGapMs);
}
await catalogue.finishJobRun(runId, { ok: true, counts });
-- log.log(`[OpenVibe.Shoes] offers: ${counts.offers} offers from ${counts.candidates} shoe candidates over ${counts.pages} pages`);
++ log.log(`[Open.Shoes] offers: ${counts.offers} offers from ${counts.candidates} shoe candidates over ${counts.pages} pages`);
return counts;
} catch (err) {
await catalogue.finishJobRun(runId, { ok: false, counts, error: err.message });
@@@ -95,7 +95,7 @@ function createOffersJob({ config, catalogue, upstreams, log = console }) {
const tick = () => {
if (running) return;
running = true;
-- runOnce().catch((err) => log.warn('[OpenVibe.Shoes] offers job failed:', err.message)).finally(() => { running = false; });
++ runOnce().catch((err) => log.warn('[Open.Shoes] offers job failed:', err.message)).finally(() => { running = false; });
};
timer = setInterval(tick, config.jobs.offersIntervalMs);
timer.unref();
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex bb20ade..aa2ce5e 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -16,14 +16,14 @@ const cache = require('openvibe-shared/cache-policy');
const { esc } = require('./html');
const NETWORK_URL = 'https://openvibe.network';
--const SITE_NAME = 'OpenVibe.Shoes';
++const SITE_NAME = 'Open.Shoes';
const MENU_NAME = 'Shoes';
const TAGLINE = 'Every shoe, every market, the right price.';
const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
const NAV = [
-- { label: 'Home', href: '/', icon: 'fa-shoe-prints' },
++ { label: 'Home', href: '/', icon: 'fa-house' },
{ label: 'Shoes', href: '/shoes', icon: 'fa-magnifying-glass' },
-- { label: 'Drops', href: '/drops', icon: 'fa-calendar-days' },
++ { label: 'Drops', href: '/drops', icon: 'fa-clock' },
{ label: 'Watches', href: '/watches', icon: 'fa-bell' },
{ label: 'Developers', href: '/developers', icon: 'fa-code', more: true },
];
@@@ -59,7 +59,7 @@ function layout(o) {
links: NAV.map((l) => ({ ...l, active: l.href === active || (l.href !== '/' && pathNow.startsWith(`${l.href}/`)) })),
appBar: [
{ label: 'Shoes', href: '/shoes', icon: 'fa-magnifying-glass' },
-- { label: 'Drops', href: '/drops', icon: 'fa-calendar-days' },
++ { label: 'Drops', href: '/drops', icon: 'fa-clock' },
{ label: 'Watches', href: '/watches', icon: 'fa-bell' },
],
silentLogin: `${String(config.baseUrl).replace(/\/+$/, '')}/auth/login?silent=1&next={url}`,
ddiff --git a/server/upstreams.js b/server/upstreams.js
iindex aa3fbf0..6ac03db 100644
---- a/server/upstreams.js
++++ b/server/upstreams.js
@@@ -36,7 +36,7 @@ function createUpstreams({ config, fetchImpl = globalThis.fetch, log = console,
try {
const res = await fetchImpl(url, {
method,
-- headers: { 'user-agent': 'OpenVibe.Shoes/0.1 (+https://open.shoes)', accept: 'application/json, text/html;q=0.9', ...headers },
++ headers: { 'user-agent': 'Open.Shoes/0.1 (+https://open.shoes)', accept: 'application/json, text/html;q=0.9', ...headers },
body,
signal: ctrl.signal,
});
@@@ -79,7 +79,7 @@ function createUpstreams({ config, fetchImpl = globalThis.fetch, log = console,
const body = await request(`${dealsBase()}/api/v1/products/${encodeURIComponent(slug)}`, { json: true });
return body && body.product ? body.product : null;
} catch (err) {
-- log.warn('[OpenVibe.Shoes] deals product failed:', err.message);
++ log.warn('[Open.Shoes] deals product failed:', err.message);
return null;
}
}
ddiff --git a/server/views/index.js b/server/views/index.js
iindex b632742..5892ade 100644
---- a/server/views/index.js
++++ b/server/views/index.js
@@@ -21,7 +21,7 @@ const rooms = require('openvibe-shared/rooms');
const { esc, icon, card, rail } = kit;
const ROOM = rooms.get('shoes');
--const SITE = 'OpenVibe.Shoes';
++const SITE = 'Open.Shoes';
const money = (n, cur = 'USD') => {
if (n == null || n === '') return '—';
const v = Number(n);
ddiff --git a/test/api.test.js b/test/api.test.js
iindex 5bd3230..fb17612 100644
---- a/test/api.test.js
++++ b/test/api.test.js
@@@ -146,7 +146,7 @@ const { assert, equal } = require('./helpers/check');
const res = await t.get('/api/health');
equal(res.status, 200);
equal(res.json().service, 'openvibe-shoes');
-- equal(res.json().name, 'OpenVibe.Shoes');
++ equal(res.json().name, 'Open.Shoes');
});
await check('GET /api/v1/status: the jobs, counts and what is configured', async () => {
ddiff --git a/test/helpers/boot.js b/test/helpers/boot.js
iindex 1850419..0b6195c 100644
---- a/test/helpers/boot.js
++++ b/test/helpers/boot.js
@@@ -5,7 +5,7 @@ process.env.SHOES_API_RATE_LIMIT_PER_MIN = process.env.SHOES_API_RATE_LIMIT_PER_
process.env.SHOES_LIMITS_MINUTE = process.env.SHOES_LIMITS_MINUTE || '100000';
process.env.SHOES_LIMITS_HOUR = process.env.SHOES_LIMITS_HOUR || '100000';
/**
-- * Boots OpenVibe.Shoes on a temp database with a mock Network (test/helpers/mocks.js) and in-process public
++ * Boots Open.Shoes on a temp database with a mock Network (test/helpers/mocks.js) and in-process public
* sources and sibling services (test/helpers/upstreams.js), captures log lines, and returns a small HTTP client.
* Every test file gets its own.
*
ddiff --git a/test/nav-icons.test.js b/test/nav-icons.test.js
nnew file mode 100644
iindex 0000000..4fb7170
---- /dev/null
++++ b/test/nav-icons.test.js
@@@ -0,0 +1,22 @@
++'use strict';
++// Every icon in the bar's links is one the shared navbar draws (11-02: Home asked for fa-shoe-prints, which the
++// OpenVibe glyph set does not have and Font Awesome is not loaded, so Home showed no icon at all), and the site is
++// named for its domain, Open.Shoes.
++// node test/nav-icons.test.js
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const layout = require('../server/render/layout');
++
++const navbar = fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'openvibe-shared', 'navbar.js'), 'utf8');
++// The icons the navbar ships (NAV_ICONS) and the ones it loads beside itself (NAV_ICONS_LAZY).
++const known = new Set([...navbar.matchAll(/'(fa-[a-z0-9-]+)'\s*:\s*\[/g)].map((m) => m[1]));
++for (const m of navbar.matchAll(/NAV_ICONS_LAZY = \{([\s\S]*?)\};/g)) for (const n of m[1].match(/fa-[a-z0-9-]+/g) || []) known.add(n);
++assert.ok(known.size > 20, 'the navbar\'s icon list was read');
++const icons = [].concat(...[layout.NAV].flat(3).map((l) => (l && l.icon ? [l.icon] : [])));
++assert.ok(icons.length >= 3, 'the bar has its links');
++const missing = icons.filter((i) => !known.has(i));
++assert.deepStrictEqual(missing, [], `icons the navbar cannot draw: ${missing.join(', ')}`);
++assert.strictEqual(layout.NAV.find((l) => l.label === 'Home').icon, 'fa-house');
++assert.strictEqual(layout.SITE_NAME, 'Open.Shoes', 'named for its domain');
++console.log(`nav icons: ${icons.length} links, all drawn`);
ddiff --git a/test/pages.test.js b/test/pages.test.js
iindex 6b1854b..59e7a56 100644
---- a/test/pages.test.js
++++ b/test/pages.test.js
@@@ -35,7 +35,7 @@ const { assert, equal } = require('./helpers/check');
assert(h.includes('Questions') || h.includes('sc-faq'), 'the FAQ');
assert(h.includes('Part of OpenVibe'), 'the network section');
assert(!h.includes('undefined'), 'no undefined in the page');
-- assert(h.includes('OpenVibe.Shoes') && h.includes('open.shoes'), 'the name and the domain');
++ assert(h.includes('Open.Shoes') && h.includes('open.shoes'), 'the name and the domain');
});
await check('GET /shoes: search, facets and a real card', async () => {
@@@ -140,7 +140,7 @@ const { assert, equal } = require('./helpers/check');
assert(sitemap.text.includes('/shoe/nike-air-max-90'), 'the model page');
const llms = await t.get('/llms.txt');
equal(llms.status, 200);
-- assert(llms.text.includes('OpenVibe.Shoes'), 'the llms index');
++ assert(llms.text.includes('Open.Shoes'), 'the llms index');
const full = await t.get('/llms-full.txt');
equal(full.status, 200);
assert(full.text.includes('Nike Air Max 90'), 'the full pages');