openvibers/openvibe.trade Commits

Trade: a product home built with the showcase kit above the instrument search. (#13)

OP OpenVibers committed 7 Oct 2026 · 9ae28142cd · 8 files, +38 −16

Page 1 of the front page now opens with what OpenVibe.Trade is for, built with the shared showcase kit (openvibe-shared v2.10.1), above the instrument search, list and recent documents it already had.

- server/render/views.js: tradeShowcase() renders a hero ("Market information that shows its source": instruments, filings and observations from named sources; every value with observed, retrieved and source; stale data marked, never guessed; actions Find an instrument / Source freshness) and a "What is here" grid (deterministic resolution by ticker, CIK or name; values with their times and stale badges; filings with feeds; private watchlists and alert rules, and context that says whether an editor wrote it or AI drafted it for a person to review). The copy restates the README; the "What this site is not" line and the site-wide disclaimer stay. Later pages keep the plain h1. The search form gains id find.
- server/http/pages.js: page 1 asks for showcase.css.
- server/render/layout.js: renderPage forwards styles to openvibe-publishing renderDocument.
- test/perf-budget.test.js: budgets raised for the showcase (html 18.2 -> 24.6 KB raw; css 4.7 -> 16.2 KB raw for showcase.css).
- package.json / lockfile, README, STATUS.json: openvibe-shared v2.6.0 -> v2.10.1.

Tests: ov test test/layout.test.js test/layout-boost.test.js test/pages.test.js test/perf-budget.test.js: 4 passed.

Changes

ddiff --git a/README.md b/README.md
iindex 7f09dc1..bd069a6 100644
---- a/README.md
++++ b/README.md
@@@ -223,7 +223,7 @@ services need to call Trade, is under [Grants the Network must hold](#grants-the
- **PostgreSQL 18 and Valkey 9** (OpenVibe.Host `roles/data/`, ADR-035): every read and write is async through
`openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional: without `VALKEY_URL` they count per process).
- **Packages** (pinned by release tarball): `openvibe-contracts` v0.97.0, `openvibe-publishing`
-- v1.3.0 (revisions, authorship, seo, index-hooks, ssr, ingest, publication), `openvibe-shared` v2.6.0 (Frame, app icon,
++ v1.3.0 (revisions, authorship, seo, index-hooks, ssr, ingest, publication), `openvibe-shared` v2.10.1 (Frame, app icon,
footer, legal, release, metrics, ready), `openvibe-sdk` v0.26.0 (events outbox and inbox, webhook
signatures v2, service tokens, per-actor limits, service kit lifecycle).
- **OpenVibe.Network:** SSO (OAuth client `trade`, redirect `https://openvibe.trade/auth/callback`),
ddiff --git a/STATUS.json b/STATUS.json
iindex 407fd60..bed16d4 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -12,7 +12,7 @@
"packages": [
"openvibe-contracts v0.97.0",
"openvibe-publishing v1.3.0",
-- "openvibe-shared v2.6.0",
++ "openvibe-shared v2.10.1",
"openvibe-sdk v0.26.0"
],
"capabilities": [
@@@ -36,7 +36,7 @@
"deployed": true,
"deployment": "Deployed with `sudo ovhost deploy trade`: unit openvibe-trade, 127.0.0.1:4860 loopback only; on 2026-09-23 /api/ready answered 200 with status degraded [freshness], which is honest: no feed is configured; Network OAuth client trade and its grants exist; no Events subscription; production database held 0 instruments and 0 observations. Deployed internally, not launched: openvibe.trade still serves the OpenVibe.Sites placeholder until a person verifies the SEC XBRL terms and enables sec-xbrl-filings in Sources, a licensed market data feed exists, and editors add instruments",
"placeholderInSites": true,
-- "updated": "2026-10-04",
++ "updated": "2026-10-07",
"features": [
"informational only: a route-inventory test refuses any execution, custody or advice endpoint",
"instruments with exact, deterministic resolution (CIK, ticker, name)",
ddiff --git a/package-lock.json b/package-lock.json
iindex 72acf81..b426fe1 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.97.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"pg": "^8.23.0"
},
"devDependencies": {
@@@ -936,9 +936,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "2.6.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
-- "integrity": "sha512-IKKvWTM/9UonQ4msrzM4yir0QcTUNbhyqeN1r5ywWavU4VTsL1sI5QdHQlxge7BAsMrQ4OvMEPlHjuAfUJM3rg==",
++ "version": "2.10.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
++ "integrity": "sha512-6fUOw22bSvxg0K33BefAyAkNRkETe9euLNT0CHs2mLIvzYR1q38KiHRUQIWj8FESbVANF0pXMojhBz5TeqpMtg==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex 7cc5cac..3909051 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.97.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.3.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.6.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"pg": "^8.23.0"
},
"devDependencies": {
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 8fb6762..7138704 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -14,6 +14,7 @@
*/
const express = require('express');
const ovServe = require('openvibe-shared/serve');
++const showcase = require('openvibe-shared/showcase');
const frame = require('openvibe-shared/frame');
const seo = require('openvibe-publishing/seo');
const cache = require('openvibe-shared/cache-policy');
@@@ -45,6 +46,7 @@ function createPages(ctx) {
title: null, canonical, decision: listing(pageNo === 1 ? '/' : `/?page=${pageNo}`),
// The home page carries the site's ai-summary meta and WebPage JSON-LD (v1.3.0 forwards it).
summary: pageNo === 1 ? SITE_SUMMARY : null,
++ styles: pageNo === 1 ? [showcase.STYLESHEET] : [],
feeds: [{ type: 'rss', href: '/feed.xml', title: 'Recent documents (RSS)' }, { type: 'atom', href: '/atom.xml', title: 'Recent documents (Atom)' }, { type: 'json', href: '/feed.json', title: 'Recent documents (JSON Feed)' }],
jsonLd: [seo.compact({ '@context': 'https://schema.org', '@type': 'WebSite', url: urls.abs('/'), name: 'OpenVibe.Trade', inLanguage: 'en' })],
body: views.home({ page, recent, urls, sync: await sync.state(), pager }),
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 0d8c79c..3fcaf7e 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -97,6 +97,7 @@ function renderPage(o) {
navLinks: NAV_LINKS,
home: '/',
css: asset('css/trade.css'),
++ styles: o.styles, // openvibe-shared stylesheet names (the home page's showcase.css)
release: RELEASE,
account,
header: `<p class="disclaimer" role="note"><strong>${esc(DISCLAIMER)}</strong> Every number shows when it was observed and where it came from; stale sources are labelled as stale.</p>`,
ddiff --git a/server/render/views.js b/server/render/views.js
iindex 53a0c15..645a343 100644
---- a/server/render/views.js
++++ b/server/render/views.js
@@@ -12,6 +12,7 @@
* - AI context carries its disclosure; the disclaimer is in the layout on every page
*/
const { html, raw, breadcrumbsHtml, paginationHtml } = require('openvibe-publishing/ssr');
++const showcase = require('openvibe-shared/showcase');
const ISO = (v) => (v == null ? null : (typeof v === 'number' ? new Date(v).toISOString() : String(v)));
@@@ -41,6 +42,24 @@ function notice(n) {
// ── Home ─────────────────────────────────────────────────────────────────────
++/** Page 1 of the front page opens with what OpenVibe.Trade is for (openvibe-shared/showcase). */
++function tradeShowcase() {
++ return showcase.hero({
++ eyebrow: 'OpenVibe.Trade',
++ title: 'Market information that shows', accent: 'its source',
++ lede: 'Instruments, filings and market observations from named sources. Every value shows when it was observed, when it was retrieved and which source stated it; when a source falls behind, its data is marked stale and never replaced with a guess.',
++ actions: [{ label: 'Find an instrument', href: '#find', primary: true }, { label: 'Source freshness', href: '/sources' }],
++ }) + showcase.features({
++ title: 'What is here',
++ items: [
++ { icon: 'ov:search', title: 'Find an instrument', text: 'By ticker, SEC CIK or company name. One match takes you straight there; several are listed, never guessed between.' },
++ { icon: 'ov:history', title: 'Values with their times', text: 'Each observation keeps the exact value as stated, its source, and when it was observed and retrieved. Stale data carries a badge.' },
++ { icon: 'ov:docs', title: 'Filings and documents', text: 'Documents gathered by OpenVibe.Sources with their publication and retrieval dates, and feeds in RSS, Atom and JSON.' },
++ { icon: 'ov:bell', title: 'Watchlists and alert rules', text: 'Private to you when signed in. Context on an instrument is written by an editor, or drafted by AI and reviewed by a person, and says which.' },
++ ],
++ });
++}
++
function home({ page, recent, urls, sync, pager }) {
const rows = page.instruments.map((i) => html`<tr>
<td><a href="${urls.path.instrument(i)}"><strong>${i.symbol}</strong></a></td>
@@@ -49,11 +68,11 @@ function home({ page, recent, urls, sync, pager }) {
const docs = recent.map((r) => html`<li><a href="${urls.path.instrument(r.instrument)}#${r.document.id}">${r.instrument.symbol}</a>
${r.document.form_type ? html`<span class="badge">${r.document.form_type}</span>` : ''} ${r.document.title || r.document.filer_name || 'Untitled document'}
— ${r.document.published_at ? html`published ${when(r.document.published_at)}` : html`<span class="muted">no publication date stated</span>`}, retrieved ${when(r.document.retrieved_at)}</li>`);
++ const first = !pager || pager.page === 1;
return html`
--<h1>Sourced market information</h1>
--<p class="lede">Instruments with the observations and filings that sources published about them. Every value shows when it was observed, when it was retrieved and which source stated it. When a source falls behind, its data is marked stale — it is never replaced with a guess.</p>
++${first ? raw(tradeShowcase()) : html`<h1>Sourced market information</h1>`}
<p class="muted">What this site is not: there is no order entry, no brokerage, no custody of money or assets, no marketplace and no personal recommendation here.</p>
--<form class="search" method="get" action="/resolve" role="search">
++<form class="search" id="find" method="get" action="/resolve" role="search">
<label for="q">Find an instrument by ticker, CIK or company name</label>
<input id="q" name="q" type="search" maxlength="200" required>
<button type="submit">Find</button>
ddiff --git a/test/perf-budget.test.js b/test/perf-budget.test.js
iindex d253c69..3d71cbc 100644
---- a/test/perf-budget.test.js
++++ b/test/perf-budget.test.js
@@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
-- htmlRawKB: 22, // measured 18.2 (fresh database)
-- htmlBrotliKB: 5.5, // 4.7
++ htmlRawKB: 26, // measured 24.6 (fresh database; 18.2 before the showcase hero and features, 2026-10-07)
++ htmlBrotliKB: 7, // 6.2 (4.7 before the showcase)
jsFiles: 5, // 5 (theme-loader, web-runtime, navbar, footer, boost: openvibe-shared/shell adds web-runtime, 2026-10-04)
jsRawKB: 245, // 239.1 (212.2 before openvibe-shared/shell's web-runtime.js)
jsBrotliKB: 59, // 56.3 (49.9 before web-runtime.js)
-- cssFiles: 2, // 1
-- cssRawKB: 6.5, // 4.7
-- cssBrotliKB: 2, // 1.3
++ cssFiles: 2, // 2 (trade.css, openvibe-shared showcase.css)
++ cssRawKB: 17.5, // 16.2 (4.7 before showcase.css)
++ cssBrotliKB: 4.4, // 3.9 (1.3 before showcase.css)
externalFiles: 1, // 0
};