Files
Q-Mintership-Alpha/assets/js/StatsBoard.js

5855 lines
193 KiB
JavaScript

const MINTER_STATS_IDENTIFIER_PREFIX = "Mintership-stats"
const MINTER_STATS_PROGRESS_IDENTIFIER_PREFIX = "Mintership-stats-progress"
const STATS_SNAPSHOT_CACHE_TTL_MS = 120000
const STATS_BOARD_SYNC_CACHE_TTL_MS = 30000
const STATS_GROUP_CACHE_TTL_MS = 120000
const STATS_LIVE_CACHE_TTL_MS = 30000
const STATS_COMPILE_BATCH_SIZE = 30
const NOMINATOR_METHOD_START_TS = Date.parse("2026-06-01T00:00:00Z")
const STATS_LEGACY_CLASSIFICATION_WINDOW_MS = 7 * 24 * 60 * 60 * 1000
const STATS_FOUNDER_EFFECTIVE_LEVEL = 10
const STATS_COMPILE_MODAL_TYPE = "stats-compile"
const STATS_PAGE_TITLE = "Mintership Stats"
const STATS_PAGE_STATUS_LABEL = "Beta"
const STATS_PAGE_DOCUMENT_TITLE = `${STATS_PAGE_TITLE} - ${STATS_PAGE_STATUS_LABEL}`
const statsBoardState = {
snapshotResources: [],
latestSnapshot: null,
latestSourceResource: null,
latestProgressCheckpoint: null,
latestValidationReport: null,
latestGroupData: null,
latestLiveMintingData: null,
lastLoadedAt: 0,
latestSourceLoadedAt: 0,
latestProgressLoadedAt: 0,
latestGroupLoadedAt: 0,
latestLiveMintingLoadedAt: 0,
compiling: false,
pauseRequested: false,
}
const statsCompileModalState = {
phase: "options",
workflow: "compile",
hidden: false,
timestampMode: "now",
customTimestampInput: "",
snapshotTimestamp: Date.now(),
identifierPreview: "",
message: "",
subtitle: "",
steps: [],
errorMessage: "",
validationReport: null,
}
if (typeof window !== "undefined") {
window.getStatsCompileModalState = () => ({
compiling: Boolean(statsBoardState.compiling),
pauseRequested: Boolean(statsBoardState.pauseRequested),
phase: String(statsCompileModalState.phase || "options"),
workflow: String(statsCompileModalState.workflow || "compile"),
hidden: Boolean(statsCompileModalState.hidden),
})
}
const getStatsBoardTimestamp = (resource = {}) =>
Number(resource?.updated || resource?.created || 0)
const getStatsSnapshotSourceTimestamp = (snapshot = null) =>
Number(
snapshot?.source?.latestCardTimestamp ||
snapshot?.source?.latestSourceTimestamp ||
snapshot?.compiledAt ||
snapshot?.generatedAt ||
0
)
const getStatsLatestPublishedTimestamp = () => {
const latestSnapshotTimestamp = Number(
statsBoardState.latestSnapshot?.generatedAt || 0
)
if (latestSnapshotTimestamp > 0) {
return latestSnapshotTimestamp
}
const latestCheckpointTimestamp = Number(
statsBoardState.latestProgressCheckpoint?.snapshotTimestamp ||
statsBoardState.latestProgressCheckpoint?.generatedAt ||
0
)
return latestCheckpointTimestamp > 0 ? latestCheckpointTimestamp : 0
}
const getStatsCompileProgressIdentifier = (snapshotTimestamp = Date.now()) =>
`${MINTER_STATS_PROGRESS_IDENTIFIER_PREFIX}-${snapshotTimestamp}`
const normalizeStatsSourceResource = (resource = {}) => ({
name: String(resource?.name || "").trim(),
identifier: String(resource?.identifier || "").trim(),
created: Number(resource?.created || 0),
updated: Number(resource?.updated || 0),
})
const normalizeStatsCompileStepDetail = (detail = "") =>
String(detail || "").trim()
const formatStatsDate = (timestamp = 0) => {
if (!timestamp) return "Unavailable"
try {
return new Date(timestamp).toLocaleString()
} catch (error) {
return "Unavailable"
}
}
const formatStatsPercent = (value = 0) => {
const numeric = Number(value)
if (!Number.isFinite(numeric) || numeric <= 0) {
return "0%"
}
return `${Math.round(numeric * 1000) / 10}%`
}
const getStatsResumeCheckpointSummary = (checkpoint = null) => {
if (!checkpoint) {
return null
}
const processed = Math.max(0, Number(checkpoint.nextIndex || 0))
const total = Math.max(
processed,
Number(
checkpoint?.source?.cardCount ||
checkpoint?.source?.resources?.length ||
0
)
)
const remaining = Math.max(total - processed, 0)
return {
checkpointIdentifier: String(checkpoint?.progressIdentifier || "").trim(),
processed,
total,
remaining,
nextCardNumber: total > 0 ? Math.min(processed + 1, total) : processed + 1,
}
}
const normalizeStatsSectionKey = (value = "") => {
const normalized = String(value || "")
.trim()
.toLowerCase()
.replace(/[^a-z0-9\-]/g, "")
if (!normalized) {
return "live"
}
const sectionAliases = {
live: "live",
liveminting: "live",
"live-minting": "live",
livestats: "live",
livelevels: "live",
"live-levels": "live",
historic: "historic",
history: "historic",
historical: "historic",
historicdata: "historic",
"historic-data": "historic",
historicmintershipdata: "historic",
"historic-mintership-data": "historic",
publish: "publish",
"publish-data": "publish",
publishdata: "publish",
publishstats: "publish",
publisheddata: "publish",
publishedstats: "publish",
"published-data": "publish",
publishedsnapshot: "publish",
"published-snapshot": "publish",
"publish-snapshot": "publish",
"publish-stats": "publish",
snapshot: "publish",
nominator: "nominator",
nominators: "nominator",
nominatorsstats: "nominator",
nominatorsleaderboard: "nominator-leaderboard",
nominatorstats: "nominator",
nominatorsstat: "nominator",
nominatorleaderboard: "nominator-leaderboard",
nominatorleaderboards: "nominator-leaderboard",
legacy: "legacy",
legacystats: "legacy",
legacyleaderboard: "legacy-leaderboard",
legacyleaderboards: "legacy-leaderboard",
admin: "admin",
minteradmin: "admin",
minteradminstats: "admin",
minteradminleaderboard: "admin-leaderboard",
minteradminleaderboards: "admin-leaderboard",
minterleaderboard: "nominator-leaderboard",
minterleaderboards: "nominator-leaderboard",
nominatorleaderboardview: "nominator-leaderboard",
}
return sectionAliases[normalized] || normalized
}
const getStatsSectionTargetId = (section = "") => {
switch (normalizeStatsSectionKey(section)) {
case "live":
return "stats-section-live"
case "historic":
return "stats-section-historic"
case "publish":
return "stats-section-publish"
case "legacy":
return "stats-section-legacy"
case "legacy-leaderboard":
return "stats-legacy-leaderboard"
case "admin":
return "stats-section-admin"
case "admin-leaderboard":
return "stats-admin-leaderboard"
case "nominator-leaderboard":
return "stats-nominator-leaderboard"
case "nominator":
return "stats-section-nominator"
case "live":
default:
return "stats-section-live"
}
}
const getStatsSectionLinks = () =>
Array.from(document.querySelectorAll("[data-stats-section-link]"))
const setStatsBoardSectionActiveState = (section = "") => {
const normalizedSection = normalizeStatsSectionKey(section)
getStatsSectionLinks().forEach((link) => {
const linkSection = normalizeStatsSectionKey(
link.getAttribute("data-stats-section-link") || ""
)
if (linkSection === normalizedSection) {
link.classList.add("stats-section-nav-link--active")
link.setAttribute("aria-current", "page")
} else {
link.classList.remove("stats-section-nav-link--active")
link.removeAttribute("aria-current")
}
})
}
const focusStatsBoardSection = async (
section = "",
{ behavior = "smooth" } = {}
) => {
const normalizedSection = normalizeStatsSectionKey(section)
setStatsBoardSectionActiveState(normalizedSection)
const targetId = getStatsSectionTargetId(normalizedSection)
const target = document.getElementById(targetId)
if (!target) {
return null
}
try {
target.scrollIntoView({ behavior, block: "start" })
} catch (error) {
target.scrollIntoView()
}
return target
}
const hasStatsBoardNominationPublishFields = (cardData = {}) =>
Boolean(cardData?.nominator || cardData?.nominatorAddress)
const classifyStatsBoardEra = (resource = {}, cardData = null) => {
const createdAt = Number(resource?.created || resource?.updated || 0)
const legacyWindowStart =
NOMINATOR_METHOD_START_TS - STATS_LEGACY_CLASSIFICATION_WINDOW_MS
const legacyWindowEnd =
NOMINATOR_METHOD_START_TS + STATS_LEGACY_CLASSIFICATION_WINDOW_MS
if (createdAt > 0 && createdAt < legacyWindowStart) {
return {
createdAt,
isLegacy: true,
classificationSource: "timestamp",
}
}
if (createdAt > 0 && createdAt > legacyWindowEnd) {
return {
createdAt,
isLegacy: false,
classificationSource: "timestamp",
}
}
const hasNominationPublishFields =
hasStatsBoardNominationPublishFields(cardData)
return {
createdAt,
isLegacy: !hasNominationPublishFields,
classificationSource: hasNominationPublishFields
? "publish-payload"
: "publish-payload-legacy",
}
}
const getStatsBoardCanPublish = () =>
Boolean(userState.isAdmin || userState.isMinterAdmin)
const getStatsBoardRoot = () => document.querySelector(".stats-board-main")
const getStatsBoardContentRoot = () =>
document.getElementById("stats-board-content")
const getStatsSnapshotContainer = () =>
document.getElementById("stats-snapshot-container")
const getStatsStatusEl = () => document.getElementById("stats-status")
const getStatsSummaryGrid = () => document.getElementById("stats-summary-grid")
const getStatsLeaderboardContainer = () =>
document.getElementById("stats-leaderboard-container")
const getStatsSnapshotMetaContainer = () =>
document.getElementById("stats-snapshot-meta")
const getStatsHistoryContainer = () =>
document.getElementById("stats-history-container")
const getStatsSyncBannerEl = () =>
document.getElementById("stats-sync-banner")
const getStatsCompileModalContent = () =>
document.getElementById("stats-compile-modalContent")
const getStatsCompileTimestampModeEl = () =>
document.getElementById("stats-compile-timestamp-mode")
const getStatsCompileCustomTimestampEl = () =>
document.getElementById("stats-compile-custom-timestamp")
const getStatsCompileIdentifierPreviewEl = () =>
document.getElementById("stats-compile-identifier-preview")
const getStatsCompileStatusEl = () =>
document.getElementById("stats-compile-status")
const getStatsCompileNoteEl = () =>
document.getElementById("stats-compile-note")
const formatStatsTimestampInputValue = (timestamp = Date.now()) => {
try {
const date = new Date(timestamp)
const offsetMs = date.getTimezoneOffset() * 60000
return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16)
} catch (error) {
return ""
}
}
const resolveStatsCompileTimestamp = () => {
const mode = String(
getStatsCompileTimestampModeEl()?.value || statsCompileModalState.timestampMode || "now"
).trim()
const customValue = String(
getStatsCompileCustomTimestampEl()?.value ||
statsCompileModalState.customTimestampInput ||
""
).trim()
if (mode === "custom") {
const parsedCustomTimestamp = Date.parse(customValue)
return Number.isFinite(parsedCustomTimestamp)
? parsedCustomTimestamp
: Date.now()
}
if (mode === "latest") {
return getStatsLatestPublishedTimestamp() || Date.now()
}
if (mode === "minus-1-day") {
return Date.now() - 24 * 60 * 60 * 1000
}
if (mode === "minus-6-hours") {
return Date.now() - 6 * 60 * 60 * 1000
}
if (mode === "minus-1-week") {
return Date.now() - 7 * 24 * 60 * 60 * 1000
}
return Date.now()
}
const buildStatsCompileSteps = (workflow = "compile") => {
const normalizedWorkflow = String(workflow || "compile").trim().toLowerCase()
const isValidation = normalizedWorkflow === "validation"
if (isValidation) {
return [
{
key: "load-source",
label: "Load source cards",
detail: "Pulling the published MinterBoard cards and cached metadata.",
substeps: [
"Fetch the published card archive from QDN.",
"Deduplicate records so each card is only counted once.",
"Prime the validation run with cached card metadata.",
],
status: "active",
},
{
key: "classify-era",
label: "Separate legacy cards",
detail:
"Cards before June 1, 2026 are legacy by default. Near the cutover, the publish payload decides.",
substeps: [
"Resolve each card's published timestamp.",
"Treat pre-June 2026 cards as legacy without any invite lookups.",
"Inspect only the cutover-window payloads for nominator markers.",
],
status: "pending",
},
{
key: "aggregate",
label: "Audit source coverage",
detail: "Checking for missing fields and cards that could not be compiled.",
substeps: [
"Verify each card has the data needed for stats classification.",
"Track records that failed validation so they can be reviewed.",
"Summarize the cards that can and cannot support the stats views.",
],
status: "pending",
},
{
key: "publish",
label: "Verify published stats data",
detail:
"Confirming the latest snapshot and checkpoint were published by an admin account.",
substeps: [
"Load the latest stats snapshot history.",
"Ignore non-admin published stats resources.",
"Confirm the latest checkpoint author before reporting results.",
],
status: "pending",
},
{
key: "refresh",
label: "Summarize findings",
detail: "Presenting the validation report without publishing anything new.",
substeps: [
"Compile the audit summary.",
"Surface any issue counts and ignored resources.",
"Leave the dashboard data untouched.",
],
status: "pending",
},
]
}
return [
{
key: "load-source",
label: "Load source cards",
detail: "Pulling the published MinterBoard cards and cached metadata.",
substeps: [
"Fetch the published card archive from QDN.",
"Deduplicate records so each card is only counted once.",
"Prime the compile run with cached card metadata.",
],
status: "active",
},
{
key: "classify-era",
label: "Separate legacy cards",
detail:
"Cards before June 1, 2026 are legacy by default. Near the cutover, the publish payload decides.",
substeps: [
"Resolve each card's published timestamp.",
"Treat pre-June 2026 cards as legacy without any invite lookups.",
"Inspect only the cutover-window payloads for nominator markers.",
],
status: "pending",
},
{
key: "aggregate",
label: "Aggregate leaderboard data",
detail: "Counting nominator-era rows and preparing legacy/admin rollups.",
substeps: [
"Group nominations by nominator.",
"Track legacy, admin, and current-era publisher totals.",
"Sort rows by submissions, conversions, and recency.",
],
status: "pending",
},
{
key: "publish",
label: "Update data objects",
detail: "Writing the final stats snapshot and resumable checkpoint to QDN.",
substeps: [
"Serialize the final snapshot object.",
"Serialize the resumable checkpoint object.",
"Publish both together with one multi-resource QDN request.",
],
status: "pending",
},
{
key: "refresh",
label: "Refresh dashboard",
detail: "Reloading the Stats page so the new snapshot is visible.",
substeps: [
"Clear cached snapshot state.",
"Fetch the latest published snapshot.",
"Rerender the dashboard panels and history.",
],
status: "pending",
},
]
}
const getStatsCompileActiveStep = (steps = []) => {
const normalizedSteps = Array.isArray(steps) ? steps : []
return (
normalizedSteps.find((step) => step?.status === "active") ||
normalizedSteps.find((step) => step?.status === "error") ||
normalizedSteps.find((step) => step?.status === "done") ||
normalizedSteps[0] ||
null
)
}
const buildStatsCompileWorkflowBannerState = () => {
const phase = String(statsCompileModalState.phase || "options")
const workflow = String(statsCompileModalState.workflow || "compile")
const isValidation = workflow === "validation"
const isRecreate = workflow === "recreate"
const steps = Array.isArray(statsCompileModalState.steps)
? statsCompileModalState.steps
: []
const activeStep = getStatsCompileActiveStep(steps)
const completedSteps = steps.filter(
(step) => step?.status === "done" || step?.status === "error"
).length
const totalSteps = steps.length
const isHidden = Boolean(statsCompileModalState.hidden)
const currentStatusLine = activeStep
? `${activeStep.label || "Stats workflow"}${
activeStep.detail ? ` - ${activeStep.detail}` : ""
}`
: statsCompileModalState.message ||
(isValidation
? "Validating stats data..."
: isRecreate
? "Re-creating stats data..."
: "Updating stats snapshot...")
const meta = []
if (totalSteps > 0) {
const visibleStepIndex = Math.min(
totalSteps,
Math.max(
1,
completedSteps + (phase === "progress" && activeStep ? 1 : 0)
)
)
meta.push(`Step ${visibleStepIndex}/${totalSteps}`)
}
if (phase === "progress") {
meta.push(
isValidation
? "Validation runs read-only"
: "Update batches remain resumable"
)
}
if (phase === "paused") {
meta.push(
`Checkpoint: ${statsBoardState.latestProgressCheckpoint?.progressIdentifier || "Unavailable"}`
)
}
if (statsCompileModalState.subtitle) {
meta.push(statsCompileModalState.subtitle)
}
let primaryAction = ""
if (phase === "progress" && !isValidation) {
primaryAction = `
<button type="button" class="stats-action-button stats-action-button--primary" onclick="requestStatsCompilePause()">
Pause after current batch
</button>
`
} else if (phase === "paused") {
primaryAction = `
<button type="button" class="stats-action-button stats-action-button--primary" onclick="continuePreviousStatsCompilation()">
Resume update
</button>
`
} else if (phase === "complete" && !isValidation) {
primaryAction = `
<button type="button" class="stats-action-button stats-action-button--primary" onclick="refreshStatsBoardView({ force: true })">
Refresh board
</button>
`
} else if (phase === "complete" && isValidation) {
primaryAction = `
<button type="button" class="stats-action-button stats-action-button--primary" onclick="recreateStatsDataFromModal()">
Re-create stats data
</button>
`
}
const secondaryAction = `
<button type="button" class="stats-action-button" onclick="${isHidden ? "showStatsCompileProgressModal()" : "closeStatsCompileModal()"}">
${isHidden ? "Show window" : "Hide window"}
</button>
`
return {
tone:
phase === "error"
? "stale"
: phase === "paused" || phase === "progress"
? "progress"
: "current",
kicker: isValidation
? "Validation"
: isRecreate
? "Re-create"
: "Updating",
title:
phase === "complete"
? isValidation
? "Stats validation complete"
: isRecreate
? "Stats data re-created"
: "Stats snapshot updated"
: phase === "paused"
? "Stats update saved"
: phase === "error"
? isValidation
? "Stats validation stopped"
: "Stats update stopped"
: activeStep?.label ||
(isValidation
? "Validating stats data"
: isRecreate
? "Re-creating stats data"
: "Updating stats snapshot"),
message:
statsCompileModalState.message ||
activeStep?.detail ||
(isValidation
? "Working through the validation checks."
: "Working through the snapshot update and publish steps."),
meta,
currentStatusLine,
primaryAction,
secondaryAction,
}
}
const buildStatsCompileWorkflowBannerHtml = (state = {}) => {
const meta = Array.isArray(state.meta) ? state.meta : []
return `
<section class="stats-sync-banner stats-sync-banner--${qEscapeAttr(
state.tone || "progress"
)}">
<div class="stats-sync-banner-copy">
<p class="stats-sync-banner-kicker">${qEscapeHtml(
state.kicker || "Updating"
)}</p>
<h3 class="stats-sync-banner-title">${qEscapeHtml(
state.title || "Stats workflow"
)}</h3>
<p class="stats-sync-banner-text">${qEscapeHtml(
state.message || ""
)}</p>
${
meta.length > 0
? `
<div class="stats-sync-banner-meta">
${meta
.map((item) => `<span>${qEscapeHtml(String(item || ""))}</span>`)
.join("")}
</div>
`
: ""
}
</div>
<div class="stats-sync-banner-actions">
${state.primaryAction || ""}
${state.secondaryAction || ""}
</div>
</section>
`
}
const renderStatsCompileWorkflowBanner = () => {
const bannerEl = getStatsSyncBannerEl()
const statusEl = getStatsStatusEl()
if (!bannerEl || !statusEl) {
return
}
const phase = String(statsCompileModalState.phase || "options")
if (
!statsBoardState.compiling &&
phase !== "paused" &&
phase !== "progress" &&
phase !== "error"
) {
return
}
const state = buildStatsCompileWorkflowBannerState()
bannerEl.innerHTML = buildStatsCompileWorkflowBannerHtml(state)
bannerEl.hidden = false
statusEl.textContent = state.currentStatusLine || state.message || ""
}
const buildStatsMetricCardHtml = (label, value, note = "") => `
<article class="stats-metric-card">
<p class="stats-metric-label">${qEscapeHtml(label)}</p>
<div class="stats-metric-value">${qEscapeHtml(String(value))}</div>
${
note
? `<p class="stats-metric-note">${qEscapeHtml(note)}</p>`
: ""
}
</article>
`
const buildStatsLeaderboardTableHtml = (rows = [], { legacyCardCount = 0 } = {}) => {
if (!Array.isArray(rows) || rows.length === 0) {
return `
<div class="stats-empty-state">
${
Number(legacyCardCount || 0) > 0
? "No nominator-era records were found in the latest snapshot. Legacy cards are summarized separately below."
: "No nominators were found in the latest snapshot."
}
</div>
`
}
return `
<div class="stats-table-wrap">
<table class="stats-table">
<thead>
<tr>
<th>Nominator</th>
<th>Nominations</th>
<th>Converted</th>
<th>Approved</th>
<th>Pending</th>
<th>Kicked</th>
<th>Banned</th>
<th>Conversion</th>
<th>Last Nomination</th>
</tr>
</thead>
<tbody>
${rows
.map(
(row) => `
<tr>
<td>
<div class="stats-table-primary">${qEscapeHtml(
row.displayName || "Unknown"
)}</div>
<div class="stats-table-secondary">${qEscapeHtml(
row.address || "Address unavailable"
)}</div>
</td>
<td>${qEscapeHtml(String(row.nominationCount || 0))}</td>
<td>${qEscapeHtml(String(row.convertedCount || 0))}</td>
<td>${qEscapeHtml(String(row.approvedCount || 0))}</td>
<td>${qEscapeHtml(String(row.pendingCount || 0))}</td>
<td>${qEscapeHtml(String(row.kickedCount || 0))}</td>
<td>${qEscapeHtml(String(row.bannedCount || 0))}</td>
<td>${qEscapeHtml(String(row.conversionLabel || "0%"))}</td>
<td>${qEscapeHtml(formatStatsDate(row.lastNominationAt || 0))}</td>
</tr>
`
)
.join("")}
</tbody>
</table>
</div>
`
}
const buildStatsFlexibleTableHtml = ({
rows = [],
emptyText = "No data found.",
headers = [],
rowRenderer = null,
} = {}) => {
if (!Array.isArray(rows) || rows.length === 0) {
return `
<div class="stats-empty-state">${qEscapeHtml(emptyText)}</div>
`
}
return `
<div class="stats-table-wrap">
<table class="stats-table">
<thead>
<tr>
${headers
.map((header) => `<th>${qEscapeHtml(String(header || ""))}</th>`)
.join("")}
</tr>
</thead>
<tbody>
${rows
.map((row, index) =>
typeof rowRenderer === "function"
? rowRenderer(row, index)
: ""
)
.join("")}
</tbody>
</table>
</div>
`
}
const buildStatsPublisherRows = (
records = [],
{ adminAddressSet = new Set(), currentMinterAddressSet = new Set() } = {}
) => {
const rowsByKey = new Map()
const summary = {
totalCards: 0,
legacyCards: 0,
currentCards: 0,
invitedCount: 0,
pendingCount: 0,
currentMinterCount: 0,
kickedCount: 0,
bannedCount: 0,
legacyInvitedCount: 0,
legacyPendingCount: 0,
legacyCurrentMinterCount: 0,
legacyKickedCount: 0,
legacyBannedCount: 0,
adminCards: 0,
}
for (const record of Array.isArray(records) ? records : []) {
const isLegacy = Boolean(record?.isLegacy)
const isApprovedInvite = Boolean(record?.isApprovedInvite)
const isPendingInvite = Boolean(record?.isPendingInvite)
const isKicked = Boolean(record?.isKicked)
const isBanned = Boolean(record?.isBanned)
const publisherName = String(record?.nominatorName || "Unknown").trim()
const publisherAddress = String(record?.nominatorAddress || "").trim()
const publisherKey =
publisherAddress.toLowerCase() || publisherName.toLowerCase()
if (!publisherKey) {
continue
}
summary.totalCards += 1
if (isLegacy) {
summary.legacyCards += 1
} else {
summary.currentCards += 1
}
if (isApprovedInvite) {
summary.invitedCount += 1
if (isLegacy) {
summary.legacyInvitedCount += 1
}
}
if (isPendingInvite) {
summary.pendingCount += 1
if (isLegacy) {
summary.legacyPendingCount += 1
}
}
const isCurrentMinter = resolveStatsRecordCurrentMinterStatus(
record,
currentMinterAddressSet
)
if (isCurrentMinter) {
summary.currentMinterCount += 1
if (isLegacy) {
summary.legacyCurrentMinterCount += 1
}
}
if (isKicked) {
summary.kickedCount += 1
if (isLegacy) {
summary.legacyKickedCount += 1
}
}
if (isBanned) {
summary.bannedCount += 1
if (isLegacy) {
summary.legacyBannedCount += 1
}
}
if (adminAddressSet.has(publisherAddress.toLowerCase())) {
summary.adminCards += 1
}
let row = rowsByKey.get(publisherKey)
if (!row) {
row = {
key: publisherKey,
displayName: publisherName || "Unknown",
address: publisherAddress || "",
cardCount: 0,
legacyCardCount: 0,
currentCardCount: 0,
invitedCount: 0,
pendingCount: 0,
currentMinterCount: 0,
kickedCount: 0,
bannedCount: 0,
legacyInvitedCount: 0,
legacyPendingCount: 0,
legacyCurrentMinterCount: 0,
legacyKickedCount: 0,
legacyBannedCount: 0,
adminCardCount: 0,
lastPublishedAt: 0,
lastLegacyPublishedAt: 0,
isAdminPublisher: adminAddressSet.has(publisherAddress.toLowerCase()),
}
rowsByKey.set(publisherKey, row)
}
row.cardCount += 1
if (isLegacy) {
row.legacyCardCount += 1
if (isApprovedInvite) {
row.legacyInvitedCount += 1
}
if (isPendingInvite) {
row.legacyPendingCount += 1
}
if (isCurrentMinter) {
row.legacyCurrentMinterCount += 1
}
if (isKicked) {
row.legacyKickedCount += 1
}
if (isBanned) {
row.legacyBannedCount += 1
}
} else {
row.currentCardCount += 1
}
if (isApprovedInvite) {
row.invitedCount += 1
}
if (isPendingInvite) {
row.pendingCount += 1
}
if (isCurrentMinter) {
row.currentMinterCount += 1
}
if (isKicked) {
row.kickedCount += 1
}
if (isBanned) {
row.bannedCount += 1
}
if (row.isAdminPublisher) {
row.adminCardCount += 1
}
row.lastPublishedAt = Math.max(row.lastPublishedAt || 0, record.createdAt || 0)
if (isLegacy) {
row.lastLegacyPublishedAt = Math.max(
row.lastLegacyPublishedAt || 0,
record.createdAt || 0
)
}
}
const rows = Array.from(rowsByKey.values()).sort((a, b) => {
if (b.cardCount !== a.cardCount) {
return b.cardCount - a.cardCount
}
if (b.currentMinterCount !== a.currentMinterCount) {
return b.currentMinterCount - a.currentMinterCount
}
return b.lastPublishedAt - a.lastPublishedAt
})
return {
rows,
summary,
}
}
const buildStatsSnapshotRollups = (session = null) => {
const records = Array.isArray(session?.records) ? session.records : []
const referenceData = session?.referenceData || {}
const adminAddressSet = buildStatsGroupAddressSet(
referenceData.minterAdminAddresses || []
)
const currentMinterAddressSet = buildStatsCurrentMinterAddressSet(referenceData)
const allPublisherAggregation = buildStatsPublisherRows(records, {
adminAddressSet,
currentMinterAddressSet,
})
const publisherRows = Array.isArray(allPublisherAggregation.rows)
? allPublisherAggregation.rows
: []
const publisherSummary = allPublisherAggregation.summary || {}
const legacyPublisherRows = publisherRows
.filter((row) => row.legacyCardCount > 0)
.sort((a, b) => {
const legacyPublishDiff =
Number(b.lastLegacyPublishedAt || 0) - Number(a.lastLegacyPublishedAt || 0)
if (legacyPublishDiff !== 0) {
return legacyPublishDiff
}
if (b.legacyCardCount !== a.legacyCardCount) {
return b.legacyCardCount - a.legacyCardCount
}
if (b.cardCount !== a.cardCount) {
return b.cardCount - a.cardCount
}
return String(a.displayName || "").localeCompare(String(b.displayName || ""))
})
const adminPublisherRows = publisherRows.filter((row) => row.isAdminPublisher)
const adminCardCount = adminPublisherRows.reduce(
(total, row) => total + Number(row.cardCount || 0),
0
)
const adminLegacyCardCount = adminPublisherRows.reduce(
(total, row) => total + Number(row.legacyCardCount || 0),
0
)
const adminCurrentCardCount = adminPublisherRows.reduce(
(total, row) => total + Number(row.currentCardCount || 0),
0
)
const adminCurrentMinterCount = adminPublisherRows.reduce(
(total, row) => total + Number(row.currentMinterCount || 0),
0
)
const legacyPublisherSummary = {
totalCards: Number(publisherSummary.legacyCards || 0),
publisherCount: legacyPublisherRows.length,
invitedCount: Number(publisherSummary.legacyInvitedCount || 0),
pendingCount: Number(publisherSummary.legacyPendingCount || 0),
currentMinterCount: Number(publisherSummary.legacyCurrentMinterCount || 0),
kickedCount: Number(publisherSummary.legacyKickedCount || 0),
bannedCount: Number(publisherSummary.legacyBannedCount || 0),
}
return {
adminAddressSet,
publisherRows,
publisherSummary: {
...publisherSummary,
publisherCount: publisherRows.length,
},
legacyPublisherRows,
legacyPublisherSummary,
adminPublisherRows,
adminSummary: {
cardCount: adminCardCount,
publisherCount: adminPublisherRows.length,
legacyCardCount: adminLegacyCardCount,
currentCardCount: adminCurrentCardCount,
currentMinterCount: adminCurrentMinterCount,
},
}
}
const buildStatsSnapshotMetaHtml = (snapshot = null) => {
if (!snapshot) {
return `
<div class="stats-empty-state">
Publish a snapshot to see the latest nominator intelligence here.
</div>
`
}
const source = snapshot.source || {}
const summary = snapshot.summary || {}
const generatedBy = snapshot.generatedBy || {}
return `
<div class="stats-meta-stack">
<div class="stats-meta-row">
<span class="stats-meta-label">Published</span>
<strong>${qEscapeHtml(formatStatsDate(snapshot.generatedAt || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Publisher</span>
<strong>${qEscapeHtml(generatedBy.name || "Unknown")}</strong>
</div>
${
snapshot.compiledAt
? `
<div class="stats-meta-row">
<span class="stats-meta-label">Compiled</span>
<strong>${qEscapeHtml(formatStatsDate(snapshot.compiledAt || 0))}</strong>
</div>
`
: ""
}
${
source.latestCardTimestamp
? `
<div class="stats-meta-row">
<span class="stats-meta-label">Source updated</span>
<strong>${qEscapeHtml(
formatStatsDate(source.latestCardTimestamp || 0)
)}</strong>
</div>
`
: ""
}
<div class="stats-meta-row">
<span class="stats-meta-label">Source cards</span>
<strong>${qEscapeHtml(String(source.cardCount || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Legacy cutoff</span>
<strong>${qEscapeHtml(
formatStatsDate(source.legacyCutoff || NOMINATOR_METHOD_START_TS)
)}</strong>
</div>
${
source.progressIdentifier
? `
<div class="stats-meta-row">
<span class="stats-meta-label">Checkpoint</span>
<strong>${qEscapeHtml(source.progressIdentifier || "Unavailable")}</strong>
</div>
`
: ""
}
<div class="stats-meta-row">
<span class="stats-meta-label">Nominators</span>
<strong>${qEscapeHtml(String(summary.uniqueNominators || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Converted</span>
<strong>${qEscapeHtml(String(summary.totalConvertedToMinter || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Approved</span>
<strong>${qEscapeHtml(String(summary.totalApprovedInvites || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Pending</span>
<strong>${qEscapeHtml(String(summary.totalPendingInvites || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Disciplinary</span>
<strong>${qEscapeHtml(String(summary.totalKickedAndBanned || 0))}</strong>
</div>
${buildStatsLegacyCalloutHtml(snapshot)}
</div>
`
}
const buildStatsHistoryHtml = (resources = []) => {
if (!Array.isArray(resources) || resources.length === 0) {
return `
<div class="stats-empty-state">
No published stats snapshots were found yet.
</div>
`
}
return `
<div class="stats-history-list">
${resources
.slice(0, 6)
.map(
(resource) => `
<article class="stats-history-item">
<div>
<h3>${qEscapeHtml(resource.identifier || "Snapshot")}</h3>
<p>${qEscapeHtml(formatStatsDate(getStatsBoardTimestamp(resource)))}</p>
</div>
<span class="stats-history-badge">Snapshot</span>
</article>
`
)
.join("")}
</div>
`
}
const buildStatsSyncBannerState = ({
snapshot = null,
sourceResource = null,
progressCheckpoint = null,
canPublish = false,
} = {}) => {
const latestSourceTimestamp = getStatsBoardTimestamp(sourceResource)
const snapshotSourceTimestamp = getStatsSnapshotSourceTimestamp(snapshot)
const progressSourceTimestamp = Number(
progressCheckpoint?.source?.latestCardTimestamp || 0
)
const snapshotIsMissing = !snapshot
const snapshotIsStale = Boolean(
snapshot && latestSourceTimestamp > snapshotSourceTimestamp
)
const hasIncompleteProgress = Boolean(
progressCheckpoint && !progressCheckpoint.completed
)
const progressTotal = Number(
progressCheckpoint?.source?.cardCount ||
progressCheckpoint?.source?.resources?.length ||
0
)
const progressProcessed = Number(progressCheckpoint?.nextIndex || 0)
const progressRemaining = Math.max(progressTotal - progressProcessed, 0)
const progressCheckpointIdentifier = String(
progressCheckpoint?.progressIdentifier || ""
)
const resumeSummary = getStatsResumeCheckpointSummary(progressCheckpoint)
let tone = "current"
let kicker = "Data status"
let title = "Stats are current"
let message = "The current stats snapshot matches the latest known board data."
let primaryAction = ""
if (hasIncompleteProgress) {
tone = "progress"
kicker = "Update saved"
title = "A previous stats update is ready to resume"
message = resumeSummary
? `Checkpoint ${resumeSummary.checkpointIdentifier || "Unavailable"} has processed ${resumeSummary.processed} of ${resumeSummary.total} cards. Resuming will continue at card ${resumeSummary.nextCardNumber} with ${resumeSummary.remaining} remaining.`
: `A resumable checkpoint is saved for ${
progressProcessed || 0
} of ${progressTotal || 0} cards. Any admin can continue from where it left off.`
primaryAction = canPublish
? `<button type="button" class="stats-action-button stats-action-button--primary" onclick="continuePreviousStatsCompilation()">
Resume update
</button>`
: ""
} else if (snapshotIsMissing) {
tone = "stale"
kicker = "No snapshot yet"
title = "Stats have not been updated yet"
message =
"A snapshot has not been updated yet, so the Stats dashboard is still showing placeholder values."
primaryAction = canPublish
? `<button type="button" class="stats-action-button stats-action-button--primary" onclick="openStatsCompileModal()">
Update stats
</button>`
: ""
} else if (snapshotIsStale) {
tone = "stale"
kicker = "Update needed"
title = "Stats are out of date"
message = `Latest board activity is newer than the published snapshot from ${formatStatsDate(
snapshotSourceTimestamp
)}.`
primaryAction = canPublish
? `<button type="button" class="stats-action-button stats-action-button--primary" onclick="openStatsCompileModal()">
Update stats
</button>`
: ""
}
const secondaryAction = `<button type="button" class="stats-action-button" onclick="refreshStatsBoardView({ force: true })">
Refresh latest snapshot
</button>`
return {
tone,
kicker,
title,
message,
progressTotal,
progressProcessed,
progressRemaining,
latestSourceTimestamp,
snapshotSourceTimestamp,
progressSourceTimestamp,
progressCheckpointIdentifier,
hasIncompleteProgress,
snapshotIsMissing,
snapshotIsStale,
primaryAction,
secondaryAction,
progressLabel: hasIncompleteProgress
? resumeSummary
? `Saved checkpoint: ${resumeSummary.processed}/${resumeSummary.total} cards, ${resumeSummary.remaining} remaining`
: `Saved checkpoint: ${progressProcessed}/${progressTotal || 0}`
: snapshotIsStale
? `Snapshot captured: ${formatStatsDate(snapshotSourceTimestamp)}`
: `Snapshot captured: ${formatStatsDate(snapshotSourceTimestamp)}`,
}
}
const buildStatsSyncBannerHtml = (state = {}) => {
const tone = String(state.tone || "current")
const progressInfo =
state.hasIncompleteProgress && state.progressCheckpointIdentifier
? `
<div class="stats-sync-banner-meta">
<span>Checkpoint: ${qEscapeHtml(state.progressCheckpointIdentifier)}</span>
<span>Progress: ${qEscapeHtml(
`${state.progressProcessed || 0}/${state.progressTotal || 0}`
)}</span>
<span>Remaining: ${qEscapeHtml(String(state.progressRemaining || 0))}</span>
</div>
`
: `
<div class="stats-sync-banner-meta">
<span>Snapshot: ${qEscapeHtml(
formatStatsDate(state.snapshotSourceTimestamp || 0)
)}</span>
<span>Source: ${qEscapeHtml(
formatStatsDate(state.latestSourceTimestamp || 0)
)}</span>
</div>
`
return `
<section class="stats-sync-banner stats-sync-banner--${qEscapeAttr(tone)}">
<div class="stats-sync-banner-copy">
<p class="stats-sync-banner-kicker">${qEscapeHtml(state.kicker || "Data status")}</p>
<h3 class="stats-sync-banner-title">${qEscapeHtml(state.title || "Stats status")}</h3>
<p class="stats-sync-banner-text">${qEscapeHtml(state.message || "")}</p>
${progressInfo}
</div>
<div class="stats-sync-banner-actions">
${
state.primaryAction || ""
}
${state.secondaryAction || ""}
</div>
</section>
`
}
const renderStatsBoardSyncBanner = (
snapshot = null,
sourceResource = null,
progressCheckpoint = null,
canPublish = false
) => {
const bannerEl = getStatsSyncBannerEl()
if (!bannerEl) {
return
}
const state = buildStatsSyncBannerState({
snapshot,
sourceResource,
progressCheckpoint,
canPublish,
})
bannerEl.innerHTML = buildStatsSyncBannerHtml(state)
bannerEl.hidden = false
}
const buildStatsLegacyCalloutHtml = (snapshot = null) => {
const legacyStats = snapshot?.legacyStats || {}
const legacyCardCount = Number(legacyStats.cardCount || 0)
if (legacyCardCount <= 0) {
return ""
}
return `
<div class="stats-legacy-callout">
<div class="stats-legacy-callout-header">
<span class="stats-meta-label">Legacy era</span>
<strong>
Cards published before June 2026 are excluded from the nominator leaderboard.
</strong>
<span class="stats-legacy-callout-note">
Legacy current-in-group rate: ${qEscapeHtml(legacyStats.currentLabel || "0%")}
</span>
</div>
<div class="stats-legacy-chip-grid">
${buildStatsMetricCardHtml(
"Legacy cards",
legacyStats.cardCount || 0,
"Published before the new nominator method"
)}
${buildStatsMetricCardHtml(
"Legacy publishers",
legacyStats.publisherCount || 0
)}
${buildStatsMetricCardHtml(
"Legacy approved",
legacyStats.invitedCount || 0
)}
${buildStatsMetricCardHtml(
"Legacy pending",
legacyStats.pendingCount || 0
)}
${buildStatsMetricCardHtml(
"Legacy kicked / banned",
(legacyStats.kickedCount || 0) + (legacyStats.bannedCount || 0)
)}
${buildStatsMetricCardHtml(
"Still in group",
legacyStats.currentMinterCount || 0
)}
</div>
</div>
`
}
const buildStatsLegacySectionSummaryHtml = (snapshot = null) => {
const legacyStats = snapshot?.legacyStats || {}
return `
${buildStatsMetricCardHtml(
"Legacy cards",
legacyStats.cardCount || 0,
"Published before the nominator method cutover"
)}
${buildStatsMetricCardHtml(
"Legacy publishers",
legacyStats.publisherCount || 0
)}
${buildStatsMetricCardHtml(
"Invited",
legacyStats.invitedCount || 0
)}
${buildStatsMetricCardHtml(
"Still in group",
legacyStats.currentMinterCount || 0,
"Publisher remains in the Minter group"
)}
${buildStatsMetricCardHtml(
"Kicked / banned",
(legacyStats.kickedCount || 0) + (legacyStats.bannedCount || 0)
)}
${buildStatsMetricCardHtml(
"Pending",
legacyStats.pendingCount || 0
)}
`
}
const buildStatsAdminSectionSummaryHtml = (snapshot = null) => {
const adminStats = snapshot?.adminStats || {}
const referenceData = snapshot?.referenceData || {}
return `
${buildStatsMetricCardHtml(
"Admin-published cards",
adminStats.cardCount || 0,
"Cards published by current minter admins"
)}
${buildStatsMetricCardHtml(
"Admin publishers",
adminStats.publisherCount || 0
)}
${buildStatsMetricCardHtml(
"Legacy published",
adminStats.legacyCardCount || 0
)}
${buildStatsMetricCardHtml(
"Current-era published",
adminStats.currentCardCount || 0
)}
${buildStatsMetricCardHtml(
"Admin publishers still in roster",
adminStats.currentMinterCount || 0,
"Based on the current Minter Admin roster"
)}
${buildStatsMetricCardHtml(
"Known admins",
Array.isArray(referenceData.minterAdminAddresses)
? referenceData.minterAdminAddresses.length
: 0
)}
`
}
const buildStatsLegacyLeaderboardHtml = (snapshot = null) => {
const legacyStats = snapshot?.legacyStats || {}
return buildStatsFlexibleTableHtml({
rows: Array.isArray(legacyStats.leaderboard) ? legacyStats.leaderboard : [],
emptyText:
legacyStats.cardCount > 0
? "No legacy publishers were grouped yet."
: "No legacy cards were found in the latest snapshot.",
headers: [
"Publisher",
"Cards",
"Invited",
"Still in group",
"Kicked",
"Banned",
"Last Legacy Publish",
],
rowRenderer: (row) => `
<tr>
<td>
<div class="stats-table-primary">${qEscapeHtml(row.displayName || "Unknown")}</div>
<div class="stats-table-secondary">${qEscapeHtml(row.address || "Address unavailable")}</div>
</td>
<td>${qEscapeHtml(String(row.cardCount || 0))}</td>
<td>${qEscapeHtml(String(row.legacyInvitedCount || 0))}</td>
<td>${qEscapeHtml(String(row.legacyCurrentMinterCount || 0))}</td>
<td>${qEscapeHtml(String(row.legacyKickedCount || 0))}</td>
<td>${qEscapeHtml(String(row.legacyBannedCount || 0))}</td>
<td>${qEscapeHtml(formatStatsDate(row.lastLegacyPublishedAt || row.lastPublishedAt || 0))}</td>
</tr>
`,
})
}
const buildStatsAdminLeaderboardHtml = (snapshot = null) => {
const adminStats = snapshot?.adminStats || {}
return buildStatsFlexibleTableHtml({
rows: Array.isArray(adminStats.leaderboard) ? adminStats.leaderboard : [],
emptyText:
"No current admin publishers were found in the latest snapshot.",
headers: [
"Admin publisher",
"Cards",
"Legacy",
"Current-era",
"Still in group",
"Last Published",
],
rowRenderer: (row) => `
<tr>
<td>
<div class="stats-table-primary">${qEscapeHtml(row.displayName || "Unknown")}</div>
<div class="stats-table-secondary">${qEscapeHtml(row.address || "Address unavailable")}</div>
</td>
<td>${qEscapeHtml(String(row.cardCount || 0))}</td>
<td>${qEscapeHtml(String(row.legacyCardCount || 0))}</td>
<td>${qEscapeHtml(String(row.currentCardCount || 0))}</td>
<td>${qEscapeHtml(String(row.currentMinterCount || 0))}</td>
<td>${qEscapeHtml(formatStatsDate(row.lastPublishedAt || 0))}</td>
</tr>
`,
})
}
const buildStatsLegacySectionNoteHtml = (snapshot = null) => {
const legacyStats = snapshot?.legacyStats || {}
if (!legacyStats.cardCount) {
return `
<div class="stats-empty-state">
Legacy activity will appear here once snapshot data is compiled.
</div>
`
}
return `
<div class="stats-meta-stack">
<div class="stats-meta-row">
<span class="stats-meta-label">Legacy cards</span>
<strong>${qEscapeHtml(String(legacyStats.cardCount || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Invited</span>
<strong>${qEscapeHtml(String(legacyStats.invitedCount || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Current in group</span>
<strong>${qEscapeHtml(String(legacyStats.currentMinterCount || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Kicked / banned</span>
<strong>${qEscapeHtml(
String((legacyStats.kickedCount || 0) + (legacyStats.bannedCount || 0))
)}</strong>
</div>
<div class="stats-empty-state">
Legacy cards are published by the minter themselves. This section tracks
the publisher's group lifecycle so we can compare it against the new
nominator method.
</div>
</div>
`
}
const buildStatsAdminSectionNoteHtml = (snapshot = null) => {
const adminStats = snapshot?.adminStats || {}
const referenceData = snapshot?.referenceData || {}
if (!adminStats.cardCount) {
return `
<div class="stats-empty-state">
Admin-published cards will appear here once current roster data is available.
</div>
`
}
return `
<div class="stats-meta-stack">
<div class="stats-meta-row">
<span class="stats-meta-label">Admin-published cards</span>
<strong>${qEscapeHtml(String(adminStats.cardCount || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Legacy published</span>
<strong>${qEscapeHtml(String(adminStats.legacyCardCount || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Current-era published</span>
<strong>${qEscapeHtml(String(adminStats.currentCardCount || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Known admins</span>
<strong>${qEscapeHtml(
String(
Array.isArray(referenceData.minterAdminAddresses)
? referenceData.minterAdminAddresses.length
: 0
)
)}</strong>
</div>
<div class="stats-empty-state">
This section is organized around current Minter Admin membership and the
cards published by those accounts. It gives us a foundation for future
scoring and admin-specific leaderboards.
</div>
</div>
`
}
const buildStatsSourceResourceList = (resources = []) => {
const deduped = new Map()
for (const resource of Array.isArray(resources) ? resources : []) {
const normalized = normalizeStatsSourceResource(resource)
if (!normalized.name || !normalized.identifier) {
continue
}
const key = `${normalized.name}::${normalized.identifier}`
const current = deduped.get(key)
if (!current || getStatsBoardTimestamp(normalized) >= getStatsBoardTimestamp(current)) {
deduped.set(key, normalized)
}
}
return Array.from(deduped.values()).sort((a, b) => {
const timeDiff = getStatsBoardTimestamp(a) - getStatsBoardTimestamp(b)
if (timeDiff !== 0) {
return timeDiff
}
const identifierDiff = String(a.identifier || "").localeCompare(
String(b.identifier || "")
)
if (identifierDiff !== 0) {
return identifierDiff
}
return String(a.name || "").localeCompare(String(b.name || ""))
})
}
const sortStatsResourcesNewestFirst = (resources = []) =>
buildStatsSourceResourceList(resources).sort((a, b) => {
const timeDiff = getStatsBoardTimestamp(b) - getStatsBoardTimestamp(a)
if (timeDiff !== 0) {
return timeDiff
}
const identifierDiff = String(b.identifier || "").localeCompare(
String(a.identifier || "")
)
if (identifierDiff !== 0) {
return identifierDiff
}
return String(b.name || "").localeCompare(String(a.name || ""))
})
const isStatsProgressIdentifier = (identifier = "") =>
String(identifier || "")
.trim()
.startsWith(MINTER_STATS_PROGRESS_IDENTIFIER_PREFIX)
const fetchStatsBoardQdnJsonResource = async (resourceMeta = null) => {
if (!resourceMeta || !resourceMeta.name || !resourceMeta.identifier) {
return null
}
try {
const response = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: resourceMeta.name,
service: "BLOG_POST",
identifier: resourceMeta.identifier,
})
if (typeof response === "string") {
try {
return JSON.parse(response)
} catch (parseError) {
return response
}
}
return response || null
} catch (error) {
console.warn("Unable to load stats board QDN resource:", resourceMeta, error)
return null
}
}
const fetchLatestStatsSourceResource = async (force = false) => {
const now = Date.now()
if (
!force &&
statsBoardState.latestSourceResource &&
now - statsBoardState.latestSourceLoadedAt < STATS_BOARD_SYNC_CACHE_TTL_MS
) {
return statsBoardState.latestSourceResource
}
const resource = await searchSimple(
"BLOG_POST",
minterCardIdentifierPrefix,
"",
1,
0,
"",
true,
true,
0
).catch(() => null)
statsBoardState.latestSourceResource = resource || null
statsBoardState.latestSourceLoadedAt = now
return resource || null
}
const fetchLatestStatsProgressCheckpoint = async (
force = false,
adminAddressSet = null
) => {
const now = Date.now()
if (
!force &&
statsBoardState.latestProgressCheckpoint &&
now - statsBoardState.latestProgressLoadedAt < STATS_BOARD_SYNC_CACHE_TTL_MS
) {
return statsBoardState.latestProgressCheckpoint
}
const resource = await searchSimple(
"BLOG_POST",
MINTER_STATS_PROGRESS_IDENTIFIER_PREFIX,
"",
100,
0,
"",
true,
true,
0
).catch(() => null)
const verifiedAdminAddressSet =
adminAddressSet instanceof Set
? adminAddressSet
: (await fetchStatsBoardGroupData(force).catch(() => null))
?.allAdminAddressSet || new Set()
const progressResources = await filterAdminPublishedStatsResources(
buildStatsSourceResourceList(Array.isArray(resource) ? resource : resource ? [resource] : []),
verifiedAdminAddressSet
)
let checkpoint = null
for (const progressResource of progressResources) {
const payload = await fetchStatsBoardQdnJsonResource(progressResource)
const progressIdentifier = String(
payload?.progressIdentifier || progressResource?.identifier || ""
).trim()
const isValidProgressPayload = Boolean(
payload &&
typeof payload === "object" &&
(payload.compileType === "stats-progress" ||
isStatsProgressIdentifier(progressIdentifier)) &&
payload.source
)
if (!isValidProgressPayload) {
continue
}
checkpoint = {
...payload,
progressIdentifier,
}
break
}
statsBoardState.latestProgressCheckpoint = checkpoint || null
statsBoardState.latestProgressLoadedAt = now
return checkpoint || null
}
const normalizeStatsGroupMemberAddress = (member = {}) =>
String(member?.member || member?.address || "").trim()
const buildStatsGroupAddressSet = (members = []) =>
new Set(
(Array.isArray(members) ? members : [])
.map((member) => normalizeStatsGroupMemberAddress(member).toLowerCase())
.filter(Boolean)
)
const buildStatsCurrentMinterAddressSet = (referenceData = {}) =>
buildStatsGroupAddressSet([
...(Array.isArray(referenceData?.minterGroupAddresses)
? referenceData.minterGroupAddresses
: []),
...(Array.isArray(referenceData?.minterAdminAddresses)
? referenceData.minterAdminAddresses
: []),
])
const resolveStatsRecordCurrentMinterStatus = (
record = {},
currentMinterAddressSet = new Set()
) => {
const normalizedAddressSet =
currentMinterAddressSet instanceof Set ? currentMinterAddressSet : new Set()
const fallbackStatus = Boolean(record?.isConverted)
if (normalizedAddressSet.size === 0) {
return fallbackStatus
}
const targetAddress = String(
record?.isLegacy
? record?.nominatorAddress || ""
: record?.nomineeAddress || ""
).trim()
if (!targetAddress) {
return fallbackStatus
}
return normalizedAddressSet.has(targetAddress.toLowerCase())
}
const fetchStatsBoardGroupData = async (force = false) => {
const now = Date.now()
if (
!force &&
statsBoardState.latestGroupData &&
now - statsBoardState.latestGroupLoadedAt < STATS_GROUP_CACHE_TTL_MS
) {
return statsBoardState.latestGroupData
}
const [minterGroupMembers, minterAdmins, adminGroupMembers] = await Promise.all([
typeof fetchMinterGroupMembers === "function"
? fetchMinterGroupMembers().catch(() => [])
: Promise.resolve([]),
typeof fetchMinterGroupAdmins === "function"
? fetchMinterGroupAdmins().catch(() => [])
: Promise.resolve([]),
typeof fetchAllAdminGroupsMembers === "function"
? fetchAllAdminGroupsMembers().catch(() => [])
: Promise.resolve([]),
])
const allAdminMembers = [
...(Array.isArray(adminGroupMembers) ? adminGroupMembers : []),
...(Array.isArray(minterAdmins) ? minterAdmins : []),
]
const groupData = {
minterGroupMembers: Array.isArray(minterGroupMembers)
? minterGroupMembers
: [],
minterAdmins: Array.isArray(minterAdmins) ? minterAdmins : [],
adminGroupMembers: Array.isArray(adminGroupMembers) ? adminGroupMembers : [],
minterGroupAddressSet: buildStatsGroupAddressSet(minterGroupMembers),
minterAdminAddressSet: buildStatsGroupAddressSet(minterAdmins),
allAdminAddressSet: buildStatsGroupAddressSet(allAdminMembers),
loadedAt: now,
}
statsBoardState.latestGroupData = groupData
statsBoardState.latestGroupLoadedAt = now
return groupData
}
const fetchMinterGroupDetails = async () => {
try {
const response = await fetch(`${baseUrl}/groups/694`, {
method: "GET",
headers: { Accept: "application/json" },
})
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`)
}
const groupDetails = await response.json()
return groupDetails && typeof groupDetails === "object" ? groupDetails : null
} catch (error) {
console.error("Error fetching MINTER group details:", error)
return null
}
}
const fetchMinterOnlineLevels = async () => {
try {
const response = await fetch(`${baseUrl}/addresses/online/levels`, {
method: "GET",
headers: { Accept: "application/json" },
})
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`)
}
const levelData = await response.json()
if (!Array.isArray(levelData)) {
return []
}
return levelData
.map((row) => ({
level: Number(row?.level ?? 0),
count: Number(row?.count ?? 0),
}))
.filter((row) => Number.isFinite(row.level) && row.level >= 0)
.sort((a, b) => a.level - b.level)
} catch (error) {
console.error("Error fetching online minter levels:", error)
return []
}
}
const splitStatsLiveLevelRows = (onlineLevels = []) => {
const rowsByLevel = new Map()
let founderAccountCount = 0
for (const row of Array.isArray(onlineLevels) ? onlineLevels : []) {
const level = Number(row?.level ?? 0)
const count = Number(row?.count ?? 0)
if (!Number.isFinite(level) || level < 0) {
continue
}
if (level === STATS_FOUNDER_EFFECTIVE_LEVEL) {
founderAccountCount += count
continue
}
rowsByLevel.set(level, Number(rowsByLevel.get(level) || 0) + count)
}
const actualLevelRows = []
for (let level = 0; level < STATS_FOUNDER_EFFECTIVE_LEVEL; level += 1) {
actualLevelRows.push({
level,
count: Number(rowsByLevel.get(level) || 0),
})
}
return {
actualLevelRows,
founderAccountCount,
}
}
const fetchStatsBoardLiveMintingData = async (
force = false,
groupData = null
) => {
const now = Date.now()
if (
!force &&
statsBoardState.latestLiveMintingData &&
now - statsBoardState.latestLiveMintingLoadedAt < STATS_LIVE_CACHE_TTL_MS
) {
return statsBoardState.latestLiveMintingData
}
const resolvedGroupData =
groupData || (await fetchStatsBoardGroupData(force).catch(() => null))
const [groupDetails, onlineLevels] = await Promise.all([
fetchMinterGroupDetails().catch(() => null),
fetchMinterOnlineLevels().catch(() => []),
])
const hasOnlineLevelData = Array.isArray(onlineLevels) && onlineLevels.length > 0
const { actualLevelRows, founderAccountCount } = splitStatsLiveLevelRows(
onlineLevels
)
const totalOnlineMinters = actualLevelRows.reduce(
(total, row) => total + Number(row.count || 0),
0
) + Number(founderAccountCount || 0)
const actualLevelCount = actualLevelRows.filter(
(row) => Number(row.count || 0) > 0
).length
const highestActualLevel = actualLevelRows.reduce(
(highest, row) =>
Number(row.count || 0) > 0 ? Math.max(highest, Number(row.level || 0)) : highest,
-1
)
const founderAccountShare =
totalOnlineMinters > 0 ? founderAccountCount / totalOnlineMinters : 0
const liveMintingData = {
groupDetails: groupDetails || null,
memberCount: Number(
groupDetails?.memberCount ||
resolvedGroupData?.minterGroupMembers?.length ||
0
),
totalOnlineMinters,
actualLevelCount,
highestActualLevel,
activeLevelCount: actualLevelCount,
highestActiveLevel: highestActualLevel,
founderEffectiveLevel: STATS_FOUNDER_EFFECTIVE_LEVEL,
founderAccountCount,
founderAccountShare,
onlineLevelSourceCount: hasOnlineLevelData ? onlineLevels.length : 0,
actualLevelRows,
onlineLevels: actualLevelRows,
loadedAt: now,
}
statsBoardState.latestLiveMintingData = liveMintingData
statsBoardState.latestLiveMintingLoadedAt = now
return liveMintingData
}
const getStatsLiveMintingSummaryGrid = () =>
document.getElementById("stats-live-summary-grid")
const getStatsLiveMintingLevelsContainer = () =>
document.getElementById("stats-live-levels-container")
const getStatsLiveFounderAccountsContainer = () =>
document.getElementById("stats-live-founder-container")
const getStatsLiveMintingMetaContainer = () =>
document.getElementById("stats-live-group-meta-container")
const buildStatsLiveGroupMetaHtml = (liveMintingData = null) => {
const groupDetails = liveMintingData?.groupDetails || {}
const memberCount = Number(liveMintingData?.memberCount || 0)
if (!groupDetails || Object.keys(groupDetails).length === 0) {
return `
<div class="stats-empty-state">
Live MINTER group details are unavailable right now.
</div>
`
}
return `
<div class="stats-meta-stack">
<div class="stats-meta-row">
<span class="stats-meta-label">Group ID</span>
<strong>${qEscapeHtml(String(groupDetails.groupId ?? "694"))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Group name</span>
<strong>${qEscapeHtml(groupDetails.groupName || "MINTER")}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Owner</span>
<strong>${qEscapeHtml(groupDetails.owner || "Unavailable")}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Member count</span>
<strong>${qEscapeHtml(String(memberCount))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Approval threshold</span>
<strong>${qEscapeHtml(groupDetails.approvalThreshold || "Unavailable")}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Minimum block delay</span>
<strong>${qEscapeHtml(String(groupDetails.minimumBlockDelay ?? "Unavailable"))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Maximum block delay</span>
<strong>${qEscapeHtml(String(groupDetails.maximumBlockDelay ?? "Unavailable"))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Open</span>
<strong>${qEscapeHtml(groupDetails.isOpen ? "Yes" : "No")}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Created</span>
<strong>${qEscapeHtml(formatStatsDate(groupDetails.created || 0))}</strong>
</div>
<div class="stats-meta-row">
<span class="stats-meta-label">Updated</span>
<strong>${qEscapeHtml(formatStatsDate(groupDetails.updated || 0))}</strong>
</div>
${
groupDetails.description
? `
<div class="stats-empty-state">
${qEscapeHtml(groupDetails.description)}
</div>
`
: ""
}
</div>
`
}
const buildStatsLiveMintingLevelsHtml = (liveMintingData = null) => {
if (Number(liveMintingData?.onlineLevelSourceCount || 0) <= 0) {
return `
<div class="stats-empty-state">
No online level data was returned yet.
</div>
`
}
const rows = Array.isArray(liveMintingData?.actualLevelRows)
? liveMintingData.actualLevelRows
: []
const totalOnlineMinters = Number(liveMintingData?.totalOnlineMinters || 0)
return buildStatsFlexibleTableHtml({
rows,
emptyText: "No online level data was returned yet.",
headers: ["Actual level", "Online minters", "% of Total"],
rowRenderer: (row) => {
const count = Number(row.count || 0)
const percentOfTotal = totalOnlineMinters > 0 ? count / totalOnlineMinters : 0
return `
<tr>
<td>
<div class="stats-table-primary">Level ${qEscapeHtml(String(row.level || 0))}</div>
<div class="stats-table-secondary">Actual level from /addresses/online/levels</div>
</td>
<td>${qEscapeHtml(String(count))}</td>
<td>${qEscapeHtml(formatStatsPercent(percentOfTotal))}</td>
</tr>
`
},
})
}
const buildStatsLiveFounderAccountsHtml = (liveMintingData = null) => {
const founderAccountCount = Number(liveMintingData?.founderAccountCount || 0)
const totalOnlineMinters = Number(liveMintingData?.totalOnlineMinters || 0)
const founderAccountShare =
Number.isFinite(Number(liveMintingData?.founderAccountShare))
? Number(liveMintingData?.founderAccountShare || 0)
: totalOnlineMinters > 0
? founderAccountCount / totalOnlineMinters
: 0
const effectiveLevel = Number(
liveMintingData?.founderEffectiveLevel ?? STATS_FOUNDER_EFFECTIVE_LEVEL
)
return `
<div class="stats-legacy-chip-grid stats-live-founder-grid">
${buildStatsMetricCardHtml(
"Founder accounts",
founderAccountCount,
`Effective level ${effectiveLevel}`
)}
${buildStatsMetricCardHtml(
"% of total online",
formatStatsPercent(founderAccountShare),
"Count divided by all online minters"
)}
</div>
<div class="stats-empty-state">
Founder accounts are shown separately from the actual level table because
the level 10 value is an effective level, not a true minter level. That
keeps the actual level distribution honest until the lower levels are
fully represented. They are not given higher rewards, but they are given
a higher likelihood of being the block signer as an underlying added
security measure. Founder accounts are rewarded exactly the same way as
any other account.
</div>
`
}
const renderStatsLiveMintingSection = (liveMintingData = null) => {
const summaryGrid = getStatsLiveMintingSummaryGrid()
const levelsContainer = getStatsLiveMintingLevelsContainer()
const founderContainer = getStatsLiveFounderAccountsContainer()
const metaContainer = getStatsLiveMintingMetaContainer()
if (summaryGrid) {
summaryGrid.innerHTML = `
${buildStatsMetricCardHtml(
"MINTER Group Members",
Number(liveMintingData?.memberCount || 0),
"From /groups/694"
)}
${buildStatsMetricCardHtml(
"Currently active minters",
Number(liveMintingData?.totalOnlineMinters || 0),
"Summed from /addresses/online/levels, including founder accounts"
)}
${buildStatsMetricCardHtml(
"Actual levels reported",
Number(liveMintingData?.actualLevelCount || 0),
"Levels 0-9 with at least one online address"
)}
${buildStatsMetricCardHtml(
"Highest actual level",
Number(liveMintingData?.highestActualLevel ?? -1) >= 0
? `Level ${Number(liveMintingData?.highestActualLevel || 0)}`
: "None",
"Highest non-founder level with online activity"
)}
`
}
if (levelsContainer) {
levelsContainer.innerHTML = buildStatsLiveMintingLevelsHtml(liveMintingData)
}
if (founderContainer) {
founderContainer.innerHTML = buildStatsLiveFounderAccountsHtml(
liveMintingData
)
}
if (metaContainer) {
metaContainer.innerHTML = buildStatsLiveGroupMetaHtml(liveMintingData)
}
}
const createStatsCompileSession = ({
snapshotTimestamp = Date.now(),
sourceResources = [],
referenceData = {},
} = {}) => {
const normalizedSourceResources = buildStatsSourceResourceList(sourceResources)
const latestCardTimestamp = normalizedSourceResources.reduce(
(latestTimestamp, resource) =>
Math.max(latestTimestamp, getStatsBoardTimestamp(resource)),
0
)
return {
schemaVersion: 2,
compileType: "stats-progress",
progressIdentifier: getStatsCompileProgressIdentifier(snapshotTimestamp),
snapshotTimestamp,
createdAt: Date.now(),
updatedAt: Date.now(),
completed: false,
resumedFromProgress: false,
batchSize: STATS_COMPILE_BATCH_SIZE,
nextIndex: 0,
batchesProcessed: 0,
generatedBy: {
name: userState.accountName || "",
address: userState.accountAddress || "",
},
source: {
prefix: MINTER_STATS_IDENTIFIER_PREFIX,
cardCount: normalizedSourceResources.length,
legacyCutoff: NOMINATOR_METHOD_START_TS,
latestCardTimestamp,
resources: normalizedSourceResources,
},
referenceData: {
minterGroupAddresses: Array.isArray(referenceData.minterGroupAddresses)
? referenceData.minterGroupAddresses.map((value) =>
String(value || "").trim()
)
: [],
minterAdminAddresses: Array.isArray(referenceData.minterAdminAddresses)
? referenceData.minterAdminAddresses.map((value) =>
String(value || "").trim()
)
: [],
},
summary: {
totalNominations: 0,
uniqueNominators: 0,
uniqueNominees: 0,
totalConvertedToMinter: 0,
totalApprovedInvites: 0,
totalPendingInvites: 0,
totalKickedAndBanned: 0,
legacyCardCount: 0,
legacyConvertedToMinter: 0,
legacyApprovedInvites: 0,
legacyPendingInvites: 0,
legacyKickedAndBanned: 0,
},
uniqueNomineeKeys: {},
nominatorRowsByKey: {},
records: [],
validationIssueCount: 0,
validationIssues: [],
finalSnapshotIdentifier: "",
finalPublishedAt: 0,
}
}
const normalizeStatsCompileRecord = (record = {}) => ({
cardIdentifier: String(record.cardIdentifier || "").trim(),
createdAt: Number(record.createdAt || 0),
isLegacy: Boolean(record.isLegacy),
nomineeName: String(record.nomineeName || ""),
nomineeAddress: String(record.nomineeAddress || ""),
nominatorName: String(record.nominatorName || ""),
nominatorAddress: String(record.nominatorAddress || ""),
inviteDisplayStatus: String(record.inviteDisplayStatus || ""),
isConverted: Boolean(record.isConverted),
isApprovedInvite: Boolean(record.isApprovedInvite),
isPendingInvite: Boolean(record.isPendingInvite),
isKicked: Boolean(record.isKicked),
isBanned: Boolean(record.isBanned),
})
const resetStatsCompileSessionAggregates = (session = null) => {
if (!session) {
return null
}
session.summary = {
totalNominations: 0,
uniqueNominators: 0,
uniqueNominees: 0,
totalConvertedToMinter: 0,
totalApprovedInvites: 0,
totalPendingInvites: 0,
totalKickedAndBanned: 0,
legacyCardCount: 0,
legacyConvertedToMinter: 0,
legacyApprovedInvites: 0,
legacyPendingInvites: 0,
legacyKickedAndBanned: 0,
}
session.uniqueNomineeKeys = {}
session.nominatorRowsByKey = {}
session.records = []
return session
}
const rebuildStatsCompileSessionAggregates = (session = null, records = null) => {
if (!session) {
return null
}
const recordsToReplay = Array.isArray(records)
? records.map((record) => normalizeStatsCompileRecord(record))
: Array.isArray(session.records)
? session.records.map((record) => normalizeStatsCompileRecord(record))
: []
resetStatsCompileSessionAggregates(session)
recordsToReplay.forEach((record) => addStatsCompileRecordToSession(session, record))
return session
}
const normalizeStatsResourcePublisherAddress = (payload = null) =>
String(
payload?.generatedBy?.address ||
payload?.generatedByAddress ||
payload?.publishedByAddress ||
payload?.publishedBy?.address ||
""
).trim()
const isStatsResourcePublishedByAdmin = (
payload = null,
adminAddressSet = new Set()
) => {
const normalizedAddress = normalizeStatsResourcePublisherAddress(payload).toLowerCase()
return Boolean(normalizedAddress) && adminAddressSet instanceof Set && adminAddressSet.has(normalizedAddress)
}
const hydrateStatsCompileSession = (checkpoint = null) => {
if (!checkpoint) {
return null
}
const sourceResources = buildStatsSourceResourceList(
checkpoint?.source?.resources || []
)
const session = createStatsCompileSession({
snapshotTimestamp:
Number(checkpoint.snapshotTimestamp || checkpoint.generatedAt || Date.now()) ||
Date.now(),
sourceResources,
})
session.progressIdentifier = String(
checkpoint.progressIdentifier || session.progressIdentifier || ""
).trim()
session.createdAt = Number(checkpoint.createdAt || checkpoint.generatedAt || Date.now())
session.updatedAt = Number(checkpoint.updatedAt || checkpoint.compiledAt || Date.now())
session.completed = Boolean(checkpoint.completed)
session.resumedFromProgress = true
session.batchSize = Number(checkpoint.batchSize || session.batchSize) || STATS_COMPILE_BATCH_SIZE
session.nextIndex = Math.max(0, Number(checkpoint.nextIndex || 0))
session.batchesProcessed = Math.max(0, Number(checkpoint.batchesProcessed || 0))
session.generatedBy = {
name: String(checkpoint.generatedBy?.name || "").trim(),
address: String(checkpoint.generatedBy?.address || "").trim(),
}
session.finalSnapshotIdentifier = String(checkpoint.finalSnapshotIdentifier || "")
session.finalPublishedAt = Number(checkpoint.finalPublishedAt || 0)
session.validationIssueCount = Math.max(
0,
Number(checkpoint.validationIssueCount || 0)
)
session.validationIssues = Array.isArray(checkpoint.validationIssues)
? checkpoint.validationIssues.map((issue) => ({
identifier: String(issue?.identifier || "").trim(),
reason: String(issue?.reason || "").trim(),
detail: String(issue?.detail || "").trim(),
}))
: []
session.records = Array.isArray(checkpoint.records)
? checkpoint.records.map((record) => normalizeStatsCompileRecord(record))
: []
session.referenceData = {
minterGroupAddresses: Array.isArray(checkpoint.referenceData?.minterGroupAddresses)
? checkpoint.referenceData.minterGroupAddresses.map((value) =>
String(value || "").trim()
)
: [],
minterAdminAddresses: Array.isArray(checkpoint.referenceData?.minterAdminAddresses)
? checkpoint.referenceData.minterAdminAddresses.map((value) =>
String(value || "").trim()
)
: [],
}
const normalizedSummary = checkpoint.summary || {}
session.summary = {
...session.summary,
totalNominations: Number(normalizedSummary.totalNominations || 0),
uniqueNominators: Number(normalizedSummary.uniqueNominators || 0),
uniqueNominees: Number(normalizedSummary.uniqueNominees || 0),
totalConvertedToMinter: Number(normalizedSummary.totalConvertedToMinter || 0),
totalApprovedInvites: Number(normalizedSummary.totalApprovedInvites || 0),
totalPendingInvites: Number(normalizedSummary.totalPendingInvites || 0),
totalKickedAndBanned: Number(normalizedSummary.totalKickedAndBanned || 0),
legacyCardCount: Number(normalizedSummary.legacyCardCount || 0),
legacyConvertedToMinter: Number(normalizedSummary.legacyConvertedToMinter || 0),
legacyApprovedInvites: Number(normalizedSummary.legacyApprovedInvites || 0),
legacyPendingInvites: Number(normalizedSummary.legacyPendingInvites || 0),
legacyKickedAndBanned: Number(normalizedSummary.legacyKickedAndBanned || 0),
}
session.uniqueNomineeKeys = Array.isArray(checkpoint.uniqueNomineeKeys)
? checkpoint.uniqueNomineeKeys.reduce((acc, key) => {
const normalizedKey = String(key || "").trim()
if (normalizedKey) {
acc[normalizedKey] = true
}
return acc
}, {})
: {
...(checkpoint.uniqueNomineeKeys || {}),
}
session.nominatorRowsByKey = {
...(checkpoint.nominatorRowsByKey || {}),
}
if (session.records.length > 0) {
rebuildStatsCompileSessionAggregates(session)
}
return session
}
const serializeStatsCompileSession = (session = null) => {
if (!session) {
return null
}
return {
schemaVersion: Number(session.schemaVersion || 2),
compileType: "stats-progress",
progressIdentifier: String(session.progressIdentifier || ""),
snapshotTimestamp: Number(session.snapshotTimestamp || Date.now()),
createdAt: Number(session.createdAt || Date.now()),
updatedAt: Number(session.updatedAt || Date.now()),
completed: Boolean(session.completed),
resumedFromProgress: Boolean(session.resumedFromProgress),
batchSize: Number(session.batchSize || STATS_COMPILE_BATCH_SIZE),
nextIndex: Number(session.nextIndex || 0),
batchesProcessed: Number(session.batchesProcessed || 0),
generatedBy: {
name: String(session.generatedBy?.name || userState.accountName || "").trim(),
address: String(
session.generatedBy?.address || userState.accountAddress || ""
).trim(),
},
source: {
prefix: String(session.source?.prefix || MINTER_STATS_IDENTIFIER_PREFIX),
cardCount: Number(session.source?.cardCount || 0),
legacyCutoff: Number(
session.source?.legacyCutoff || NOMINATOR_METHOD_START_TS
),
latestCardTimestamp: Number(session.source?.latestCardTimestamp || 0),
resources: Array.isArray(session.source?.resources)
? session.source.resources.map((resource) => normalizeStatsSourceResource(resource))
: [],
},
referenceData: {
minterGroupAddresses: Array.isArray(
session.referenceData?.minterGroupAddresses
)
? session.referenceData.minterGroupAddresses.map((value) =>
String(value || "").trim()
)
: [],
minterAdminAddresses: Array.isArray(
session.referenceData?.minterAdminAddresses
)
? session.referenceData.minterAdminAddresses.map((value) =>
String(value || "").trim()
)
: [],
},
summary: {
totalNominations: Number(session.summary?.totalNominations || 0),
uniqueNominators: Number(session.summary?.uniqueNominators || 0),
uniqueNominees: Number(session.summary?.uniqueNominees || 0),
totalConvertedToMinter: Number(session.summary?.totalConvertedToMinter || 0),
totalApprovedInvites: Number(session.summary?.totalApprovedInvites || 0),
totalPendingInvites: Number(session.summary?.totalPendingInvites || 0),
totalKickedAndBanned: Number(session.summary?.totalKickedAndBanned || 0),
legacyCardCount: Number(session.summary?.legacyCardCount || 0),
legacyConvertedToMinter: Number(session.summary?.legacyConvertedToMinter || 0),
legacyApprovedInvites: Number(session.summary?.legacyApprovedInvites || 0),
legacyPendingInvites: Number(session.summary?.legacyPendingInvites || 0),
legacyKickedAndBanned: Number(session.summary?.legacyKickedAndBanned || 0),
},
uniqueNomineeKeys: {
...(session.uniqueNomineeKeys || {}),
},
nominatorRowsByKey: {
...(session.nominatorRowsByKey || {}),
},
records: Array.isArray(session.records)
? session.records.map((record) => normalizeStatsCompileRecord(record))
: [],
validationIssueCount: Number(session.validationIssueCount || 0),
validationIssues: Array.isArray(session.validationIssues)
? session.validationIssues.map((issue) => ({
identifier: String(issue?.identifier || "").trim(),
reason: String(issue?.reason || "").trim(),
detail: String(issue?.detail || "").trim(),
}))
: [],
finalSnapshotIdentifier: String(session.finalSnapshotIdentifier || ""),
finalPublishedAt: Number(session.finalPublishedAt || 0),
}
}
const buildStatsPublishResourceFromPayload = async (
identifier = "",
payload = null
) => {
if (!identifier || !payload || !userState.accountName) {
return null
}
const data64 = (await objectToBase64(payload)) || btoa(JSON.stringify(payload))
return {
name: userState.accountName,
service: "BLOG_POST",
identifier,
data64,
}
}
const publishStatsResources = async (resources = []) => {
const publishList = Array.isArray(resources) ? resources.filter(Boolean) : []
if (publishList.length === 0) {
return []
}
if (typeof publishMultipleResources === "function") {
const response = await publishMultipleResources(publishList)
if (!response) {
throw new Error("QDN multi-resource publish failed.")
}
} else {
for (const resource of publishList) {
await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: resource.name,
service: resource.service,
identifier: resource.identifier,
data64: resource.data64,
})
}
}
return publishList.map((resource) => resource.identifier)
}
const filterAdminPublishedStatsResources = async (
resources = [],
adminAddressSet = new Set()
) => {
const normalizedResources = buildStatsSourceResourceList(resources)
const normalizedAdminAddressSet =
adminAddressSet instanceof Set ? adminAddressSet : new Set()
if (normalizedResources.length === 0 || normalizedAdminAddressSet.size === 0) {
return []
}
const tasks = normalizedResources.map((resource) => async () => {
const payload = await fetchStatsBoardQdnJsonResource(resource)
return isStatsResourcePublishedByAdmin(payload, normalizedAdminAddressSet)
? resource
: null
})
const verifiedResources =
typeof runWithConcurrency === "function"
? await runWithConcurrency(tasks, 5)
: await Promise.all(tasks.map((task) => task()))
return sortStatsResourcesNewestFirst(verifiedResources.filter(Boolean))
}
const buildStatsSnapshotPublishResource = async (
snapshot = null,
publishTimestamp = snapshot?.generatedAt || Date.now()
) => {
if (!snapshot) {
return null
}
const identifier = `${MINTER_STATS_IDENTIFIER_PREFIX}-${publishTimestamp}`
return buildStatsPublishResourceFromPayload(identifier, snapshot)
}
const buildStatsCompileCheckpointPublishResource = async (session = null) => {
if (!session) {
return null
}
const payload = serializeStatsCompileSession(session)
if (!payload) {
return null
}
const identifier = String(
payload.progressIdentifier || session.progressIdentifier || ""
).trim()
return buildStatsPublishResourceFromPayload(identifier, payload)
}
const publishStatsSnapshotBundle = async (
snapshot = null,
session = null,
{ publishTimestamp = snapshot?.generatedAt || Date.now() } = {}
) => {
if (!snapshot) {
return {
snapshotIdentifier: "",
checkpointIdentifier: "",
}
}
if (session) {
session.generatedBy = {
name: userState.accountName || "",
address: userState.accountAddress || "",
}
}
const checkpointPayload = session ? serializeStatsCompileSession(session) : null
const [snapshotResource, checkpointResource] = await Promise.all([
buildStatsSnapshotPublishResource(snapshot, publishTimestamp),
checkpointPayload
? buildStatsPublishResourceFromPayload(
String(checkpointPayload.progressIdentifier || session?.progressIdentifier || "").trim(),
checkpointPayload
)
: null,
])
const identifiers = await publishStatsResources([snapshotResource, checkpointResource])
statsBoardState.snapshotResources = []
statsBoardState.lastLoadedAt = 0
if (checkpointPayload) {
statsBoardState.latestProgressCheckpoint = checkpointPayload
statsBoardState.latestProgressLoadedAt = Date.now()
}
await fetchLatestPublishedStatsSnapshot(true)
return {
snapshotIdentifier:
identifiers[0] || snapshotResource?.identifier || "",
checkpointIdentifier:
identifiers[1] || checkpointResource?.identifier || "",
}
}
const addStatsCompileRecordToSession = (session = null, record = null) => {
if (!session || !record) {
return
}
const normalizedRecord = normalizeStatsCompileRecord(record)
session.records.push(normalizedRecord)
if (normalizedRecord.isLegacy) {
session.summary.legacyCardCount += 1
if (normalizedRecord.isConverted) {
session.summary.legacyConvertedToMinter += 1
}
if (normalizedRecord.isApprovedInvite) {
session.summary.legacyApprovedInvites += 1
}
if (normalizedRecord.isPendingInvite) {
session.summary.legacyPendingInvites += 1
}
if (normalizedRecord.isKicked || normalizedRecord.isBanned) {
session.summary.legacyKickedAndBanned += 1
}
return
}
session.summary.totalNominations += 1
if (normalizedRecord.isConverted) {
session.summary.totalConvertedToMinter += 1
}
if (normalizedRecord.isApprovedInvite) {
session.summary.totalApprovedInvites += 1
}
if (normalizedRecord.isPendingInvite) {
session.summary.totalPendingInvites += 1
}
if (normalizedRecord.isKicked || normalizedRecord.isBanned) {
session.summary.totalKickedAndBanned += 1
}
const nomineeKey = normalizedRecord.nomineeAddress || normalizedRecord.nomineeName.toLowerCase()
if (nomineeKey && !session.uniqueNomineeKeys[nomineeKey]) {
session.uniqueNomineeKeys[nomineeKey] = true
session.summary.uniqueNominees += 1
}
const nominatorKey =
normalizedRecord.nominatorAddress || normalizedRecord.nominatorName.toLowerCase()
if (!nominatorKey) {
return
}
let row = session.nominatorRowsByKey[nominatorKey]
if (!row) {
row = {
key: nominatorKey,
displayName: normalizedRecord.nominatorName || "Unknown",
address: normalizedRecord.nominatorAddress || "",
nominationCount: 0,
convertedCount: 0,
approvedCount: 0,
pendingCount: 0,
kickedCount: 0,
bannedCount: 0,
lastNominationAt: 0,
}
session.nominatorRowsByKey[nominatorKey] = row
session.summary.uniqueNominators += 1
}
row.nominationCount += 1
if (normalizedRecord.isConverted) {
row.convertedCount += 1
}
if (normalizedRecord.isApprovedInvite) {
row.approvedCount += 1
}
if (normalizedRecord.isPendingInvite) {
row.pendingCount += 1
}
if (normalizedRecord.isKicked) {
row.kickedCount += 1
}
if (normalizedRecord.isBanned) {
row.bannedCount += 1
}
row.lastNominationAt = Math.max(row.lastNominationAt || 0, normalizedRecord.createdAt || 0)
}
const buildStatsResumableCheckpointFromRecords = ({
checkpoint = null,
sourceResources = [],
records = [],
nextIndex = 0,
} = {}) => {
if (!checkpoint) {
return null
}
const normalizedSourceResources = buildStatsSourceResourceList(sourceResources)
const session = createStatsCompileSession({
snapshotTimestamp:
Number(checkpoint.snapshotTimestamp || checkpoint.generatedAt || Date.now()) ||
Date.now(),
sourceResources: normalizedSourceResources,
referenceData: {
minterGroupAddresses: [],
minterAdminAddresses: [],
},
})
session.progressIdentifier = String(
checkpoint.progressIdentifier || session.progressIdentifier || ""
).trim()
session.createdAt = Number(checkpoint.createdAt || checkpoint.generatedAt || Date.now())
session.updatedAt = Number(checkpoint.updatedAt || checkpoint.compiledAt || Date.now())
session.completed = false
session.resumedFromProgress = true
session.batchSize = Number(checkpoint.batchSize || session.batchSize) || STATS_COMPILE_BATCH_SIZE
session.nextIndex = Math.max(
0,
Math.min(Number(nextIndex || 0), normalizedSourceResources.length)
)
session.batchesProcessed = Math.max(0, Number(checkpoint.batchesProcessed || 0))
session.generatedBy = {
name: String(checkpoint.generatedBy?.name || "").trim(),
address: String(checkpoint.generatedBy?.address || "").trim(),
}
session.validationIssueCount = Math.max(
0,
Number(checkpoint.validationIssueCount || 0)
)
session.validationIssues = Array.isArray(checkpoint.validationIssues)
? checkpoint.validationIssues.map((issue) => ({
identifier: String(issue?.identifier || "").trim(),
reason: String(issue?.reason || "").trim(),
detail: String(issue?.detail || "").trim(),
}))
: []
session.finalSnapshotIdentifier = ""
session.finalPublishedAt = 0
session.records = []
session.uniqueNomineeKeys = {}
session.nominatorRowsByKey = {}
session.summary = {
totalNominations: 0,
uniqueNominators: 0,
uniqueNominees: 0,
totalConvertedToMinter: 0,
totalApprovedInvites: 0,
totalPendingInvites: 0,
totalKickedAndBanned: 0,
legacyCardCount: 0,
legacyConvertedToMinter: 0,
legacyApprovedInvites: 0,
legacyPendingInvites: 0,
legacyKickedAndBanned: 0,
}
const normalizedRecords = Array.isArray(records) ? records : []
normalizedRecords.forEach((record) => addStatsCompileRecordToSession(session, record))
return serializeStatsCompileSession(session)
}
const buildStatsSnapshotFromSession = (session = null) => {
if (!session) {
return null
}
rebuildStatsCompileSessionAggregates(session)
const rollups = buildStatsSnapshotRollups(session)
const currentCards = Array.isArray(session.records)
? session.records.filter((record) => !record.isLegacy)
: []
const legacyCards = Array.isArray(session.records)
? session.records.filter((record) => record.isLegacy)
: []
const legacyPublisherSummary = rollups.legacyPublisherSummary || {}
const legacyPublisherRows = Array.isArray(rollups.legacyPublisherRows)
? rollups.legacyPublisherRows
: []
const adminSummary = rollups.adminSummary || {}
const adminPublisherRows = Array.isArray(rollups.adminPublisherRows)
? rollups.adminPublisherRows
: []
const referenceData = {
minterGroupAddresses: Array.isArray(session.referenceData?.minterGroupAddresses)
? session.referenceData.minterGroupAddresses.map((value) =>
String(value || "").trim()
)
: [],
minterAdminAddresses: Array.isArray(
session.referenceData?.minterAdminAddresses
)
? session.referenceData.minterAdminAddresses.map((value) =>
String(value || "").trim()
)
: [],
}
const currentMinterAddressSet = buildStatsCurrentMinterAddressSet(referenceData)
const uniqueNomineeKeys = new Set()
const nominatorRowsByKey = {}
currentCards.forEach((record) => {
const nomineeKey = String(
record.nomineeAddress || record.nomineeName || ""
).toLowerCase()
if (nomineeKey) {
uniqueNomineeKeys.add(nomineeKey)
}
const nominatorKey = String(
record.nominatorAddress || record.nominatorName || ""
).toLowerCase()
if (!nominatorKey) {
return
}
const row =
nominatorRowsByKey[nominatorKey] || {
key: nominatorKey,
displayName: record.nominatorName || "Unknown",
address: record.nominatorAddress || "",
nominationCount: 0,
convertedCount: 0,
approvedCount: 0,
pendingCount: 0,
kickedCount: 0,
bannedCount: 0,
lastNominationAt: 0,
}
row.nominationCount += 1
if (resolveStatsRecordCurrentMinterStatus(record, currentMinterAddressSet)) {
row.convertedCount += 1
}
if (record.isApprovedInvite) {
row.approvedCount += 1
}
if (record.isPendingInvite) {
row.pendingCount += 1
}
if (record.isKicked) {
row.kickedCount += 1
}
if (record.isBanned) {
row.bannedCount += 1
}
row.lastNominationAt = Math.max(row.lastNominationAt || 0, record.createdAt || 0)
nominatorRowsByKey[nominatorKey] = row
})
const nominators = Object.values(nominatorRowsByKey)
.map((row) => ({
...row,
conversionLabel: formatStatsPercent(
row.nominationCount > 0 ? row.convertedCount / row.nominationCount : 0
),
}))
.sort((a, b) => {
if (b.nominationCount !== a.nominationCount) {
return b.nominationCount - a.nominationCount
}
if (b.convertedCount !== a.convertedCount) {
return b.convertedCount - a.convertedCount
}
return b.lastNominationAt - a.lastNominationAt
})
const uniqueNominators = nominators.length
const uniqueNominees = uniqueNomineeKeys.size
const currentConvertedToMinter = currentCards.filter((record) =>
resolveStatsRecordCurrentMinterStatus(record, currentMinterAddressSet)
).length
const currentApprovedInvites = currentCards.filter(
(record) => record.isApprovedInvite
).length
const currentPendingInvites = currentCards.filter(
(record) => record.isPendingInvite
).length
const currentKickedAndBanned = currentCards.filter(
(record) => record.isKicked || record.isBanned
).length
return {
schemaVersion: 2,
generatedAt: Number(session.snapshotTimestamp || Date.now()),
compiledAt: Date.now(),
generatedBy: {
name: userState.accountName || "",
address: userState.accountAddress || "",
},
compile: {
completed: Boolean(session.completed),
resumedFromProgress: Boolean(session.resumedFromProgress),
checkpointIdentifier: String(session.progressIdentifier || ""),
batchesProcessed: Number(session.batchesProcessed || 0),
batchSize: Number(session.batchSize || STATS_COMPILE_BATCH_SIZE),
finishedAt: session.completed ? Date.now() : 0,
},
source: {
prefix: MINTER_STATS_IDENTIFIER_PREFIX,
cardCount: Number(session.source?.cardCount || 0),
legacyCutoff: Number(
session.source?.legacyCutoff || NOMINATOR_METHOD_START_TS
),
latestCardTimestamp: Number(session.source?.latestCardTimestamp || 0),
progressIdentifier: String(session.progressIdentifier || ""),
},
referenceData,
summary: {
totalNominations: Number(currentCards.length || 0),
uniqueNominators,
uniqueNominees,
totalConvertedToMinter: Number(currentConvertedToMinter || 0),
totalApprovedInvites: Number(currentApprovedInvites || 0),
totalPendingInvites: Number(currentPendingInvites || 0),
totalKickedAndBanned: Number(currentKickedAndBanned || 0),
legacyCardCount: Number(legacyCards.length || 0),
legacyConvertedToMinter: Number(
legacyPublisherSummary.currentMinterCount || 0
),
legacyApprovedInvites: Number(legacyPublisherSummary.invitedCount || 0),
legacyPendingInvites: Number(legacyPublisherSummary.pendingCount || 0),
legacyKickedAndBanned: Number(
(legacyPublisherSummary.kickedCount || 0) +
(legacyPublisherSummary.bannedCount || 0)
),
conversionLabel: formatStatsPercent(
currentCards.length > 0
? Number(currentConvertedToMinter || 0) / currentCards.length
: 0
),
legacyConversionLabel: formatStatsPercent(
legacyCards.length > 0
? Number(legacyPublisherSummary.currentMinterCount || 0) / legacyCards.length
: 0
),
},
nominators,
legacyCards,
cards: currentCards,
legacyStats: {
cardCount: Number(legacyPublisherSummary.totalCards || legacyCards.length || 0),
publisherCount: Number(legacyPublisherRows.length || 0),
invitedCount: Number(legacyPublisherSummary.invitedCount || 0),
pendingCount: Number(legacyPublisherSummary.pendingCount || 0),
currentMinterCount: Number(legacyPublisherSummary.currentMinterCount || 0),
kickedCount: Number(legacyPublisherSummary.kickedCount || 0),
bannedCount: Number(legacyPublisherSummary.bannedCount || 0),
leaderboard: legacyPublisherRows,
invitedLabel: formatStatsPercent(
legacyCards.length > 0
? Number(legacyPublisherSummary.invitedCount || 0) / legacyCards.length
: 0
),
currentLabel: formatStatsPercent(
legacyCards.length > 0
? Number(legacyPublisherSummary.currentMinterCount || 0) / legacyCards.length
: 0
),
},
adminStats: {
cardCount: Number(adminSummary.cardCount || 0),
publisherCount: Number(adminSummary.publisherCount || 0),
legacyCardCount: Number(adminSummary.legacyCardCount || 0),
currentCardCount: Number(adminSummary.currentCardCount || 0),
currentMinterCount: Number(adminSummary.currentMinterCount || 0),
leaderboard: adminPublisherRows,
currentLabel: formatStatsPercent(
Number(adminSummary.cardCount || 0) > 0
? Number(adminSummary.currentMinterCount || 0) /
Number(adminSummary.cardCount || 0)
: 0
),
},
}
}
const publishStatsCompileCheckpoint = async (session = null) => {
if (!session) {
return null
}
if (!getStatsBoardCanPublish()) {
alert("Only Minter Admins and App Admins can publish stats snapshots.")
return null
}
if (!userState.accountName) {
alert("A registered name is required to publish stats snapshots.")
return null
}
session.generatedBy = {
name: userState.accountName || "",
address: userState.accountAddress || "",
}
const resource = await buildStatsCompileCheckpointPublishResource(session)
if (!resource) {
return null
}
await publishStatsResources([resource])
const payload = serializeStatsCompileSession(session)
statsBoardState.latestProgressCheckpoint = payload
statsBoardState.latestProgressLoadedAt = Date.now()
return resource.identifier
}
const compileStatsBoardRecord = async (resource = {}, { onIssue = null } = {}) => {
const emitIssue = (reason = "", detail = "") => {
if (typeof onIssue === "function") {
onIssue({
identifier: String(resource?.identifier || "").trim(),
reason: String(reason || "").trim(),
detail: String(detail || "").trim(),
})
}
}
try {
const cardData = await fetchMinterBoardCardDataCached(resource)
if (!cardData || !cardData.poll) {
emitIssue("missing-poll", "Unable to load poll data for this card.")
return null
}
const eraClassification = classifyStatsBoardEra(resource, cardData)
const createdAt = Number(eraClassification.createdAt || 0)
const isLegacy = Boolean(eraClassification.isLegacy)
const publisherName = getStatsBoardNominatorName(
cardData,
cardData.publishedBy || resource.name || ""
)
const publisherAddress = getStatsBoardNominatorAddress(
cardData,
cardData.publishedByAddress || ""
)
const nomineeName = getStatsBoardNomineeName(cardData)
const nominatorName = getStatsBoardNominatorName(
cardData,
cardData.publishedBy || resource.name || ""
)
const nominatorAddress = publisherAddress
const nomineeAddress = isLegacy
? publisherAddress || getStatsBoardNomineeAddress(cardData, "")
: typeof resolveCardNomineeAddress === "function"
? await resolveCardNomineeAddress(resource, cardData).catch(() => "")
: getStatsBoardNomineeAddress(cardData, "")
const targetName = isLegacy ? publisherName : nomineeName
const targetAddress = isLegacy ? publisherAddress : nomineeAddress
if (!targetName && !targetAddress) {
emitIssue(
"missing-target",
"Unable to resolve a target publisher or nominee address for stats classification."
)
}
let inviteDisplayStatus = ""
let isApprovedInvite = false
let isPendingInvite = false
let isKicked = false
let isBanned = false
if (targetName || targetAddress) {
const inviteState = await resolveMinterBoardListTimelineState(
targetAddress || "",
targetName || "",
true
).catch(() => ({
displayStatus: "",
hasApprovedInvite: false,
hasPendingInvite: false,
hasKicked: false,
hasBanned: false,
}))
inviteDisplayStatus =
inviteState.displayStatus ||
getMinterBoardInviteDisplayStatus(inviteState) ||
""
isApprovedInvite = inviteDisplayStatus === "invited"
isPendingInvite = inviteDisplayStatus === "pending"
isKicked = inviteDisplayStatus === "kicked"
isBanned = inviteDisplayStatus === "banned"
}
const convertedLookupKey = targetAddress || targetName || resource.name || ""
const isConverted = convertedLookupKey
? await verifyMinterCached(convertedLookupKey).catch(() => false)
: false
return {
cardIdentifier: resource.identifier || "",
createdAt,
isLegacy,
publisherName,
publisherAddress,
nomineeName,
nomineeAddress,
nominatorName,
nominatorAddress,
inviteDisplayStatus,
isConverted,
isApprovedInvite,
isPendingInvite,
isKicked,
isBanned,
}
} catch (error) {
emitIssue(
"compile-error",
String(error?.message || error || "Unable to compile this card.")
)
console.warn("Unable to compile stats record:", resource?.identifier, error)
return null
}
}
const processStatsCompileBatch = async (
session = null,
{ onProgress = null, onIssue = null } = {}
) => {
if (!session) {
return null
}
const emitProgress = (update = {}) => {
if (typeof onProgress === "function") {
onProgress(update)
}
}
const totalResources = Array.isArray(session.source?.resources)
? session.source.resources.length
: 0
if (totalResources <= 0) {
session.completed = true
session.summary.uniqueNominators = Object.keys(session.nominatorRowsByKey || {}).length
session.summary.uniqueNominees = Object.keys(session.uniqueNomineeKeys || {}).length
emitProgress({
key: "load-source",
status: "done",
detail: "No cards were found for compilation.",
})
emitProgress({
key: "classify-era",
status: "done",
detail: "No records needed classification.",
})
emitProgress({
key: "aggregate",
status: "done",
detail: "No leaderboard rows were built.",
})
return session
}
if (session.nextIndex >= totalResources) {
session.completed = true
session.summary.uniqueNominators = Object.keys(session.nominatorRowsByKey || {}).length
session.summary.uniqueNominees = Object.keys(session.uniqueNomineeKeys || {}).length
return session
}
const batchStartIndex = Math.max(0, Number(session.nextIndex || 0))
const batchEndIndex = Math.min(
totalResources,
batchStartIndex + Math.max(1, Number(session.batchSize || STATS_COMPILE_BATCH_SIZE))
)
const batchResources = session.source.resources.slice(batchStartIndex, batchEndIndex)
emitProgress({
key: "classify-era",
status: "active",
detail:
batchResources.length > 0
? `Classifying card data by publish date and payload markers (${batchStartIndex + 1}-${batchEndIndex} of ${totalResources}).`
: "No cards left to classify.",
})
const tasks = batchResources.map(
(resource) => async () =>
compileStatsBoardRecord(resource, {
onIssue,
})
)
const batchRecords = (await runWithConcurrency(tasks, 5)).filter(Boolean)
batchRecords.forEach((record) => addStatsCompileRecordToSession(session, record))
const failedRecords = Math.max(batchResources.length - batchRecords.length, 0)
if (failedRecords > 0) {
session.validationIssueCount = Number(session.validationIssueCount || 0) + failedRecords
}
session.nextIndex = batchEndIndex
session.batchesProcessed = Number(session.batchesProcessed || 0) + 1
session.updatedAt = Date.now()
session.completed = session.nextIndex >= totalResources
session.summary.uniqueNominators = Object.keys(session.nominatorRowsByKey || {}).length
session.summary.uniqueNominees = Object.keys(session.uniqueNomineeKeys || {}).length
emitProgress({
key: "classify-era",
status: "done",
detail: `Processed ${session.nextIndex}/${totalResources} cards.`,
})
emitProgress({
key: "aggregate",
status: session.completed ? "done" : "active",
detail:
session.summary.totalNominations > 0
? session.completed
? `Built ${session.summary.uniqueNominators || 0} nominator rows and ${session.summary.legacyCardCount || 0} legacy cards.`
: `Built ${session.summary.uniqueNominators || 0} nominator rows and ${session.summary.legacyCardCount || 0} legacy cards so far.`
: "No nominator-era rows were built yet.",
})
return session
}
const ensureStatsCompileModal = () => {
if (document.getElementById(`${STATS_COMPILE_MODAL_TYPE}-modal`)) {
return
}
if (typeof createModal === "function") {
createModal(STATS_COMPILE_MODAL_TYPE)
}
}
const closeStatsCompileModal = () => {
if (typeof closeModal === "function") {
closeModal(STATS_COMPILE_MODAL_TYPE)
}
if (
statsBoardState.compiling ||
statsCompileModalState.phase === "progress" ||
statsCompileModalState.phase === "paused"
) {
statsCompileModalState.hidden = true
renderStatsCompileWorkflowBanner()
return
}
statsCompileModalState.hidden = false
statsCompileModalState.phase = "options"
statsCompileModalState.workflow = "compile"
statsCompileModalState.timestampMode = "now"
statsCompileModalState.customTimestampInput = formatStatsTimestampInputValue()
statsCompileModalState.snapshotTimestamp = Date.now()
statsCompileModalState.identifierPreview = ""
statsCompileModalState.message = ""
statsCompileModalState.subtitle = ""
statsCompileModalState.steps = []
statsCompileModalState.errorMessage = ""
statsCompileModalState.validationReport = null
}
const updateStatsCompileModalPreview = () => {
const modeEl = getStatsCompileTimestampModeEl()
const customEl = getStatsCompileCustomTimestampEl()
const customWrap = document.getElementById("stats-compile-custom-wrap")
const previewEl = getStatsCompileIdentifierPreviewEl()
const statusEl = getStatsCompileStatusEl()
const noteEl = getStatsCompileNoteEl()
if (modeEl) {
statsCompileModalState.timestampMode = String(modeEl.value || "now")
}
if (customEl) {
statsCompileModalState.customTimestampInput = String(customEl.value || "")
}
const resolvedTimestamp = resolveStatsCompileTimestamp()
statsCompileModalState.snapshotTimestamp = resolvedTimestamp
statsCompileModalState.identifierPreview = `${MINTER_STATS_IDENTIFIER_PREFIX}-${resolvedTimestamp}`
if (customWrap) {
customWrap.style.display =
statsCompileModalState.timestampMode === "custom" ? "flex" : "none"
}
if (previewEl) {
previewEl.textContent = statsCompileModalState.identifierPreview
}
if (statusEl) {
statusEl.textContent =
statsCompileModalState.timestampMode === "custom"
? "Custom timestamps backdate the snapshot label only; they do not schedule a future publish."
: statsCompileModalState.timestampMode === "latest"
? "The latest snapshot timestamp will be reused when available."
: "The selected timestamp will be baked into the snapshot identifier."
}
if (noteEl) {
noteEl.textContent =
"Cards published before June 2026 are counted as legacy activity and excluded from the nominator leaderboard."
}
}
const buildStatsValidationReportHtml = (report = null) => {
if (!report) {
return ""
}
const issueSamples = Array.isArray(report.issueSamples) ? report.issueSamples : []
const verifiedCheckpointValue = report.verifiedProgressCheckpoint ? "Yes" : "No"
return `
<div class="stats-validation-report">
<div class="stats-validation-report-grid">
${buildStatsMetricCardHtml(
"Source cards",
report.sourceCount || 0,
"Cards scanned during validation"
)}
${buildStatsMetricCardHtml(
"Compiled cards",
report.compiledCount || 0,
"Cards that passed the audit"
)}
${buildStatsMetricCardHtml(
"Issues found",
report.validationIssueCount || 0,
report.validationIssueCount > 0
? "Review the sample issues below"
: "No missing data was detected"
)}
${buildStatsMetricCardHtml(
"Verified snapshots",
report.verifiedSnapshotCount || 0,
"Admin-published snapshot history"
)}
${buildStatsMetricCardHtml(
"Verified checkpoint",
verifiedCheckpointValue,
report.verifiedProgressIdentifier
? `Checkpoint ${report.verifiedProgressIdentifier}`
: "No active admin checkpoint"
)}
</div>
${
issueSamples.length > 0
? `
<div class="stats-validation-issues">
<div class="stats-validation-issues-header">
<strong>Sample issues</strong>
<span>${qEscapeHtml(String(issueSamples.length))} shown</span>
</div>
<div class="stats-validation-issues-list">
${issueSamples
.map(
(issue) => `
<article class="stats-validation-issue">
<p class="stats-validation-issue-id">${qEscapeHtml(
issue.identifier || "Unknown card"
)}</p>
<p class="stats-validation-issue-reason">${qEscapeHtml(
issue.reason || "validation-issue"
)}</p>
${
issue.detail
? `<p class="stats-validation-issue-detail">${qEscapeHtml(
issue.detail
)}</p>`
: ""
}
</article>
`
)
.join("")}
</div>
</div>
`
: `
<div class="stats-empty-state">
No blocking source issues were found during validation.
</div>
`
}
</div>
`
}
const buildStatsCompileOptionsHtml = () => {
const customTimestampValue =
statsCompileModalState.customTimestampInput ||
formatStatsTimestampInputValue(statsCompileModalState.snapshotTimestamp)
const selectedMode = String(statsCompileModalState.timestampMode || "latest")
const identifierPreview = statsCompileModalState.identifierPreview ||
`${MINTER_STATS_IDENTIFIER_PREFIX}-${statsCompileModalState.snapshotTimestamp}`
const customWrapStyle = selectedMode === "custom" ? "" : "display: none;"
const latestProgressCheckpoint = statsBoardState.latestProgressCheckpoint
const hasResumableProgress = Boolean(
latestProgressCheckpoint && !latestProgressCheckpoint.completed
)
const resumableProgressSummary = getStatsResumeCheckpointSummary(
latestProgressCheckpoint
)
const resumableProgressMarkup = hasResumableProgress
? `
<div class="stats-compile-resume-card">
<div>
<p class="stats-compile-resume-kicker">Resume previous update</p>
<h3>Continue a saved stats checkpoint</h3>
<p>
${qEscapeHtml(
resumableProgressSummary
? `Checkpoint ${resumableProgressSummary.checkpointIdentifier || "Unavailable"} has processed ${resumableProgressSummary.processed} of ${resumableProgressSummary.total} cards.`
: `Checkpoint ${latestProgressCheckpoint.progressIdentifier || "Unavailable"} has processed ${Number(
latestProgressCheckpoint.nextIndex || 0
)} of ${Number(
latestProgressCheckpoint.source?.cardCount ||
latestProgressCheckpoint.source?.resources?.length ||
0
)} cards.`
)}
</p>
<p class="stats-compile-resume-note">
${
resumableProgressSummary
? `Resuming will continue at card ${resumableProgressSummary.nextCardNumber} with ${resumableProgressSummary.remaining} remaining. Any admin can resume from where the previous batch stopped.`
: "Any admin can resume from where the previous batch stopped."
}
</p>
</div>
<button
type="button"
class="stats-action-button stats-action-button--primary"
onclick="continuePreviousStatsCompilation()"
>
Resume update
</button>
</div>
`
: ""
return `
<div class="stats-compile-modal-shell">
<div class="stats-compile-modal-header">
<div>
<p class="stats-compile-modal-kicker">Update stats</p>
<h2 class="stats-compile-modal-title">Prepare a stats update</h2>
<p class="stats-compile-modal-subtitle">
Cards published before June 2026 are treated as legacy activity and are not counted as nominator submissions. Choose the latest snapshot mode to update the current resource in place, validate the existing data, or re-create the stats from scratch.
</p>
</div>
<span class="stats-compile-modal-badge">Admins only</span>
</div>
<div class="stats-compile-modal-note">
Pick the timestamp that will be baked into the snapshot identifier. Choosing <strong>Update latest snapshot</strong> keeps the newest stats identifier in place when one already exists, rather than creating a brand-new entry. Validation is read-only and re-create starts over from the beginning.
When the most recent checkpoint is already finished, Update stats rechecks the source list and record coverage first. If it finds new cards or missing data points, it resumes from the first recoverable point; otherwise it performs a clean rebuild.
</div>
<div class="stats-compile-field">
<label class="stats-compile-label" for="stats-compile-timestamp-mode">
Snapshot timestamp
</label>
<select
id="stats-compile-timestamp-mode"
class="stats-compile-select"
onchange="updateStatsCompileModalPreview()"
>
<option value="latest" ${selectedMode === "latest" ? "selected" : ""}>
Update latest snapshot
</option>
<option value="now" ${selectedMode === "now" ? "selected" : ""}>
Use current time
</option>
<option value="minus-6-hours" ${selectedMode === "minus-6-hours" ? "selected" : ""}>
Backdate 6 hours
</option>
<option value="minus-1-day" ${selectedMode === "minus-1-day" ? "selected" : ""}>
Backdate 1 day
</option>
<option value="minus-1-week" ${selectedMode === "minus-1-week" ? "selected" : ""}>
Backdate 1 week
</option>
<option value="custom" ${selectedMode === "custom" ? "selected" : ""}>
Custom date and time
</option>
</select>
</div>
<div
id="stats-compile-custom-wrap"
class="stats-compile-field stats-compile-field--custom"
style="${customWrapStyle}"
>
<label class="stats-compile-label" for="stats-compile-custom-timestamp">
Custom date and time
</label>
<input
id="stats-compile-custom-timestamp"
class="stats-compile-input"
type="datetime-local"
value="${qEscapeAttr(customTimestampValue)}"
onchange="updateStatsCompileModalPreview()"
/>
</div>
<div class="stats-compile-preview">
<span class="stats-compile-preview-label">Identifier preview</span>
<code id="stats-compile-identifier-preview" class="stats-compile-preview-code">${qEscapeHtml(
identifierPreview
)}</code>
</div>
${resumableProgressMarkup}
<div id="stats-compile-status" class="stats-compile-status">
${
hasResumableProgress && resumableProgressSummary
? `Resuming will continue at card ${resumableProgressSummary.nextCardNumber} of ${resumableProgressSummary.total} (${resumableProgressSummary.remaining} remaining).`
: "Each update batch saves a resumable checkpoint, so another admin can continue later if needed."
}
</div>
<p id="stats-compile-note" class="stats-compile-note">
Legacy cards are excluded from the nominator leaderboard but remain available in the snapshot summary.
You can hide the workflow window while it runs and keep following the live status banner.
</p>
<div class="stats-compile-actions">
<button type="button" class="stats-action-button" onclick="closeStatsCompileModal()">
Cancel
</button>
<button
type="button"
class="stats-action-button"
title="Audit the source data and admin-published stats resources without publishing anything."
onclick="startStatsValidationFromModal()"
>
Validate data
</button>
<button
type="button"
class="stats-action-button stats-action-button--warning"
title="Restart the stats update from the beginning."
onclick="recreateStatsDataFromModal()"
>
Re-create stats data
</button>
<button
type="button"
class="stats-action-button stats-action-button--primary"
onclick="startStatsCompileFromModal()"
>
Update stats
</button>
</div>
</div>
`
}
const buildStatsCompileProgressHtml = () => {
const steps = Array.isArray(statsCompileModalState.steps)
? statsCompileModalState.steps
: []
const phase = String(statsCompileModalState.phase || "progress")
const workflow = String(statsCompileModalState.workflow || "compile")
const isValidation = workflow === "validation"
const isRecreate = workflow === "recreate"
const isPaused = phase === "paused"
const title =
phase === "complete"
? isValidation
? "Stats data validated"
: isRecreate
? "Stats data re-created"
: "Stats snapshot updated"
: isPaused
? "Stats update saved"
: phase === "error"
? isValidation
? "Stats validation failed"
: "Stats update failed"
: isValidation
? "Validating stats data"
: isRecreate
? "Re-creating stats data"
: "Updating stats snapshot"
const badgeLabel =
phase === "complete"
? isValidation
? "Validated"
: isRecreate
? "Re-created"
: "Updated"
: isPaused
? "Saved"
: phase === "error"
? "Error"
: "Working"
const kickerLabel = isValidation
? "Validation"
: isRecreate
? "Re-create"
: "Updating"
const statusMessage =
phase === "complete"
? statsCompileModalState.message ||
(isValidation
? "The validation run finished."
: "The snapshot is live and the dashboard is ready to refresh.")
: isPaused
? statsCompileModalState.message ||
"A resumable checkpoint was saved and can be continued later."
: phase === "error"
? statsCompileModalState.errorMessage ||
(isValidation
? "Something interrupted the validation."
: "Something interrupted the update.")
: statsCompileModalState.message ||
(isValidation
? "Working through the validation checks."
: "Working through the snapshot update and publish steps.")
const footerNote =
phase === "complete"
? isValidation
? "Validation does not publish anything. Use the results to decide whether to re-create the stats data."
: "You can refresh the Stats board to see the updated snapshot history."
: isPaused
? "Any admin can continue this saved checkpoint from the Stats page."
: phase === "error"
? "You can close this modal and try again once the underlying issue is resolved."
: isValidation
? "Validation runs batch-by-batch through the data without publishing."
: "This run keeps going batch-by-batch until it finishes. You can hide this window and keep following the live status banner, or use Pause after current batch to save a checkpoint."
return `
<div class="publish-progress-modal-shell stats-compile-progress-shell">
<div class="publish-progress-modal-header">
<div>
<p class="publish-progress-modal-kicker">${qEscapeHtml(kickerLabel)}</p>
<h2 class="publish-progress-modal-title">${qEscapeHtml(title)}</h2>
${
statsCompileModalState.subtitle
? `<p class="publish-progress-modal-subtitle">${qEscapeHtml(
statsCompileModalState.subtitle
)}</p>`
: `<p class="publish-progress-modal-subtitle">
Cards published before June 2026 are summarized as legacy activity and not treated as nominators.
</p>`
}
</div>
<span class="publish-progress-modal-badge">${qEscapeHtml(badgeLabel)}</span>
</div>
<p class="publish-progress-modal-message">${qEscapeHtml(statusMessage)}</p>
<div class="publish-progress-step-list" role="list">
${steps
.map((step, index) => buildBoardPublishProgressStepHtml(step, index))
.join("")}
</div>
${isValidation ? buildStatsValidationReportHtml(statsCompileModalState.validationReport) : ""}
<div class="publish-progress-modal-footer">
${getBoardInlineLoadingHTML(
phase === "complete"
? isValidation
? "Validation completed successfully."
: isRecreate
? "Stats data re-created successfully."
: "Snapshot updated successfully."
: isPaused
? "Checkpoint saved successfully."
: phase === "error"
? isValidation
? "Validation stopped."
: "Updating stopped."
: isValidation
? "Working through the validation checks..."
: "Working through the stats update..."
)}
<div class="stats-compile-footer-actions">
${
phase === "complete"
? isValidation
? `<button type="button" class="stats-action-button stats-action-button--primary" onclick="recreateStatsDataFromModal()">
Re-create stats data
</button>`
: `<button type="button" class="stats-action-button stats-action-button--primary" onclick="refreshStatsBoardView({ force: true }); closeStatsCompileModal();">
Refresh board
</button>`
: isPaused
? `<button type="button" class="stats-action-button stats-action-button--primary" onclick="continuePreviousStatsCompilation()">
Resume update
</button>`
: phase === "progress" && !isValidation
? `<button type="button" class="stats-action-button stats-action-button--primary" onclick="requestStatsCompilePause()" ${
statsBoardState.pauseRequested ? "disabled" : ""
}>
${
statsBoardState.pauseRequested
? "Pausing..."
: "Pause after current batch"
}
</button>`
: ""
}
<button type="button" class="stats-action-button" onclick="closeStatsCompileModal()">
Close
</button>
</div>
</div>
<p class="publish-progress-modal-footer-note">${qEscapeHtml(footerNote)}</p>
</div>
`
}
const renderStatsCompileModal = () => {
ensureStatsCompileModal()
const modal = document.getElementById(`${STATS_COMPILE_MODAL_TYPE}-modal`)
const modalContent = getStatsCompileModalContent()
if (!modal || !modalContent) {
return
}
if (statsCompileModalState.hidden) {
modal.style.display = "none"
return
}
modalContent.innerHTML =
statsCompileModalState.phase === "options"
? buildStatsCompileOptionsHtml()
: buildStatsCompileProgressHtml()
modal.style.display = "block"
if (statsCompileModalState.phase === "options") {
updateStatsCompileModalPreview()
}
}
const buildStatsSectionNavLinkHtml = (
sectionKey = "",
label = "",
note = "",
isActive = false
) => {
const routeHash =
typeof buildBoardRouteHash === "function"
? buildBoardRouteHash({ board: "stats", section: sectionKey })
: `#/${["stats", sectionKey].filter(Boolean).join("/")}`
return `
<a
href="${qEscapeAttr(routeHash)}"
class="stats-section-nav-link${isActive ? " stats-section-nav-link--active" : ""}"
data-stats-section-link="${qEscapeAttr(sectionKey)}"
${isActive ? 'aria-current="page"' : ""}
>
<span class="stats-section-nav-link-label">${qEscapeHtml(label)}</span>
${
note
? `<span class="stats-section-nav-link-note">${qEscapeHtml(note)}</span>`
: ""
}
</a>
`
}
const buildStatsSectionNavHtml = (activeSection = "live") => {
const normalizedSection = normalizeStatsSectionKey(activeSection)
return `
<nav class="stats-section-nav" aria-label="Stats sections">
${buildStatsSectionNavLinkHtml(
"live",
"Live Minting Stats",
"Current MINTER group and online level counts",
normalizedSection === "live"
)}
${buildStatsSectionNavLinkHtml(
"historic",
"Historic Mintership Data",
"Published long-term stats and leaderboards",
normalizedSection === "historic"
)}
${buildStatsSectionNavLinkHtml(
"publish",
"Publish Data",
"Snapshot metrics, metadata, and recent history",
normalizedSection === "publish"
)}
${buildStatsSectionNavLinkHtml(
"nominator",
"Nominator Stats",
"The current publish-era leaderboard",
normalizedSection === "nominator"
)}
${buildStatsSectionNavLinkHtml(
"legacy",
"Legacy Stats",
"Pre-June 2026 publisher lifecycle data",
normalizedSection === "legacy"
)}
${buildStatsSectionNavLinkHtml(
"admin",
"Minter Admin Stats",
"Current admin-publisher overview",
normalizedSection === "admin"
)}
${buildStatsSectionNavLinkHtml(
"nominator-leaderboard",
"Minter Leaderboards",
"Jump to the nominator leaderboard",
normalizedSection === "nominator-leaderboard"
)}
${buildStatsSectionNavLinkHtml(
"legacy-leaderboard",
"Legacy Leaderboards",
"Jump to legacy publisher ranking",
normalizedSection === "legacy-leaderboard"
)}
${buildStatsSectionNavLinkHtml(
"admin-leaderboard",
"MinterAdmin Leaderboards",
"Jump to admin publisher ranking",
normalizedSection === "admin-leaderboard"
)}
</nav>
`
}
const renderStatsSectionNavState = (activeSection = "live") => {
setStatsBoardSectionActiveState(activeSection)
}
const renderStatsLegacySection = (snapshot = null) => {
const summaryGrid = document.getElementById("stats-legacy-summary-grid")
const leaderboardContainer = document.getElementById(
"stats-legacy-leaderboard-container"
)
const metaContainer = document.getElementById("stats-legacy-meta-container")
if (summaryGrid) {
summaryGrid.innerHTML = buildStatsLegacySectionSummaryHtml(snapshot)
}
if (leaderboardContainer) {
leaderboardContainer.innerHTML = buildStatsLegacyLeaderboardHtml(snapshot)
}
if (metaContainer) {
metaContainer.innerHTML = buildStatsLegacySectionNoteHtml(snapshot)
}
}
const renderStatsAdminSection = (snapshot = null) => {
const summaryGrid = document.getElementById("stats-admin-summary-grid")
const leaderboardContainer = document.getElementById(
"stats-admin-leaderboard-container"
)
const metaContainer = document.getElementById("stats-admin-meta-container")
if (summaryGrid) {
summaryGrid.innerHTML = buildStatsAdminSectionSummaryHtml(snapshot)
}
if (leaderboardContainer) {
leaderboardContainer.innerHTML = buildStatsAdminLeaderboardHtml(snapshot)
}
if (metaContainer) {
metaContainer.innerHTML = buildStatsAdminSectionNoteHtml(snapshot)
}
}
const openStatsCompileModal = () => {
const latestSnapshotTimestamp = getStatsLatestPublishedTimestamp()
statsCompileModalState.hidden = false
statsCompileModalState.phase = "options"
statsCompileModalState.workflow = "compile"
statsCompileModalState.timestampMode = latestSnapshotTimestamp ? "latest" : "now"
statsCompileModalState.customTimestampInput = formatStatsTimestampInputValue()
statsCompileModalState.snapshotTimestamp = latestSnapshotTimestamp || Date.now()
statsCompileModalState.identifierPreview =
`${MINTER_STATS_IDENTIFIER_PREFIX}-${statsCompileModalState.snapshotTimestamp}`
statsCompileModalState.message = ""
statsCompileModalState.subtitle = ""
statsCompileModalState.steps = buildStatsCompileSteps("compile")
statsCompileModalState.errorMessage = ""
statsCompileModalState.validationReport = null
renderStatsCompileModal()
}
const showStatsCompileProgressModal = () => {
statsCompileModalState.hidden = false
renderStatsCompileModal()
renderStatsCompileWorkflowBanner()
}
const updateStatsCompileProgressModal = (updates = {}) => {
if (typeof updates.phase !== "undefined") {
statsCompileModalState.phase = String(updates.phase || "progress")
}
if (typeof updates.message !== "undefined") {
statsCompileModalState.message = String(updates.message || "")
}
if (typeof updates.subtitle !== "undefined") {
statsCompileModalState.subtitle = String(updates.subtitle || "")
}
if (typeof updates.errorMessage !== "undefined") {
statsCompileModalState.errorMessage = String(updates.errorMessage || "")
}
if (Array.isArray(updates.steps)) {
statsCompileModalState.steps = updates.steps
}
renderStatsCompileWorkflowBanner()
if (!statsCompileModalState.hidden) {
renderStatsCompileModal()
}
}
const areStatsSourceResourcesPrefixCompatible = (
previousResources = [],
currentResources = []
) => {
const normalizedPrevious = buildStatsSourceResourceList(previousResources)
const normalizedCurrent = buildStatsSourceResourceList(currentResources)
if (
normalizedPrevious.length === 0 ||
normalizedCurrent.length < normalizedPrevious.length
) {
return false
}
for (let index = 0; index < normalizedPrevious.length; index += 1) {
const previous = normalizedPrevious[index]
const current = normalizedCurrent[index]
if (
!current ||
previous.name !== current.name ||
previous.identifier !== current.identifier ||
Number(previous.created || 0) !== Number(current.created || 0) ||
Number(previous.updated || 0) !== Number(current.updated || 0)
) {
return false
}
}
return true
}
const buildStatsResumeCheckpointFromCompletedRun = async (checkpoint = null) => {
if (!checkpoint || !checkpoint.completed) {
return null
}
const previousResources = buildStatsSourceResourceList(
checkpoint?.source?.resources || []
)
if (previousResources.length === 0) {
return null
}
const currentResources = await fetchStatsBoardSourceResources(true)
if (
!areStatsSourceResourcesPrefixCompatible(previousResources, currentResources)
) {
return null
}
const previousRecords = Array.isArray(checkpoint.records)
? checkpoint.records.map((record) => normalizeStatsCompileRecord(record))
: []
if (previousRecords.length > previousResources.length) {
return null
}
let resumeIndex = previousRecords.length
for (let index = 0; index < previousRecords.length; index += 1) {
const sourceIdentifier = String(previousResources[index]?.identifier || "").trim()
const recordIdentifier = String(previousRecords[index]?.cardIdentifier || "").trim()
if (!sourceIdentifier || sourceIdentifier !== recordIdentifier) {
resumeIndex = index
break
}
}
const hasNewCards = currentResources.length > previousResources.length
const hasTrailingGaps = previousRecords.length < previousResources.length
const hasMismatch = resumeIndex < previousRecords.length
if (!hasNewCards && !hasTrailingGaps && !hasMismatch) {
return null
}
if (resumeIndex <= 0) {
return null
}
const recordsToRetain = previousRecords.slice(0, resumeIndex)
return buildStatsResumableCheckpointFromRecords({
checkpoint,
sourceResources: currentResources,
records: recordsToRetain,
nextIndex: resumeIndex,
})
}
const startStatsWorkflowFromModal = async ({
workflow = "compile",
publishTimestamp = resolveStatsCompileTimestamp(),
resumeCheckpoint = null,
} = {}) => {
const normalizedWorkflow =
workflow === "validation"
? "validation"
: workflow === "recreate"
? "recreate"
: "compile"
const resumeSummary = getStatsResumeCheckpointSummary(resumeCheckpoint)
const isResumeRun = Boolean(resumeSummary) && normalizedWorkflow !== "validation"
const isResumeRefreshOnly =
Boolean(isResumeRun) &&
Boolean(resumeCheckpoint?.completed) &&
Number(resumeSummary?.remaining || 0) === 0
const effectivePublishTimestamp = isResumeRun
? Number(
resumeCheckpoint?.snapshotTimestamp ||
resumeCheckpoint?.generatedAt ||
publishTimestamp ||
Date.now()
) || Date.now()
: publishTimestamp
statsCompileModalState.workflow = normalizedWorkflow
statsCompileModalState.hidden = false
statsCompileModalState.snapshotTimestamp = effectivePublishTimestamp
statsCompileModalState.identifierPreview =
`${MINTER_STATS_IDENTIFIER_PREFIX}-${effectivePublishTimestamp}`
statsCompileModalState.phase = "progress"
statsCompileModalState.message = isResumeRun
? isResumeRefreshOnly
? `Refreshing the completed stats snapshot for ${formatStatsDate(
effectivePublishTimestamp
)}${
resumeSummary?.checkpointIdentifier
? ` from checkpoint ${resumeSummary.checkpointIdentifier}`
: ""
}.`
: `Resuming stats update for ${formatStatsDate(effectivePublishTimestamp)}${
resumeSummary?.checkpointIdentifier
? ` from checkpoint ${resumeSummary.checkpointIdentifier}`
: ""
}.`
: normalizedWorkflow === "validation"
? `Starting stats data validation for ${formatStatsDate(effectivePublishTimestamp)}.`
: normalizedWorkflow === "recreate"
? `Re-creating stats data from scratch for ${formatStatsDate(effectivePublishTimestamp)}.`
: "Starting stats update..."
statsCompileModalState.subtitle = isResumeRun
? isResumeRefreshOnly
? `Checkpoint ${resumeSummary?.checkpointIdentifier || "Unavailable"} already covers ${resumeSummary?.total || 0} cards. The published snapshot will be rechecked before publishing.`
: `Continuing checkpoint ${resumeSummary?.checkpointIdentifier || "Unavailable"} at card ${resumeSummary?.nextCardNumber || 1} of ${resumeSummary?.total || 0} (${resumeSummary?.remaining || 0} remaining).`
: normalizedWorkflow === "validation"
? "This audits source data and admin-published stats resources without publishing anything."
: `Snapshot identifier will be ${statsCompileModalState.identifierPreview}.`
statsCompileModalState.steps = buildStatsCompileSteps(normalizedWorkflow)
statsCompileModalState.errorMessage = ""
statsCompileModalState.validationReport = null
renderStatsCompileModal()
await compileStatsProgressRun({
publishTimestamp: effectivePublishTimestamp,
resumeCheckpoint,
workflow: normalizedWorkflow,
})
}
const startStatsCompileFromModal = async () => {
const latestCheckpoint =
statsBoardState.latestProgressCheckpoint ||
(await fetchLatestStatsProgressCheckpoint(true))
if (latestCheckpoint && !latestCheckpoint.completed) {
await continuePreviousStatsCompilation()
return
}
if (latestCheckpoint && latestCheckpoint.completed) {
const resumableCheckpoint =
await buildStatsResumeCheckpointFromCompletedRun(latestCheckpoint)
if (resumableCheckpoint && !resumableCheckpoint.completed) {
await startStatsWorkflowFromModal({
workflow: "compile",
publishTimestamp: resolveStatsCompileTimestamp(),
resumeCheckpoint: resumableCheckpoint,
})
return
}
}
await startStatsWorkflowFromModal({
workflow: "compile",
publishTimestamp: resolveStatsCompileTimestamp(),
})
}
const startStatsValidationFromModal = async () => {
await startStatsWorkflowFromModal({ workflow: "validation" })
}
const recreateStatsDataFromModal = async () => {
await startStatsWorkflowFromModal({ workflow: "recreate" })
}
const renderStatsBoardSnapshot = (snapshot = null, resources = []) => {
const summaryGrid = getStatsSummaryGrid()
const leaderboardContainer = getStatsLeaderboardContainer()
const snapshotMetaContainer = getStatsSnapshotMetaContainer()
const historyContainer = getStatsHistoryContainer()
const snapshotContainer = getStatsSnapshotContainer()
const statusEl = getStatsStatusEl()
if (snapshotContainer) {
snapshotContainer.style.display = "block"
}
if (!snapshot) {
if (summaryGrid) {
summaryGrid.innerHTML = `
${buildStatsMetricCardHtml("Total nominations", "0", "Waiting for a published snapshot")}
${buildStatsMetricCardHtml("Unique nominators", "0")}
${buildStatsMetricCardHtml("Converted to minters", "0")}
${buildStatsMetricCardHtml("Approved invites", "0")}
${buildStatsMetricCardHtml("Pending invites", "0")}
${buildStatsMetricCardHtml("Kicked / banned", "0")}
${buildStatsMetricCardHtml("Legacy cards", "0", "Cards published before June 2026")}
`
}
if (leaderboardContainer) {
leaderboardContainer.innerHTML = `
<div class="stats-empty-state">
No stats snapshot is loaded yet.
</div>
`
}
if (snapshotMetaContainer) {
snapshotMetaContainer.innerHTML = buildStatsSnapshotMetaHtml(null)
}
if (historyContainer) {
historyContainer.innerHTML = buildStatsHistoryHtml(resources)
}
if (statusEl) {
statusEl.textContent = "No published stats snapshot found yet."
}
renderStatsLegacySection(null)
renderStatsAdminSection(null)
return
}
const summary = snapshot.summary || {}
if (summaryGrid) {
summaryGrid.innerHTML = `
${buildStatsMetricCardHtml(
"Total nominations",
summary.totalNominations || 0,
`${snapshot.source?.cardCount || 0} cards compiled`
)}
${buildStatsMetricCardHtml(
"Unique nominators",
summary.uniqueNominators || 0
)}
${buildStatsMetricCardHtml(
"Converted to minters",
summary.totalConvertedToMinter || 0,
`Conversion rate ${summary.conversionLabel || "0%"}`
)}
${buildStatsMetricCardHtml(
"Approved invites",
summary.totalApprovedInvites || 0
)}
${buildStatsMetricCardHtml(
"Pending invites",
summary.totalPendingInvites || 0
)}
${buildStatsMetricCardHtml(
"Kicked / banned",
summary.totalKickedAndBanned || 0
)}
${buildStatsMetricCardHtml(
"Legacy cards",
summary.legacyCardCount || 0,
"Cards published before June 2026"
)}
`
}
if (leaderboardContainer) {
leaderboardContainer.innerHTML = buildStatsLeaderboardTableHtml(
Array.isArray(snapshot.nominators) ? snapshot.nominators : [],
{ legacyCardCount: summary.legacyCardCount || 0 }
)
}
if (snapshotMetaContainer) {
snapshotMetaContainer.innerHTML = buildStatsSnapshotMetaHtml(snapshot)
}
if (historyContainer) {
historyContainer.innerHTML = buildStatsHistoryHtml(resources)
}
if (statusEl) {
statusEl.textContent = `Loaded stats snapshot published ${formatStatsDate(
snapshot.generatedAt || 0
)}.${
Number(summary.legacyCardCount || 0) > 0
? ` Legacy cards before June 2026 were summarized separately.`
: ""
}`
}
renderStatsLegacySection(snapshot)
renderStatsAdminSection(snapshot)
}
const fetchStatsSnapshotResources = async (
force = false,
adminAddressSet = null
) => {
const now = Date.now()
if (
!force &&
statsBoardState.snapshotResources.length > 0 &&
now - statsBoardState.lastLoadedAt < STATS_SNAPSHOT_CACHE_TTL_MS
) {
return statsBoardState.snapshotResources
}
const resources = await searchSimple(
"BLOG_POST",
MINTER_STATS_IDENTIFIER_PREFIX,
"",
100,
0,
"",
false
).catch(() => [])
const deduped = new Map()
for (const resource of Array.isArray(resources) ? resources : []) {
const identifier = String(resource?.identifier || "").trim()
const name = String(resource?.name || "").trim()
if (!identifier || !name) continue
if (isStatsProgressIdentifier(identifier)) continue
const key = `${name}::${identifier}`
const current = deduped.get(key)
if (!current || getStatsBoardTimestamp(resource) >= getStatsBoardTimestamp(current)) {
deduped.set(key, resource)
}
}
const sorted = Array.from(deduped.values()).sort(
(a, b) => getStatsBoardTimestamp(b) - getStatsBoardTimestamp(a)
)
const verifiedResources = await filterAdminPublishedStatsResources(
sorted,
adminAddressSet instanceof Set
? adminAddressSet
: (await fetchStatsBoardGroupData(force).catch(() => null))
?.allAdminAddressSet || new Set()
)
statsBoardState.snapshotResources = verifiedResources
statsBoardState.lastLoadedAt = now
return verifiedResources
}
const fetchLatestPublishedStatsSnapshot = async (
force = false,
adminAddressSet = null,
resources = null
) => {
const verifiedAdminAddressSet =
adminAddressSet instanceof Set
? adminAddressSet
: (await fetchStatsBoardGroupData(force).catch(() => null))
?.allAdminAddressSet || new Set()
const verifiedResources = Array.isArray(resources)
? resources
: await fetchStatsSnapshotResources(force, verifiedAdminAddressSet)
const resourcesToUse = Array.isArray(resources)
? verifiedResources
: await filterAdminPublishedStatsResources(
verifiedResources,
verifiedAdminAddressSet
)
for (const latestResource of resourcesToUse) {
const snapshot = await fetchStatsBoardQdnJsonResource(latestResource)
const isValidSnapshot = Boolean(
snapshot &&
typeof snapshot === "object" &&
!isStatsProgressIdentifier(latestResource?.identifier || "") &&
snapshot.source &&
snapshot.summary &&
isStatsResourcePublishedByAdmin(snapshot, verifiedAdminAddressSet)
)
if (!isValidSnapshot) {
continue
}
statsBoardState.latestSnapshot = snapshot
return snapshot
}
statsBoardState.latestSnapshot = null
return null
}
const getStatsBoardNomineeName = (cardData = {}) =>
getCardNomineeName(cardData) || "Unknown"
const getStatsBoardNomineeAddress = (cardData = {}, fallbackAddress = "") =>
getCardNomineeAddress(cardData, fallbackAddress || "")
const getStatsBoardNominatorName = (cardData = {}, resourceName = "") =>
getCardNominatorName(cardData, resourceName || "Unknown")
const getStatsBoardNominatorAddress = (cardData = {}, fallbackAddress = "") =>
getCardNominatorAddress(cardData, fallbackAddress || "")
const fetchStatsBoardSourceResources = async (force = false) => {
const cardResources = await fetchCachedBoardSearchResources(
minterCardIdentifierPrefix,
0,
0,
force
).catch(() => [])
return buildStatsSourceResourceList(cardResources)
}
const buildStatsLiveSnapshot = async ({
snapshotTimestamp = Date.now(),
onProgress = null,
} = {}) => {
const emitProgress = (update = {}) => {
if (typeof onProgress === "function") {
onProgress(update)
}
}
emitProgress({
key: "load-source",
status: "active",
detail: "Pulling published MinterBoard cards and cached metadata.",
})
const [uniqueResources, groupData] = await Promise.all([
fetchStatsBoardSourceResources(true),
fetchStatsBoardGroupData(true),
])
emitProgress({
key: "load-source",
status: "done",
detail: `Loaded ${uniqueResources.length} unique card records.`,
})
emitProgress({
key: "classify-era",
status: "active",
detail:
uniqueResources.length > 0
? `Classifying card data by publish date and payload markers (0/${uniqueResources.length}).`
: "No cards found for classification.",
})
const totalResources = uniqueResources.length
let classifiedCount = 0
const classifyProgressStride = Math.max(1, Math.ceil(totalResources / 12))
const emitClassifyProgress = () => {
classifiedCount += 1
if (
classifiedCount === totalResources ||
classifiedCount % classifyProgressStride === 0
) {
emitProgress({
key: "classify-era",
status: "active",
detail: `Classifying card data by publish date and payload markers (${classifiedCount}/${totalResources}).`,
})
}
}
const tasks = uniqueResources.map((resource) => async () => {
const record = await compileStatsBoardRecord(resource)
emitClassifyProgress()
return record
})
const records = (await runWithConcurrency(tasks, 5)).filter(Boolean)
emitProgress({
key: "classify-era",
status: "done",
detail: `Classified ${records.length} cards for analysis using timestamp and payload checks.`,
})
emitProgress({
key: "aggregate",
status: "active",
detail: "Building the nominator-era leaderboard and legacy totals.",
})
const currentMinterAddressSet = buildStatsCurrentMinterAddressSet({
minterGroupAddresses: Array.from(groupData?.minterGroupAddressSet || []),
minterAdminAddresses: Array.from(groupData?.minterAdminAddressSet || []),
})
const nominatorMap = new Map()
const uniqueNominees = new Set()
const currentRecords = []
const legacyRecords = []
let totalConvertedToMinter = 0
let totalApprovedInvites = 0
let totalPendingInvites = 0
let totalKickedAndBanned = 0
let legacyConvertedToMinter = 0
let legacyApprovedInvites = 0
let legacyPendingInvites = 0
let legacyKickedAndBanned = 0
for (const record of records) {
const isCurrentMinter = resolveStatsRecordCurrentMinterStatus(
record,
currentMinterAddressSet
)
if (record.isLegacy) {
legacyRecords.push(record)
if (isCurrentMinter) {
legacyConvertedToMinter += 1
}
if (record.isApprovedInvite) {
legacyApprovedInvites += 1
}
if (record.isPendingInvite) {
legacyPendingInvites += 1
}
if (record.isKicked || record.isBanned) {
legacyKickedAndBanned += 1
}
continue
}
currentRecords.push(record)
const nomineeKey = record.nomineeAddress || record.nomineeName.toLowerCase()
if (nomineeKey) {
uniqueNominees.add(nomineeKey)
}
if (isCurrentMinter) {
totalConvertedToMinter += 1
}
if (record.isApprovedInvite) {
totalApprovedInvites += 1
}
if (record.isPendingInvite) {
totalPendingInvites += 1
}
if (record.isKicked || record.isBanned) {
totalKickedAndBanned += 1
}
const nominatorKey =
record.nominatorAddress || record.nominatorName.toLowerCase()
const row =
nominatorMap.get(nominatorKey) || {
key: nominatorKey,
displayName: record.nominatorName || "Unknown",
address: record.nominatorAddress || "",
nominationCount: 0,
convertedCount: 0,
approvedCount: 0,
pendingCount: 0,
kickedCount: 0,
bannedCount: 0,
lastNominationAt: 0,
}
row.nominationCount += 1
if (isCurrentMinter) row.convertedCount += 1
if (record.isApprovedInvite) row.approvedCount += 1
if (record.isPendingInvite) row.pendingCount += 1
if (record.isKicked) row.kickedCount += 1
if (record.isBanned) row.bannedCount += 1
row.lastNominationAt = Math.max(row.lastNominationAt, record.createdAt || 0)
nominatorMap.set(nominatorKey, row)
}
const nominators = Array.from(nominatorMap.values())
.map((row) => ({
...row,
conversionLabel: formatStatsPercent(
row.nominationCount > 0 ? row.convertedCount / row.nominationCount : 0
),
}))
.sort((a, b) => {
if (b.nominationCount !== a.nominationCount) {
return b.nominationCount - a.nominationCount
}
if (b.convertedCount !== a.convertedCount) {
return b.convertedCount - a.convertedCount
}
return b.lastNominationAt - a.lastNominationAt
})
emitProgress({
key: "aggregate",
status: "done",
detail: `Built ${nominators.length} nominator rows and ${legacyRecords.length} legacy cards.`,
})
return {
schemaVersion: 1,
generatedAt: snapshotTimestamp,
compiledAt: Date.now(),
generatedBy: {
name: userState.accountName || "",
address: userState.accountAddress || "",
},
source: {
prefix: MINTER_STATS_IDENTIFIER_PREFIX,
cardCount: uniqueResources.length,
legacyCutoff: NOMINATOR_METHOD_START_TS,
latestCardTimestamp: uniqueResources.reduce(
(latestTimestamp, resource) =>
Math.max(latestTimestamp, getStatsBoardTimestamp(resource)),
0
),
},
summary: {
totalNominations: currentRecords.length,
uniqueNominators: nominatorMap.size,
uniqueNominees: uniqueNominees.size,
totalConvertedToMinter,
totalApprovedInvites,
totalPendingInvites,
totalKickedAndBanned,
legacyCardCount: legacyRecords.length,
legacyConvertedToMinter,
legacyApprovedInvites,
legacyPendingInvites,
legacyKickedAndBanned,
conversionLabel: formatStatsPercent(
currentRecords.length > 0
? totalConvertedToMinter / currentRecords.length
: 0
),
legacyConversionLabel: formatStatsPercent(
legacyRecords.length > 0
? legacyConvertedToMinter / legacyRecords.length
: 0
),
},
nominators,
legacyCards: legacyRecords,
cards: currentRecords,
}
}
const publishStatsSnapshot = async (
snapshot,
{ publishTimestamp = snapshot?.generatedAt || Date.now() } = {}
) => {
if (!snapshot) return null
if (!getStatsBoardCanPublish()) {
alert("Only Minter Admins and App Admins can publish stats snapshots.")
return null
}
if (!userState.accountName) {
alert("A registered name is required to publish stats snapshots.")
return null
}
const resource = await buildStatsSnapshotPublishResource(
snapshot,
publishTimestamp
)
if (!resource) {
return null
}
await publishStatsResources([resource])
statsBoardState.snapshotResources = []
statsBoardState.lastLoadedAt = 0
await fetchLatestPublishedStatsSnapshot(true)
return resource.identifier
}
const refreshStatsBoardView = async ({ force = false } = {}) => {
const statusEl = getStatsStatusEl()
if (statusEl) {
statusEl.textContent = force
? "Refreshing live minting stats, published snapshot, and checkpoint state..."
: "Loading live minting stats, published snapshot, and checkpoint state..."
}
const groupData = await fetchStatsBoardGroupData(force)
const adminAddressSet =
groupData?.allAdminAddressSet || groupData?.minterAdminAddressSet || new Set()
const liveMintingDataPromise = fetchStatsBoardLiveMintingData(force, groupData)
const resources = await fetchStatsSnapshotResources(force, adminAddressSet)
const [liveMintingData, snapshot, sourceResource, progressCheckpoint] =
await Promise.all([
liveMintingDataPromise,
fetchLatestPublishedStatsSnapshot(force, adminAddressSet, resources),
fetchLatestStatsSourceResource(force),
fetchLatestStatsProgressCheckpoint(force, adminAddressSet),
])
renderStatsLiveMintingSection(liveMintingData)
renderStatsBoardSnapshot(snapshot, resources)
renderStatsBoardSyncBanner(
snapshot,
sourceResource,
progressCheckpoint,
getStatsBoardCanPublish()
)
if (
statsBoardState.compiling ||
statsCompileModalState.phase === "progress" ||
statsCompileModalState.phase === "paused" ||
statsCompileModalState.phase === "error"
) {
renderStatsCompileWorkflowBanner()
}
}
const setStatsCompileButtonBusy = (busy = false) => {
const button = document.getElementById("compile-stats-button")
if (!button) return
button.disabled = busy
button.textContent = busy ? "Updating..." : "Update stats"
}
const requestStatsCompilePause = () => {
if (!statsBoardState.compiling) {
return
}
statsBoardState.pauseRequested = true
const statusEl = getStatsStatusEl()
if (statusEl) {
statusEl.textContent = "Pause requested. The current batch will finish, then a checkpoint will be saved."
}
statsCompileModalState.message =
"Pause requested. The current batch will finish, then a checkpoint will be saved."
statsCompileModalState.subtitle =
statsCompileModalState.subtitle ||
"The compile run will stop at the next safe checkpoint."
updateStatsCompileProgressModal({
phase: "progress",
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
steps: statsCompileModalState.steps,
})
}
const compileAndPublishStats = async ({ publishTimestamp = Date.now() } = {}) => {
if (!getStatsBoardCanPublish()) {
return
}
if (statsBoardState.compiling) {
return
}
if (!userState.accountName) {
alert("A registered name is required to publish stats snapshots.")
return
}
statsBoardState.compiling = true
statsBoardState.pauseRequested = false
setStatsCompileButtonBusy(true)
statsCompileModalState.hidden = false
const statusEl = getStatsStatusEl()
const modalSteps = buildStatsCompileSteps()
const applyStepUpdate = (update = {}) => {
if (!update || !update.key) {
return
}
statsCompileModalState.steps = Array.isArray(statsCompileModalState.steps)
? statsCompileModalState.steps
: modalSteps
if (typeof setBoardPublishProgressStepStatus === "function") {
statsCompileModalState.steps = setBoardPublishProgressStepStatus(
statsCompileModalState.steps,
update.key,
update.status || "pending",
update.detail || null
)
}
if (update.message) {
statsCompileModalState.message = String(update.message)
}
if (update.subtitle) {
statsCompileModalState.subtitle = String(update.subtitle)
}
updateStatsCompileProgressModal({
phase: statsCompileModalState.phase,
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
steps: statsCompileModalState.steps,
})
}
statsCompileModalState.phase = "progress"
statsCompileModalState.snapshotTimestamp = publishTimestamp
statsCompileModalState.identifierPreview =
`${MINTER_STATS_IDENTIFIER_PREFIX}-${publishTimestamp}`
statsCompileModalState.message = `Updating stats snapshot for ${formatStatsDate(
publishTimestamp
)}.`
statsCompileModalState.subtitle = `Snapshot identifier: ${statsCompileModalState.identifierPreview}.`
statsCompileModalState.steps = modalSteps
updateStatsCompileProgressModal({
phase: "progress",
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
steps: statsCompileModalState.steps,
})
if (statusEl) {
statusEl.textContent = "Updating live nomination stats..."
}
try {
applyStepUpdate({
key: "load-source",
status: "active",
detail: "Loading the published card archive.",
})
const compiledSnapshot = await buildStatsLiveSnapshot({
snapshotTimestamp: publishTimestamp,
onProgress: applyStepUpdate,
})
applyStepUpdate({
key: "publish",
status: "active",
detail: `Updating ${compiledSnapshot.summary.totalNominations || 0} nominator-era cards and ${
compiledSnapshot.summary.legacyCardCount || 0
} legacy cards.`,
})
const publishedIdentifier = await publishStatsSnapshot(compiledSnapshot, {
publishTimestamp,
})
applyStepUpdate({
key: "publish",
status: "done",
detail: `Updated ${publishedIdentifier || "stats snapshot"}.`,
})
if (statusEl) {
statusEl.textContent = "Stats snapshot updated. Refreshing view..."
}
applyStepUpdate({
key: "refresh",
status: "active",
detail: "Refreshing the Stats board with the new snapshot.",
})
await refreshStatsBoardView({ force: true })
applyStepUpdate({
key: "refresh",
status: "done",
detail: "Stats board refreshed.",
})
statsCompileModalState.phase = "complete"
statsCompileModalState.message = `Updated ${publishedIdentifier || "the stats snapshot"} at ${formatStatsDate(
publishTimestamp
)}.`
statsCompileModalState.subtitle = `Legacy cards before June 2026 were summarized separately.`
updateStatsCompileProgressModal({
phase: "complete",
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
steps: statsCompileModalState.steps,
})
if (statusEl) {
statusEl.textContent = `Updated stats snapshot at ${formatStatsDate(publishTimestamp)}.`
}
} catch (error) {
console.error("Unable to update stats:", error)
statsCompileModalState.phase = "error"
statsCompileModalState.errorMessage =
"Unable to update stats right now."
statsCompileModalState.message =
"The update workflow hit a problem before the snapshot could be published."
statsCompileModalState.subtitle = ""
if (statsCompileModalState.steps.length > 0 && typeof setBoardPublishProgressStepStatus === "function") {
statsCompileModalState.steps = setBoardPublishProgressStepStatus(
statsCompileModalState.steps,
"publish",
"error",
"Updating failed."
)
}
updateStatsCompileProgressModal({
phase: "error",
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
errorMessage: statsCompileModalState.errorMessage,
steps: statsCompileModalState.steps,
})
if (statusEl) {
statusEl.textContent = "Unable to update stats right now."
}
alert("Unable to update stats right now. Please try again.")
} finally {
statsBoardState.compiling = false
setStatsCompileButtonBusy(false)
}
}
const compileStatsProgressRun = async ({
publishTimestamp = Date.now(),
resumeCheckpoint = null,
workflow = "compile",
} = {}) => {
if (!getStatsBoardCanPublish()) {
return
}
if (statsBoardState.compiling) {
return
}
const normalizedWorkflow =
workflow === "validation"
? "validation"
: workflow === "recreate"
? "recreate"
: "compile"
if (normalizedWorkflow !== "validation" && !userState.accountName) {
alert("A registered name is required to publish stats snapshots.")
return
}
statsBoardState.compiling = true
statsBoardState.pauseRequested = false
setStatsCompileButtonBusy(true)
const statusEl = getStatsStatusEl()
const modalSteps = buildStatsCompileSteps(normalizedWorkflow)
const isResumeRun = Boolean(resumeCheckpoint) && normalizedWorkflow !== "validation"
const resumeSummary = getStatsResumeCheckpointSummary(resumeCheckpoint)
const isResumeRefreshOnly =
Boolean(isResumeRun) &&
Boolean(resumeCheckpoint?.completed) &&
Number(resumeSummary?.remaining || 0) === 0
let session = null
let lastSavedCheckpointNextIndex = -1
const applyStepUpdate = (update = {}) => {
if (!update || !update.key) {
return
}
statsCompileModalState.steps = Array.isArray(statsCompileModalState.steps)
? statsCompileModalState.steps
: modalSteps
if (typeof setBoardPublishProgressStepStatus === "function") {
statsCompileModalState.steps = setBoardPublishProgressStepStatus(
statsCompileModalState.steps,
update.key,
update.status || "pending",
update.detail || null
)
}
if (update.message) {
statsCompileModalState.message = String(update.message)
}
if (update.subtitle) {
statsCompileModalState.subtitle = String(update.subtitle)
}
updateStatsCompileProgressModal({
phase: statsCompileModalState.phase,
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
steps: statsCompileModalState.steps,
})
}
try {
statsCompileModalState.phase = "progress"
statsCompileModalState.steps = modalSteps
statsCompileModalState.errorMessage = ""
statsCompileModalState.workflow = normalizedWorkflow
if (isResumeRun) {
session = hydrateStatsCompileSession(resumeCheckpoint)
if (!session) {
throw new Error("Unable to restore the previous compilation checkpoint.")
}
} else {
applyStepUpdate({
key: "load-source",
status: "active",
detail: "Loading the published card archive.",
})
const [sourceResources, referenceData] = await Promise.all([
fetchStatsBoardSourceResources(true),
fetchStatsBoardGroupData(true),
])
session = createStatsCompileSession({
snapshotTimestamp: publishTimestamp,
sourceResources,
referenceData: {
minterGroupAddresses: Array.from(
referenceData?.minterGroupAddressSet || []
),
minterAdminAddresses: Array.from(
referenceData?.minterAdminAddressSet || []
),
},
})
applyStepUpdate({
key: "load-source",
status: "done",
detail: `Loaded ${session.source.cardCount || 0} unique card records.`,
})
}
if (isResumeRun) {
const referenceData = await fetchStatsBoardGroupData(true)
session.referenceData = {
minterGroupAddresses: Array.from(
referenceData?.minterGroupAddressSet || []
),
minterAdminAddresses: Array.from(
referenceData?.minterAdminAddressSet || []
),
}
} else if (
!session.referenceData ||
!Array.isArray(session.referenceData.minterAdminAddresses) ||
session.referenceData.minterAdminAddresses.length === 0
) {
const referenceData = await fetchStatsBoardGroupData(true)
session.referenceData = {
minterGroupAddresses: Array.from(
referenceData?.minterGroupAddressSet || []
),
minterAdminAddresses: Array.from(
referenceData?.minterAdminAddressSet || []
),
}
}
if (normalizedWorkflow === "validation") {
session.validationIssueCount = 0
session.validationIssues = []
}
publishTimestamp = Number(session.snapshotTimestamp || publishTimestamp || Date.now())
statsCompileModalState.snapshotTimestamp = publishTimestamp
statsCompileModalState.identifierPreview =
`${MINTER_STATS_IDENTIFIER_PREFIX}-${publishTimestamp}`
statsCompileModalState.message = isResumeRun
? isResumeRefreshOnly
? `Refreshing the completed stats snapshot for ${formatStatsDate(
publishTimestamp
)}${
resumeSummary?.checkpointIdentifier
? ` from checkpoint ${resumeSummary.checkpointIdentifier}`
: ""
}.`
: `Resuming stats update for ${formatStatsDate(publishTimestamp)}${
resumeSummary?.checkpointIdentifier
? ` from checkpoint ${resumeSummary.checkpointIdentifier}`
: ""
}.`
: normalizedWorkflow === "validation"
? `Validating stats data for ${formatStatsDate(publishTimestamp)}.`
: normalizedWorkflow === "recreate"
? `Re-creating stats data from scratch for ${formatStatsDate(publishTimestamp)}.`
: `Updating stats snapshot for ${formatStatsDate(publishTimestamp)}.`
statsCompileModalState.subtitle = isResumeRun
? resumeSummary
? isResumeRefreshOnly
? `Checkpoint ${resumeSummary.checkpointIdentifier || session.progressIdentifier || "Unavailable"} already covers ${resumeSummary.total} cards. The published snapshot will be rechecked before publishing.`
: `Continuing checkpoint ${resumeSummary.checkpointIdentifier || session.progressIdentifier || "Unavailable"} at card ${resumeSummary.nextCardNumber} of ${resumeSummary.total} (${resumeSummary.remaining} remaining).`
: `Continuing checkpoint ${session.progressIdentifier || "Unavailable"}.`
: normalizedWorkflow === "validation"
? "This audit will not publish any data."
: `Checkpoint identifier: ${session.progressIdentifier || getStatsCompileProgressIdentifier(publishTimestamp)}.`
updateStatsCompileProgressModal({
phase: "progress",
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
steps: statsCompileModalState.steps,
})
if (statusEl) {
statusEl.textContent = isResumeRun
? resumeSummary
? isResumeRefreshOnly
? `Refreshing completed checkpoint ${resumeSummary.checkpointIdentifier || session.progressIdentifier || "Unavailable"} before publishing the snapshot.`
: `Resuming checkpoint ${resumeSummary.checkpointIdentifier || session.progressIdentifier || "Unavailable"} at card ${resumeSummary.nextCardNumber} of ${resumeSummary.total} (${resumeSummary.remaining} remaining).`
: "Continuing a saved stats checkpoint..."
: normalizedWorkflow === "validation"
? "Validating stats data..."
: normalizedWorkflow === "recreate"
? "Re-creating stats data from scratch..."
: "Updating stats snapshot..."
}
if (isResumeRun) {
applyStepUpdate({
key: "load-source",
status: "done",
detail: `Loaded saved checkpoint for ${session.source.cardCount || 0} cards.`,
})
applyStepUpdate({
key: "classify-era",
status: isResumeRefreshOnly ? "done" : "active",
detail: isResumeRefreshOnly
? `Loaded a completed checkpoint for ${session.source.cardCount || 0} cards. Rechecking the published snapshot before publishing.`
: `Resuming at card ${Math.min(
(session.nextIndex || 0) + 1,
session.source.cardCount || 0
)} of ${session.source.cardCount || 0}.`,
})
if (isResumeRefreshOnly) {
applyStepUpdate({
key: "aggregate",
status: "done",
detail:
"Rebuilding the snapshot summary from the saved records and the latest roster.",
})
}
}
while (
!session.completed &&
!statsBoardState.pauseRequested &&
(session.nextIndex || 0) < (session.source?.cardCount || 0)
) {
await processStatsCompileBatch(session, {
onProgress: applyStepUpdate,
onIssue:
normalizedWorkflow === "validation"
? (issue) => {
if (!issue) {
return
}
session.validationIssues = Array.isArray(session.validationIssues)
? session.validationIssues
: []
session.validationIssues.push(issue)
}
: null,
})
if (normalizedWorkflow !== "validation" && !session.completed) {
const checkpointProgress = `${session.nextIndex || 0}/${session.source?.cardCount || 0}`
applyStepUpdate({
key: "publish",
status: "active",
detail: `Saving checkpoint ${checkpointProgress}.`,
})
await publishStatsCompileCheckpoint(session)
lastSavedCheckpointNextIndex = Number(session.nextIndex || 0)
applyStepUpdate({
key: "publish",
status: "done",
detail: `Saved checkpoint ${checkpointProgress}.`,
})
}
if (!session.completed && !statsBoardState.pauseRequested) {
await qBoardDelay(0)
}
}
const totalResources = Number(session.source?.cardCount || 0)
if (normalizedWorkflow === "validation") {
applyStepUpdate({
key: "publish",
status: "active",
detail: "Loading the latest published stats resources and checkpoint.",
})
const validationGroupData = await fetchStatsBoardGroupData(true)
const verifiedAdminAddressSet =
validationGroupData?.allAdminAddressSet ||
validationGroupData?.minterAdminAddressSet ||
new Set()
const [verifiedSnapshotResources, verifiedCheckpoint] = await Promise.all([
fetchStatsSnapshotResources(true, verifiedAdminAddressSet),
fetchLatestStatsProgressCheckpoint(true, verifiedAdminAddressSet),
])
const validationIssueCount = Math.max(
Number(session.validationIssueCount || 0),
Array.isArray(session.validationIssues)
? session.validationIssues.length
: 0
)
const validationReport = {
sourceCount: totalResources,
compiledCount: Number(session.records?.length || 0),
currentCount: Number(session.summary?.totalNominations || 0),
legacyCount: Number(session.summary?.legacyCardCount || 0),
validationIssueCount,
verifiedSnapshotCount: Number(verifiedSnapshotResources.length || 0),
verifiedProgressCheckpoint: Boolean(verifiedCheckpoint && !verifiedCheckpoint.completed),
verifiedProgressIdentifier: String(
verifiedCheckpoint?.progressIdentifier || ""
).trim(),
verifiedProgressPublisher: String(
verifiedCheckpoint?.generatedBy?.name ||
verifiedCheckpoint?.generatedBy?.address ||
""
).trim(),
issueSamples: Array.isArray(session.validationIssues)
? session.validationIssues.slice(0, 6)
: [],
}
statsBoardState.latestValidationReport = validationReport
statsCompileModalState.validationReport = validationReport
const validationStatusText = validationIssueCount
? `Validation finished with ${validationIssueCount} issue(s) to review.`
: "Validation completed successfully."
applyStepUpdate({
key: "publish",
status: "done",
detail: validationReport.verifiedProgressCheckpoint
? `Verified ${validationReport.verifiedSnapshotCount || 0} admin-published snapshots and a resumable checkpoint.`
: `Verified ${validationReport.verifiedSnapshotCount || 0} admin-published snapshots.`,
})
applyStepUpdate({
key: "refresh",
status: "active",
detail: "Summarizing the validation findings.",
})
if (statusEl) {
statusEl.textContent = validationIssueCount
? `Validation finished with ${validationIssueCount} issue(s) to review.`
: "Validation completed successfully."
}
statsCompileModalState.phase = "complete"
statsCompileModalState.message = validationIssueCount
? `Validated ${validationReport.sourceCount} cards with ${validationIssueCount} issue(s) to review.`
: `Validated ${validationReport.sourceCount} cards successfully.`
statsCompileModalState.subtitle = validationReport.verifiedProgressCheckpoint
? `Verified checkpoint ${validationReport.verifiedProgressIdentifier || "Unavailable"} was published by ${validationReport.verifiedProgressPublisher || "an admin"}.`
: "No verified resumable checkpoint is currently available."
updateStatsCompileProgressModal({
phase: "complete",
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
steps: statsCompileModalState.steps,
})
applyStepUpdate({
key: "refresh",
status: "done",
detail: validationStatusText,
})
await refreshStatsBoardView({ force: true })
if (statusEl) {
statusEl.textContent = validationStatusText
}
return
}
if (session.completed) {
applyStepUpdate({
key: "publish",
status: "active",
detail:
normalizedWorkflow === "recreate"
? `Re-creating the completed snapshot and checkpoint for ${session.summary.totalNominations || 0} nominator-era cards and ${
session.summary.legacyCardCount || 0
} legacy cards.`
: isResumeRefreshOnly
? `Refreshing the completed snapshot and checkpoint for ${session.summary.totalNominations || 0} nominator-era cards and ${
session.summary.legacyCardCount || 0
} legacy cards.`
: `Updating the completed snapshot and checkpoint for ${session.summary.totalNominations || 0} nominator-era cards and ${
session.summary.legacyCardCount || 0
} legacy cards.`,
})
const compiledSnapshot = buildStatsSnapshotFromSession(session)
session.summary = {
...session.summary,
totalNominations: Number(compiledSnapshot?.summary?.totalNominations || 0),
uniqueNominators: Number(compiledSnapshot?.summary?.uniqueNominators || 0),
uniqueNominees: Number(compiledSnapshot?.summary?.uniqueNominees || 0),
totalConvertedToMinter: Number(
compiledSnapshot?.summary?.totalConvertedToMinter || 0
),
totalApprovedInvites: Number(
compiledSnapshot?.summary?.totalApprovedInvites || 0
),
totalPendingInvites: Number(
compiledSnapshot?.summary?.totalPendingInvites || 0
),
totalKickedAndBanned: Number(
compiledSnapshot?.summary?.totalKickedAndBanned || 0
),
legacyCardCount: Number(compiledSnapshot?.summary?.legacyCardCount || 0),
legacyConvertedToMinter: Number(
compiledSnapshot?.summary?.legacyConvertedToMinter || 0
),
legacyApprovedInvites: Number(
compiledSnapshot?.summary?.legacyApprovedInvites || 0
),
legacyPendingInvites: Number(
compiledSnapshot?.summary?.legacyPendingInvites || 0
),
legacyKickedAndBanned: Number(
compiledSnapshot?.summary?.legacyKickedAndBanned || 0
),
}
session.finalSnapshotIdentifier =
`${MINTER_STATS_IDENTIFIER_PREFIX}-${publishTimestamp}`
session.finalPublishedAt = Date.now()
session.completed = true
const publishedResources = await publishStatsSnapshotBundle(
compiledSnapshot,
session,
{
publishTimestamp,
}
)
applyStepUpdate({
key: "publish",
status: "done",
detail:
normalizedWorkflow === "recreate"
? `Re-created ${publishedResources.snapshotIdentifier || "stats snapshot"} and ${
publishedResources.checkpointIdentifier || "checkpoint"
}.`
: isResumeRefreshOnly
? `Refreshed ${publishedResources.snapshotIdentifier || "stats snapshot"} and ${
publishedResources.checkpointIdentifier || "checkpoint"
}.`
: `Updated ${publishedResources.snapshotIdentifier || "stats snapshot"} and ${
publishedResources.checkpointIdentifier || "checkpoint"
}.`,
})
if (statusEl) {
statusEl.textContent =
normalizedWorkflow === "recreate"
? "Stats data re-created. Refreshing view..."
: isResumeRefreshOnly
? "Stats snapshot refreshed. Refreshing view..."
: "Stats snapshot and checkpoint updated. Refreshing view..."
}
applyStepUpdate({
key: "refresh",
status: "active",
detail: "Refreshing the Stats board with the new snapshot.",
})
await refreshStatsBoardView({ force: true })
applyStepUpdate({
key: "refresh",
status: "done",
detail:
normalizedWorkflow === "recreate"
? "Stats board refreshed after re-creating the data."
: "Stats board refreshed.",
})
statsCompileModalState.phase = "complete"
statsCompileModalState.message =
normalizedWorkflow === "recreate"
? `Re-created ${publishedResources.snapshotIdentifier || "the stats snapshot"} and ${publishedResources.checkpointIdentifier || "the checkpoint"} at ${formatStatsDate(
publishTimestamp
)}.`
: isResumeRefreshOnly
? `Refreshed ${
publishedResources.snapshotIdentifier || "the stats snapshot"
} and ${
publishedResources.checkpointIdentifier || "the checkpoint"
} at ${formatStatsDate(publishTimestamp)}.`
: `Updated ${
publishedResources.snapshotIdentifier || "the stats snapshot"
} and ${
publishedResources.checkpointIdentifier || "the checkpoint"
} at ${formatStatsDate(publishTimestamp)}.`
statsCompileModalState.subtitle = isResumeRefreshOnly
? "The completed checkpoint was rechecked against the current roster before publishing."
: `Legacy cards before June 2026 were summarized separately.`
updateStatsCompileProgressModal({
phase: "complete",
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
steps: statsCompileModalState.steps,
})
if (statusEl) {
statusEl.textContent =
normalizedWorkflow === "recreate"
? `Re-created stats data at ${formatStatsDate(publishTimestamp)}.`
: isResumeRefreshOnly
? `Refreshed stats snapshot and checkpoint at ${formatStatsDate(
publishTimestamp
)}.`
: `Updated stats snapshot and checkpoint at ${formatStatsDate(
publishTimestamp
)}.`
}
} else {
applyStepUpdate({
key: "publish",
status: "active",
detail: `Saving a resumable checkpoint after ${session.nextIndex || 0} of ${totalResources} cards.`,
})
session.finalSnapshotIdentifier = ""
session.finalPublishedAt = 0
session.completed = false
if (lastSavedCheckpointNextIndex !== Number(session.nextIndex || 0)) {
await publishStatsCompileCheckpoint(session)
}
applyStepUpdate({
key: "publish",
status: "done",
detail: `Saved a resumable checkpoint after ${session.nextIndex || 0} of ${totalResources} cards.`,
})
statsCompileModalState.phase = "paused"
statsCompileModalState.message = `Saved a resumable checkpoint after ${session.nextIndex || 0} of ${totalResources} cards.`
statsCompileModalState.subtitle = `Checkpoint identifier: ${session.progressIdentifier || "Unavailable"}.`
updateStatsCompileProgressModal({
phase: "paused",
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
steps: statsCompileModalState.steps,
})
if (statusEl) {
statusEl.textContent = `Saved a resumable checkpoint after ${session.nextIndex || 0} of ${totalResources} cards.`
}
await refreshStatsBoardView({ force: true })
if (statusEl) {
statusEl.textContent = `Saved a resumable checkpoint after ${session.nextIndex || 0} of ${totalResources} cards.`
}
}
} catch (error) {
console.error("Unable to update stats:", error)
statsCompileModalState.phase = "error"
statsCompileModalState.errorMessage =
"Unable to update stats right now."
statsCompileModalState.message =
"The update workflow hit a problem before the snapshot could be published."
statsCompileModalState.subtitle = ""
if (
statsCompileModalState.steps.length > 0 &&
typeof setBoardPublishProgressStepStatus === "function"
) {
statsCompileModalState.steps = setBoardPublishProgressStepStatus(
statsCompileModalState.steps,
"publish",
"error",
"Updating failed."
)
}
updateStatsCompileProgressModal({
phase: "error",
message: statsCompileModalState.message,
subtitle: statsCompileModalState.subtitle,
errorMessage: statsCompileModalState.errorMessage,
steps: statsCompileModalState.steps,
})
if (statusEl) {
statusEl.textContent = "Unable to update stats right now."
}
alert("Unable to update stats right now. Please try again.")
} finally {
statsBoardState.compiling = false
statsBoardState.pauseRequested = false
setStatsCompileButtonBusy(false)
}
}
const continuePreviousStatsCompilation = async () => {
if (statsBoardState.compiling) {
return
}
const checkpoint =
statsBoardState.latestProgressCheckpoint ||
(await fetchLatestStatsProgressCheckpoint(true))
if (!checkpoint || checkpoint.completed) {
openStatsCompileModal()
return
}
statsCompileModalState.workflow = "compile"
statsCompileModalState.hidden = false
statsCompileModalState.phase = "progress"
const resumeSummary = getStatsResumeCheckpointSummary(checkpoint)
statsCompileModalState.snapshotTimestamp =
Number(checkpoint.snapshotTimestamp || checkpoint.generatedAt || Date.now()) ||
Date.now()
statsCompileModalState.identifierPreview =
`${MINTER_STATS_IDENTIFIER_PREFIX}-${statsCompileModalState.snapshotTimestamp}`
statsCompileModalState.message = `Resuming the saved stats update for ${formatStatsDate(
statsCompileModalState.snapshotTimestamp
)}${
resumeSummary?.checkpointIdentifier
? ` from checkpoint ${resumeSummary.checkpointIdentifier}`
: ""
}.`
statsCompileModalState.subtitle = resumeSummary
? `Checkpoint ${resumeSummary.checkpointIdentifier || "Unavailable"} will continue at card ${resumeSummary.nextCardNumber} of ${resumeSummary.total} (${resumeSummary.remaining} remaining).`
: `Checkpoint identifier: ${checkpoint.progressIdentifier || "Unavailable"}.`
statsCompileModalState.steps = buildStatsCompileSteps("compile")
statsCompileModalState.errorMessage = ""
renderStatsCompileModal()
await compileStatsProgressRun({
publishTimestamp: statsCompileModalState.snapshotTimestamp,
resumeCheckpoint: checkpoint,
workflow: "compile",
})
}
const loadStatsPage = async () => {
if (typeof detachAdminBoardInfiniteScroll === "function") {
detachAdminBoardInfiniteScroll()
}
if (typeof detachMinterBoardInfiniteScroll === "function") {
detachMinterBoardInfiniteScroll()
}
qMintershipActiveBoard = "stats"
document.title = STATS_PAGE_DOCUMENT_TITLE
clearQMintershipBodyContent()
const canPublish = getStatsBoardCanPublish()
const avatarUrl = `/arbitrary/THUMBNAIL/${userState.accountName}/qortal_avatar`
const requestedStatsSection = String(qMintershipRouteState.section || "").trim()
const initialStatsSection = normalizeStatsSectionKey(
requestedStatsSection || "live"
)
const heroRoleChips = []
if (userState.isAdmin) {
heroRoleChips.push(`
<div class="stats-hero-chip stats-hero-chip--role stats-hero-chip--admin">
App Admin
</div>
`)
}
if (userState.isMinterAdmin) {
heroRoleChips.push(`
<div class="stats-hero-chip stats-hero-chip--role stats-hero-chip--minter">
Minter Admin
</div>
`)
}
const mainContent = document.createElement("div")
mainContent.innerHTML = `
<div class="stats-board-main mbr-parallax-background cid-ttRnlSkg2R">
<div class="stats-board-shell">
<header class="stats-hero">
<div class="stats-hero-copy">
<p class="stats-hero-kicker">Nominator intelligence</p>
<h1>${qEscapeHtml(STATS_PAGE_TITLE)}</h1>
<p class="stats-hero-description">
A live, readable view of the current MINTER group, historic mintership
data, and the latest publish snapshot. Live minting stats come first,
the published long-term views sit in the middle, and publish data lives below.
This page is still in beta, so not every future stat is shown yet.
</p>
</div>
<div class="stats-hero-chip-stack">
<div class="stats-hero-chip">
<img src="${avatarUrl}" alt="" class="stats-hero-avatar" />
<span>${qEscapeHtml(userState.accountName || "Guest")}</span>
</div>
<div class="stats-hero-chip stats-hero-chip--role stats-hero-chip--beta">
Beta
</div>
${heroRoleChips.join("")}
<div class="stats-hero-chip stats-hero-chip--muted">
Snapshot-driven and public by default
</div>
</div>
</header>
<div class="stats-actions">
<button
type="button"
id="refresh-stats-button"
class="stats-action-button"
>
Refresh latest snapshot
</button>
${
canPublish
? `<button
type="button"
id="compile-stats-button"
class="stats-action-button stats-action-button--primary"
>
Update stats
</button>`
: ""
}
</div>
<p id="stats-status" class="stats-status">
Loading live minting stats, published snapshot, and checkpoint state...
</p>
<div id="stats-sync-banner" class="stats-sync-banner" hidden></div>
${buildStatsSectionNavHtml(initialStatsSection)}
<section id="stats-section-live" class="stats-section">
<div class="stats-section-header">
<div>
<p class="stats-panel-kicker">Live Minting Stats</p>
<h2>Current MINTER group activity</h2>
</div>
<p class="stats-panel-copy">
Pulled directly from the live Qortal APIs so we can see the current
MINTER roster, online actual levels, founder accounts, and group
status at a glance.
</p>
</div>
<section class="stats-summary-grid" id="stats-live-summary-grid"></section>
<div class="stats-panels-grid">
<section class="stats-panel stats-panel--wide" id="stats-live-levels-panel">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">Online levels</p>
<h2>Currently online minters by actual level</h2>
</div>
<p class="stats-panel-copy">
Counts are sourced from <code>/addresses/online/levels</code> and
founder accounts are displayed separately below.
</p>
</div>
<div id="stats-live-levels-container"></div>
</section>
<section class="stats-panel" id="stats-live-group-panel">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">MINTER group</p>
<h2>Live group details</h2>
</div>
</div>
<div id="stats-live-group-meta-container"></div>
</section>
</div>
<section class="stats-panel stats-panel--wide" id="stats-live-founder-panel">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">Founder accounts</p>
<h2>Effective level 10 accounts</h2>
</div>
<p class="stats-panel-copy">
These accounts are weighted like level 10 for signing purposes, but
they are shown separately from the actual level table.
</p>
</div>
<div id="stats-live-founder-container"></div>
</section>
</section>
<section id="stats-section-historic" class="stats-section">
<div class="stats-section-header">
<div>
<p class="stats-panel-kicker">Historic Mintership Data</p>
<h2>Published long-term stats and leaderboards</h2>
</div>
<p class="stats-panel-copy">
These are the published long-term views: the nominator leaderboard,
legacy lifecycle data, and current admin-publisher history.
</p>
</div>
<section id="stats-section-nominator" class="stats-section">
<div class="stats-section-header">
<div>
<p class="stats-panel-kicker">Nominator Stats</p>
<h2>Nominator leaderboard</h2>
</div>
<p class="stats-panel-copy">
The leaderboard below is ranked by nominator-era submissions, then
conversion count, then recency.
</p>
</div>
<section class="stats-panel stats-panel--wide" id="stats-nominator-leaderboard">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">Leaderboard</p>
<h2>Nominator leaderboard</h2>
</div>
<p class="stats-panel-copy">
Ranked by nominator-era submissions, then conversion count, then recency.
</p>
</div>
<div id="stats-leaderboard-container"></div>
</section>
</section>
<section id="stats-section-legacy" class="stats-section">
<div class="stats-section-header">
<div>
<p class="stats-panel-kicker">Legacy Stats</p>
<h2>Legacy publisher lifecycle</h2>
</div>
<p class="stats-panel-copy">
Cards published before June 1, 2026 belong to the legacy flow. We track
whether the publisher was invited, stayed in the group, or was kicked or banned.
</p>
</div>
<section class="stats-summary-grid" id="stats-legacy-summary-grid"></section>
<div class="stats-panels-grid">
<section class="stats-panel stats-panel--wide" id="stats-legacy-leaderboard">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">Legacy Leaderboards</p>
<h2>Legacy publisher leaderboard</h2>
</div>
<p class="stats-panel-copy">
Ranked by legacy card count, then current group membership, then recency.
</p>
</div>
<div id="stats-legacy-leaderboard-container"></div>
</section>
<section class="stats-panel">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">Legacy Notes</p>
<h2>Legacy-era summary</h2>
</div>
</div>
<div id="stats-legacy-meta-container"></div>
</section>
</div>
</section>
<section id="stats-section-admin" class="stats-section">
<div class="stats-section-header">
<div>
<p class="stats-panel-kicker">Minter Admin Stats</p>
<h2>Current admin publisher overview</h2>
</div>
<p class="stats-panel-copy">
This view focuses on cards published by the current Minter Admin roster, which gives us a base for later scoring and admin-specific leaderboards.
</p>
</div>
<section class="stats-summary-grid" id="stats-admin-summary-grid"></section>
<div class="stats-panels-grid">
<section class="stats-panel stats-panel--wide" id="stats-admin-leaderboard">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">MinterAdmin Leaderboards</p>
<h2>Admin publisher leaderboard</h2>
</div>
<p class="stats-panel-copy">
Ranked by admin-published card count, then current-era contribution, then recency.
</p>
</div>
<div id="stats-admin-leaderboard-container"></div>
</section>
<section class="stats-panel">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">Admin Notes</p>
<h2>Current admin roster context</h2>
</div>
</div>
<div id="stats-admin-meta-container"></div>
</section>
</div>
</section>
</section>
<section id="stats-section-publish" class="stats-section">
<div class="stats-section-header">
<div>
<p class="stats-panel-kicker">Publish Data</p>
<h2>Snapshot metrics, metadata, and history</h2>
</div>
<p class="stats-panel-copy">
The live publish snapshot is grouped here so the headline numbers,
publish metadata, and recent snapshot history are easy to scan together.
</p>
</div>
<section class="stats-summary-grid" id="stats-summary-grid"></section>
<div class="stats-panels-grid">
<section class="stats-panel" id="stats-snapshot-panel">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">Snapshot</p>
<h2>Latest published snapshot</h2>
</div>
</div>
<div id="stats-snapshot-meta"></div>
</section>
<section class="stats-panel" id="stats-history-panel">
<div class="stats-panel-header">
<div>
<p class="stats-panel-kicker">History</p>
<h2>Recent snapshots</h2>
</div>
</div>
<div id="stats-history-container"></div>
</section>
</div>
</section>
<div id="stats-snapshot-container" style="display: none;"></div>
<div id="stats-board-content"></div>
</div>
</div>
`
document.body.appendChild(mainContent)
const refreshButton = document.getElementById("refresh-stats-button")
if (refreshButton) {
refreshButton.addEventListener("click", async () => {
await refreshStatsBoardView({ force: true })
})
}
const compileButton = document.getElementById("compile-stats-button")
if (compileButton) {
compileButton.addEventListener("click", async () => {
openStatsCompileModal()
})
}
renderStatsSectionNavState(initialStatsSection)
if (requestedStatsSection) {
await focusStatsBoardSection(initialStatsSection, { behavior: "auto" })
} else {
window.scrollTo({ top: 0, behavior: "auto" })
}
await refreshStatsBoardView({ force: true })
}