feat: Phase 3 — wire panel modules (heatmap, agent-board, dual-brain, LoRA, sovereignty, earth)
Some checks failed
CI / validate (pull_request) Failing after 12s
CI / auto-merge (pull_request) Has been skipped

- core/theme.js: export NEXUS with full NEXUS.theme.* properties used by all 6 panels
- core/ticker.js: add subscribe() convenience export so panels can self-register
- data/gitea.js: also write state.agentStatus, activeAgentCount, zoneIntensity, commits, commitHashes
- data/loaders.js: also write state.sovereignty
- data/bitcoin.js: also write state.blockHeight, lastBlockHeight, newBlockDetected, starPulseIntensity
- data/weather.js: also write state.weather
- app.js: import + init all 6 panel modules, bootstrap data polling, call globalTicker.tick()

Fixes #412
Refs #409

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Whitestone
2026-03-24 18:17:48 -04:00
parent c0a673038b
commit 2c0f7f7a16
7 changed files with 155 additions and 34 deletions

View File

@@ -1,6 +1,8 @@
// modules/data/bitcoin.js — Blockstream block height polling
// Writes to S: lastKnownBlockHeight, _starPulseIntensity
// Writes to S: lastKnownBlockHeight, _starPulseIntensity (legacy)
// Writes to state: blockHeight, lastBlockHeight, newBlockDetected, starPulseIntensity
import { S } from '../state.js';
import { state } from '../core/state.js';
const BITCOIN_REFRESH_MS = 60 * 1000;
@@ -11,12 +13,15 @@ export async function fetchBlockHeight() {
const height = parseInt(await res.text(), 10);
if (isNaN(height)) return null;
const isNew = S.lastKnownBlockHeight !== null && height > S.lastKnownBlockHeight;
const prev = S.lastKnownBlockHeight;
const isNew = prev !== null && height > prev;
S.lastKnownBlockHeight = height;
if (isNew) S._starPulseIntensity = 1.0;
if (isNew) {
S._starPulseIntensity = 1.0;
}
state.blockHeight = height;
state.lastBlockHeight = prev || 0;
state.newBlockDetected = isNew;
if (isNew) state.starPulseIntensity = 1.0;
return { height, isNewBlock: isNew };
} catch {

View File

@@ -1,6 +1,17 @@
// modules/data/gitea.js — All Gitea API calls
// Writes to S: _activeAgentCount, _matrixCommitHashes, agentStatus
// Writes to S: _activeAgentCount, _matrixCommitHashes (legacy)
// Writes to state: agentStatus, activeAgentCount, zoneIntensity, commits, commitHashes
import { S } from '../state.js';
import { state } from '../core/state.js';
// Zone intensity — agent name → author regex (mirrors panels/heatmap.js HEATMAP_ZONES)
const _ZONE_PATTERNS = [
{ name: 'Claude', pattern: /^claude$/i },
{ name: 'Timmy', pattern: /^timmy/i },
{ name: 'Kimi', pattern: /^kimi/i },
{ name: 'Perplexity', pattern: /^perplexity/i },
];
const _ZONE_MAX_WEIGHT = 8;
const GITEA_BASE = 'http://143.198.27.163:3000/api/v1';
const GITEA_TOKEN = 'dc0517a965226b7a0c5ffdd961b1ba26521ac592';
@@ -119,22 +130,49 @@ export async function fetchAgentStatus() {
export async function refreshCommitData() {
const commits = await fetchNexusCommits();
S._matrixCommitHashes = commits.slice(0, 20)
.map(c => (c.sha || '').slice(0, 7))
.filter(h => h.length > 0);
const hashes = commits.slice(0, 20).map(c => (c.sha || '').slice(0, 7)).filter(Boolean);
// Legacy write
S._matrixCommitHashes = hashes;
// Core state writes
state.commits = commits;
state.commitHashes = hashes;
// Compute per-zone intensity (24 h decay window)
const now = Date.now();
const rawWeights = {};
for (const c of commits) {
const author = c.commit?.author?.name || c.author?.login || '';
const age = now - new Date(c.commit?.author?.date || 0).getTime();
if (age > DAY_MS) continue;
const weight = 1 - age / DAY_MS;
for (const { name, pattern } of _ZONE_PATTERNS) {
if (pattern.test(author)) { rawWeights[name] = (rawWeights[name] || 0) + weight; break; }
}
}
state.zoneIntensity = Object.fromEntries(
_ZONE_PATTERNS.map(z => [z.name, Math.min((rawWeights[z.name] || 0) / _ZONE_MAX_WEIGHT, 1)])
);
return commits;
}
export async function refreshAgentData() {
try {
const data = await fetchAgentStatus();
S._activeAgentCount = data.agents.filter(a => a.status === 'working').length;
const count = data.agents.filter(a => a.status === 'working').length;
S._activeAgentCount = count;
state.agentStatus = data;
state.activeAgentCount = count;
return data;
} catch {
const fallback = { agents: AGENT_NAMES.map(n => ({
name: n.toLowerCase(), status: 'unreachable', issue: null, prs_today: 0, local: false,
})) };
S._activeAgentCount = 0;
state.agentStatus = fallback;
state.activeAgentCount = 0;
return fallback;
}
}

View File

@@ -1,6 +1,8 @@
// modules/data/loaders.js — Static file loaders (portals.json, sovereignty-status.json, SOUL.md)
// Writes to S: sovereigntyScore, sovereigntyLabel
// Writes to S: sovereigntyScore, sovereigntyLabel (legacy)
// Writes to state: sovereignty
import { S } from '../state.js';
import { state } from '../core/state.js';
// --- SOUL.md (cached) ---
let _soulMdCache = null;
@@ -37,6 +39,7 @@ export async function fetchSovereigntyStatus() {
S.sovereigntyScore = score;
S.sovereigntyLabel = label;
state.sovereignty = { score, label, assessment_type: assessmentType };
return { score, label, assessmentType };
} catch {

View File

@@ -1,5 +1,6 @@
// modules/data/weather.js — Open-Meteo weather fetch
// Writes to: weatherState (returned), scene effects applied by caller
// Writes to state: weather
import { state } from '../core/state.js';
const WEATHER_LAT = 43.2897;
const WEATHER_LON = -72.1479;
@@ -28,7 +29,9 @@ export async function fetchWeatherData() {
const code = cur.weather_code;
const { condition, icon } = weatherCodeToLabel(code);
const cloudcover = typeof cur.cloud_cover === 'number' ? cur.cloud_cover : 50;
return { code, temp: cur.temperature_2m, wind: cur.wind_speed_10m, condition, icon, cloudcover };
const result = { code, temp: cur.temperature_2m, wind: cur.wind_speed_10m, condition, icon, cloudcover };
state.weather = result;
return result;
}
export { WEATHER_REFRESH_MS };