From ee8b3c0779d5ae7bd253466b96fa0476b04a0db9 Mon Sep 17 00:00:00 2001 From: Rene Fichtmueller Date: Wed, 1 Apr 2026 11:12:38 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20hot=20topics=20daily=20rotation=20?= =?UTF-8?q?=E2=80=94=2030+=20topic=20pool,=20seeded=20shuffle,=20next-refr?= =?UTF-8?q?esh=20countdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Expanded research pool to 9 topics (was 3), evergreen to 12 (was 3) - Conference topics: added Photonics West, CIOE, NFOEC follow-up, year-end review - Standards topics: 3 rotating variants (IEEE tracker, SFF-8024 registry, OIF CEI-112G) - seededShuffle(): day-of-year as seed — stable within the day, different every day - API response adds refreshes_at (next midnight UTC) for frontend countdown - Dashboard subtitle shows 'rotates daily · next refresh in Xh' - Hot topic cards now pass full title + angle into generateBlog() correctly --- packages/api/src/routes/hot-topics.ts | 265 ++++++++++++++++++++++++-- packages/dashboard/hot-topics.js | 33 +++- packages/dashboard/index.html | 2 +- 3 files changed, 278 insertions(+), 22 deletions(-) diff --git a/packages/api/src/routes/hot-topics.ts b/packages/api/src/routes/hot-topics.ts index a2fac04..f500b1c 100644 --- a/packages/api/src/routes/hot-topics.ts +++ b/packages/api/src/routes/hot-topics.ts @@ -121,7 +121,8 @@ hotTopicsRouter.get("/", async (_req, res) => { `).catch(() => ({ rows: [] })); // Cluster news by theme - const newsThemes: Record = {}; + type NewsRow = (typeof recentNews.rows)[number]; + const newsThemes: Record = {}; for (const n of recentNews.rows) { const theme = detectNewsTheme(n.title); if (!newsThemes[theme]) newsThemes[theme] = []; @@ -157,10 +158,17 @@ hotTopicsRouter.get("/", async (_req, res) => { const urgencyOrder: Record = { breaking: 0, hot: 1, trending: 2, emerging: 3 }; topics.sort((a, b) => (urgencyOrder[a.urgency] ?? 4) - (urgencyOrder[b.urgency] ?? 4)); + // Next daily rotation: tomorrow 00:00 UTC + const tomorrow = new Date(); + tomorrow.setUTCDate(tomorrow.getUTCDate() + 1); + tomorrow.setUTCHours(0, 0, 0, 0); + res.json({ - topics: topics.slice(0, 12), + topics: topics.slice(0, 6), total: topics.length, generated_at: new Date().toISOString(), + refreshes_at: tomorrow.toISOString(), + day_seed: getDaySeed(), sources: ["internal_price_data", "competitor_alerts", "hype_cycle_model", "news_articles", "conference_calendar", "research_papers"], }); } catch (err) { @@ -189,6 +197,17 @@ function getConferenceTopics(year: number): HotTopic[] { const topics: HotTopic[] = []; // OFC = March, ECOC = September, CIOE = September, Photonics West = January + if (month >= 1 && month <= 2) { + topics.push({ + title: `Photonics West ${year}: What the Laser and Photonics Research Means for Transceivers`, + description: "SPIE Photonics West is where the component-level research lands first. What was shown and how long until it ships?", + blog_type: "technology_deep_dive", + urgency: month === 1 ? "hot" : "trending", + source: "SPIE Photonics West", + source_type: "conference", + suggested_angle: "Photonics West signal reading: The research demos that become products in 2-3 years", + }); + } if (month >= 2 && month <= 4) { topics.push({ title: `OFC ${year} Highlights: What was announced and what matters`, @@ -200,6 +219,17 @@ function getConferenceTopics(year: number): HotTopic[] { suggested_angle: "OFC show floor reality: 5 announcements that actually change your procurement strategy", }); } + if (month >= 5 && month <= 7) { + topics.push({ + title: `NFOEC / OFC ${year} Follow-Up: Which Products Actually Shipped?`, + description: "Six months after OFC demos — checking which announcements turned into shipping hardware and which were still vapor.", + blog_type: "market_alert", + urgency: "trending", + source: "OFC Conference Follow-Up", + source_type: "conference", + suggested_angle: "Post-OFC reality check: Announcement vs reality 6 months later", + }); + } if (month >= 8 && month <= 10) { topics.push({ title: `ECOC ${year}: European market signals and new products`, @@ -211,23 +241,89 @@ function getConferenceTopics(year: number): HotTopic[] { suggested_angle: "ECOC takeaways: What European carriers are actually deploying (not just demoing)", }); } + if (month >= 9 && month <= 11) { + topics.push({ + title: `CIOE ${year} Shenzhen: Chinese Manufacturers Show Their Cards`, + description: "CIOE is where InnoLight, HG Genuine, and Chinese OEMs announce next-gen products. First look at the 2025 roadmap.", + blog_type: "technology_deep_dive", + urgency: "trending", + source: "CIOE Shenzhen", + source_type: "conference", + suggested_angle: "CIOE market signal: What Chinese manufacturers shipping in 12 months means for Western pricing", + }); + } + if (month >= 11 || month <= 1) { + topics.push({ + title: `${year} Year-End Optics Market Review: Who Won, Who Lost`, + description: "Annual review: which speeds gained share, which vendors shipped on roadmap, and what the price curves looked like.", + blog_type: "market_alert", + urgency: "hot", + source: "TIP Market Data", + source_type: "internal_data", + suggested_angle: "Year-end optics market: The numbers that matter for your ${year + 1} budget", + }); + } - // Always relevant - topics.push({ - title: `${year} Standards Update: IEEE 802.3 / OIF / MSA tracker`, - description: "What's ratified, what's in draft, and what it means for your next-gen optics roadmap.", - blog_type: "technology_deep_dive", - urgency: "trending", - source: "IEEE / OIF / MSA", - source_type: "research", - suggested_angle: "Standards reality check: Which specs are production-ready vs. still in committee", - }); + // Always relevant — rotate between 3 variants using day seed + const standardsTopics: HotTopic[] = [ + { + title: `${year} Standards Update: IEEE 802.3 / OIF / MSA tracker`, + description: "What's ratified, what's in draft, and what it means for your next-gen optics roadmap.", + blog_type: "technology_deep_dive", + urgency: "trending", + source: "IEEE / OIF / MSA", + source_type: "research", + suggested_angle: "Standards reality check: Which specs are production-ready vs. still in committee", + }, + { + title: `SFF-8024 Transceiver Management Update ${year}: What's New in the Identifier Registry`, + description: "The SFF Committee's transceiver identifier registry updated again. New form factors, management interfaces, and what switches need firmware updates to support them.", + blog_type: "technology_deep_dive", + urgency: "trending", + source: "SFF Committee", + source_type: "research", + suggested_angle: "SFF registry changes: The low-level updates that cause high-level compatibility headaches", + }, + { + title: `OIF CEI-112G Standard: What It Means for the Next Generation of DAC and AOC`, + description: "CEI-112G is the electrical interface spec that enables 112G PAM4 — the building block of 400G and 800G. What it enables and when it matters.", + blog_type: "technology_deep_dive", + urgency: "emerging", + source: "OIF CEI", + source_type: "research", + suggested_angle: "CEI-112G explained: Why the electrical interface standard matters more than the optics spec", + }, + ]; + const seed = getDaySeed() + 3; + topics.push(seededShuffle(standardsTopics, seed)[0]); return topics; } +/** + * Seeded shuffle — same seed = same order. Uses day-of-year as seed so + * topics rotate daily but are stable within the same day. + */ +function seededShuffle(arr: T[], seed: number): T[] { + const out = [...arr]; + let s = seed; + for (let i = out.length - 1; i > 0; i--) { + s = (s * 1664525 + 1013904223) & 0x7fffffff; + const j = s % (i + 1); + [out[i], out[j]] = [out[j], out[i]]; + } + return out; +} + +function getDaySeed(): number { + const now = new Date(); + const start = new Date(now.getFullYear(), 0, 0); + const dayOfYear = Math.floor((now.getTime() - start.getTime()) / 86400000); + return now.getFullYear() * 1000 + dayOfYear; +} + function getResearchTopics(year: number): HotTopic[] { - return [ + const ALL_RESEARCH: HotTopic[] = [ { title: "Silicon Photonics: From Lab to Production — Where Are We Really?", description: "Intel, Broadcom, Marvell, and startups (Lightmatter, Ayar Labs) are all pushing SiPho. But production yields and packaging costs tell a different story.", @@ -255,11 +351,68 @@ function getResearchTopics(year: number): HotTopic[] { source_type: "manufacturer", suggested_angle: "CPO: Why it's 3 years away (and has been for the last 5 years)", }, + { + title: "1.6T Optics: Who Ships First and What the Specs Actually Mean", + description: "Vendors are announcing 1.6T but the standards aren't final. Here's what's real, what's vaporware, and what lead times to expect.", + blog_type: "technology_deep_dive", + urgency: "emerging", + source: "OIF / IEEE", + source_type: "research", + suggested_angle: "1.6T reality check: Sorting PR from actual shipping hardware", + }, + { + title: "OSFP vs QSFP-DD: The 400G/800G Form Factor Fight Isn't Over", + description: "Both coexist in the market. Hyperscalers went OSFP for 800G; enterprise is on QSFP-DD. What does this mean for your optics inventory?", + blog_type: "comparison", + urgency: "trending", + source: "QSFP-DD MSA / OSFP MSA", + source_type: "research", + suggested_angle: "Form factor fragmentation: How to not end up with the wrong 800G optics", + }, + { + title: "Coherent ZR vs ZR+: The Spec Sheet Lies You Need to Know", + description: "OpenZR+ vs 400ZR implementations from different vendors are NOT interoperable by default. The details matter.", + blog_type: "comparison", + urgency: "trending", + source: "OIF Implementation Agreement", + source_type: "research", + suggested_angle: "Coherent interop truth: Why your ZR link won't work with mixed vendors out of the box", + }, + { + title: "BiDi Optics: The Most Misunderstood Product in the Catalog", + description: "Single-strand operation sounds simple. The wavelength pairing rules, fiber polarity requirements, and what goes wrong when teams mix Tx/Rx wavelengths.", + blog_type: "tutorial", + urgency: "trending", + source: "Field Cases", + source_type: "internal_data", + suggested_angle: "BiDi done right: Why half your BiDi deployments have the wrong wavelength on the wrong end", + }, + { + title: "Transceiver DDM/DOM: How to Actually Use the Diagnostic Data", + description: "Every modern optic reports temperature, voltage, Tx power, Rx power, and bias current. Most teams never check it. Here's what the numbers tell you.", + blog_type: "tutorial", + urgency: "trending", + source: "SFF Committee / Field Data", + source_type: "research", + suggested_angle: "DDM as a maintenance tool: Catching failing optics before they take down your link", + }, + { + title: "800G Deployment Wave: What Hyperscalers Are Actually Installing in ${year}", + description: "Meta, Google, and Microsoft are ordering 800G at scale. What form factors, vendors, and reach variants are winning?", + blog_type: "market_alert", + urgency: "hot", + source: "LightCounting / Hyperscaler CapEx Reports", + source_type: "research", + suggested_angle: "800G market signal: What hyperscaler spending tells enterprise about their 2027 refresh cycle", + }, ]; + // Pick 2 different research topics per day + const shuffled = seededShuffle(ALL_RESEARCH, getDaySeed() + 1); + return shuffled.slice(0, 2); } function getEvergreenTopics(year: number): HotTopic[] { - return [ + const ALL_EVERGREEN: HotTopic[] = [ { title: `The ${year} Transceiver Buying Guide: OEM vs Compatible`, description: "Updated pricing data, vendor quality tiers, and the procurement playbook for every speed class.", @@ -287,5 +440,89 @@ function getEvergreenTopics(year: number): HotTopic[] { source_type: "internal_data", suggested_angle: "Your $350 optic isn't broken — your connector is dirty. The MPO reality guide.", }, + { + title: "Transceiver Compatibility: Why Your New Optic Doesn't Work and How to Fix It", + description: "DOM lockout, software coding, MSA compliance, and the vendor unlock codes that nobody documents.", + blog_type: "tutorial", + urgency: "trending", + source: "Support Cases", + source_type: "internal_data", + suggested_angle: "Compatibility reality: The 4 reasons optics get rejected and exactly how to resolve each one", + }, + { + title: `${year} Price Tracker: Which Transceiver Speeds Are Getting Cheaper Fastest?`, + description: "25G SFP28 hit floor pricing. 100G QSFP28 is still dropping. 400G is at inflection. Where to buy now, where to wait.", + blog_type: "market_alert", + urgency: "hot", + source: "TIP Price Observations", + source_type: "internal_data", + suggested_angle: "Buy signal analysis: The optic speeds where waiting 6 months saves 30%", + }, + { + title: "Power Budget 101: Why Your 10km LR Link Only Reaches 7km", + description: "TX power, receiver sensitivity, fiber loss, connector loss, and the splice margin most engineers forget to account for.", + blog_type: "tutorial", + urgency: "trending", + source: "Field Experience", + source_type: "internal_data", + suggested_angle: "Loss budget done right: The calculation every engineer should do before ordering LR optics", + }, + { + title: "Cisco vs Arista vs Juniper Optics Lock-In: The True Cost Analysis", + description: "OEM optics from major vendors carry a 300-800% premium. Here's the legal situation, the unlock process, and when it's actually worth paying OEM prices.", + blog_type: "buying_guide", + urgency: "trending", + source: "Vendor Pricing / Legal Docs", + source_type: "internal_data", + suggested_angle: "OEM lock-in economics: The numbers behind why compatible optics work and when they don't", + }, + { + title: "40G End-of-Life: What to Do With Your QSFP+ Inventory in ${year}", + description: "40G is in decline. Resale values, upgrade paths to 100G, and when to write off the inventory.", + blog_type: "market_alert", + urgency: "trending", + source: "TIP Price Data", + source_type: "internal_data", + suggested_angle: "40G sunset planning: The migration math that justifies skipping 40G refresh entirely", + }, + { + title: "DAC vs AOC vs Optic+Cable: The Short-Reach Decision Tree", + description: "For ≤5m: DAC. For 5-100m: AOC. For >100m: pluggable. But the breakeven points shift with port density and heat budgets.", + blog_type: "comparison", + urgency: "trending", + source: "Field Data", + source_type: "internal_data", + suggested_angle: "Short-reach optics: The decision framework that saves budget without sacrificing reliability", + }, + { + title: "Gray Optics: Market Pricing vs New — When the Risk Is Worth It", + description: "eBay, Alibaba, and gray market brokers sell transceiver inventory at 40-70% below retail. What to check, what can go wrong, and what the warranty situation actually is.", + blog_type: "buying_guide", + urgency: "trending", + source: "Market Research", + source_type: "internal_data", + suggested_angle: "Gray market guide: The due diligence checklist that separates a deal from a disaster", + }, + { + title: "Wavelength Division Multiplexing in ${year}: CWDM4 vs LWDM vs DWDM Decision Guide", + description: "WDM optics for data center interconnect. The wavelength plans, the reach limits, and when DWDM pays off vs CWDM.", + blog_type: "technology_deep_dive", + urgency: "trending", + source: "IEEE / OIF", + source_type: "research", + suggested_angle: "WDM selection guide: Matching wavelength technology to your actual DCI requirements", + }, + { + title: "Temperature-Hardened Optics: When ETSI Class 4.1 Actually Matters", + description: "Industrial, outdoor, and telecom deployments require optics rated for -40°C to +85°C. What the specs mean and which products are actually qualified.", + blog_type: "buying_guide", + urgency: "emerging", + source: "ETSI / Telecom Field Cases", + source_type: "internal_data", + suggested_angle: "Industrial optics selection: When your data center optic will kill your outdoor deployment", + }, ]; + // Pick 3 different evergreen topics per day + const shuffled = seededShuffle(ALL_EVERGREEN, getDaySeed() + 2); + return shuffled.slice(0, 3); } diff --git a/packages/dashboard/hot-topics.js b/packages/dashboard/hot-topics.js index d230d5a..06afd5d 100644 --- a/packages/dashboard/hot-topics.js +++ b/packages/dashboard/hot-topics.js @@ -123,6 +123,7 @@ // Hot topics loader window.loadHotTopics = function() { var grid = document.getElementById('hot-topics-grid'); + var subtitle = document.getElementById('hot-topics-subtitle'); if (!grid) return; grid.innerHTML = '
Discovering hot topics...
'; @@ -131,17 +132,28 @@ grid.innerHTML = '
Hype Cycle Analysis
'; return; } + + // Update subtitle with next-refresh countdown + if (subtitle && data.refreshes_at) { + var nextRefresh = new Date(data.refreshes_at); + var hoursLeft = Math.round((nextRefresh - new Date()) / 3600000); + subtitle.textContent = 'auto-discovered · rotates daily · next refresh in ' + hoursLeft + 'h'; + } + var colors = { breaking: '#c1121f', hot: '#FF8100', trending: '#e6a800', emerging: '#2d6a4f' }; - grid.innerHTML = data.topics.slice(0, 6).map(function(t) { + grid.innerHTML = data.topics.map(function(t) { var c = colors[t.urgency] || '#888'; - var escaped = encodeURIComponent(t.blog_type || 'hype_cycle'); - var title = (t.title || '').replace(/'/g, "\\'").replace(/"/g, '"'); - return '
' + + // Pass full topic title and angle as data attributes to avoid quote-escaping hell + var cardId = 'ht-' + Math.random().toString(36).slice(2, 8); + // Store topic data for onclick + window['_ht_' + cardId] = t; + return '
' + '
' + - '' + t.urgency + '' + + '' + (t.urgency || '') + '' + '' + (t.source_type || '') + '
' + - '
' + t.title + '
' + - '
' + ((t.suggested_angle || t.description || '')).slice(0, 80) + '
' + + '
' + (t.title || '') + '
' + + '
' + (t.suggested_angle || t.description || '').slice(0, 90) + '
' + '
'; }).join(''); }).catch(function() { @@ -152,6 +164,13 @@ }); }; + // Generate blog from hot topic card — uses title + angle from stored topic object + window._generateFromHotTopic = function(cardId) { + var t = window['_ht_' + cardId]; + if (!t) return; + generateBlog(t.blog_type || 'hype_cycle', null, t.title, t.suggested_angle || t.description); + }; + // Auto-load hot topics when blog tab activates var origActivateTab = window.activateTab; if (origActivateTab) { diff --git a/packages/dashboard/index.html b/packages/dashboard/index.html index 3b49d66..42c0a89 100644 --- a/packages/dashboard/index.html +++ b/packages/dashboard/index.html @@ -862,7 +862,7 @@