From dad4750a861aeb23753946ddd456c69baaff5f9e Mon Sep 17 00:00:00 2001 From: Rene Fichtmueller Date: Wed, 1 Apr 2026 22:14:14 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Changelog=20=E2=80=94=20CHANGELOG=5FPEN?= =?UTF-8?q?DING.md,=20/api/changelog=20route,=20Overview=20tab=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CHANGELOG_PENDING.md: 26 entries from v0.1.0 to today in JSON-line format - GET /api/changelog: parses and serves entries as JSON array - Overview tab: changelog card with type badges (FEAT/FIX/UI/DATA/AI/INFRA), dates, show recent/all toggle --- CHANGELOG_PENDING.md | 36 +++++++++++++++ packages/api/src/index.ts | 2 + packages/api/src/routes/changelog.ts | 30 +++++++++++++ packages/dashboard/index.html | 66 ++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 CHANGELOG_PENDING.md create mode 100644 packages/api/src/routes/changelog.ts diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md new file mode 100644 index 0000000..14c11f9 --- /dev/null +++ b/CHANGELOG_PENDING.md @@ -0,0 +1,36 @@ +# TIP Changelog + +Format: `{"d":"YYYY-MM-DD","t":"TYPE","m":"Description"}` +Types: FEAT · FIX · UI · DATA · AI · INFRA + +--- + +{"d":"2026-04-01","t":"FEAT","m":"Procurement Intelligence Engine (WS0c): stock_snapshots, abc_classification, reorder_signals, product_lifecycle_events, market_intelligence tables"} +{"d":"2026-04-01","t":"FEAT","m":"Crawler LLM: Ollama-based two-stage extractor (page type detection + structured product extraction) with vendor profiles for 7 vendors"} +{"d":"2026-04-01","t":"FEAT","m":"ABC classification: dynamic A/B/C turnover scoring from price observations, compatibility breadth, vendor count — computed daily"} +{"d":"2026-04-01","t":"FEAT","m":"Reorder signals: buy_now/wait/hold/monitor with signal strength and reasons — computed daily from stock trends, price trends, lead times"} +{"d":"2026-04-01","t":"DATA","m":"Market intelligence seeded: OFC 2026, AWS CapEx $105B, Azure CapEx $80B+, Coherent 400G ZR+ lead times 16-20w, EU TED €2.1B tenders, ECOC 2026, IEEE 802.3df"} +{"d":"2026-04-01","t":"DATA","m":"Lifecycle events seeded: Cisco SFP-10G-LR EOL 2026-06-30, Juniper SFPP-10GE-ER EOL 2026-09-01, 400ZR ratified, 800G MSA draft"} +{"d":"2026-04-01","t":"UI","m":"Procurement Intel tab: Reorder Signals, ABC Classes table, Market Intel cards, Lifecycle Events — live on dashboard"} +{"d":"2026-04-01","t":"FEAT","m":"Market intelligence scraper: OFC/ECOC, IEEE 802.3, EU TED, Farnell/Mouser lead times, LightReading, FierceTelecom — weekly via pg-boss"} +{"d":"2026-04-01","t":"FIX","m":"Dashboard: garbage product names (scraped-*, All Optical Transceivers) no longer shown as product titles — isGarbageName() filter"} +{"d":"2026-04-01","t":"FIX","m":"Dashboard: competitor comparable prices shown as inline tooltip (ⓘ) instead of block element breaking price row layout"} +{"d":"2026-04-01","t":"UI","m":"Dashboard: 100% VERIFIED badge with white-on-green sub-items (Price ✓, Image ✓, Details ✓) — explicit === true checks, no false positives"} +{"d":"2026-04-01","t":"UI","m":"Dashboard list view: SKU + descriptive name on two lines, Verified column with ★ 100% badge"} +{"d":"2026-04-01","t":"UI","m":"Dashboard detail view: manufacturer product name above image, temperature range decoded (COM → 0–70°C), close button visible on light background"} +{"d":"2026-04-01","t":"DATA","m":"Migration 018: garbage data cleanup — marks scraped-* and category-page scrapes as data_confidence=garbage"} +{"d":"2026-04-01","t":"FEAT","m":"Migration 017: verification tags — price_verified, image_verified, details_verified, fully_verified columns + compute_transceiver_verification() function"} +{"d":"2026-03-31","t":"DATA","m":"Migration 016: data_confidence scoring (garbage/low/medium/high)"} +{"d":"2026-03-31","t":"FEAT","m":"Migration 013: v0.2.0 Sales Intelligence tables — competitor_alerts, price_changes, generated_datasheets, sales_forecasts, blog_posts_v2"} +{"d":"2026-03-31","t":"FEAT","m":"Transport planner route: GET /api/transport — city-pair fiber route recommendations with switch and transceiver BOM"} +{"d":"2026-03-31","t":"FEAT","m":"Blog Engine v2: market_alert, migration_guide, competitor_analysis, buying_guide types with data enrichment pipeline"} +{"d":"2026-03-31","t":"FEAT","m":"Competitor alerts route: GET /api/competitor-alerts — price changes, new products, stock events with acknowledge workflow"} +{"d":"2026-03-30","t":"FEAT","m":"Switch→Flexoptix Finder: GET /api/finder — enter switch model, get matching Flexoptix transceivers with prices and shop links"} +{"d":"2026-03-30","t":"FEAT","m":"MCP Server: 12 tools including find_transceiver, get_compatibility, get_hype_cycle, generate_blog, plan_transport"} +{"d":"2026-03-30","t":"FEAT","m":"Norton-Bass Hype Cycle engine: multigenerational diffusion model for 15 transceiver technologies with adoption curves"} +{"d":"2026-03-30","t":"DATA","m":"440 switches seeded including Cisco, Arista, Juniper, Edgecore, Mellanox, whitebox OCP switches"} +{"d":"2026-03-30","t":"DATA","m":"33,993 compatibility entries — transceiver↔switch compatibility matrix"} +{"d":"2026-03-30","t":"FEAT","m":"Price monitoring: 23 scrapers, 60+ data sources, pg-boss scheduler with 2h/4h/6h/8h cycles — R-SCAN permanent monitoring"} +{"d":"2026-03-30","t":"FEAT","m":"Qdrant vector DB integration: hybrid full-text + semantic search across products, FAQ, datasheets, news"} +{"d":"2026-03-30","t":"INFRA","m":"Stack deployed: PostgreSQL 17 + TimescaleDB port 5433, Qdrant, Cloudflare R2 for images, PM2 on Erik (IONOS VPS)"} +{"d":"2026-03-30","t":"DATA","m":"v0.1.0: 5,018 transceivers, 351 vendors seeded from 23 initial scrapers"} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index fbb6d96..c7db5c8 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -21,6 +21,7 @@ import { datasheetRouter } from "./routes/datasheets"; import { hotTopicsRouter } from "./routes/hot-topics"; import { adoptionRouter } from "./routes/adoption"; import { procurementRouter } from "./routes/procurement"; +import { changelogRouter } from "./routes/changelog"; const app = express(); @@ -58,6 +59,7 @@ app.use("/api/datasheets", datasheetRouter); app.use("/api/adoption", adoptionRouter); app.use("/api/hot-topics", hotTopicsRouter); app.use("/api/procurement", procurementRouter); +app.use("/api/changelog", changelogRouter); // Dashboard (static HTML) app.use("/dashboard", express.static(join(__dirname, "..", "..", "dashboard"))); diff --git a/packages/api/src/routes/changelog.ts b/packages/api/src/routes/changelog.ts new file mode 100644 index 0000000..da71b64 --- /dev/null +++ b/packages/api/src/routes/changelog.ts @@ -0,0 +1,30 @@ +/** + * GET /api/changelog — Serves CHANGELOG_PENDING.md as parsed JSON. + * Each line of the form {"d":"...","t":"...","m":"..."} is returned as an array. + */ +import { Router, Request, Response } from "express"; +import { readFileSync } from "fs"; +import { join } from "path"; + +export const changelogRouter = Router(); + +const CHANGELOG_PATH = join(__dirname, "..", "..", "..", "..", "CHANGELOG_PENDING.md"); + +changelogRouter.get("/", (_req: Request, res: Response) => { + try { + const raw = readFileSync(CHANGELOG_PATH, "utf-8"); + const entries = raw + .split("\n") + .filter((line) => line.trim().startsWith("{")) + .map((line) => { + try { return JSON.parse(line.trim()); } + catch { return null; } + }) + .filter(Boolean); + + res.json({ entries, total: entries.length }); + } catch (err) { + console.error("Changelog read error:", err); + res.status(500).json({ error: "Could not read changelog" }); + } +}); diff --git a/packages/dashboard/index.html b/packages/dashboard/index.html index 5cd7449..5dd8235 100644 --- a/packages/dashboard/index.html +++ b/packages/dashboard/index.html @@ -640,6 +640,27 @@ .compare-best { background: var(--green-light); font-weight: 600; } .compare-cb { width: 16px; height: 16px; cursor: pointer; accent-color: var(--purple); } + /* === CHANGELOG === */ + .cl-entry { + display: flex; gap: 0.6rem; align-items: baseline; + padding: 0.4rem 0; border-bottom: 1px solid var(--border); + font-size: 0.78rem; + } + .cl-entry:last-child { border-bottom: none; } + .cl-date { font-family: var(--mono); font-size: 0.68rem; color: var(--text-dim); white-space: nowrap; flex-shrink: 0; } + .cl-type { + font-size: 0.62rem; font-weight: 800; text-transform: uppercase; + letter-spacing: 0.07em; padding: 1px 6px; border-radius: 3px; + white-space: nowrap; flex-shrink: 0; + } + .cl-FEAT { background: rgba(255,129,0,0.12); color: var(--accent); } + .cl-FIX { background: rgba(193,18,31,0.1); color: #c1121f; } + .cl-UI { background: rgba(124,92,252,0.1); color: #7c5cfc; } + .cl-DATA { background: rgba(45,106,79,0.1); color: #2d6a4f; } + .cl-AI { background: rgba(26,26,46,0.1); color: #1a1a2e; } + .cl-INFRA { background: rgba(136,136,136,0.1);color: #666; } + .cl-msg { color: var(--text); line-height: 1.4; } + /* === PROCUREMENT TAB === */ .proc-btn { background: var(--surface2); border: 1px solid var(--border); @@ -773,6 +794,17 @@
API Endpoints
+ +
+
+
Changelog
+
+ + +
+
+
+
@@ -3014,6 +3046,39 @@ el('compare-overlay').addEventListener('click', function(e) { if (e.target === this) this.classList.remove('visible'); }); +// ─── CHANGELOG ─────────────────────────────────────────────────────────────── + +var changelogEntries = []; +var changelogExpanded = false; + +async function loadChangelog() { + try { + var d = await api('/api/changelog'); + changelogEntries = d.entries || []; + el('changelog-total').textContent = changelogEntries.length + ' entries'; + renderChangelog(); + } catch(e) { + el('changelog-list').innerHTML = '
Not available in preview — runs on production server.
'; + } +} + +function toggleChangelog() { + changelogExpanded = !changelogExpanded; + el('changelog-toggle-btn').textContent = changelogExpanded ? 'Show recent' : 'Show all'; + renderChangelog(); +} + +function renderChangelog() { + var entries = changelogExpanded ? changelogEntries : changelogEntries.slice(0, 8); + el('changelog-list').innerHTML = entries.map(function(e) { + return '
' + + '' + esc(e.d) + '' + + '' + esc(e.t) + '' + + '' + esc(e.m) + '' + + '
'; + }).join(''); +} + // ─── PROCUREMENT INTEL ─────────────────────────────────────────────────────── var procCurrentSignalFilter = ''; @@ -3220,6 +3285,7 @@ async function loadProcLifecycle() { // INIT loadOverview(); +loadChangelog();