openvibers/openvibe.deals Commits
Deals: the empty home draws the kit's empty state with one button (#284)
Changes
@@@ -214,7 +214,8 @@ function createPages(ctx) {title: 'Today\'s deals, every price with its time',description: 'Deals from Amazon, Walmart, Best Buy and more, by shop, category and end date, with price watches and crowdfunding. Every price says when it was seen.',// The home page carries the site summary an AI can read (ai-summary meta + WebPage JSON-LD).-- summary: SITE_SUMMARY, styles: [showcase.STYLESHEET],++ // The empty home draws the shared kit's empty state; its look and button are in kit.css, loaded only then.++ summary: SITE_SUMMARY, styles: [showcase.STYLESHEET, ...(total === 0 ? ['kit.css'] : [])],decision: pageDecision('/', { empty: total === 0 }),canonical: seo.canonicalUrl(config.baseUrl, '/'),feeds: FEEDS,@@@ -11,6 +11,7 @@*/const { html, raw } = require('openvibe-publishing/ssr');const showcase = require('openvibe-shared/showcase');++const kit = require('openvibe-shared/kit');const { ago, when, priceText } = require('./views');const { remindLink } = require('./remind');const { campaignCard } = require('../http/crowdfunding');@@@ -150,7 +151,7 @@ function homePage(d, urls) {];return html`${hero}--${d.empty ? html`<p class="empty hm-empty">No deals have been posted yet. <a href="/submit">Submit the first one</a>.</p>` : ''}++${d.empty ? raw(kit.empty({ icon: 'tag', title: 'No deals yet', text: 'No deal has been posted yet.', action: { href: '/submit', label: 'Post the first deal', icon: 'plus' } })) : ''}${raw(pickBlock({ key: 'deals', label: 'The deals', head: 'The deals', panes: dealPanes }))}${raw(pickBlock({ key: 'browse', label: 'Ways to browse', head: 'Ways to browse', panes: browsePanes }))}<details class="hm-fold"><summary>How it works<span>prices, votes, shops, watches</span></summary>${raw(showcase.features({@@@ -26,7 +26,9 @@ const { boot, check, done, robotsOf, jsonLd, HOUR } = require('./helpers/boot');assert.strictEqual((await t.ctx.store.db.prepare('SELECT COUNT(*) AS n FROM deal_offers').get()).n, 0);const r = await t.get('/');assert.strictEqual(r.status, 200);-- assert.match(r.text, /No deals have been posted yet/);++ assert.match(r.text, /class="ovk-empty"/, 'the empty home draws the kit empty state');++ assert.match(r.text, /No deal has been posted yet/);++ assert.match(r.text, /class="ovk-btn[^"]*" href="\/submit"/, 'the empty state offers one button forward');assert.strictEqual(robotsOf(r.text), 'noindex, follow');assert.match(r.headers.get('cache-control'), /public, max-age=60/);});@@@ -15,14 +15,14 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');// 2026-11-08: openvibe-shared v4.150.0; CI measured JS 353.9 KB raw / 84.4 KB brotli.// 2026-11-08: openvibe-shared v4.149.0; CI measured JS 352.9 KB raw / 84.2 KB brotli.const BUDGETS = {-- htmlRawKB: 42, // 41.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 40.1 with the short home's pick blocks and folds (2026-10-30); 33.2 with the Part of OpenVibe section (Shared 4.13, 2026-10-26); 29.1 with the curated front page's search, questions (FAQ + JSON-LD) and closing call (2026-10-22); 23.5 before++ htmlRawKB: 43, // 42.4 measured 2026-11-10: the empty home's kit empty state (the measured database is fresh); 41.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 40.1 with the short home's pick blocks and folds (2026-10-30); 33.2 with the Part of OpenVibe section (Shared 4.13, 2026-10-26); 29.1 with the curated front page's search, questions (FAQ + JSON-LD) and closing call (2026-10-22); 23.5 beforehtmlBrotliKB: 10, // 8.3 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 7.1 with the curated front page (6.0 before; 4.5 before the showcase)jsFiles: 5, // 5 (openvibe-shared 2.10's shell adds web-runtime.js)jsRawKB: 375.1, // 375.1: measured 367.1 with openvibe-shared 4.161.0, plus room for 4.162.0–4.163.0, 2026-11-09jsBrotliKB: 89.5, // 89.5: measured 87.5 with openvibe-shared 4.163.0, plus room for 4.162.0–4.163.0, 2026-11-09-- cssFiles: 2, // 2 (deals.css, openvibe-shared showcase.css)-- cssRawKB: 51, // 49.9 with 2026-11-01: category and store tiles (2026-11-01); 48.2 with the pick chips and fold rules (2026-10-30); 45.1 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 43.3 touch targets on phones (2026-10-29); 38.2 openvibe-shared 4.26.1 (hero visual); 30.4 with Shared 4.13.0 (2026-10-26: account links); 24.2 with the front page's stats, cards, chips and questions (19.0 before)-- cssBrotliKB: 11, // 10.1 with the pick chips and fold rules (2026-10-30); 8.1 openvibe-shared 4.26.1 (hero visual); 6.5 with Shared 4.13.0 (2026-10-26: account links); 5.4 with the front page (2026-10-22); 4.7 with the crowdfunding rules; 1.5 before showcase.css++ cssFiles: 3, // 3 on the empty home (deals.css, shared showcase.css and kit.css for the empty state); 2 on every other home++ cssRawKB: 100, // 98.7 measured 2026-11-10: kit.css on the empty home; 49.9 with 2026-11-01: category and store tiles (2026-11-01); 48.2 with the pick chips and fold rules (2026-10-30); 45.1 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 43.3 touch targets on phones (2026-10-29); 38.2 openvibe-shared 4.26.1 (hero visual); 30.4 with Shared 4.13.0 (2026-10-26: account links); 24.2 with the front page's stats, cards, chips and questions (19.0 before)++ cssBrotliKB: 21, // 20.2 measured 2026-11-10: kit.css on the empty home; 10.1 with the pick chips and fold rules (2026-10-30); 8.1 openvibe-shared 4.26.1 (hero visual); 6.5 with Shared 4.13.0 (2026-10-26: account links); 5.4 with the front page (2026-10-22); 4.7 with the crowdfunding rules; 1.5 before showcase.cssexternalFiles: 1, // 0};
@@ -214,7 +214,8 @@ function createPages(ctx) {
@@ -214,7 +214,8 @@ function createPages(ctx) {
title: 'Today\'s deals, every price with its time',
title: 'Today\'s deals, every price with its time',
description: 'Deals from Amazon, Walmart, Best Buy and more, by shop, category and end date, with price watches and crowdfunding. Every price says when it was seen.',
description: 'Deals from Amazon, Walmart, Best Buy and more, by shop, category and end date, with price watches and crowdfunding. Every price says when it was seen.',
// The home page carries the site summary an AI can read (ai-summary meta + WebPage JSON-LD).
// The home page carries the site summary an AI can read (ai-summary meta + WebPage JSON-LD).
- summary: SITE_SUMMARY, styles: [showcase.STYLESHEET],
+ // The empty home draws the shared kit's empty state; its look and button are in kit.css, loaded only then.
+ summary: SITE_SUMMARY, styles: [showcase.STYLESHEET, ...(total === 0 ? ['kit.css'] : [])],
decision: pageDecision('/', { empty: total === 0 }),
decision: pageDecision('/', { empty: total === 0 }),
canonical: seo.canonicalUrl(config.baseUrl, '/'),
canonical: seo.canonicalUrl(config.baseUrl, '/'),
feeds: FEEDS,
feeds: FEEDS,
@@ -11,6 +11,7 @@
@@ -11,6 +11,7 @@
*/
*/
const { html, raw } = require('openvibe-publishing/ssr');
const { html, raw } = require('openvibe-publishing/ssr');
const showcase = require('openvibe-shared/showcase');
const showcase = require('openvibe-shared/showcase');
+const kit = require('openvibe-shared/kit');
const { ago, when, priceText } = require('./views');
const { ago, when, priceText } = require('./views');
const { remindLink } = require('./remind');
const { remindLink } = require('./remind');
const { campaignCard } = require('../http/crowdfunding');
const { campaignCard } = require('../http/crowdfunding');
@@ -150,7 +151,7 @@ function homePage(d, urls) {
@@ -150,7 +151,7 @@ function homePage(d, urls) {
];
];
return html`${hero}
return html`${hero}
-${d.empty ? html`<p class="empty hm-empty">No deals have been posted yet. <a href="/submit">Submit the first one</a>.</p>` : ''}
+${d.empty ? raw(kit.empty({ icon: 'tag', title: 'No deals yet', text: 'No deal has been posted yet.', action: { href: '/submit', label: 'Post the first deal', icon: 'plus' } })) : ''}
${raw(pickBlock({ key: 'deals', label: 'The deals', head: 'The deals', panes: dealPanes }))}
${raw(pickBlock({ key: 'deals', label: 'The deals', head: 'The deals', panes: dealPanes }))}
${raw(pickBlock({ key: 'browse', label: 'Ways to browse', head: 'Ways to browse', panes: browsePanes }))}
${raw(pickBlock({ key: 'browse', label: 'Ways to browse', head: 'Ways to browse', panes: browsePanes }))}
<details class="hm-fold"><summary>How it works<span>prices, votes, shops, watches</span></summary>${raw(showcase.features({
<details class="hm-fold"><summary>How it works<span>prices, votes, shops, watches</span></summary>${raw(showcase.features({
@@ -26,7 +26,9 @@ const { boot, check, done, robotsOf, jsonLd, HOUR } = require('./helpers/boot');
@@ -26,7 +26,9 @@ const { boot, check, done, robotsOf, jsonLd, HOUR } = require('./helpers/boot');
assert.strictEqual((await t.ctx.store.db.prepare('SELECT COUNT(*) AS n FROM deal_offers').get()).n, 0);
assert.strictEqual((await t.ctx.store.db.prepare('SELECT COUNT(*) AS n FROM deal_offers').get()).n, 0);
const r = await t.get('/');
const r = await t.get('/');
assert.strictEqual(r.status, 200);
assert.strictEqual(r.status, 200);
- assert.match(r.text, /No deals have been posted yet/);
+ assert.match(r.text, /class="ovk-empty"/, 'the empty home draws the kit empty state');
+ assert.match(r.text, /No deal has been posted yet/);
+ assert.match(r.text, /class="ovk-btn[^"]*" href="\/submit"/, 'the empty state offers one button forward');
assert.strictEqual(robotsOf(r.text), 'noindex, follow');
assert.strictEqual(robotsOf(r.text), 'noindex, follow');
assert.match(r.headers.get('cache-control'), /public, max-age=60/);
assert.match(r.headers.get('cache-control'), /public, max-age=60/);
});
});
@@ -15,14 +15,14 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');
@@ -15,14 +15,14 @@ const { measure, check, format } = require('openvibe-shared/perf-budget');
// 2026-11-08: openvibe-shared v4.150.0; CI measured JS 353.9 KB raw / 84.4 KB brotli.
// 2026-11-08: openvibe-shared v4.150.0; CI measured JS 353.9 KB raw / 84.4 KB brotli.
// 2026-11-08: openvibe-shared v4.149.0; CI measured JS 352.9 KB raw / 84.2 KB brotli.
// 2026-11-08: openvibe-shared v4.149.0; CI measured JS 352.9 KB raw / 84.2 KB brotli.
const BUDGETS = {
const BUDGETS = {
- htmlRawKB: 42, // 41.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 40.1 with the short home's pick blocks and folds (2026-10-30); 33.2 with the Part of OpenVibe section (Shared 4.13, 2026-10-26); 29.1 with the curated front page's search, questions (FAQ + JSON-LD) and closing call (2026-10-22); 23.5 before
+ htmlRawKB: 43, // 42.4 measured 2026-11-10: the empty home's kit empty state (the measured database is fresh); 41.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 40.1 with the short home's pick blocks and folds (2026-10-30); 33.2 with the Part of OpenVibe section (Shared 4.13, 2026-10-26); 29.1 with the curated front page's search, questions (FAQ + JSON-LD) and closing call (2026-10-22); 23.5 before
htmlBrotliKB: 10, // 8.3 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 7.1 with the curated front page (6.0 before; 4.5 before the showcase)
htmlBrotliKB: 10, // 8.3 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 7.1 with the curated front page (6.0 before; 4.5 before the showcase)
jsFiles: 5, // 5 (openvibe-shared 2.10's shell adds web-runtime.js)
jsFiles: 5, // 5 (openvibe-shared 2.10's shell adds web-runtime.js)
jsRawKB: 375.1, // 375.1: measured 367.1 with openvibe-shared 4.161.0, plus room for 4.162.0–4.163.0, 2026-11-09
jsRawKB: 375.1, // 375.1: measured 367.1 with openvibe-shared 4.161.0, plus room for 4.162.0–4.163.0, 2026-11-09
jsBrotliKB: 89.5, // 89.5: measured 87.5 with openvibe-shared 4.163.0, plus room for 4.162.0–4.163.0, 2026-11-09
jsBrotliKB: 89.5, // 89.5: measured 87.5 with openvibe-shared 4.163.0, plus room for 4.162.0–4.163.0, 2026-11-09
- cssFiles: 2, // 2 (deals.css, openvibe-shared showcase.css)
- cssRawKB: 51, // 49.9 with 2026-11-01: category and store tiles (2026-11-01); 48.2 with the pick chips and fold rules (2026-10-30); 45.1 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 43.3 touch targets on phones (2026-10-29); 38.2 openvibe-shared 4.26.1 (hero visual); 30.4 with Shared 4.13.0 (2026-10-26: account links); 24.2 with the front page's stats, cards, chips and questions (19.0 before)
- cssBrotliKB: 11, // 10.1 with the pick chips and fold rules (2026-10-30); 8.1 openvibe-shared 4.26.1 (hero visual); 6.5 with Shared 4.13.0 (2026-10-26: account links); 5.4 with the front page (2026-10-22); 4.7 with the crowdfunding rules; 1.5 before showcase.css
+ cssFiles: 3, // 3 on the empty home (deals.css, shared showcase.css and kit.css for the empty state); 2 on every other home
+ cssRawKB: 100, // 98.7 measured 2026-11-10: kit.css on the empty home; 49.9 with 2026-11-01: category and store tiles (2026-11-01); 48.2 with the pick chips and fold rules (2026-10-30); 45.1 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 43.3 touch targets on phones (2026-10-29); 38.2 openvibe-shared 4.26.1 (hero visual); 30.4 with Shared 4.13.0 (2026-10-26: account links); 24.2 with the front page's stats, cards, chips and questions (19.0 before)
+ cssBrotliKB: 21, // 20.2 measured 2026-11-10: kit.css on the empty home; 10.1 with the pick chips and fold rules (2026-10-30); 8.1 openvibe-shared 4.26.1 (hero visual); 6.5 with Shared 4.13.0 (2026-10-26: account links); 5.4 with the front page (2026-10-22); 4.7 with the crowdfunding rules; 1.5 before showcase.css
externalFiles: 1, // 0
externalFiles: 1, // 0
};
};