openvibers/openvibe.trade Commits
Trade: a product home built with the showcase kit above the instrument search. (#13)
Changes
@@@ -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, webhooksignatures v2, service tokens, per-actor limits, service kit lifecycle).- **OpenVibe.Network:** SSO (OAuth client `trade`, redirect `https://openvibe.trade/auth/callback`),@@@ -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)",@@@ -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"@@@ -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": {@@@ -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 }),@@@ -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>`,@@@ -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>@@@ -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};
@@ -223,7 +223,7 @@ services need to call Trade, is under [Grants the Network must hold](#grants-the
@@ -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
- **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).
`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`
- **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
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).
signatures v2, service tokens, per-actor limits, service kit lifecycle).
- **OpenVibe.Network:** SSO (OAuth client `trade`, redirect `https://openvibe.trade/auth/callback`),
- **OpenVibe.Network:** SSO (OAuth client `trade`, redirect `https://openvibe.trade/auth/callback`),
@@ -12,7 +12,7 @@
@@ -12,7 +12,7 @@
"packages": [
"packages": [
"openvibe-contracts v0.97.0",
"openvibe-contracts v0.97.0",
"openvibe-publishing v1.3.0",
"openvibe-publishing v1.3.0",
- "openvibe-shared v2.6.0",
+ "openvibe-shared v2.10.1",
"openvibe-sdk v0.26.0"
"openvibe-sdk v0.26.0"
],
],
"capabilities": [
"capabilities": [
@@ -36,7 +36,7 @@
@@ -36,7 +36,7 @@
"deployed": true,
"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",
"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,
"placeholderInSites": true,
- "updated": "2026-10-04",
+ "updated": "2026-10-07",
"features": [
"features": [
"informational only: a route-inventory test refuses any execution, custody or advice endpoint",
"informational only: a route-inventory test refuses any execution, custody or advice endpoint",
"instruments with exact, deterministic resolution (CIK, ticker, name)",
"instruments with exact, deterministic resolution (CIK, ticker, name)",
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.97.0",
"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-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-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"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -936,9 +936,9 @@
@@ -936,9 +936,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"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",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -30,7 +30,7 @@
@@ -30,7 +30,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.97.0",
"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-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-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"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -14,6 +14,7 @@
@@ -14,6 +14,7 @@
*/
*/
const express = require('express');
const express = require('express');
const ovServe = require('openvibe-shared/serve');
const ovServe = require('openvibe-shared/serve');
+const showcase = require('openvibe-shared/showcase');
const frame = require('openvibe-shared/frame');
const frame = require('openvibe-shared/frame');
const seo = require('openvibe-publishing/seo');
const seo = require('openvibe-publishing/seo');
const cache = require('openvibe-shared/cache-policy');
const cache = require('openvibe-shared/cache-policy');
@@ -45,6 +46,7 @@ function createPages(ctx) {
@@ -45,6 +46,7 @@ function createPages(ctx) {
title: null, canonical, decision: listing(pageNo === 1 ? '/' : `/?page=${pageNo}`),
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).
// 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,
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)' }],
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' })],
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 }),
body: views.home({ page, recent, urls, sync: await sync.state(), pager }),
@@ -97,6 +97,7 @@ function renderPage(o) {
@@ -97,6 +97,7 @@ function renderPage(o) {
navLinks: NAV_LINKS,
navLinks: NAV_LINKS,
home: '/',
home: '/',
css: asset('css/trade.css'),
css: asset('css/trade.css'),
+ styles: o.styles, // openvibe-shared stylesheet names (the home page's showcase.css)
release: RELEASE,
release: RELEASE,
account,
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>`,
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>`,
@@ -12,6 +12,7 @@
@@ -12,6 +12,7 @@
* - AI context carries its disclosure; the disclaimer is in the layout on every page
* - AI context carries its disclosure; the disclaimer is in the layout on every page
*/
*/
const { html, raw, breadcrumbsHtml, paginationHtml } = require('openvibe-publishing/ssr');
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)));
const ISO = (v) => (v == null ? null : (typeof v === 'number' ? new Date(v).toISOString() : String(v)));
@@ -41,6 +42,24 @@ function notice(n) {
@@ -41,6 +42,24 @@ function notice(n) {
// ── Home ─────────────────────────────────────────────────────────────────────
// ── 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 }) {
function home({ page, recent, urls, sync, pager }) {
const rows = page.instruments.map((i) => html`<tr>
const rows = page.instruments.map((i) => html`<tr>
<td><a href="${urls.path.instrument(i)}"><strong>${i.symbol}</strong></a></td>
<td><a href="${urls.path.instrument(i)}"><strong>${i.symbol}</strong></a></td>
@@ -49,11 +68,11 @@ function home({ page, recent, urls, sync, pager }) {
@@ -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>
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.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>`);
— ${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`
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>
<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>
<label for="q">Find an instrument by ticker, CIK or company name</label>
<input id="q" name="q" type="search" maxlength="200" required>
<input id="q" name="q" type="search" maxlength="200" required>
<button type="submit">Find</button>
<button type="submit">Find</button>
@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
@@ -12,14 +12,14 @@ const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
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)
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)
jsRawKB: 245, // 239.1 (212.2 before openvibe-shared/shell's web-runtime.js)
jsBrotliKB: 59, // 56.3 (49.9 before 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
externalFiles: 1, // 0
};
};