Skip to content

bug: solve history stored in localStorage has no size cap β€” a speedcuber with thousands of solves will eventually exceed the 5MB localStorage quota, causing a silent QuotaExceededError that wipes the entire session historyΒ #94

Description

@prince-pokharna

πŸ› Problem Statement

CubeIt stores all solve history persistently in the browser's Local Storage:

"All solves and statistics are securely saved in your browser's Local Storage. No database required."

Each solve record likely stores: time, scramble (WCA notation string ~20 chars), timestamp, plus computed Ao5/Ao12 stats. A serious speedcuber might log 10,000+ solves across sessions.

At ~200 bytes per solve Γ— 10,000 solves = ~2 MB. Browsers enforce a 5 MB localStorage quota per origin. When the quota is exceeded, localStorage.setItem() throws a QuotaExceededError (a DOMException). If this exception is not caught, the entire solve attempt is silently lost β€” no timer result saved, no feedback to the user.

For a speedcuber who just achieved a new personal best, losing that solve to a silent storage error is unacceptable.

πŸ’‘ Proposed Fix

1. Wrap all localStorage writes in a try/catch with user-facing feedback

// src/utils/storage.js

const MAX_SOLVES = 5000; // configurable cap

export function saveSolve(solve) {
  try {
    const history = loadHistory();

    // Enforce max solve cap β€” drop oldest entries if needed
    if (history.length >= MAX_SOLVES) {
      history.shift(); // Remove oldest
    }

    history.push(solve);
    localStorage.setItem('cubeit_history', JSON.stringify(history));
  } catch (e) {
    if (e instanceof DOMException && e.name === 'QuotaExceededError') {
      // Notify user β€” do NOT silently fail
      console.error('[CubeIt] LocalStorage quota exceeded. Pruning oldest 100 solves.');
      pruneOldestSolves(100);
      // Retry once after pruning
      try {
        const pruned = loadHistory();
        pruned.push(solve);
        localStorage.setItem('cubeit_history', JSON.stringify(pruned));
      } catch (retryError) {
        // Storage is critically full β€” alert user
        alert('⚠️ Storage is full. Please export your history and clear old solves.');
      }
    }
  }
}

function pruneOldestSolves(count) {
  const history = loadHistory();
  history.splice(0, count);
  localStorage.setItem('cubeit_history', JSON.stringify(history));
}

2. Add a storage usage indicator in the Analytics panel

// src/components/Analytics.jsx

function StorageUsageBar() {
  const used = new Blob([localStorage.getItem('cubeit_history') || '']).size;
  const total = 5 * 1024 * 1024; // ~5MB
  const pct = Math.round((used / total) * 100);

  return (
    <div className="storage-indicator">
      <span>Storage: {(used / 1024).toFixed(0)} KB / 5 MB ({pct}%)</span>
      {pct > 80 && (
        <span className="storage-warning">⚠️ Storage nearly full β€” consider exporting history</span>
      )}
    </div>
  );
}

3. Add a one-click JSON export button

Allow users to export their full history before clearing:

export function exportHistory() {
  const history = loadHistory();
  const blob = new Blob([JSON.stringify(history, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `cubeit_history_${new Date().toISOString().split('T')[0]}.json`;
  a.click();
}

πŸ“ Files to Modify

File Change
src/utils/storage.js Add QuotaExceededError handler + auto-pruning
src/components/Analytics.jsx Add storage usage indicator
src/components/Timer.jsx Wire export button

Suggested labels: bug, storage, ux

I would like to work on this. Could you please assign it to me?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions