Thanks to Juris's team :-)
This app runs entirely in the browser, awesome.
Try the same in your favorite framework.
All the code is pure Javascript, no fancy terms, concepts.
You only need Notepad or Vi to modify it.
You can test it here : TodoApp
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Juris Todo App - Router, Auth & Storage</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--orange: #ff6b35;
--orange-light: #ff8c5a;
--orange-dark: #e55a2b;
--orange-pale: #fff4f1;
--primary: #ff6b35;
--primary-dark: #e55a2b;
--primary-light: #ff8c5a;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--gray-50: #fafafa;
--gray-100: #f5f5f5;
--gray-200: #e5e5e5;
--gray-300: #d4d4d4;
--gray-400: #a3a3a3;
--gray-500: #6b7280;
--gray-600: #525252;
--gray-700: #404040;
--gray-800: #262626;
--gray-900: #171717;
--white: #ffffff;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--border-radius: 0.5rem;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', sans-serif;
line-height: 1.6;
color: var(--gray-900);
background: var(--white);
font-size: 16px;
}
/* Layout Components */
.app-container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.header {
background: var(--white);
border-bottom: 1px solid var(--gray-200);
padding: 1rem 0;
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--orange);
text-decoration: none;
}
.nav {
display: flex;
gap: 1rem;
align-items: center;
}
.nav-link {
color: var(--gray-600);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: var(--border-radius);
}
.nav-link:hover {
background: var(--gray-100);
color: var(--orange);
}
.nav-link.active {
background: var(--orange);
color: var(--white);
}
.main-content {
flex: 1;
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1rem;
width: 100%;
}
/* Form Components */
.form-container {
max-width: 400px;
margin: 2rem auto;
background: var(--white);
padding: 2rem;
border-radius: var(--border-radius);
box-shadow: var(--shadow-lg);
}
.form-title {
font-size: 1.5rem;
font-weight: 600;
text-align: center;
margin-bottom: 2rem;
color: var(--gray-900);
}
.form-group {
margin-bottom: 1rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--gray-700);
}
.form-input {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--gray-300);
border-radius: var(--border-radius);
font-size: 1rem;
}
.form-input:focus {
outline: none;
border-color: var(--orange);
box-shadow: 0 0 0 3px rgba(255, 107, 53, 0.1);
}
.form-input.error {
border-color: var(--danger);
}
.form-error {
color: var(--danger);
font-size: 0.875rem;
margin-top: 0.25rem;
}
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: var(--border-radius);
font-size: 1rem;
font-weight: 500;
cursor: pointer;
text-decoration: none;
display: inline-block;
text-align: center;
touch-action: manipulation;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.btn-primary {
background: var(--orange);
color: var(--white);
}
.btn-primary:hover {
background: var(--orange-dark);
}
.btn-secondary {
background: var(--gray-200);
color: var(--gray-800);
}
.btn-secondary:hover {
background: var(--gray-300);
}
.btn-danger {
background: var(--danger);
color: var(--white);
}
.btn-success {
background: var(--success);
color: var(--white);
}
.btn-full {
width: 100%;
}
/* Todo Components */
.todo-dashboard {
display: grid;
gap: 2rem;
}
.todo-lists {
background: var(--white);
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow);
}
.section-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--gray-900);
}
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border: 1px solid var(--gray-200);
border-radius: var(--border-radius);
margin-bottom: 0.5rem;
cursor: pointer;
}
.list-item:hover {
border-color: var(--orange);
background: var(--gray-50);
}
.list-item.active {
border-color: var(--orange);
background: var(--orange-pale);
}
.list-info {
flex: 1;
}
.list-name {
font-weight: 500;
color: var(--gray-900);
}
.list-count {
color: var(--gray-500);
font-size: 0.875rem;
}
.list-actions {
display: flex;
gap: 0.5rem;
}
.btn-small {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
.todo-form {
background: var(--white);
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow);
margin-bottom: 2rem;
}
.todo-input {
display: flex;
gap: 0.5rem;
}
.todo-input input {
flex: 1;
}
.todo-list {
background: var(--white);
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow);
}
.todo-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 0;
border-bottom: 1px solid var(--gray-100);
}
.todo-item:last-child {
border-bottom: none;
}
.todo-checkbox {
width: 1.25rem;
height: 1.25rem;
cursor: pointer;
}
.todo-text {
flex: 1;
color: var(--gray-700);
}
.todo-text.completed {
text-decoration: line-through;
color: var(--gray-400);
}
.todo-actions {
display: flex;
gap: 0.5rem;
}
.filters {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.filter-btn {
padding: 0.5rem 1rem;
border: 1px solid var(--gray-300);
background: var(--white);
color: var(--gray-600);
border-radius: var(--border-radius);
cursor: pointer;
}
.filter-btn.active {
background: var(--orange);
color: var(--white);
border-color: var(--orange);
}
/* Responsive Design */
@media (min-width: 768px) {
.todo-dashboard {
grid-template-columns: 300px 1fr;
}
.main-content {
padding: 3rem 2rem;
}
}
/* Loading & Empty States */
.loading {
text-align: center;
padding: 2rem;
color: var(--gray-500);
}
.empty-state {
text-align: center;
padding: 3rem;
color: var(--gray-500);
}
.empty-state h3 {
margin-bottom: 1rem;
color: var(--gray-700);
}
/* Utility Classes */
.text-center {
text-align: center;
}
.text-sm {
font-size: 0.875rem;
}
.text-danger {
color: var(--danger);
}
.text-success {
color: var(--success);
}
.mt-1 {
margin-top: 0.25rem;
}
.mt-2 {
margin-top: 0.5rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.hidden {
display: none;
}
/* Toast notifications */
.toast {
position: fixed;
top: 1rem;
right: 1rem;
background: var(--white);
border: 1px solid var(--gray-200);
border-radius: var(--border-radius);
padding: 1rem;
box-shadow: var(--shadow-lg);
z-index: 1000;
}
.toast.success {
border-left: 4px solid var(--success);
}
.toast.error {
border-left: 4px solid var(--danger);
}
</style>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/juris@0.5.1/juris.mini.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script>
const StatePersistenceManager = (props, context) => {
const { getState, setState, subscribe } = context;
// Enhanced configuration with timing controls
const config = {
domains: props.domains || [],
excludeDomains: props.excludeDomains || ['temp', 'cache', 'session', 'geolocation', 'persistence'],
keyPrefix: props.keyPrefix || 'app_state_',
debounceMs: props.debounceMs || 1000,
debug: props.debug || false,
autoDetectNewDomains: props.autoDetectNewDomains || true,
watchIntervalMs: props.watchIntervalMs || 5000,
// Timing controls for layout shift prevention
aggressiveRestore: props.aggressiveRestore !== false, // Default: true
restoreDelay: props.restoreDelay || 0,
priorityDomains: props.priorityDomains || [], // Restore these first
earlyRestoreTimeout: props.earlyRestoreTimeout || 50,
// Granular domain timing
domainRestoreConfig: props.domainRestoreConfig || {},
// Save timing controls
immediateSave: props.immediateSave || [], // Save changes immediately
criticalSave: props.criticalSave || [], // Save with shorter debounce
criticalDebounceMs: props.criticalDebounceMs || 200,
// User-specific domains (requires authentication)
userSpecificDomains: props.userSpecificDomains || [],
userIdPath: props.userIdPath || 'auth.user.id', // Path to user ID in state
requireAuth: props.requireAuth || false // Whether user-specific domains require auth
};
// Internal state
let saveTimers = new Map();
let isRestoring = false;
let domainSubscriptions = new Map();
let domainWatcher = null;
let restoreQueue = [];
let isProcessingRestoreQueue = false;
return {
hooks: {
onRegister: () => {
console.log('💾 StatePersistenceManager initializing with timing controls...');
// Initialize persistence state - DIRECT INJECTION
if (context.juris && context.juris.stateManager && context.juris.stateManager.state) {
context.juris.stateManager.state.persistence = {
isEnabled: true,
lastSave: null,
lastRestore: null,
errors: [],
stats: {
domainsTracked: 0,
totalSaves: 0,
totalRestores: 0,
priorityRestores: 0,
delayedRestores: 0
}
};
} else {
// Fallback if direct access not available
setState('persistence.isEnabled', true);
setState('persistence.lastSave', null);
setState('persistence.lastRestore', null);
setState('persistence.errors', []);
setState('persistence.stats', {
domainsTracked: 0,
totalSaves: 0,
totalRestores: 0,
priorityRestores: 0,
delayedRestores: 0
});
}
// Restore state with timing controls
if (config.aggressiveRestore) {
restoreAllDomainsWithTiming();
} else {
setTimeout(() => restoreAllDomainsWithTiming(), config.restoreDelay);
}
// Setup monitoring after early restore
setTimeout(() => {
setupDomainMonitoring();
if (config.autoDetectNewDomains) {
setupDomainWatcher();
}
}, Math.max(config.earlyRestoreTimeout, 100));
// Setup cross-tab sync
window.addEventListener('storage', handleStorageEvent);
window.addEventListener('beforeunload', saveAllTrackedDomains);
console.log('✅ StatePersistenceManager ready with timing controls');
},
onUnregister: () => {
console.log('💾 StatePersistenceManager cleanup');
// Save all before cleanup
saveAllTrackedDomains();
// Clear all timers
saveTimers.forEach(timer => clearTimeout(timer));
saveTimers.clear();
if (domainWatcher) {
clearInterval(domainWatcher);
domainWatcher = null;
}
// Unsubscribe from all domains
domainSubscriptions.forEach(unsubscribe => unsubscribe());
domainSubscriptions.clear();
// Remove storage listener
window.removeEventListener('storage', handleStorageEvent);
window.removeEventListener('beforeunload', saveAllTrackedDomains);
console.log('✅ StatePersistenceManager cleaned up');
}
},
api: {
saveDomain: (domain, immediate = false) => saveDomain(domain, immediate),
saveAllDomains: () => saveAllTrackedDomains(),
restoreDomain: (domain) => restoreDomain(domain),
restoreAllDomains: () => restoreAllDomainsWithTiming(),
addDomain: (domain) => addDomainTracking(domain),
removeDomain: (domain) => removeDomainTracking(domain),
clearDomain: (domain) => clearDomainStorage(domain),
clearAllStorage: () => clearAllStorage(),
getStorageStats: () => getStorageStats(),
getTrackedDomains: () => Array.from(domainSubscriptions.keys()),
exportState: () => exportState(),
importState: (data) => importState(data),
refreshDomainDetection: () => setupDomainMonitoring(),
forceImmediateSave: (domain) => saveDomain(domain, true),
restorePriorityDomains: () => restorePriorityDomains(),
getRestoreQueue: () => [...restoreQueue],
updateTimingConfig: (newConfig) => Object.assign(config, newConfig),
getConfig: () => ({ ...config }) // Get current configuration
}
};
// Enhanced restore with timing controls
function restoreAllDomainsWithTiming() {
log('📂 Starting timed restore sequence...');
// Get all stored domains by scanning localStorage
const storedDomains = [];
const keyPrefix = config.keyPrefix;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(keyPrefix)) {
// Extract domain from key
let remainder = key.substring(keyPrefix.length);
// Split by underscore and take first part as domain
let domain = remainder.split('_')[0];
// Only include if it's a valid domain and not already included
if (domain && !storedDomains.includes(domain)) {
storedDomains.push(domain);
}
}
}
log(`📂 Found stored domains: [${storedDomains.join(', ')}]`);
if (storedDomains.length === 0) {
log('📂 No stored domains found');
// DIRECT INJECTION for lastRestore
if (context.juris && context.juris.stateManager && context.juris.stateManager.state && context.juris.stateManager.state.persistence) {
context.juris.stateManager.state.persistence.lastRestore = Date.now();
}
return { restored: [], failed: [] };
}
// Sort domains by priority and timing configuration
const sortedDomains = storedDomains.sort((a, b) => {
const configA = config.domainRestoreConfig[a] || { priority: 999, delay: 0 };
const configB = config.domainRestoreConfig[b] || { priority: 999, delay: 0 };
if (configA.priority !== configB.priority) {
return configA.priority - configB.priority;
}
return configA.delay - configB.delay;
});
log(`📂 Restore order: [${sortedDomains.join(', ')}]`);
// Process domains with their configured timing
let cumulativeDelay = 0;
const results = { restored: [], failed: [], priority: [], delayed: [] };
sortedDomains.forEach((domain, index) => {
const domainConfig = config.domainRestoreConfig[domain] || { priority: 999, delay: 0, aggressive: false };
if (domainConfig.aggressive || config.priorityDomains.includes(domain)) {
// Immediate restore for aggressive/priority domains
if (restoreDomain(domain)) {
results.restored.push(domain);
results.priority.push(domain);
// DIRECT INJECTION for stats
if (context.juris && context.juris.stateManager && context.juris.stateManager.state && context.juris.stateManager.state.persistence) {
context.juris.stateManager.state.persistence.stats.priorityRestores =
(context.juris.stateManager.state.persistence.stats.priorityRestores || 0) + 1;
}
} else {
results.failed.push(domain);
}
log(`⚡ Priority restore completed for ${domain} immediately`);
} else {
// Delayed restore for non-critical domains
const restoreDelay = cumulativeDelay + domainConfig.delay;
setTimeout(() => {
if (restoreDomain(domain)) {
results.restored.push(domain);
results.delayed.push(domain);
// DIRECT INJECTION for stats
if (context.juris && context.juris.stateManager && context.juris.stateManager.state && context.juris.stateManager.state.persistence) {
context.juris.stateManager.state.persistence.stats.delayedRestores =
(context.juris.stateManager.state.persistence.stats.delayedRestores || 0) + 1;
}
} else {
results.failed.push(domain);
}
}, restoreDelay);
log(`⏳ Delayed restore scheduled for ${domain} at ${restoreDelay}ms`);
cumulativeDelay += domainConfig.delay;
}
});
// Update final restore timestamp
const finalDelay = Math.max(cumulativeDelay, config.earlyRestoreTimeout);
setTimeout(() => {
// DIRECT INJECTION for lastRestore
if (context.juris && context.juris.stateManager && context.juris.stateManager.state && context.juris.stateManager.state.persistence) {
context.juris.stateManager.state.persistence.lastRestore = Date.now();
}
log(`📂 Restore sequence completed. Priority: ${results.priority.length}, Delayed: ${results.delayed.length}, Failed: ${results.failed.length}`);
}, finalDelay);
return results;
}
function restorePriorityDomains() {
log('⚡ Restoring priority domains only...');
const priorityResults = { restored: [], failed: [] };
config.priorityDomains.forEach(domain => {
if (restoreDomain(domain)) {
priorityResults.restored.push(domain);
} else {
priorityResults.failed.push(domain);
}
});
log(`⚡ Priority restore completed: [${priorityResults.restored.join(', ')}]`);
return priorityResults;
}
function setupDomainMonitoring() {
log('🔍 Setting up domain monitoring...');
const allState = context.juris.stateManager.state;
const availableDomains = Object.keys(allState);
log(`🔍 Available domains in state:`, availableDomains);
// Clear existing subscriptions
domainSubscriptions.forEach(unsubscribe => unsubscribe());
domainSubscriptions.clear();
// Determine which domains to track
let domainsToTrack = [];
if (config.domains.length > 0) {
domainsToTrack = config.domains.filter(domain => {
const exists = availableDomains.includes(domain);
if (!exists) {
log(`⚠️ Configured domain '${domain}' not found in state`);
}
return exists && !config.excludeDomains.includes(domain);
});
} else {
domainsToTrack = availableDomains.filter(domain =>
!config.excludeDomains.includes(domain)
);
}
log(`📊 Domains to track: [${domainsToTrack.join(', ')}]`);
// Track each domain
domainsToTrack.forEach(domain => {
addDomainTracking(domain);
});
// Update stats - DIRECT INJECTION
if (context.juris && context.juris.stateManager && context.juris.stateManager.state && context.juris.stateManager.state.persistence) {
context.juris.stateManager.state.persistence.stats.domainsTracked = domainSubscriptions.size;
}
log(`✅ Now tracking ${domainSubscriptions.size} domains: [${Array.from(domainSubscriptions.keys()).join(', ')}]`);
}
function setupDomainWatcher() {
domainWatcher = setInterval(() => {
const currentDomains = Object.keys(context.juris.stateManager.state);
const trackedDomains = Array.from(domainSubscriptions.keys());
const newDomains = currentDomains.filter(domain =>
!trackedDomains.includes(domain) &&
!config.excludeDomains.includes(domain) &&
(config.domains.length === 0 || config.domains.includes(domain))
);
if (newDomains.length > 0) {
log(`🆕 Detected new domains: [${newDomains.join(', ')}]`);
newDomains.forEach(domain => addDomainTracking(domain));
// DIRECT INJECTION for stats
if (context.juris && context.juris.stateManager && context.juris.stateManager.state && context.juris.stateManager.state.persistence) {
context.juris.stateManager.state.persistence.stats.domainsTracked = domainSubscriptions.size;
}
}
}, config.watchIntervalMs);
log(`👀 Domain watcher started (checking every ${config.watchIntervalMs}ms)`);
}
function addDomainTracking(domain) {
if (domainSubscriptions.has(domain)) {
log(`⚠️ Domain ${domain} already being tracked`);
return false;
}
try {
const testValue = getState(domain);
log(`🔍 Testing domain '${domain}': ${testValue !== undefined ? 'exists' : 'undefined'}`);
// Use internal subscription
const unsubscribe = context.juris.stateManager.subscribeInternal(domain, () => {
if (!isRestoring) {
const currentValue = getState(domain);
log(`🔄 State change detected in domain: ${domain}`, { currentValue });
debouncedSave(domain, currentValue);
}
});
domainSubscriptions.set(domain, unsubscribe);
log(`➕ Added tracking for domain: ${domain}`);
return true;
} catch (error) {
logError(`Failed to add tracking for domain ${domain}:`, error);
return false;
}
}
function removeDomainTracking(domain) {
const unsubscribe = domainSubscriptions.get(domain);
if (unsubscribe) {
unsubscribe();
domainSubscriptions.delete(domain);
log(`➖ Removed tracking for domain: ${domain}`);
return true;
}
log(`⚠️ Domain ${domain} was not being tracked`);
return false;
}
function debouncedSave(domain, value) {
// Clear existing timer
if (saveTimers.has(domain)) {
clearTimeout(saveTimers.get(domain));
}
// Determine save timing based on domain configuration
let saveDelay = config.debounceMs;
if (config.immediateSave.includes(domain)) {
// Immediate save for critical domains
saveDomain(domain, true);
return;
} else if (config.criticalSave.includes(domain)) {
// Faster save for critical domains
saveDelay = config.criticalDebounceMs;
}
const timer = setTimeout(() => {
saveDomain(domain, false);
saveTimers.delete(domain);
}, saveDelay);
saveTimers.set(domain, timer);
const saveType = config.criticalSave.includes(domain) ? 'CRITICAL' : 'NORMAL';
log(`⏰ ${saveType} save scheduled for domain: ${domain} in ${saveDelay}ms`);
}
function saveDomain(domain, immediate = false) {
try {
const value = getState(domain);
if (value === undefined || value === null) {
log(`⚠️ Skipping save for undefined domain: ${domain}`);
return false;
}
const dataPackage = {
value: value,
timestamp: Date.now(),
domain: domain,
immediate: immediate
};
// Check if domain requires user-specific storage
const isUserSpecific = config.userSpecificDomains.includes(domain);
const currentUserId = getUserId();
if (isUserSpecific && config.requireAuth && !currentUserId) {
log(`⚠️ Skipping save for user-specific domain '${domain}' - no authenticated user`);
return false;
}
// Build storage key
let storageKey = config.keyPrefix + domain;
if (isUserSpecific && currentUserId) {
storageKey = `${storageKey}_${currentUserId}`;
}
localStorage.setItem(storageKey, JSON.stringify(dataPackage));
// Update statistics - DIRECT INJECTION
if (context.juris && context.juris.stateManager && context.juris.stateManager.state && context.juris.stateManager.state.persistence) {
const stats = context.juris.stateManager.state.persistence.stats;
stats.totalSaves = (stats.totalSaves || 0) + 1;
context.juris.stateManager.state.persistence.lastSave = {
domain,
timestamp: Date.now(),
size: JSON.stringify(dataPackage).length,
immediate
};
}
const saveType = immediate ? 'IMMEDIATE' : 'DEBOUNCED';
log(`💾 ${saveType} saved domain: ${domain} (${JSON.stringify(dataPackage).length} bytes) key: ${storageKey}`);
return true;
} catch (error) {
logError(`Failed to save domain ${domain}:`, error);
return false;
}
}
function restoreDomain(domain) {
try {
const isUserSpecific = config.userSpecificDomains.includes(domain);
const currentUserId = getUserId();
// Build storage key
let storageKey = config.keyPrefix + domain;
if (isUserSpecific && currentUserId) {
storageKey = `${storageKey}_${currentUserId}`;
}
const stored = localStorage.getItem(storageKey);
if (!stored) {
log(`📂 No stored data for domain: ${domain} (key: ${storageKey})`);
return false;
}
const data = JSON.parse(stored);
// DIRECT INJECTION - Restore directly to state without triggering subscriptions
isRestoring = true;
// Direct injection into the state manager's internal state
if (context.juris && context.juris.stateManager && context.juris.stateManager.state) {
context.juris.stateManager.state[domain] = data.value;
log(`📂 DIRECT INJECT: ${domain} directly injected into state`);
} else {
// Fallback to setState if direct access not available
setState(domain, data.value);
log(`📂 FALLBACK: ${domain} restored via setState`);
}
isRestoring = false;
// Update statistics - DIRECT INJECTION
if (context.juris && context.juris.stateManager && context.juris.stateManager.state && context.juris.stateManager.state.persistence) {
const stats = context.juris.stateManager.state.persistence.stats;
stats.totalRestores = (stats.totalRestores || 0) + 1;
context.juris.stateManager.state.persistence.lastRestore = {
domain,
timestamp: Date.now(),
dataTimestamp: data.timestamp
};
}
log(`📂 Restored domain: ${domain} (saved ${new Date(data.timestamp).toLocaleString()})`);
return true;
} catch (error) {
logError(`Failed to restore domain ${domain}:`, error);
return false;
}
}
function saveAllTrackedDomains() {
log('💾 Saving all tracked domains...');
const savedDomains = [];
const failedDomains = [];
domainSubscriptions.forEach((unsubscribe, domain) => {
if (saveDomain(domain, true)) {
savedDomains.push(domain);
} else {
failedDomains.push(domain);
}
});
log(`💾 Saved ${savedDomains.length} domains: [${savedDomains.join(', ')}]`);
if (failedDomains.length > 0) {
logError(`❌ Failed to save ${failedDomains.length} domains: [${failedDomains.join(', ')}]`);
}
return { saved: savedDomains, failed: failedDomains };
}
function handleStorageEvent(event) {
if (!event.key || !event.key.startsWith(config.keyPrefix)) {
return;
}
// Extract domain from key
let remainder = event.key.substring(config.keyPrefix.length);
let domain = remainder.split('_')[0];
if (domainSubscriptions.has(domain)) {
log(`🔄 Storage changed externally for domain: ${domain}`);
if (event.newValue) {
// Parse and directly inject the new value
try {
const data = JSON.parse(event.newValue);
isRestoring = true;
// DIRECT INJECTION for cross-tab sync
if (context.juris && context.juris.stateManager && context.juris.stateManager.state) {
context.juris.stateManager.state[domain] = data.value;
log(`🔄 DIRECT INJECT: ${domain} synced from external tab`);
} else {
setState(domain, data.value);
log(`🔄 FALLBACK: ${domain} synced via setState`);
}
isRestoring = false;
} catch (error) {
logError(`Failed to parse external change for ${domain}:`, error);
}
} else {
// Value was deleted externally
isRestoring = true;
if (context.juris && context.juris.stateManager && context.juris.stateManager.state) {
delete context.juris.stateManager.state[domain];
log(`🔄 DIRECT DELETE: ${domain} removed from state`);
} else {
setState(domain, undefined);
log(`🔄 FALLBACK DELETE: ${domain} removed via setState`);
}
isRestoring = false;
}
}
}
function clearDomainStorage(domain) {
try {
const isUserSpecific = config.userSpecificDomains.includes(domain);
const currentUserId = getUserId();
let storageKey = config.keyPrefix + domain;
if (isUserSpecific && currentUserId) {
storageKey = `${storageKey}_${currentUserId}`;
}
localStorage.removeItem(storageKey);
log(`🗑️ Cleared storage for domain: ${domain} (key: ${storageKey})`);
return true;
} catch (error) {
logError(`Failed to clear storage for domain ${domain}:`, error);
return false;
}
}
function clearAllStorage() {
try {
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(config.keyPrefix)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
log(`🗑️ Cleared ${keysToRemove.length} storage entries`);
return true;
} catch (error) {
logError('Failed to clear all storage:', error);
return false;
}
}
function getStorageStats() {
let totalSize = 0;
let entryCount = 0;
const domains = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(config.keyPrefix)) {
const value = localStorage.getItem(key);
totalSize += key.length + (value ? value.length : 0);
entryCount++;
// Extract domain from key
let remainder = key.substring(config.keyPrefix.length);
let domain = remainder.split('_')[0];
domains.push(domain);
}
}
return {
totalSize,
entryCount,
domains: [...new Set(domains)], // Remove duplicates
trackedDomains: Array.from(domainSubscriptions.keys()),
config: {
aggressiveRestore: config.aggressiveRestore,
priorityDomains: config.priorityDomains,
immediateSave: config.immediateSave,
criticalSave: config.criticalSave,
keyPrefix: config.keyPrefix,
userSpecificDomains: config.userSpecificDomains
}
};
}
function exportState() {
const exportData = {
timestamp: Date.now(),
domains: {}
};
domainSubscriptions.forEach((unsubscribe, domain) => {
exportData.domains[domain] = getState(domain);
});
return exportData;
}
function importState(data) {
try {
if (!data.domains) {
throw new Error('Invalid import data format');
}
isRestoring = true;
Object.entries(data.domains).forEach(([domain, value]) => {
// DIRECT INJECTION for import as well
if (context.juris && context.juris.stateManager && context.juris.stateManager.state) {
context.juris.stateManager.state[domain] = value;
log(`📥 DIRECT INJECT: ${domain} imported directly into state`);
} else {
setState(domain, value);
log(`📥 FALLBACK: ${domain} imported via setState`);
}
saveDomain(domain, true);
});
isRestoring = false;
log(`📥 Imported ${Object.keys(data.domains).length} domains`);
return true;
} catch (error) {
isRestoring = false;
logError('Import failed:', error);
return false;
}
}
// Helper function to get user ID from configurable path
function getUserId() {
try {
const pathParts = config.userIdPath.split('.');
let value = context.juris.stateManager.state;
for (const part of pathParts) {
if (value && typeof value === 'object') {
value = value[part];
} else {
return null;
}
}
return value;
} catch (error) {
log(`⚠️ Failed to get user ID from path: ${config.userIdPath}`, error);
return null;
}
}
function log(message, ...args) {
if (config.debug) {
console.log(`💾 [StatePersistence] ${message}`, ...args);
}
}
function logError(message, error = null) {
console.error(`💾 [StatePersistence] ${message}`, error);
// DIRECT INJECTION for errors
if (context.juris && context.juris.stateManager && context.juris.stateManager.state && context.juris.stateManager.state.persistence) {
const errors = context.juris.stateManager.state.persistence.errors;
errors.push({
message,
error: error ? error.message : null,
timestamp: Date.now()
});
if (errors.length > 10) {
errors.splice(0, errors.length - 10);
}
}
}
};
// URL State Sync with Route Guards
const UrlStateSync = (props, context) => {
const { getState, setState } = context;
const routeGuards = {
'/': ['authenticated'],
'/lists': ['authenticated'],
'/profile': ['authenticated'],
'/settings': ['authenticated']
};
return {
hooks: {
onRegister: () => {
console.log('🧭 UrlStateSync initializing...');
handleUrlChange();
window.addEventListener('hashchange', handleUrlChange);
window.addEventListener('popstate', handleUrlChange);
}
}
};
async function handleUrlChange() {
const hash = window.location.hash.substring(1) || '/';
const segments = parseSegments(hash);
// Check route guards
const guardResult = await checkRouteAccess(hash);
if (guardResult.allowed) {
setState('url.path', hash);
setState('url.segments', segments);
console.log('🧭 URL updated:', hash);
} else {
console.log('🚫 Route access denied, redirecting to:', guardResult.redirect);
window.location.hash = guardResult.redirect;
}
}
async function checkRouteAccess(path) {
const guards = getRouteGuards(path);
const isAuthenticated = getState('auth.isLoggedIn', false);
if (guards.includes('authenticated') && !isAuthenticated) {
return { allowed: false, redirect: '/login' };
}
return { allowed: true };
}
function getRouteGuards(path) {
if (routeGuards[path]) return routeGuards[path];
const segments = path.split('/').filter(Boolean);
for (let i = segments.length - 1; i > 0; i--) {
const parentPath = '/' + segments.slice(0, i).join('/');
if (routeGuards[parentPath]) return routeGuards[parentPath];
}
return [];
}
function parseSegments(path) {
const parts = path.split('/').filter(Boolean);
return {
full: path,
parts: parts,
base: parts[0] || '',
sub: parts[1] || '',
id: parts[2] || ''
};
}
};
// Authentication Manager
const AuthManager = (props, context) => {
const { getState, setState } = context;
return {
hooks: {
onRegister: () => {
console.log('🔐 AuthManager initializing...');
checkExistingAuth();
}
},
api: {
login: async (email, password) => {
setState('auth.loading', true);
setState('auth.error', null);
try {
await new Promise(resolve => setTimeout(resolve, 1000));
const users = getState('storage.users', []);
const user = users.find(u => u.email === email && u.password === hashPassword(password));
if (!user) {
throw new Error('Invalid email or password');
}
const token = generateToken();
setState('auth.user', user);
setState('auth.token', token);
setState('auth.isLoggedIn', true);
// Store in localStorage
localStorage.setItem('todo_auth_token', token);
localStorage.setItem('todo_auth_user', JSON.stringify(user));
console.log('✅ Login successful');
// Trigger user-specific data restore after login
setTimeout(() => {
context.headless.StatePersistenceManager.restoreAllDomains();
}, 100);
return { success: true };
} catch (error) {
setState('auth.error', error.message);
console.error('❌ Login failed:', error.message);
return { success: false, error: error.message };
} finally {
setState('auth.loading', false);
}
},
register: async (email, password, name) => {
setState('auth.loading', true);
setState('auth.error', null);
try {
await new Promise(resolve => setTimeout(resolve, 1000));
const users = getState('storage.users', []);
if (users.find(u => u.email === email)) {
throw new Error('Email already exists');
}
const newUser = {
id: Date.now().toString(),
email,
password: hashPassword(password),
name,
createdAt: new Date().toISOString()
};
const updatedUsers = [...users, newUser];
setState('storage.users', updatedUsers);
console.log('✅ Registration successful');
return { success: true };
} catch (error) {
setState('auth.error', error.message);
console.error('❌ Registration failed:', error.message);
return { success: false, error: error.message };
} finally {
setState('auth.loading', false);
}
},
logout: () => {
// Save current state before logout
context.headless.StatePersistenceManager.saveAllDomains();
setState('auth.user', null);
setState('auth.token', null);
setState('auth.isLoggedIn', false);
// Clear user-specific state
setState('ui.selectedListId', null);
setState('todos.lists', []);
setState('todos.items', {});
setState('todos.filter', 'all');
localStorage.removeItem('todo_auth_token');
localStorage.removeItem('todo_auth_user');
window.location.hash = '/login';
console.log('👋 Logged out');
}
}
};
function checkExistingAuth() {
const token = localStorage.getItem('todo_auth_token');
const userStr = localStorage.getItem('todo_auth_user');
if (token && userStr) {
try {
const user = JSON.parse(userStr);
setState('auth.user', user);
setState('auth.token', token);
setState('auth.isLoggedIn', true);
console.log('🔄 Restored authentication');
// Trigger user-specific data restore after auth restore
setTimeout(() => {
context.headless.StatePersistenceManager.restoreAllDomains();
}, 100);
} catch (error) {
console.error('❌ Failed to restore auth:', error);
localStorage.removeItem('todo_auth_token');
localStorage.removeItem('todo_auth_user');
}
}
}
function hashPassword(password) {
return CryptoJS.SHA256(password).toString();
}
function generateToken() {
return CryptoJS.lib.WordArray.random(32).toString();
}
};
// Enhanced Todo Manager
const TodoManager = (props, context) => {
const { getState, setState, subscribe } = context;
return {
hooks: {
onRegister: () => {
console.log('📝 TodoManager initializing...');
loadUserTodos();
// Subscribe to auth changes
subscribe('auth.user', (user) => {
if (user) {
loadUserTodos();
} else {
setState('todos', { lists: [], items: {}, filters: {} });
setState('ui.selectedListId', null);
}
});
}
},
api: {
createList: (name) => {
const user = getState('auth.user');
if (!user) return;
const newList = {
id: Date.now().toString(),
name,
userId: user.id,
createdAt: new Date().toISOString()
};
const lists = getState('todos.lists', []);
setState('todos.lists', [...lists, newList]);
// Auto-select the new list
setState('ui.selectedListId', newList.id);
saveTodos();
console.log('✅ List created:', name);
},
deleteList: (listId) => {
const lists = getState('todos.lists', []);
const items = getState('todos.items', {});
// Remove list
const updatedLists = lists.filter(list => list.id !== listId);
setState('todos.lists', updatedLists);
// Remove all items in this list
const updatedItems = { ...items };
delete updatedItems[listId];
setState('todos.items', updatedItems);
// Clear selection if this list was selected
const selectedId = getState('ui.selectedListId');
if (selectedId === listId) {
// Auto-select the first remaining list or null
const newSelectedId = updatedLists.length > 0 ? updatedLists[0].id : null;
setState('ui.selectedListId', newSelectedId);
}
saveTodos();
console.log('🗑️ List deleted:', listId);
},
createTodo: (listId, text) => {
const newTodo = {
id: Date.now().toString(),
text,
completed: false,
createdAt: new Date().toISOString()
};
const items = getState('todos.items', {});
const listItems = items[listId] || [];
setState(`todos.items.${listId}`, [...listItems, newTodo]);
saveTodos();
console.log('✅ Todo created:', text);
},
toggleTodo: (listId, todoId) => {
const items = getState(`todos.items.${listId}`, []);
const updatedItems = items.map(item =>
item.id === todoId ? { ...item, completed: !item.completed } : item
);
setState(`todos.items.${listId}`, updatedItems);
saveTodos();
},
deleteTodo: (listId, todoId) => {
const items = getState(`todos.items.${listId}`, []);
const updatedItems = items.filter(item => item.id !== todoId);
setState(`todos.items.${listId}`, updatedItems);
saveTodos();
},
setFilter: (filter) => {
setState('todos.filter', filter);
},
selectList: (listId) => {
setState('ui.selectedListId', listId);
console.log('📋 Selected list:', listId);
}
}
};
function loadUserTodos() {
const user = getState('auth.user');
if (!user) return;
const stored = localStorage.getItem(`todo_data_${user.id}`);
if (stored) {
try {
const data = JSON.parse(stored);
setState('todos.lists', data.lists || []);
setState('todos.items', data.items || {});
console.log('📚 Todos loaded for user:', user.email);
} catch (error) {
console.error('❌ Failed to load todos:', error);
}
}
}
function saveTodos() {
const user = getState('auth.user');
if (!user) return;
const data = {
lists: getState('todos.lists', []),
items: getState('todos.items', {}),
lastSaved: new Date().toISOString()
};
localStorage.setItem(`todo_data_${user.id}`, JSON.stringify(data));
console.log('💾 Todos saved');
}
};
// ==================== UI COMPONENTS ====================
// App Layout
const AppLayout = (props, context) => {
const { getState } = context;
return {
render: () => ({
div: {
className: 'app-container',
children: () => {
const isLoggedIn = getState('auth.isLoggedIn', false);
const currentPath = getState('url.path', '/');
if (!isLoggedIn && !['/login', '/register'].includes(currentPath)) {
return [{ AuthLayout: {} }];
}
if (['/login', '/register'].includes(currentPath)) {
return [{ AuthLayout: {} }];
}
return [
{ AppHeader: {} },
{ MainContent: {} }
];
}
}
})
};
};
// App Header
const AppHeader = (props, context) => {
const { getState } = context;
return {
render: () => ({
header: {
className: 'header',
children: [{
div: {
className: 'header-content',
children: [
{
a: {
className: 'logo',
href: '#/',
text: '📝 {Juris} Demo Todo App',
onclick: (e) => {
e.preventDefault();
window.location.hash = '/';
}
}
},
{
nav: {
className: 'nav',
children: [
{
a: {
className: () => {
const path = getState('url.path', '/');
return path === '/' ? 'nav-link active' : 'nav-link';
},
href: '#/',
text: 'Dashboard',
onclick: (e) => {
e.preventDefault();
window.location.hash = '/';
}
}
},
{
a: {
className: () => {
const path = getState('url.path', '/');
return path === '/profile' ? 'nav-link active' : 'nav-link';
},
href: '#/profile',
text: 'Profile',
onclick: (e) => {
e.preventDefault();
window.location.hash = '/profile';
}
}
},
{
button: {
className: 'btn btn-secondary btn-small',
text: 'Logout',
onclick: () => context.headless.AuthManager.logout()
}
}
]
}
}
]
}
}]
}
})
};
};
// Auth Layout
const AuthLayout = (props, context) => {
const { getState } = context;
return {
render: () => ({
div: {
className: 'app-container',
children: [{
main: {
className: 'main-content',
children: () => {
const path = getState('url.path', '/');
if (path === '/register') {
return [{ RegisterForm: {} }];
}
return [{ LoginForm: {} }];
}
}
}]
}
})
};
};
// Login Form
const LoginForm = (props, context) => {
const { getState, setState, headless } = context;
return {
render: () => ({
div: {
className: 'form-container fade-in',
children: [
{
h2: {
className: 'form-title',
text: 'Welcome Back'
}
},
{
form: {
onsubmit: async (e) => {
e.preventDefault();
const email = getState('auth.form.email', '');
const password = getState('auth.form.password', '');
const result = await headless.AuthManager.login(email, password);
if (result.success) {
window.location.hash = '/';
}
},
children: [
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Email'
}
},
{
input: {
type: 'email',
className: 'form-input',
placeholder: 'Enter your email',
value: () => getState('auth.form.email', ''),
oninput: (e) => setState('auth.form.email', e.target.value),
required: true
}
}
]
}
},
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Password'
}
},
{
input: {
type: 'password',
className: 'form-input',
placeholder: 'Enter your password',
value: () => getState('auth.form.password', ''),
oninput: (e) => setState('auth.form.password', e.target.value),
required: true
}
}
]
}
},
{
div: {
className: () => getState('auth.error') ? 'form-error' : 'hidden',
text: () => getState('auth.error', '')
}
},
{
button: {
type: 'submit',
className: 'btn btn-primary btn-full',
disabled: () => getState('auth.loading', false),
text: () => getState('auth.loading') ? 'Signing in...' : 'Sign In'
}
},
{
div: {
className: 'text-center mt-2',
children: [
{
span: {
text: "Don't have an account? "
}
},
{
a: {
href: '#/register',
text: 'Sign up',
onclick: (e) => {
e.preventDefault();
window.location.hash = '/register';
}
}
}
]
}
}
]
}
}
]
}
})
};
};
// Register Form
const RegisterForm = (props, context) => {
const { getState, setState, headless } = context;
return {
render: () => ({
div: {
className: 'form-container fade-in',
children: [
{
h2: {
className: 'form-title',
text: 'Create Account'
}
},
{
form: {
onsubmit: async (e) => {
e.preventDefault();
const name = getState('auth.form.name', '');
const email = getState('auth.form.email', '');
const password = getState('auth.form.password', '');
const result = await headless.AuthManager.register(email, password, name);
if (result.success) {
setState('auth.form', {});
window.location.hash = '/login';
}
},
children: [
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Full Name'
}
},
{
input: {
type: 'text',
className: 'form-input',
placeholder: 'Enter your full name',
value: () => getState('auth.form.name', ''),
oninput: (e) => setState('auth.form.name', e.target.value),
required: true
}
}
]
}
},
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Email'
}
},
{
input: {
type: 'email',
className: 'form-input',
placeholder: 'Enter your email',
value: () => getState('auth.form.email', ''),
oninput: (e) => setState('auth.form.email', e.target.value),
required: true
}
}
]
}
},
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Password'
}
},
{
input: {
type: 'password',
className: 'form-input',
placeholder: 'Create a password',
value: () => getState('auth.form.password', ''),
oninput: (e) => setState('auth.form.password', e.target.value),
required: true
}
}
]
}
},
{
div: {
className: () => getState('auth.error') ? 'form-error' : 'hidden',
text: () => getState('auth.error', '')
}
},
{
button: {
type: 'submit',
className: 'btn btn-primary btn-full',
disabled: () => getState('auth.loading', false),
text: () => getState('auth.loading') ? 'Creating account...' : 'Create Account'
}
},
{
div: {
className: 'text-center mt-2',
children: [
{
span: {
text: "Already have an account? "
}
},
{
a: {
href: '#/login',
text: 'Sign in',
onclick: (e) => {
e.preventDefault();
window.location.hash = '/login';
}
}
}
]
}
}
]
}
}
]
}
})
};
};
// Main Content
const MainContent = (props, context) => {
const { getState } = context;
return {
render: () => ({
main: {
className: 'main-content',
children: () => {
const segments = getState('url.segments', { base: '' });
switch (segments.base) {
case '':
return [{ TodoDashboard: {} }];
case 'lists':
if (segments.sub) {
return [{ TodoListDetail: { listId: segments.sub } }];
}
return [{ TodoDashboard: {} }];
case 'profile':
return [{ UserProfile: {} }];
case 'settings':
return [{ AppSettings: {} }];
default:
return [{ NotFound: {} }];
}
}
}
})
};
};
// Todo Dashboard
const TodoDashboard = (props, context) => {
const { getState, setState, headless } = context;
return {
render: () => ({
div: {
className: 'todo-dashboard fade-in',
children: [
{
div: {
className: 'todo-lists',
children: [
{
div: {
className: 'section-title',
text: 'Your Todo Lists'
}
},
{
div: {
className: 'todo-form',
children: [{
form: {
onsubmit: (e) => {
e.preventDefault();
const name = getState('ui.newListName', '').trim();
if (name) {
headless.TodoManager.createList(name);
setState('ui.newListName', '');
}
},
children: [{
div: {
className: 'todo-input',
children: [
{
input: {
type: 'text',
className: 'form-input',
placeholder: 'Enter list name...',
value: () => getState('ui.newListName', ''),
oninput: (e) => setState('ui.newListName', e.target.value)
}
},
{
button: {
type: 'submit',
className: 'btn btn-primary',
text: 'Add List'
}
}
]
}
}]
}
}]
}
},
{
div: {
children: () => {
const lists = getState('todos.lists', []);
if (lists.length === 0) {
return [{
div: {
className: 'empty-state',
children: [
{
h3: {
text: 'No lists yet'
}
},
{
p: {
text: 'Create your first todo list to get started!'
}
}
]
}
}];
}
return lists.map(list => ({
TodoListItem: { list, key: list.id }
}));
}
}
}
]
}
},
{
div: {
className: 'todo-list',
children: () => {
const selectedListId = getState('ui.selectedListId');
const lists = getState('todos.lists', []);
const selectedList = lists.find(l => l.id === selectedListId);
if (!selectedList) {
return [{
div: {
className: 'empty-state',
children: [
{
h3: {
text: 'Select a list'
}
},
{
p: {
text: 'Choose a todo list from the sidebar to view and manage your tasks.'
}
}
]
}
}];
}
return [{ TodoListDetail: { listId: selectedListId } }];
}
}
}
]
}
})
};
};
// Todo List Item
const TodoListItem = (props, context) => {
const { getState, setState, headless } = context;
const { list } = props;
return {
render: () => ({
div: {
className: () => {
const selectedId = getState('ui.selectedListId');
return selectedId === list.id ? 'list-item active' : 'list-item';
},
onclick: () => headless.TodoManager.selectList(list.id),
children: [
{
div: {
className: 'list-info',
children: [
{
div: {
className: 'list-name',
text: list.name
}
},
{
div: {
className: 'list-count',
text: () => {
const items = getState(`todos.items.${list.id}`, []);
const completed = items.filter(item => item.completed).length;
return `${completed}/${items.length} completed`;
}
}
}
]
}
},
{
div: {
className: 'list-actions',
children: [{
button: {
className: 'btn btn-danger btn-small',
text: '🗑️',
onclick: (e) => {
e.stopPropagation();
if (confirm(`Delete "${list.name}"?`)) {
headless.TodoManager.deleteList(list.id);
}
}
}
}]
}
}
]
}
})
};
};
// Todo List Detail
const TodoListDetail = (props, context) => {
const { getState, setState, headless } = context;
const { listId } = props;
return {
render: () => ({
div: {
className: 'fade-in',
children: [
{
div: {
className: 'section-title',
text: () => {
const lists = getState('todos.lists', []);
const list = lists.find(l => l.id === listId);
return list ? list.name : 'Todo List';
}
}
},
{
div: {
className: 'todo-form',
children: [{
form: {
onsubmit: (e) => {
e.preventDefault();
const text = getState('ui.newTodoText', '').trim();
if (text) {
headless.TodoManager.createTodo(listId, text);
setState('ui.newTodoText', '');
}
},
children: [{
div: {
className: 'todo-input',
children: [
{
input: {
type: 'text',
className: 'form-input',
placeholder: 'Add a new todo...',
value: () => getState('ui.newTodoText', ''),
oninput: (e) => setState('ui.newTodoText', e.target.value)
}
},
{
button: {
type: 'submit',
className: 'btn btn-primary',
text: 'Add'
}
}
]
}
}]
}
}]
}
},
{
div: {
className: 'filters',
children: [
{
button: {
className: () => {
const filter = getState('todos.filter', 'all');
return filter === 'all' ? 'filter-btn active' : 'filter-btn';
},
text: 'All',
onclick: () => headless.TodoManager.setFilter('all')
}
},
{
button: {
className: () => {
const filter = getState('todos.filter', 'all');
return filter === 'active' ? 'filter-btn active' : 'filter-btn';
},
text: 'Active',
onclick: () => headless.TodoManager.setFilter('active')
}
},
{
button: {
className: () => {
const filter = getState('todos.filter', 'all');
return filter === 'completed' ? 'filter-btn active' : 'filter-btn';
},
text: 'Completed',
onclick: () => headless.TodoManager.setFilter('completed')
}
}
]
}
},
{
div: {
children: () => {
const items = getState(`todos.items.${listId}`, []);
const filter = getState('todos.filter', 'all');
let filteredItems = items;
if (filter === 'active') {
filteredItems = items.filter(item => !item.completed);
} else if (filter === 'completed') {
filteredItems = items.filter(item => item.completed);
}
if (filteredItems.length === 0) {
return [{
div: {
className: 'empty-state',
children: [
{
h3: {
text: () => {
const filter = getState('todos.filter', 'all');
if (filter === 'active') return 'No active todos';
if (filter === 'completed') return 'No completed todos';
return 'No todos yet';
}
}
},
{
p: {
text: 'Add your first todo item above!'
}
}
]
}
}];
}
return filteredItems.map(item => ({
TodoItem: { listId, item, key: item.id }
}));
}
}
}
]
}
})
};
};
// Todo Item
const TodoItem = (props, context) => {
const { headless } = context;
const { listId, item } = props;
return {
render: () => ({
div: {
className: 'todo-item',
children: [
{
input: {
type: 'checkbox',
className: 'todo-checkbox',
checked: item.completed,
onchange: () => headless.TodoManager.toggleTodo(listId, item.id)
}
},
{
span: {
className: item.completed ? 'todo-text completed' : 'todo-text',
text: item.text
}
},
{
div: {
className: 'todo-actions',
children: [{
button: {
className: 'btn btn-danger btn-small',
text: '🗑️',
onclick: () => {
if (confirm('Delete this todo?')) {
headless.TodoManager.deleteTodo(listId, item.id);
}
}
}
}]
}
}
]
}
})
};
};
// User Profile
const UserProfile = (props, context) => {
const { getState } = context;
return {
render: () => ({
div: {
className: 'fade-in',
children: [{
div: {
className: 'form-container',
children: [
{
h2: {
className: 'form-title',
text: 'Profile'
}
},
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Name'
}
},
{
div: {
className: 'form-input',
style: {
background: 'var(--gray-100)',
border: 'none',
color: 'var(--gray-700)'
},
text: () => getState('auth.user.name', 'Not available')
}
}
]
}
},
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Member Since'
}
},
{
div: {
className: 'form-input',
style: {
background: 'var(--gray-100)',
border: 'none',
color: 'var(--gray-700)'
},
text: () => {
const createdAt = getState('auth.user.createdAt');
return createdAt ? new Date(createdAt).toLocaleDateString() : 'Not available';
}
}
}
]
}
},
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Statistics'
}
},
{
div: {
style: {
background: 'var(--gray-50)',
padding: '1rem',
borderRadius: 'var(--border-radius)',
border: '1px solid var(--gray-200)'
},
children: [
{
div: {
style: { marginBottom: '0.5rem' },
children: [
{
span: {
style: { fontWeight: '500' },
text: 'Total Lists: '
}
},
{
span: {
text: () => getState('todos.lists', []).length.toString()
}
}
]
}
},
{
div: {
style: { marginBottom: '0.5rem' },
children: [
{
span: {
style: { fontWeight: '500' },
text: 'Total Todos: '
}
},
{
span: {
text: () => {
const items = getState('todos.items', {});
const total = Object.values(items).reduce((sum, list) => sum + list.length, 0);
return total.toString();
}
}
}
]
}
},
{
div: {
children: [
{
span: {
style: { fontWeight: '500' },
text: 'Completed: '
}
},
{
span: {
text: () => {
const items = getState('todos.items', {});
const completed = Object.values(items)
.flat()
.filter(item => item.completed).length;
return completed.toString();
}
}
}
]
}
}
]
}
}
]
}
}
]
}
}]
}
})
};
};
// App Settings
const AppSettings = (props, context) => {
const { getState, setState, headless } = context;
return {
render: () => ({
div: {
className: 'fade-in',
children: [{
div: {
className: 'form-container',
children: [
{
h2: {
className: 'form-title',
text: 'Settings'
}
},
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Default Filter'
}
},
{
select: {
className: 'form-input',
value: () => getState('settings.defaultFilter', 'all'),
onchange: (e) => setState('settings.defaultFilter', e.target.value),
children: [
{
option: {
value: 'all',
text: 'All Todos'
}
},
{
option: {
value: 'active',
text: 'Active Todos'
}
},
{
option: {
value: 'completed',
text: 'Completed Todos'
}
}
]
}
}
]
}
},
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
children: [
{
input: {
type: 'checkbox',
checked: () => getState('settings.autoSave', true),
onchange: (e) => setState('settings.autoSave', e.target.checked),
style: { marginRight: '0.5rem' }
}
},
{
span: {
text: 'Auto-save changes'
}
}
]
}
}
]
}
},
{
div: {
className: 'form-group',
children: [
{
label: {
className: 'form-label',
text: 'Persistence Statistics'
}
},
{
div: {
style: {
background: 'var(--gray-50)',
padding: '1rem',
borderRadius: 'var(--border-radius)',
border: '1px solid var(--gray-200)'
},
children: () => {
const stats = getState('persistence.stats', {});
return [
{
div: {
style: { marginBottom: '0.5rem' },
children: [
{
span: {
style: { fontWeight: '500' },
text: 'Domains Tracked: '
}
},
{
span: {
text: (stats.domainsTracked || 0).toString()
}
}
]
}
},
{
div: {
style: { marginBottom: '0.5rem' },
children: [
{
span: {
style: { fontWeight: '500' },
text: 'Total Saves: '
}
},
{
span: {
text: (stats.totalSaves || 0).toString()
}
}
]
}
},
{
div: {
children: [
{
span: {
style: { fontWeight: '500' },
text: 'Total Restores: '
}
},
{
span: {
text: (stats.totalRestores || 0).toString()
}
}
]
}
}
];
}
}
}
]
}
},
{
div: {
className: 'form-group',
children: [
{
button: {
className: 'btn btn-secondary btn-full',
style: { marginBottom: '0.5rem' },
text: 'Export All Data',
onclick: () => {
try {
const exportData = headless.StatePersistenceManager.exportState();
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `todo-app-backup-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
alert('Data exported successfully!');
} catch (error) {
alert('Export failed: ' + error.message);
}
}
}
}
]
}
},
{
div: {
className: 'form-group',
children: [
{
button: {
className: 'btn btn-danger btn-full',
text: 'Clear All Data',
onclick: () => {
if (confirm('This will delete all your todo lists and data. This cannot be undone. Are you sure?')) {
const user = getState('auth.user');
if (user) {
// Clear old todo data
localStorage.removeItem(`todo_data_${user.id}`);
// Clear new persistence data
headless.StatePersistenceManager.clearAllStorage();
// Reset state
setState('todos.lists', []);
setState('todos.items', {});
setState('ui.selectedListId', null);
alert('All data has been cleared.');
}
}
}
}
}
]
}
}
]
}
}]
}
})
};
};
// Not Found Page
const NotFound = (props, context) => {
return {
render: () => ({
div: {
className: 'empty-state fade-in',
children: [
{
h3: {
text: '404 - Page Not Found'
}
},
{
p: {
text: 'The page you are looking for does not exist.'
}
},
{
a: {
href: '#/',
className: 'btn btn-primary',
text: 'Go to Dashboard',
onclick: (e) => {
e.preventDefault();
window.location.hash = '/';
}
}
}
]
}
})
};
};
// ==================== APPLICATION INITIALIZATION ====================
const juris = new Juris({
components: {
AppLayout,
AppHeader,
AuthLayout,
LoginForm,
RegisterForm,
MainContent,
TodoDashboard,
TodoListItem,
TodoListDetail,
TodoItem,
UserProfile,
AppSettings,
NotFound
},
headlessComponents: {
AuthManager: { fn: AuthManager, options: { autoInit: true } },
UrlStateSync: { fn: UrlStateSync, options: { autoInit: true } },
TodoManager: { fn: TodoManager, options: { autoInit: true } },
StatePersistenceManager: {
fn: StatePersistenceManager,
options: {
autoInit: true,
debug: true, // Enable debug logging
domains: ['ui', 'todos', 'settings', 'storage'], // Specify domains to track
priorityDomains: ['ui'], // UI gets priority restore for selected list
immediateSave: ['ui'], // Save UI changes immediately (like selected list)
criticalSave: ['todos'], // Save todos with faster debounce
aggressiveRestore: true, // Restore immediately on startup
keyPrefix: 'todo_app_state_',
// User-specific domains will have user ID appended automatically
domainRestoreConfig: {
ui: { priority: 1, delay: 0, aggressive: true }, // Restore selected list first
todos: { priority: 2, delay: 0, aggressive: true },
settings: { priority: 3, delay: 100, aggressive: false },
storage: { priority: 4, delay: 200, aggressive: false }
}
}
}
},
layout: {
div: {
children: [{ AppLayout: {} }]
}
},
states: {
url: {
path: '/',
segments: { full: '/', parts: [], base: '', sub: '', id: '' }
},
auth: {
user: null,
token: null,
isLoggedIn: false,
loading: false,
error: null,
form: {}
},
todos: {
lists: [],
items: {},
filter: 'all'
},
ui: {
selectedListId: '', // This will now be persisted!
newListName: '',
newTodoText: ''
},
storage: {
users: [],
settings: {}
},
settings: {
defaultFilter: 'all',
autoSave: true
}
}
});
// Start the application
juris.render('#app');
// Auto-navigate to login if no hash
if (!window.location.hash) {
// Don't force login redirect, let auth check handle it
}
// Global access for debugging
window.juris = juris;
console.log('🚀 Todo App Ready!');
console.log('📱 Routes available:');
console.log(' - #/login (public)');
console.log(' - #/register (public)');
console.log(' - #/ (protected - dashboard)');
console.log(' - #/profile (protected)');
console.log(' - #/settings (protected)');
console.log('💾 Data persists in localStorage');
console.log('🔐 Authentication with route guards');
console.log('🧭 State-based routing');
</script>
</body>
</html>```