openvibers/openvibe.blog Commits

The Blog home opens with what OpenVibe.Blog is for (plan T11, D92; openvibe-shared v2.3.1 showcase): 'Write on OpenVibe', where to start (Start writing, or read the OpenVibe blog below), and the six things a blog really gets today (drafts and revisions, scheduling, feeds, Community comments, themes, VIP members-only posts); the official blog's posts follow unchanged. The kit's stylesheet is linked only on the home (cached; loaded styles-first on a boost swap). Home budgets raised as a decision (html 29 KB, css 17.5 KB). Suites green.

OP OpenVibers committed 29 Sept 2026 · 05b8bbbe00 · 8 files, +47 −13


Changes

ddiff --git a/README.md b/README.md
iindex 7271d8a..8d2fc5a 100644
---- a/README.md
++++ b/README.md
@@@ -253,7 +253,7 @@ Called elsewhere, as the service principal `blog`: `identity.subject.resolve` an
`openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional: without `VALKEY_URL` they count per process).
- **Packages** (all pinned by release tarball): `openvibe-publishing` v1.0.0 (async PostgreSQL stores: revisions,
schedule, taxonomy, citations, media, discussion, seo, authorship, index-hooks, ssr), `openvibe-contracts`
-- v0.79.0, `openvibe-shared` v2.2.0 (Frame, app icon, footer, legal, release, metrics, ready, theme presets),
++ v0.79.0, `openvibe-shared` v2.3.1 (Frame, app icon, footer, legal, release, metrics, ready, theme presets),
`openvibe-sdk` v0.25.0 (sso, db, PostgreSQL events outbox, service tokens, per-actor limits, testing).
- **OpenVibe.Network:**
- SSO: the OAuth client `blog` is already seeded with redirect
ddiff --git a/STATUS.json b/STATUS.json
iindex fdaf61d..888e710 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -11,7 +11,7 @@
"packages": [
"openvibe-publishing v1.0.0",
"openvibe-contracts v0.79.0",
-- "openvibe-shared v2.2.0",
++ "openvibe-shared v2.3.1",
"openvibe-sdk v0.25.0"
],
"capabilities": [
ddiff --git a/package-lock.json b/package-lock.json
iindex 9c1812c..9d1bed0 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -18,7 +18,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",
"pg": "^8.23.0"
},
"devDependencies": {
@@@ -1210,9 +1210,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "2.2.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
-- "integrity": "sha512-1q+iUuUxzPeylTYmithW4jR++Jccvn5GEOrB1B3D18Dgn6OXQhPpvhPQAa1D1k1p+dWIa1/SmggyJwSGFnTNtw==",
++ "version": "2.3.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",
++ "integrity": "sha512-K+MXnB4O+8xIFi+NQGC2SDTvDq3RkzfxQ3bTVx76Y/Rbuq7kC/L5kL3c/06xr9sPYcjyzF3Bhpv5yH2YNfR4Zw==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex 99dc126..3c6c5aa 100644
---- a/package.json
++++ b/package.json
@@@ -30,7 +30,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.0.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.2.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.3.1",
"pg": "^8.23.0"
},
"devDependencies": {
ddiff --git a/public/css/blog.css b/public/css/blog.css
iindex 635589c..c269ff7 100644
---- a/public/css/blog.css
++++ b/public/css/blog.css
@@@ -58,3 +58,11 @@ table.posts th, table.posts td { text-align: left; padding: 6px 8px; border-bott
.ov-diff { white-space: pre-wrap; }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
@media (max-width: 600px) { h1 { font-size: 1.7rem; } body { font-size: 16px; } }
++
++/* The home's showcase sections (openvibe-shared/showcase.css) sit inside the reading column, which has its own gutter. */
++.page .sc-hero, .page .sc-sec { padding-left: 0; padding-right: 0; }
++.page .sc-hero { padding-top: 24px; }
++.page .sc-hero h1 { font-size: clamp(30px, 5vw, 42px); }
++.page .sc-sec { margin-top: 36px; }
++.page .sc-sec + #posts { display: block; margin-top: 40px; }
++.page .sc-ic .ov-icon { --ovi-size: 28px !important; }
ddiff --git a/server/http/public.js b/server/http/public.js
iindex 53f4716..a5cbfed 100644
---- a/server/http/public.js
++++ b/server/http/public.js
@@@ -16,6 +16,7 @@
* `public, max-age=60`; everything else — signed-in views, drafts, previews, unlisted,
* members/VIP and private posts, refusals, 404/410 — is `private, no-store` + X-Robots-Tag.
*/
++const showcase = require('openvibe-shared/showcase');
const express = require('express');
const ovServe = require('openvibe-shared/serve');
const frame = require('openvibe-shared/frame');
@@@ -79,6 +80,26 @@ function createPublicRoutes(ctx) {
// ── Blog front pages ────────────────────────────────────
++ /** The product's own words above the official blog's posts: only features Blog has today. */
++ function blogShowcase() {
++ return showcase.hero({
++ eyebrow: 'OpenVibe.Blog',
++ title: 'Write on', accent: 'OpenVibe',
++ lede: 'A blog of your own with drafts, scheduling, feeds and comments. Sign in once with your OpenVibe account; anyone can read without one.',
++ actions: [{ label: 'Start writing', href: '/write', primary: true }, { label: 'Read the OpenVibe blog', href: '#posts' }],
++ }) + showcase.features({
++ title: 'What your blog gets',
++ items: [
++ { icon: 'ov:blog', title: 'Drafts and revisions', text: 'Every save is a revision you can go back to; publish when it is ready.' },
++ { icon: 'ov:history', title: 'Scheduling', text: 'Pick the minute a post goes live.' },
++ { icon: 'ov:news', title: 'Feeds', text: 'RSS, Atom and JSON Feed for every blog, series and category.' },
++ { icon: 'ov:community', title: 'Comments', text: 'Threads on OpenVibe.Community, moderated by you.' },
++ { icon: 'ov:theme', title: 'Your theme', text: 'Pick a theme preset and your blog wears it.' },
++ { icon: 'ov:vip', title: 'Members-only posts', text: 'Your OpenVibe.VIP members read the whole post; everyone else sees a teaser.' },
++ ],
++ }) + '<div id="posts"></div>';
++ }
++
async function blogFront(req, res, blog, { home = false } = {}) {
const page = pageNumber(req);
const restricted = await isMember(blog, req.viewer);
@@@ -115,7 +136,9 @@ function createPublicRoutes(ctx) {
jsonLd: [blog.kind === 'official'
? { '@context': 'https://schema.org', '@type': 'Blog', '@id': `${publication.abs(path)}#blog`, name: blog.title, url: publication.abs(path), description: blog.description || undefined, publisher: { '@type': 'Organization', name: 'OpenVibe', url: 'https://openvibe.network' } }
: { '@context': 'https://schema.org', '@type': 'Blog', '@id': `${publication.abs(path)}#blog`, name: blog.title, url: publication.abs(path), description: blog.description || undefined }],
-- body: pages.blogIndex({
++ // The network's blog home opens with what OpenVibe.Blog is for (openvibe-shared/showcase), then the posts.
++ ...(home && pager.page === 1 ? { styles: [showcase.STYLESHEET] } : {}),
++ body: (home && pager.page === 1 ? blogShowcase() : '') + pages.blogIndex({
blog, blogUrl: `/@${blog.handle}`, items, pager, feeds,
series: seriesShown, categories: await prune(await reading.categoriesTree(blog)),
canWrite: await access.canWrite(store, req.viewer, blog, 'create'),
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 0fa7d0c..5967bd1 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -47,7 +47,7 @@ function themeStyle(vars) {
/**
* o: title, description, decision (required), canonical, type ('website'|'article'), image,
* jsonLd [], feeds [{ type, href, title }], body (HTML), viewer, config, csrf,
-- * theme { slug, vars }, published, modified, author, prev, next, bodyClass
++ * theme { slug, vars }, published, modified, author, prev, next, bodyClass, styles [openvibe-shared stylesheet names]
*/
function renderPage(o) {
if (!o.decision) throw new TypeError('renderPage needs the gate decision');
@@@ -97,6 +97,7 @@ ${o.modified ? `<meta property="article:modified_time" content="${esc(o.modified
${seo.feedLinks(o.feeds || [])}
${appIcon.headTags({ site: 'blog' })}
<link rel="stylesheet" href="${asset('css/blog.css')}">
++${(o.styles || []).map((name) => `<link rel="stylesheet" href="${esc(ovServe.url(name))}">`).join('\n')}
<script src="${ovServe.url('theme-loader.js')}" defer></script>
<script src="${ovServe.url('navbar.js')}" defer></script>
<script src="${ovServe.url('footer.js')}" defer></script>
ddiff --git a/test/perf-budget.test.js b/test/perf-budget.test.js
iindex f7e896d..5ad9c98 100644
---- a/test/perf-budget.test.js
++++ b/test/perf-budget.test.js
@@@ -11,15 +11,17 @@ const path = require('path');
const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
++// 2026-09-29: the home opens with the showcase hero and features (six inline ring icons) and links the cached
++// /shared/showcase.css: measured html 26.2 KB (6.8 br), css 15.9 KB (4.0 br). Raised as a decision.
const BUDGETS = {
-- htmlRawKB: 22, // measured 17.4 (fresh database)
-- htmlBrotliKB: 5.5, // 4.5
++ htmlRawKB: 29, // measured 17.4 (fresh database)
++ htmlBrotliKB: 7.5, // 4.5
jsFiles: 4, // 3
jsRawKB: 230, // 197.7
jsBrotliKB: 53, // 45.6
cssFiles: 2, // 1
-- cssRawKB: 6.5, // 5.0
-- cssBrotliKB: 2, // 1.4
++ cssRawKB: 17.5, // 5.0
++ cssBrotliKB: 4.5, // 1.4
externalFiles: 1, // 0
};