1761 lines
56 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Build Power. Not Rent It. Own your digital infrastructure.">
<meta name="author" content="Bunker Operations">
<link rel="canonical" href="https://cmlite.org/blog/category/release-notes/">
<link rel="prev" href="../platform/">
<link rel="next" href="../testing/">
<link rel="icon" href="../../../assets/favicon.svg">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.6">
<title>Release Notes - Changemaker Lite</title>
<link rel="stylesheet" href="../../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="../../../stylesheets/extra.css">
<link rel="stylesheet" href="../../../stylesheets/home.css">
<link rel="stylesheet" href="../../../stylesheets/docs-comments.css">
<link rel="stylesheet" href="../../../assets/css/video-player.css">
<link rel="stylesheet" href="../../../assets/css/image-gallery.css">
<link rel="stylesheet" href="../../../assets/css/payment-widgets.css">
<script>__md_scope=new URL("../../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
<meta property="og:type" content="website" />
<meta property="og:title" content="Release Notes - Changemaker Lite" />
<meta property="og:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="og:image" content="https://cmlite.org/assets/images/social/blog/category/release-notes.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://cmlite.org/blog/category/release-notes/" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content="Release Notes - Changemaker Lite" />
<meta property="twitter:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="twitter:image" content="https://cmlite.org/assets/images/social/blog/category/release-notes.png" />
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#release-notes" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
<aside class="md-banner">
<div class="md-banner__inner md-grid md-typeset">
<button class="md-banner__button md-icon" aria-label="Don't show this again">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">
<nav class="cm-header-nav" role="navigation" aria-label="Application">
<div class="cm-header-nav__brand">
<a href="#" data-path="/home" class="cm-header-nav__brand-link">
<span class="cm-header-nav__brand-text">Changemaker Lite</span>
</a>
</div>
<div class="cm-header-nav__links">
<div class="cm-header-nav__links-inner">
<a href="#" data-path="/" class="cm-header-nav__link" data-nav-id="home" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">home</span><span class="cm-header-nav__label">Home</span></a>
<a href="#" data-path="/campaigns" class="cm-header-nav__link" data-nav-id="campaigns"><span class="material-icons-outlined">send</span><span class="cm-header-nav__label">Campaigns</span></a>
<a href="#" data-path="/events" class="cm-header-nav__link" data-nav-id="events" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">event</span><span class="cm-header-nav__label">Events</span></a>
<a href="#" data-path="/pricing" class="cm-header-nav__link" data-nav-id="pricing"><span class="material-icons-outlined">attach_money</span><span class="cm-header-nav__label">Pricing</span></a>
<a href="#" data-path="/shop" class="cm-header-nav__link" data-nav-id="shop"><span class="material-icons-outlined">shopping_bag</span><span class="cm-header-nav__label">Shop</span></a>
<a href="#" data-path="/donate" class="cm-header-nav__link" data-nav-id="donate"><span class="material-icons-outlined">favorite_border</span><span class="cm-header-nav__label">Donate</span></a>
<label for="__search" class="cm-header-nav__utility" title="Search">
<span class="material-icons-outlined">search</span>
</label>
<button class="cm-header-nav__utility" id="cm-palette-toggle" title="Toggle dark mode" type="button">
<span class="material-icons-outlined">dark_mode</span>
</button>
<a href="#" data-path="/login" class="cm-header-nav__link" id="cm-signin-link">
<span class="material-icons-outlined">login</span>
<span class="cm-header-nav__label">Sign In</span>
</a>
<div class="cm-header-nav__dropdown" id="cm-admin-dropdown" style="display:none">
<span class="cm-header-nav__link cm-header-nav__dropdown-trigger">
<span class="material-icons-outlined">person</span>
<span class="cm-header-nav__label">Admin</span>
<span class="material-icons-outlined cm-header-nav__chevron">expand_more</span>
</span>
<div class="cm-header-nav__dropdown-menu cm-header-nav__dropdown-menu--right">
<a href="#" data-path="/app" class="cm-header-nav__dropdown-item"><span class="material-icons-outlined">dashboard</span><span>Admin Panel</span></a>
<a href="#" data-path="/volunteer" class="cm-header-nav__dropdown-item"><span class="material-icons-outlined">volunteer_activism</span><span>Volunteer Portal</span></a>
<a href="#" data-path="/volunteer/profile" class="cm-header-nav__dropdown-item"><span class="material-icons-outlined">account_circle</span><span>My Profile</span></a>
<a href="#" data-path="/logout" class="cm-header-nav__dropdown-item"><span class="material-icons-outlined">logout</span><span>Logout</span></a>
</div>
</div>
</div>
<button class="cm-header-nav__hamburger" aria-label="Open navigation menu">
<span class="material-icons-outlined">menu</span>
</button>
</div>
</nav>
<div class="cm-header-nav__mobile-drawer" id="cm-mobile-drawer">
<div class="cm-header-nav__mobile-header">
<span class="cm-header-nav__brand-text">Changemaker Lite</span>
<button class="cm-header-nav__mobile-close" aria-label="Close navigation menu">
<span class="material-icons-outlined">close</span>
</button>
</div>
<div class="cm-header-nav__mobile-links">
<label for="__search" class="cm-header-nav__mobile-link" style="cursor:pointer">
<span class="material-icons-outlined">search</span>
<span>Search</span>
</label>
<button class="cm-header-nav__mobile-link cm-header-nav__utility-btn" id="cm-mobile-palette-toggle" type="button">
<span class="material-icons-outlined">dark_mode</span>
<span>Dark Mode</span>
</button>
<button class="cm-header-nav__mobile-link cm-header-nav__utility-btn" id="cm-docs-sidebar-toggle" type="button">
<span class="material-icons-outlined">menu_book</span>
<span>Docs Navigation</span>
</button>
<div class="cm-header-nav__mobile-divider"></div>
<a href="#" data-path="/" class="cm-header-nav__mobile-link" data-nav-id="home" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">home</span><span>Home</span></a>
<a href="#" data-path="/campaigns" class="cm-header-nav__mobile-link" data-nav-id="campaigns"><span class="material-icons-outlined">send</span><span>Campaigns</span></a>
<a href="#" data-path="/events" class="cm-header-nav__mobile-link" data-nav-id="events" target="_blank" rel="noopener noreferrer"><span class="material-icons-outlined">event</span><span>Events</span></a>
<a href="#" data-path="/pricing" class="cm-header-nav__mobile-link" data-nav-id="pricing"><span class="material-icons-outlined">attach_money</span><span>Pricing</span></a>
<a href="#" data-path="/shop" class="cm-header-nav__mobile-link" data-nav-id="shop"><span class="material-icons-outlined">shopping_bag</span><span>Shop</span></a>
<a href="#" data-path="/donate" class="cm-header-nav__mobile-link" data-nav-id="donate"><span class="material-icons-outlined">favorite_border</span><span>Donate</span></a>
<div class="cm-header-nav__mobile-divider"></div>
<a href="#" data-path="/login" class="cm-header-nav__mobile-link" id="cm-mobile-signin-link">
<span class="material-icons-outlined">login</span>
<span>Sign In</span>
</a>
<div class="cm-header-nav__mobile-group" data-group-id="admin" id="cm-mobile-admin-group" style="display:none">
<span class="cm-header-nav__mobile-link cm-header-nav__mobile-group-trigger" role="button">
<span class="material-icons-outlined">person</span>
<span style="flex:1">Admin</span>
<span class="material-icons-outlined cm-header-nav__mobile-chevron">expand_more</span>
</span>
<div class="cm-header-nav__mobile-group-children">
<a href="#" data-path="/app" class="cm-header-nav__mobile-link" style="padding-left:48px"><span class="material-icons-outlined">dashboard</span><span>Admin Panel</span></a>
<a href="#" data-path="/volunteer" class="cm-header-nav__mobile-link" style="padding-left:48px"><span class="material-icons-outlined">volunteer_activism</span><span>Volunteer Portal</span></a>
<a href="#" data-path="/volunteer/profile" class="cm-header-nav__mobile-link" style="padding-left:48px"><span class="material-icons-outlined">account_circle</span><span>My Profile</span></a>
<a href="#" data-path="/logout" class="cm-header-nav__mobile-link" style="padding-left:48px"><span class="material-icons-outlined">logout</span><span>Logout</span></a>
</div>
</div>
</div>
</div>
<div class="cm-header-nav__mobile-overlay" id="cm-mobile-overlay"></div>
<script>
(function() {
var h = location.hostname;
var base;
if (h === 'localhost' || h === '127.0.0.1') {
base = location.protocol + '//localhost:' + (3002 || 3000);
} else {
var parts = h.split('.');
if (parts.length >= 3) { parts[0] = 'app'; }
else { parts.unshift('app'); }
base = location.protocol + '//' + parts.join('.');
}
var links = document.querySelectorAll('[data-path]');
for (var i = 0; i < links.length; i++) {
links[i].setAttribute('href', base + links[i].getAttribute('data-path'));
}
// Highlight active nav link based on current path
var path = location.pathname;
var activeLink = null;
if (path.indexOf('/docs') === 0) activeLink = 'docs';
document.querySelectorAll('.cm-header-nav__link[data-nav-id], .cm-header-nav__mobile-link[data-nav-id]').forEach(function(el) {
if (el.getAttribute('data-nav-id') === activeLink) {
el.classList.add('cm-header-nav__link--active');
}
});
// Hamburger toggle
var hamburger = document.querySelector('.cm-header-nav__hamburger');
var drawer = document.getElementById('cm-mobile-drawer');
var overlay = document.getElementById('cm-mobile-overlay');
var closeBtn = document.querySelector('.cm-header-nav__mobile-close');
function openDrawer() { drawer.classList.add('open'); overlay.classList.add('open'); }
function closeDrawer() { drawer.classList.remove('open'); overlay.classList.remove('open'); }
if (hamburger) hamburger.addEventListener('click', openDrawer);
if (closeBtn) closeBtn.addEventListener('click', closeDrawer);
if (overlay) overlay.addEventListener('click', closeDrawer);
// Mobile group expand/collapse toggles
document.querySelectorAll('.cm-header-nav__mobile-group-trigger').forEach(function(trigger) {
trigger.addEventListener('click', function() {
var group = this.closest('.cm-header-nav__mobile-group');
var children = group.querySelector('.cm-header-nav__mobile-group-children');
var isExpanded = group.classList.contains('expanded');
if (isExpanded) {
group.classList.remove('expanded');
children.style.display = 'none';
} else {
group.classList.add('expanded');
children.style.display = 'block';
}
});
});
// Auth-aware: show Admin dropdown for logged-in users, Sign In for guests.
// Uses hidden iframe + postMessage to read auth state from the app's origin.
function showAdminMenu() {
var s1 = document.getElementById('cm-signin-link');
var s2 = document.getElementById('cm-mobile-signin-link');
var a1 = document.getElementById('cm-admin-dropdown');
var a2 = document.getElementById('cm-mobile-admin-group');
if (s1) s1.style.display = 'none';
if (s2) s2.style.display = 'none';
if (a1) a1.style.display = '';
if (a2) a2.style.display = '';
}
// 1. Same-origin check (works when MkDocs served from same origin as app)
try {
var stored = localStorage.getItem('cml-auth');
if (stored) {
var parsed = JSON.parse(stored);
if (parsed && parsed.state && parsed.state.accessToken) {
showAdminMenu();
}
}
} catch(e) {}
// 2. Cross-origin check via hidden iframe + postMessage
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = base + '/auth-check.html?origin=' + encodeURIComponent(location.origin);
window.addEventListener('message', function(event) {
if (event.origin !== base) return;
if (event.data && event.data.type === 'cml-auth-status' && event.data.authenticated) {
showAdminMenu();
}
});
document.body.appendChild(iframe);
// Palette toggle (dark/light mode)
function togglePalette() {
var inputs = document.querySelectorAll('.cm-palette-container input[name="__palette"]');
for (var i = 0; i < inputs.length; i++) {
if (!inputs[i].checked) { inputs[i].click(); break; }
}
setTimeout(updatePaletteIcon, 50);
}
function updatePaletteIcon() {
var scheme = document.body.getAttribute('data-md-color-scheme') || 'default';
var isDark = scheme === 'slate';
var icon = isDark ? 'light_mode' : 'dark_mode';
document.querySelectorAll('#cm-palette-toggle .material-icons-outlined, #cm-mobile-palette-toggle .material-icons-outlined').forEach(function(el) {
el.textContent = icon;
});
var ml = document.querySelector('#cm-mobile-palette-toggle span:not(.material-icons-outlined)');
if (ml) ml.textContent = isDark ? 'Light Mode' : 'Dark Mode';
}
var ptBtn = document.getElementById('cm-palette-toggle');
var ptBtnM = document.getElementById('cm-mobile-palette-toggle');
if (ptBtn) ptBtn.addEventListener('click', togglePalette);
if (ptBtnM) ptBtnM.addEventListener('click', function() { togglePalette(); closeDrawer(); });
// Docs sidebar toggle (opens Material's docs navigation drawer)
var docsSidebarBtn = document.getElementById('cm-docs-sidebar-toggle');
if (docsSidebarBtn) {
docsSidebarBtn.addEventListener('click', function() {
closeDrawer();
var dt = document.getElementById('__drawer');
if (dt) { dt.checked = !dt.checked; dt.dispatchEvent(new Event('change')); }
});
}
// Close custom drawer when search label is clicked on mobile + auto-focus input
document.querySelectorAll('label[for="__search"]').forEach(function(el) {
el.addEventListener('click', function() {
if (el.classList.contains('md-search__overlay')) return; // overlay has its own handler
closeDrawer();
setTimeout(function() {
var input = document.querySelector('.md-search__input');
if (input) input.focus();
}, 150);
});
});
// Search activation: Material may open search via checkbox OR by focusing the
// input directly (varies by version). Detect both and mirror as body class.
// NOTE: search DOM elements render AFTER the announce block in the template,
// so we must defer element queries until DOMContentLoaded.
var searchToggle = null;
var searchInput = null;
// Apply search layout inline styles (CSS-in-stylesheet is unreliable due to
// cross-origin Material stylesheets overriding !important rules)
function applySearchLayout(active) {
var inner = document.querySelector('.md-search__inner');
var output = document.querySelector('.md-search__output');
var scrollwrap = document.querySelector('.md-search__scrollwrap');
if (!inner) return;
var isDesktop = window.matchMedia('(min-width: 60em)').matches;
if (active) {
inner.style.setProperty('display', 'flex', 'important');
inner.style.setProperty('flex-direction', 'column', 'important');
inner.style.setProperty('overflow', 'hidden', 'important');
// Firefox needs explicit height (not just max-height) for flex children to grow
if (isDesktop) {
inner.style.setProperty('height', 'calc(100vh - 64px)', 'important');
}
if (output) {
output.style.setProperty('position', 'relative', 'important');
output.style.setProperty('flex', '1 1 0px', 'important');
output.style.setProperty('min-height', '0', 'important');
output.style.setProperty('display', 'flex', 'important');
output.style.setProperty('flex-direction', 'column', 'important');
output.style.setProperty('overflow', 'hidden', 'important');
output.style.setProperty('width', '100%', 'important');
}
if (scrollwrap) {
scrollwrap.style.setProperty('max-height', 'none', 'important');
scrollwrap.style.setProperty('flex', '1 1 0px', 'important');
scrollwrap.style.setProperty('min-height', '0', 'important');
scrollwrap.style.setProperty('overflow-y', 'auto', 'important');
}
// Force search result elements visible + ensure proper stacking (Firefox)
var resultList = document.querySelector('.md-search-result__list');
if (resultList) {
resultList.style.setProperty('display', 'block', 'important');
resultList.style.setProperty('visibility', 'visible', 'important');
resultList.style.setProperty('opacity', '1', 'important');
resultList.style.setProperty('max-height', 'none', 'important');
resultList.style.setProperty('overflow', 'visible', 'important');
resultList.style.setProperty('color', 'var(--md-default-fg-color)', 'important');
}
var resultContainer = document.querySelector('.md-search-result');
if (resultContainer) {
resultContainer.style.setProperty('display', 'block', 'important');
resultContainer.style.setProperty('visibility', 'visible', 'important');
resultContainer.style.setProperty('opacity', '1', 'important');
}
// Ensure scrollwrap has z-index above overlay
if (scrollwrap) {
scrollwrap.style.setProperty('position', 'relative', 'important');
scrollwrap.style.setProperty('z-index', '1', 'important');
scrollwrap.style.setProperty('background', 'var(--md-default-bg-color)', 'important');
}
} else {
inner.style.removeProperty('display');
inner.style.removeProperty('flex-direction');
inner.style.removeProperty('overflow');
inner.style.removeProperty('height');
if (output) {
output.style.removeProperty('position');
output.style.removeProperty('flex');
output.style.removeProperty('min-height');
output.style.removeProperty('display');
output.style.removeProperty('flex-direction');
output.style.removeProperty('overflow');
output.style.removeProperty('width');
}
if (scrollwrap) {
scrollwrap.style.removeProperty('max-height');
scrollwrap.style.removeProperty('flex');
scrollwrap.style.removeProperty('min-height');
scrollwrap.style.removeProperty('overflow-y');
scrollwrap.style.removeProperty('position');
scrollwrap.style.removeProperty('z-index');
scrollwrap.style.removeProperty('background');
}
var resultList = document.querySelector('.md-search-result__list');
if (resultList) resultList.removeAttribute('style');
var resultContainer = document.querySelector('.md-search-result');
if (resultContainer) resultContainer.removeAttribute('style');
}
}
function activateSearch() {
document.body.classList.add('cm-search-active');
if (searchToggle) searchToggle.checked = true;
applySearchLayout(true);
}
function deactivateSearch() {
document.body.classList.remove('cm-search-active');
if (searchToggle) searchToggle.checked = false;
if (searchInput) searchInput.blur();
applySearchLayout(false);
}
function isSearchActive() {
return document.body.classList.contains('cm-search-active');
}
// Custom search labels in the cm-header-nav (these exist now, in announce block)
document.querySelectorAll('label[for="__search"]').forEach(function(lbl) {
lbl.addEventListener('click', function() {
if (lbl.classList.contains('md-search__overlay')) return;
setTimeout(function() { activateSearch(); if (searchInput) searchInput.focus(); }, 50);
});
});
// Deferred bindings: attach handlers to search elements once they exist in the DOM
document.addEventListener('DOMContentLoaded', function() {
searchToggle = document.getElementById('__search');
searchInput = document.querySelector('.md-search__input');
// Detect search open via input focus
if (searchInput) {
searchInput.addEventListener('focus', activateSearch);
}
// Detect search open via checkbox
if (searchToggle) {
searchToggle.addEventListener('change', function() {
if (searchToggle.checked) activateSearch(); else deactivateSearch();
});
}
// Click on overlay (md-search__overlay label) to dismiss search
var searchOverlay = document.querySelector('.md-search__overlay');
if (searchOverlay) {
searchOverlay.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
if (isSearchActive()) deactivateSearch();
});
}
});
// Click-outside to dismiss search (on document, works immediately)
document.addEventListener('mousedown', function(e) {
if (!isSearchActive()) return;
var panel = document.querySelector('.md-search__inner');
if (panel && panel.contains(e.target)) return;
// Let the overlay's own click handler deal with it
if (e.target.closest && e.target.closest('.md-search__overlay')) return;
if (e.target.closest && e.target.closest('label[for="__search"]')) return;
if (e.target.closest && e.target.closest('.cm-header-nav__utility')) return;
deactivateSearch();
});
// Escape key to dismiss
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && isSearchActive()) setTimeout(deactivateSearch, 50);
});
// Init palette icon + observe changes
setTimeout(updatePaletteIcon, 100);
new MutationObserver(function() { updatePaletteIcon(); })
.observe(document.body, { attributes: true, attributeFilter: ['data-md-color-scheme'] });
})();
</script>
<style>
.md-banner {
background: transparent !important;
color: #ffffff !important;
padding: 0 !important;
margin: 0 !important;
overflow: visible !important;
border: none !important;
box-shadow: none !important;
position: relative;
z-index: 301;
}
.md-banner__inner {
overflow: visible !important;
margin: 0 !important;
padding: 0 !important;
max-width: 100% !important;
}
.md-banner__button {
display: none !important;
}
.cm-header-nav {
background: linear-gradient(135deg, #005a9c 0%, #007acc 100%);
height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
position: relative;
z-index: 100;
box-sizing: border-box;
}
.cm-header-nav a {
color: rgba(255, 255, 255, 0.85) !important;
}
.cm-header-nav__brand-link {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none !important;
color: #fff !important;
}
.cm-header-nav__brand-text {
font-size: 18px;
font-weight: 600;
color: #fff !important;
}
.cm-header-nav__links {
display: flex;
align-items: center;
}
.cm-header-nav__links-inner {
display: flex;
align-items: center;
gap: 16px;
}
.cm-header-nav__link {
color: rgba(255, 255, 255, 0.85) !important;
text-decoration: none !important;
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
transition: color 0.2s, border-color 0.2s;
white-space: nowrap;
padding-bottom: 2px;
border-bottom: 2px solid transparent;
}
.cm-header-nav__link:hover {
color: #fff !important;
text-decoration: none !important;
}
.cm-header-nav__link--active,
.cm-header-nav__link--active:hover {
color: #fff !important;
font-weight: 600;
border-bottom-color: #fff;
}
.cm-header-nav__link .material-icons-outlined {
font-size: 16px;
}
.cm-header-nav__hamburger {
display: none;
background: none;
border: none;
cursor: pointer;
padding: 4px 8px;
color: #fff;
}
.cm-header-nav__hamburger .material-icons-outlined {
font-size: 24px;
}
/* Desktop dropdown menus */
.cm-header-nav__dropdown {
position: relative;
display: inline-flex;
align-items: center;
}
.cm-header-nav__dropdown-trigger {
cursor: pointer;
user-select: none;
}
.cm-header-nav__dropdown-trigger .cm-header-nav__chevron {
font-size: 14px;
transition: transform 0.2s;
}
.cm-header-nav__dropdown:hover .cm-header-nav__chevron {
transform: rotate(180deg);
}
.cm-header-nav__dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
min-width: 180px;
background: #1b2838;
border-radius: 8px;
padding: 6px 0;
box-shadow: 0 6px 16px rgba(0,0,0,0.3);
z-index: 100;
margin-top: 4px;
}
.cm-header-nav__dropdown:hover .cm-header-nav__dropdown-menu {
display: block;
}
.cm-header-nav__dropdown-menu--right {
left: auto;
right: 0;
}
.cm-header-nav__dropdown-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
color: rgba(255, 255, 255, 0.85) !important;
text-decoration: none !important;
font-size: 14px;
white-space: nowrap;
transition: background 0.15s;
}
.cm-header-nav__dropdown-item:hover {
background: rgba(255,255,255,0.1);
color: #fff !important;
text-decoration: none !important;
}
.cm-header-nav__dropdown-item .material-icons-outlined {
font-size: 16px;
}
/* Mobile drawer */
.cm-header-nav__mobile-drawer {
position: fixed;
top: 0;
right: -280px;
width: 280px;
height: 100vh;
background: #0d1b2a;
z-index: 10001;
transition: right 0.3s ease;
display: flex;
flex-direction: column;
}
.cm-header-nav__mobile-drawer.open {
right: 0;
}
.cm-header-nav__mobile-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
border-bottom: 1px solid rgba(255,255,255,0.1);
background: #1b2838;
}
.cm-header-nav__mobile-close {
background: none;
border: none;
cursor: pointer;
color: rgba(255,255,255,0.85);
padding: 4px;
}
.cm-header-nav__mobile-links {
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px 0;
}
.cm-header-nav__mobile-link {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
color: rgba(255,255,255,0.85) !important;
text-decoration: none !important;
font-size: 15px;
border-radius: 4px;
}
.cm-header-nav__mobile-link:hover {
background: rgba(255,255,255,0.1);
color: #fff !important;
text-decoration: none !important;
}
.cm-header-nav__mobile-link--active {
color: #fff !important;
font-weight: 600;
background: rgba(255,255,255,0.1);
}
.cm-header-nav__mobile-link .material-icons-outlined {
font-size: 18px;
}
/* Mobile group expand/collapse */
.cm-header-nav__mobile-group-trigger {
cursor: pointer;
user-select: none;
}
.cm-header-nav__mobile-chevron {
font-size: 14px !important;
transition: transform 0.2s;
}
.cm-header-nav__mobile-group.expanded .cm-header-nav__mobile-chevron {
transform: rotate(180deg);
}
.cm-header-nav__mobile-group-children {
display: none;
}
.cm-header-nav__mobile-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 10000;
}
.cm-header-nav__mobile-overlay.open {
display: block;
}
@media (max-width: 768px) {
.cm-header-nav { padding: 0 16px; }
.cm-header-nav__links-inner { display: none; }
.cm-header-nav__hamburger { display: block; }
.cm-header-nav__dropdown-menu { display: none !important; }
}
/* Sidebar sticky offset = 0 since blue header scrolls away */
:root {
--md-header-height: 0px;
}
/* Hidden Material header — keeps search anchored near tabs */
.md-header--cm-hidden {
height: 0 !important;
min-height: 0 !important;
padding: 0 !important;
margin: 0 !important;
border: 0 !important;
overflow: visible !important;
background: transparent !important;
box-shadow: none !important;
position: sticky;
top: 0;
z-index: 200;
}
/* === DESKTOP SEARCH (>= 60em / 960px) === */
@media screen and (min-width: 60em) {
/* Fixed dropdown panel — layout (flex) applied via JS inline styles */
body.cm-search-active .md-header--cm-hidden .md-search__inner {
position: fixed !important;
top: 48px !important;
right: 16px !important;
left: auto !important;
width: min(34rem, calc(100vw - 32px)) !important;
max-height: calc(100vh - 64px) !important;
background: var(--md-default-bg-color) !important;
border-radius: 0 0 8px 8px !important;
box-shadow: 0 4px 24px rgba(0,0,0,0.25) !important;
z-index: 300 !important;
opacity: 1 !important;
transform: none !important;
visibility: visible !important;
pointer-events: auto !important;
clip-path: none !important;
}
/* Dark overlay behind search panel — catches clicks to dismiss */
body.cm-search-active .md-header--cm-hidden .md-search__overlay {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
background: rgba(0,0,0,0.54) !important;
opacity: 1 !important;
z-index: 299 !important;
border-radius: 0 !important;
transform: none !important;
cursor: default !important;
pointer-events: auto !important;
}
}
/* === MOBILE SEARCH (< 60em / 960px) === */
@media screen and (max-width: 59.984375em) {
/* Full-screen search takeover — layout (flex) applied via JS inline styles */
body.cm-search-active .md-header--cm-hidden .md-search__inner {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100% !important;
height: 100% !important;
opacity: 1 !important;
transform: none !important;
visibility: visible !important;
pointer-events: auto !important;
z-index: 300 !important;
background: var(--md-default-bg-color) !important;
clip-path: none !important;
}
}
/* Force search elements visible when active (layout handled by JS inline styles) */
body.cm-search-active .md-header--cm-hidden .md-search {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
overflow: visible !important;
}
body.cm-search-active .md-header--cm-hidden .md-search__output {
opacity: 1 !important;
visibility: visible !important;
clip-path: none !important;
transform: none !important;
}
.cm-palette-container {
height: 0 !important;
overflow: hidden !important;
}
/* Material tabs: sticky at viewport top when blue header scrolls away */
.md-tabs {
position: sticky;
top: 0;
z-index: 99;
}
/* On mobile, hide tabs (sidebar provides navigation) */
@media (max-width: 768px) {
.md-tabs { display: none; }
}
/* Utility icon styling */
.cm-header-nav__utility {
background: none;
border: none;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
padding: 4px;
display: inline-flex;
align-items: center;
transition: color 0.2s;
}
.cm-header-nav__utility:hover { color: #fff; }
.cm-header-nav__utility .material-icons-outlined { font-size: 20px; }
.cm-header-nav__utility-btn {
background: none;
border: none;
color: rgba(255,255,255,0.85);
cursor: pointer;
font-size: 15px;
font-family: inherit;
width: 100%;
text-align: left;
}
.cm-header-nav__mobile-divider {
height: 1px;
background: rgba(255,255,255,0.1);
margin: 8px 24px;
}
</style>
</div>
<script>var el=document.querySelector("[data-md-component=announce]");if(el){var content=el.querySelector(".md-typeset");__md_hash(content.innerHTML)===__md_get("__announce")&&(el.hidden=!0)}</script>
</aside>
</div>
<header class="md-header md-header--cm-hidden" data-md-component="header">
<div class="cm-palette-container">
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z"/></svg>
</label>
</form>
</div>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="../../../docs/" class="md-tabs__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 21.5c-1.35-.85-3.8-1.5-5.5-1.5-1.65 0-3.35.3-4.75 1.05-.1.05-.15.05-.25.05-.25 0-.5-.25-.5-.5V6c.6-.45 1.25-.75 2-1 1.11-.35 2.33-.5 3.5-.5 1.95 0 4.05.4 5.5 1.5 1.45-1.1 3.55-1.5 5.5-1.5 1.17 0 2.39.15 3.5.5.75.25 1.4.55 2 1v14.6c0 .25-.25.5-.5.5-.1 0-.15 0-.25-.05-1.4-.75-3.1-1.05-4.75-1.05-1.7 0-4.15.65-5.5 1.5M12 8v11.5c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5V7c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5m1 3.5c1.11-.68 2.6-1 4.5-1 .91 0 1.76.09 2.5.28V9.23c-.87-.15-1.71-.23-2.5-.23q-2.655 0-4.5.84zm4.5.17c-1.71 0-3.21.26-4.5.79v1.69c1.11-.65 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24v-1.5c-.87-.16-1.71-.23-2.5-.23m2.5 2.9c-.87-.16-1.71-.24-2.5-.24-1.83 0-3.33.27-4.5.8v1.69c1.11-.66 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24z"/></svg>
Docs
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../../" class="md-tabs__link">
Blog
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../../.." title="Changemaker Lite" class="md-nav__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../../assets/logo.svg" alt="logo">
</a>
Changemaker Lite
</label>
<div class="md-nav__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../../docs/" class="md-nav__link">
<span class="md-ellipsis">
Docs
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" checked>
<div class="md-nav__link md-nav__container">
<a href="../../" class="md-nav__link ">
<span class="md-ellipsis">
Blog
</span>
</a>
<label class="md-nav__link " for="__nav_3" id="__nav_3_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_3">
<span class="md-nav__icon md-icon"></span>
Blog
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../archive/2026/" class="md-nav__link">
<span class="md-ellipsis">
Archive
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3_3" checked>
<label class="md-nav__link" for="__nav_3_3" id="__nav_3_3_label" tabindex="0">
<span class="md-ellipsis">
Categories
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_3_3_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_3_3">
<span class="md-nav__icon md-icon"></span>
Categories
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../announcements/" class="md-nav__link">
<span class="md-ellipsis">
Announcements
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../platform/" class="md-nav__link">
<span class="md-ellipsis">
Platform
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Release Notes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../testing/" class="md-nav__link">
<span class="md-ellipsis">
Testing
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="On this page">
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<div class="md-content__inner">
<header class="md-typeset">
<h1 id="release-notes">Release Notes<a class="headerlink" href="#release-notes" title="Permanent link">&para;</a></h1>
</header>
<article class="md-post md-post--excerpt">
<header class="md-post__header">
<nav class="md-post__authors md-typeset">
<span class="md-author">
<img src="https://gitea.bnkops.com/avatars/1" alt="Bunker Operations">
</span>
</nav>
<div class="md-post__meta md-meta">
<ul class="md-meta__list">
<li class="md-meta__item">
<time datetime="2026-04-16 00:00:00+00:00">Apr 16, 2026</time></li>
<li class="md-meta__item">
in
<a href="../platform/" class="md-meta__link">Platform</a>,
<a href="./" class="md-meta__link">Release Notes</a></li>
<li class="md-meta__item">
5 min read
</li>
</ul>
</div>
</header>
<div class="md-post__content md-typeset">
<h2 id="a-smoother-fresh-install-v298-and-v299"><a class="toclink" href="../../2026/04/16/a-smoother-fresh-install-v298-and-v299/">A smoother fresh install: v2.9.8 and v2.9.9</a></h2>
<p>Installing Changemaker Lite from scratch on a clean server should be fast, quiet, and recoverable. Over the last few days we sat with a clean Ubuntu server and ran the install flow three times — once straight, once to fix what we found, and once to register the result with a Control Panel. Every round surfaced paper cuts. Both releases that landed this week are the result.</p>
<nav class="md-post__action">
<a href="../../2026/04/16/a-smoother-fresh-install-v298-and-v299/">
Continue reading
</a>
</nav>
</div>
</article>
<nav class="md-pagination">
</nav>
</div>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../platform/" class="md-footer__link md-footer__link--prev" aria-label="Previous: Platform">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
Platform
</div>
</div>
</a>
<a href="../testing/" class="md-footer__link md-footer__link--next" aria-label="Next: Testing">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
Testing
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Copyright &copy; 20242026 The Bunker Operations <a href="#__consent">Change cookie settings</a>
</div>
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
<div class="md-social">
<a href="https://gitea.bnkops.com/admin" target="_blank" rel="noopener" title="Gitea Repository" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M80 104a24 24 0 1 0 0-48 24 24 0 1 0 0 48m80-24c0 32.8-19.7 61-48 73.3V224h176c26.5 0 48-21.5 48-48v-22.7c-28.3-12.3-48-40.5-48-73.3 0-44.2 35.8-80 80-80s80 35.8 80 80c0 32.8-19.7 61-48 73.3V176c0 61.9-50.1 112-112 112H112v70.7c28.3 12.3 48 40.5 48 73.3 0 44.2-35.8 80-80 80S0 476.2 0 432c0-32.8 19.7-61 48-73.3V153.4C19.7 141 0 112.8 0 80 0 35.8 35.8 0 80 0s80 35.8 80 80m232 0a24 24 0 1 0-48 0 24 24 0 1 0 48 0M80 456a24 24 0 1 0 0-48 24 24 0 1 0 0 48"/></svg>
</a>
<a href="https://listmonk.bnkops.com/subscription/form" target="_blank" rel="noopener" title="Newsletter" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M536.4-26.3c9.8-3.5 20.6-1 28 6.3s9.8 18.2 6.3 28l-178 496.9c-5 13.9-18.1 23.1-32.8 23.1-14.2 0-27-8.6-32.3-21.7l-64.2-158c-4.5-11-2.5-23.6 5.2-32.6l94.5-112.4c5.1-6.1 4.7-15-.9-20.6s-14.6-6-20.6-.9l-112.4 94.3c-9.1 7.6-21.6 9.6-32.6 5.2L38.1 216.8c-13.1-5.3-21.7-18.1-21.7-32.3 0-14.7 9.2-27.8 23.1-32.8z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<div class="md-progress" data-md-component="progress" role="progressbar"></div>
<script id="__config" type="application/json">{"annotate": null, "base": "../../..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.code.select", "content.tabs.link", "content.tooltips", "navigation.footer", "navigation.indexes", "navigation.instant", "navigation.instant.prefetch", "navigation.instant.progress", "navigation.path", "navigation.prune", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../../assets/javascripts/bundle.79ae519e.min.js"></script>
<script src="../../../javascripts/home.js"></script>
<script src="../../../javascripts/github-widget.js"></script>
<script src="../../../javascripts/gitea-widget.js"></script>
<script src="../../../assets/js/env-config.js"></script>
<script src="../../../assets/js/video-player.js"></script>
<script src="../../../assets/js/image-gallery.js"></script>
<script src="../../../assets/js/gancio-events.js"></script>
<script src="../../../assets/js/payment-widgets.js"></script>
<script src="../../../assets/js/scheduling-poll.js"></script>
<script src="../../../assets/js/straw-poll-widget.js"></script>
<script src="../../../javascripts/ad-widgets.js"></script>
<script src="../../../javascripts/docs-comments.js"></script>
</body>
</html>