| name | jb-ruleset-timeline-ui |
| description | Visual timeline explorer for Juicebox project ruleset history. Shows configuration evolution, upcoming changes, and enables ruleset comparison. |
Juicebox V5 Ruleset Timeline UI
Visual timeline explorer for Juicebox project ruleset history. Shows the evolution of project configurations over time.
Uses Shared Components
This skill uses components from /shared/:
| Component | Purpose |
|---|
styles.css | Dark theme, cards, buttons, badges |
wallet-utils.js | Chain config, formatting utilities |
chain-config.json | RPC URLs, contract addresses |
abis/JBController.json | Ruleset query functions |
Overview
This skill generates vanilla JS/HTML interfaces for visualizing:
- Complete ruleset history for any project
- Timeline of configuration changes
- Upcoming queued rulesets
- Ruleset approval status
- Parameter comparisons between rulesets
Ruleset Timeline UI Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Juicebox Ruleset Timeline</title>
<link rel="stylesheet" href="/shared/styles.css">
<style>
.project-header { display: none; }
.project-header.visible { display: block; }
.project-name { font-size: 1.5rem; font-weight: 600; color: var(--text-secondary); }
.project-id { color: var(--text-muted); margin-top: 5px; }
.timeline-container { position: relative; padding-left: 40px; }
.timeline-line { position: absolute; left: 15px; top: 0; bottom: 0; width: 2px; background: linear-gradient(180deg, var(--jb-yellow) 0%, var(--border-color) 100%); }
.ruleset-card { position: relative; border-left: 3px solid var(--border-color); margin-bottom: var(--space-md); }
.ruleset-card.current { border-left-color: var(--success); : (, (--success-bg) , (--bg-secondary) ); }
{ : (--jb-yellow); : (, (,,,) , (--bg-secondary) ); }
{ : ; }
{ : absolute; : -; : ; : ; : ; : ; : (--border-color); : solid (--bg-secondary); }
{ : (--success); }
{ : (--jb-yellow); }
{ : flex; : space-between; : flex-start; : (--space-md); }
{ : ; : ; : (--text-secondary); }
{ : ; : ; : ; : ; }
{ : (--success-bg); : (--success); }
{ : (,,,); : (--jb-yellow); }
{ : (,,,); : (--text-muted); }
{ : (--warning-bg); : (--warning); }
{ : flex; : ; : (--space-md); : ; : (--text-muted); }
{ : grid; : (auto-fill, (, fr)); : (--space-md); }
{ : (--bg-tertiary); : ; : (--radius-md); }
{ : ; : (--text-muted); : uppercase; : ; : ; }
{ : ; : ; : (--text-secondary); }
{ : (--jb-yellow); }
{ : none; : solid (--border-color); : (--text-muted); : ; : ; : pointer; : (--space-md); : ; }
{ : (--jb-yellow); : (--jb-yellow); }
{ : none; : (--space-md); : (--space-md); : solid (--border-color); }
{ : block; }
{ : flex; : ; : (--space-md); : ; }
{ : flex; : center; : ; }
{ : ; : ; : ; }
{ : (--success); }
{ : (--jb-yellow); }
{ : ; }
{ : (--bg-tertiary); : solid (--border-color); : (--text-primary); : ; : ; : pointer; : ; : ; }
{ : (--jb-yellow); }
{ : none; : fixed; : ; : ; : ; : ; : (,,,); : ; : ; : auto; }
{ : flex; : center; : flex-start; }
{ : (--bg-secondary); : (--radius-lg); : ; : ; : ; : ; }
{ : flex; : space-between; : center; : (--space-md); }
{ : none; : none; : (--text-muted); : ; : pointer; }
{ : ; : collapse; }
, { : ; : left; : solid (--border-color); }
{ : (--text-muted); : ; }
{ : (--jb-yellow); }
</style>
</head>
<body>
<div class="container">
<h1>Ruleset Timeline</h1>
<div class="card">
<div class="row">
<input type="number" id="projectId" placeholder="Project ID" min="1" style="flex: 1;">
<select id="chainSelect" style="width: 150px;">
<option value="1">Ethereum</option>
<option value="10">Optimism</option>
<option value="8453">Base</option>
<option value="42161">Arbitrum</option>
<option value="11155111">Sepolia</option>
</select>
<button onclick="loadTimeline()">Load Timeline</button>
</div>
</div>
<div class="card project-header" id="projectHeader">
<div class="project-name" id="projectName">-</div>
<div class="project-id" id="projectIdDisplay">-</div>
</div>
<div class="legend" id="legend" style="display: none;">
<div class="legend-item"><div class="legend-dot current"></div><span>Current Ruleset</span></div>
<div class="legend-item"><div class="legend-dot upcoming"></div><span>Queued/Upcoming</span></div>
<div class="legend-item"><div class="legend-dot past"></div><span>Past Rulesets</span></div>
</div>
<div id="timelineContainer"><div class="loading">Enter a project ID to load timeline</div></div>
</div>
<div class="compare-modal" id="compareModal">
<div class="compare-content">
<div class="compare-header">
<h2>Compare Rulesets</h2>
<button class="close-btn" onclick="closeCompare()">×</button>
</div>
<div id="compareBody"></div>
</div>
</div>
<script type="module">
import { createPublicClient, http, getContract, zeroAddress } from 'https://esm.sh/viem';
import { CHAIN_CONFIGS, loadChainConfig } from '/shared/wallet-utils.js';
const CONTROLLER_ABI = [
{ name: 'currentRulesetOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'projectId', type: 'uint256' }], outputs: [{ name: 'ruleset', type: 'tuple', components: [{ name: 'cycleNumber', type: 'uint256' }, { name: 'id', type: 'uint256' }, { name: 'basedOnId', type: 'uint256' }, { name: 'start', type: 'uint256' }, { name: 'duration', type: 'uint256' }, { name: 'weight', type: 'uint256' }, { name: 'weightCutPercent', type: 'uint256' }, { : , : }, { : , : }] }, { : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }] }] },
{ : , : , : , : [{ : , : }], : [{ : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }] }, { : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }] }] },
{ : , : , : , : [{ : , : }], : [{ : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }] }, { : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }] }, { : , : }] },
{ : , : , : , : [{ : , : }, { : , : }, { : , : }], : [{ : , : , : [{ : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }] }, { : , : , : [{ : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }, { : , : }] }] }] }
];
allRulesets = [];
currentRulesetId = ;
. = loadTimeline;
. = toggleDetails;
. = compareRulesets;
. = closeCompare;
() {
projectId = .().;
chainId = (.().);
(!projectId) { (); ; }
container = .();
container. = ;
{
config = ();
chainConfig = config.[chainId];
chain = [chainId];
client = ({ chain, : (chainConfig.) });
controllerAddress = chainConfig..;
controller = ({ : controllerAddress, : , client });
.(). = ;
.(). = ;
.()..();
[currentRuleset, currentMetadata] = controller..([(projectId)]);
currentRulesetId = currentRuleset..();
upcomingRuleset = , queuedRuleset = ;
{
[upcoming, upcomingMeta] = controller..([(projectId)]);
(upcoming..() !== currentRulesetId) {
upcomingRuleset = { : upcoming, : upcomingMeta, : };
}
} (e) {}
{
[queued, queuedMeta, approvalStatus] = controller..([(projectId)]);
queuedId = queued..();
(queuedId !== currentRulesetId && (!upcomingRuleset || queuedId !== upcomingRuleset...())) {
queuedRuleset = { : queued, : queuedMeta, approvalStatus, : };
}
} (e) {}
historicalRulesets = controller..([(projectId), , ]);
allRulesets = [];
(queuedRuleset) allRulesets.(queuedRuleset);
(upcomingRuleset) allRulesets.(upcomingRuleset);
allRulesets.({ : currentRuleset, : currentMetadata, : });
( rs historicalRulesets) {
(rs...() !== currentRulesetId) {
(!allRulesets.( r...() === rs...())) {
allRulesets.({ ...rs, : });
}
}
}
allRulesets.( (b..) - (a..));
();
.().. = ;
} (error) {
.(error);
container. = ;
}
}
() {
container = .();
(allRulesets. === ) {
container. = ;
;
}
html = ;
allRulesets.( {
{ ruleset, metadata, status, approvalStatus } = rs;
startDate = ((ruleset.) * );
endDate = ruleset. > ? (((ruleset.) + (ruleset.)) * ) : ;
statusLabel = status === ? (approvalStatus) : status;
html += ;
});
html += ;
container. = html;
}
() {
details = .();
details..();
btn = details.;
btn. = details..() ? : ;
}
() {
rs1 = allRulesets[index1], rs2 = allRulesets[index2];
fields = [
{ : , : , : formatWeight },
{ : , : , : ((v) / ).() + },
{ : , : , : (v) === ? : ((v)) },
{ : , : , : ((v) / ).() + , : },
{ : , : , : ((v) / ).() + , : },
{ : , : , : v ? : , : },
{ : , : , : v ? : , : },
{ : , : , : v ? : , : },
];
html = ;
fields.( {
val1 = field. ? rs1.[field.] : rs1.[field.];
val2 = field. ? rs2.[field.] : rs2.[field.];
f1 = field.(val1), f2 = field.(val2);
html += ;
});
html += ;
.(). = html;
.()..();
}
() { .()..(); }
() {
tokens = (weight) / ;
(tokens >= ) (tokens / ).() + ;
(tokens >= ) (tokens / ).() + ;
tokens.();
}
() {
(seconds < ) .(seconds / ) + ;
(seconds < ) .(seconds / ) + ;
(seconds < ) .(seconds / ) + ;
.(seconds / ) + ;
}
() {
labels = { : , : , : , : , : , : };
labels[(status)] || ;
}
.(, { (e. === ) (); });
.().(, { (e.. === ) (); });
</script>
</body>
</html>
Key Features
Timeline Visualization
- Vertical timeline with visual indicators
- Color-coded status (current, upcoming, past)
- Chronological ordering
Ruleset Comparison
- Side-by-side parameter comparison
- Highlights changed values
- Compare any two rulesets
Detailed Information
- Expandable details for each ruleset
- All metadata fields displayed
- Approval hook status
Data Sources
The timeline uses on-chain data via:
currentRulesetOf() - Active ruleset
upcomingRulesetOf() - Next ruleset
latestQueuedRulesetOf() - Queued with approval status
allRulesetsOf() - Historical rulesets
Related Skills
/jb-explorer-ui - Contract read/write interface
/jb-event-explorer-ui - Event history
/jb-query - Query project data via CLI