### Open index.html locally
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
This command opens the index.html file, simulating the start of a local server. This is a placeholder for actual server start commands like 'python -m http.server' or 'npm start'.
```bash
echo "๐ Opening the gate..."
```
--------------------------------
### Set Up Environment Variables
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/README.md
Copy the example environment file and edit it with your API keys.
```bash
cp .env.example .env
# Edit .env with your API keys
```
--------------------------------
### Set Up Environment Variables
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/dev/guides/getting_started.md
Copy the example environment file to create your own. Optionally, add your Google Gemini API Key for AI image generation.
```bash
cp .env.example .env
```
--------------------------------
### Launch the Application
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/README.md
Start the local HTTP server to launch the application. This script simply runs `python3 -m http.server`.
```bash
./startup.sh
```
--------------------------------
### Setup Microphone Button
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/delta_snapshot.txt
Initializes a microphone button to handle audio recording using 'hold' mode. It starts recording on mousedown and stops on mouseup, providing a transcript via a callback.
```javascript
function setupMicButton(buttonElement, onTranscript) {
if (!buttonElement) return;
// Remove any existing signal to prevent duplicated events
if (buttonElement._abortController) {
buttonElement._abortController.abort();
}
buttonElement._abortController = new AbortController();
const signal = buttonElement._abortController.signal;
let isRecording = false;
buttonElement.addEventListener('mousedown', async () => {
const mode = settingsManager.get('audio', 'recordingMode');
if (mode === 'hold') {
console.log("๐ค Hold Mode: Start");
const result = await audioEngine.startRecording();
if (result.success) {
isRecording = true;
} else {
alert(`Cannot record: ${result.error}`);
}
}
});
buttonElement.addEventListener('mouseup', async () => {
// ... (rest of mouseup logic) ...
});
}
```
--------------------------------
### Test Update and Get Setting
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Tests the ability to update a specific setting ('visuals.theme' to 'light') and then retrieve it to confirm the change.
```javascript
console.log('Test 2: Update Setting');
settingsManager.update('visuals', 'theme', 'light');
assert.strictEqual(settingsManager.get('visuals', 'theme'), 'light', 'Theme should be updated to light');
console.log('โ
Update Setting verified.');
```
--------------------------------
### Setup UI Event Listeners
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Initializes event listeners for various UI elements including settings toggles, room selection, adding new rooms, adding items, and initiating ritual mode. It also sets up a custom event listener for UI refreshes.
```javascript
// --- SETTINGS & UI WIRING ---
function setupEventListeners() {
// Top Menu
document.getElementById('btn-settings').addEventListener('click', toggleSettings);
document.getElementById('close-settings').addEventListener('click', toggleSettings);
// Room Controls
const roomSelect = document.getElementById('room-select');
if (roomSelect) {
roomSelect.addEventListener('change', (e) => {
currentRoomId = e.target.value;
currentLociIndex = 0;
renderCarousel(currentRoomId);
});
}
const btnNewRoom = document.getElementById('btn-new-room');
if (btnNewRoom) {
btnNewRoom.addEventListener('click', () => {
const name = prompt("Name your new Room (e.g., 'The Library')");
if (name) {
const newRoom = stateManager.createRoom(name);
if (newRoom) {
refreshRoomList();
currentRoomId = newRoom.id;
currentLociIndex = 0;
renderCarousel(currentRoomId);
} else {
alert("Room already exists or invalid name.");
}
}
});
}
const btnAddItem = document.getElementById('btn-add-item');
if (btnAddItem) {
btnAddItem.addEventListener('click', () => {
openEditModal(null); // Create Mode
});
}
// Ritual Mode Button
document.getElementById('btn-ritual').addEventListener('click', () => {
const ritual = new RitualMode();
ritual.start();
});
// Custom Event Listener for Ritual Completion (Refresh UI)
document.addEventListener('dome:refresh', () => {
renderCarousel(currentRoomId);
});
}
```
--------------------------------
### Check for .env file
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
This script checks if the .env file exists in the current directory. If not, it prints a warning message instructing the user to create it from the example file and add their keys.
```bash
if [ ! -f .env ]; then
echo "โ ๏ธ Warning: .env file not found. Please copy .env.example to .env and add your keys."
fi
```
--------------------------------
### Handle 'toggle' recording mode
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/delta_snapshot.txt
This snippet manages the 'toggle' mode for audio recording. It checks the recording state and updates the UI accordingly, logging the start or stop action.
```javascript
const mode = settingsManager.get('audio', 'recordingMode');
if (mode === 'toggle') {
if (!isRecording) {
console.log("๐ค Toggle Mode: Start");
buttonElement.classList.add('recording');
} else {
console.log("๐ค Toggle Mode: Stop");
buttonElement.classList.remove('recording');
}
}
```
--------------------------------
### Start Recording for Whisper STT
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Initiates audio recording using the MediaDevices API, preparing for transcription with Whisper. Requires user permission for microphone access. Returns success status and any error encountered.
```javascript
async startRecording() {
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.error("Media Devices API not supported.");
return { success: false, error: "Media Devices API not supported in this browser." };
}
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.mediaRecorder = new MediaRecorder(stream);
this.audioChunks = [];
this.mediaRecorder.ondataavailable = (event) => {
this.audioChunks.push(event.data);
};
this.mediaRecorder.start();
console.log("๐๏ธ Recording started...");
return { success: true };
} catch (err) {
console.error("Could not start recording:", err);
return { success: false, error: err.message };
}
}
```
--------------------------------
### Start Carousel Tour Mode
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Initiates an automatic carousel tour by setting an interval to advance to the next item every 3 seconds. Requires at least two items in the room.
```javascript
function startTour() {
if (tourInterval) return;
const room = stateManager.getRoom(currentRoomId);
if (!room || room.items.length < 2) return;
tourInterval = setInterval(() => {
nextLoci();
}, 3000);
renderCarousel(currentRoomId);
}
```
--------------------------------
### Toggle Carousel Tour Mode
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Switches between starting and stopping the carousel tour mode. If a tour is active, it stops; otherwise, it starts.
```javascript
function toggleTour() {
if (tourInterval) stopTour();
else startTour();
}
```
--------------------------------
### Clone Repository and Navigate
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/README.md
Clone the dome-icile repository and change into the project directory.
```bash
git clone https://github.com/yourusername/dome-icile.git
cd dome-icile
```
--------------------------------
### Initialize Ritual Walk UI
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Sets up the event listener for closing the ritual walk and displays the overlay.
```javascript
document.getElementById('ritual-close').addEventListener('click', () => this.end());
this.overlay.classList.remove('hidden');
```
--------------------------------
### Run the Application with Python
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/dev/guides/getting_started.md
Serve the application using Python's built-in HTTP server. This is necessary due to the use of ES6 Modules.
```bash
./startup.sh
# Or manually: python3 -m http.server 8000
```
--------------------------------
### Agent Roster Table
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Defines the different agent personas used in the codebase simulation, outlining their roles, focus areas, and guiding mantras for decision-making.
```markdown
| Agent | Role | Focus | Mantra |
| :--- | :--- | :--- | :--- |
| **Brain** | Team Lead | Consensus | "Synthesize and Decide." |
| **Bolt** | Performance | Speed/Latency | "Speed is a feature." |
| **Boom** | Product | Features/Shipping | "Ship it." |
| **Sentinel** | Security | Safety/Validation | "Trust nothing." |
| **Palette** | UX/UI | Accessibility/Feel | "Make it feel human." |
| **Scribe** | Docs | Maintainability | "Write it down." |
| **Scope** | QA | Edge Cases | "Everything breaks." |
| **Orbit** | DevOps | Infra/Stability | "Works in Prod." |
```
--------------------------------
### Test Default Settings
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Verifies that the settings manager initializes with the correct default settings, specifically checking the 'dark' theme.
```javascript
console.log('Test 1: Default Settings');
const defaultSettings = settingsManager.settings;
assert.strictEqual(defaultSettings.visuals.theme, 'dark', 'Default theme should be dark');
console.log('โ
Default settings verified.');
```
--------------------------------
### Run Unit Tests
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/dev/guides/getting_started.md
Execute the Node.js scripts for unit testing the project's logic, including SRS math and persistence.
```bash
# Test the SRS Math
node test_srs.mjs
# Test Persistence
node test_persistence.mjs
```
--------------------------------
### Project Directory Structure
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Overview of the main directories and files within the dome-icile project.
```bash
dome-icile/
โโโ src/ # Source code
โ โโโ app.js # Main controller (UI, Events)
โ โโโ state_manager.js # Data persistence (Rooms, Items)
โ โโโ settings_manager.js # User preferences
โ โโโ srs_engine.js # Spaced Repetition Logic (Math)
โ โโโ audio_engine.js # TTS and STT Logic
โ โโโ prompt_engine.js # AI Prompt Logic (Foundry)
โ โโโ gatekeeper.js # Quiz Logic
โ โโโ styles.css # Global Styles
โโโ dev/ # Developer Documentation (You are here)
โโโ verification/ # Playwright E2E tests
โโโ test_*.mjs # Unit tests (Node.js)
โโโ index.html # Main entry point
โโโ AGENTS.md # The "Persona" workflow protocol
```
--------------------------------
### Launch Quiz Modal
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Displays the quiz modal, populates it with the question, and initiates text-to-speech for the question if the audio engine is enabled. Clears previous interaction elements and feedback.
```javascript
// --- QUIZ LOGIC ---
function launchQuizModal(quiz) {
const modal = document.getElementById('quiz-modal');
const questionEl = document.getElementById('quiz-question');
const area = document.getElementById('quiz-interaction-area');
const submitBtn = document.getElementById('btn-quiz-submit');
const feedback = document.getElementById('quiz-feedback');
// TTS: Speak the question if engine is enabled
// Only if not already speaking to avoid chaos
const ttsEngine = settingsManager.get('audio', 'engine');
// Simple check: if we are using browser/hybrid, we can try to speak
// Note: Chrome requires user interaction first, so this might fail if it's the very first action
audioEngine.speak(quiz.question);
questionEl.innerText = quiz.question;
area.innerHTML = ''; // Clear previous
feedback.innerText = '';
```
--------------------------------
### Initialize and Load State (State Manager)
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Manages the persistence of the memory palace state using localStorage. Initializes default rooms and items if no saved state is found.
```javascript
/**
* state_manager.js
* Manages the persistence of the memory palace.
* Handles room data, item stats (streaks, last reviewed), and localStorage sync.
*/
import { calculateNextReview, DEFAULT_SRS_STATS } from './srs_engine.js';
const DEFAULT_ROOMS = {
'foyer': {
id: 'foyer',
name: 'The Foyer',
items: [
{ id: 1, concept: 'Mitochondria', visualURL: 'https://placehold.co/200x200/222/bb86fc?text=Powerhouse', visual: 'Powerhouse', streak: 0, lastReviewed: 0, ...DEFAULT_SRS_STATS, dueDate: 0 },
{ id: 2, concept: 'Nucleus', visualURL: 'https://placehold.co/200x200/222/03dac6?text=Brain', visual: 'Brain', streak: 0, lastReviewed: 0, ...DEFAULT_SRS_STATS, dueDate: 0 },
{ id: 3, concept: 'Ribosome', visualURL: 'https://placehold.co/200x200/222/cf6679?text=Chef', visual: 'Chef', streak: 0, lastReviewed: 0, ...DEFAULT_SRS_STATS, dueDate: 0 }
]
}
};
class StateManager {
constructor() {
this.storageKey = 'dome_state';
this.state = this.loadState();
}
loadState() {
if (typeof localStorage === 'undefined') return { rooms: DEFAULT_ROOMS };
const saved = localStorage.getItem(this.storageKey);
if (!saved) {
// Seed defaults
return { rooms: DEFAULT_ROOMS };
}
return JSON.parse(saved);
}
saveState() {
if (typeof localStorage !== 'undefined') {
localStorage.setItem(this.storageKey, JSON.stringify(this.state));
}
}
getRoom(roomId) {
return this.state.rooms[roomId];
}
/**
* Updates an item's stats after a review/quiz.
* @param {string} roomId
* @param {number|string} itemId
* @param {number} grade - 0-5 grade or simple boolean (legacy)
*/
updateItemStats(roomId, itemId, grade) {
const room = this.state.rooms[roomId];
if (!room) return;
const itemIndex = room.items.findIndex(i => i.id == itemId);
if (itemIndex === -1) return;
let item = room.items[itemIndex];
// Handle Legacy Boolean Calls (from old Quiz mode)
// If success (true) -> Grade 4. If failure (false) -> Grade 1.
if (typeof grade === 'boolean') {
grade = grade ? 4 : 1;
}
// Apply SRS logic
const currentStats = {
interval: item.interval || 0,
repetition: item.repetition || 0,
easeFactor: item.easeFactor || 2.5
};
const newStats = calculateNextReview(currentStats, grade);
// Merge updates
item = { ...item, ...newStats, lastReviewed: Date.now() };
// Legacy support: Keep 'streak' roughly aligned with 'repetition'
item.streak = item.repetition;
// Commit change
this.state.rooms[roomId].items[itemIndex] = item;
}
}
```
--------------------------------
### Audio Engine Class Initialization
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Initializes the AudioEngine class, setting up priorities for Text-to-Speech (OpenAI, eSpeak, Web Speech API) and Speech-to-Text (OpenAI Whisper, Web Speech Recognition). It checks for browser support for speech synthesis and recognition.
```javascript
/**
* audio_engine.js
* The voice of dome-icile.
* * Capabilities:
* 1. Text-to-Speech (TTS):
* - Priority 1: OpenAI (High quality, paid)
* - Priority 2: eSpeak (Retro/Robot, free, via library)
* - Priority 3: Web Speech API (Browser native, free, offline)
* * 2. Speech-to-Text (STT):
* - Priority 1: OpenAI Whisper (High accuracy, paid)
* - Priority 2: Web Speech Recognition (Browser native, free)
*/
class AudioEngine {
constructor(config = {}) {
this.openAIKey = config.openAIKey || null;
this.useESpeak = config.useESpeak || false; // Toggle for retro robot voice
// Initialize Speech Synthesis (Check if supported)
if (typeof window !== 'undefined' && window.speechSynthesis) {
this.synth = window.speechSynthesis;
} else {
console.warn("AudioEngine: Speech Synthesis not supported in this environment.");
this.synth = null;
}
// Initialize Web Speech Recognition (Chrome/Edge/Safari support varies)
if (typeof window !== 'undefined') {
this.Recognition = window.SpeechRecognition || window.webkitSpeechRecognition;
this.recognizer = this.Recognition ? new this.Recognition() : null;
} else {
this.recognizer = null;
}
// MediaRecorder State
this.mediaRecorder = null;
this.audioChunks = [];
}
setConfig(config) {
if (config.openAIKey !== undefined) this.openAIKey = config.openAIKey;
// Update other config if needed
}
// --- TEXT TO SPEECH (The Palace Speaks to You) ---
async speak(text) {
console.log(`๐ Speaking: "${text}"`);
// 1. OpenAI TTS (If Key exists)
if (this.openAIKey) {
try {
await this.speakWithOpenAI(text);
return;
} catch (err) {
console.warn("โ ๏ธ OpenAI TTS failed, falling back...", err);
}
}
```
--------------------------------
### Mock Local Storage Implementation
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
A mock implementation of localStorage for testing purposes, providing basic getItem, setItem, and clear methods.
```javascript
const localStorageMock = (function() {
let store = {};
return {
getItem: function(key) {
return store[key] || null;
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
}
};
})();
// Assign to global
global.localStorage = localStorageMock;
```
--------------------------------
### Handle Quiz Modes (CHOICE and INPUT)
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Configures the quiz interface based on the quiz mode. For 'CHOICE' mode, it displays buttons for each choice. For 'INPUT' mode, it creates a text input field, sets up event listeners for the Enter key, and adds a microphone button for voice input.
```javascript
feedback.className = '';
if (quiz.mode === 'CHOICE') {
submitBtn.classList.add('hidden');
quiz.choices.forEach(choice => {
const btn = document.createElement('button');
btn.className = 'quiz-option-btn';
btn.innerText = choice;
btn.onclick = () => handleQuizAttempt(choice);
area.appendChild(btn);
});
} else {
// INPUT Mode
const input = document.createElement('input');
input.type = 'text';
input.id = 'quiz-input';
input.placeholder = 'Type the concept...';
input.autocomplete = 'off';
// Allow Enter key to submit
input.addEventListener('keyup', (e) => {
if (e.key === 'Enter') handleQuizAttempt(input.value);
});
area.appendChild(input);
// Add Mic Button for Quiz
const micContainer = document.createElement('div');
micContainer.style.marginTop = '10px';
const micBtn = document.createElement('button');
micBtn.className = 'btn small-btn';
micBtn.innerText = '๐๏ธ Answer';
micContainer.appendChild(micBtn);
area.appendChild(micContainer);
setupMicButton(micBtn, (text) => {
input.value = text;
// Optional: Auto-submit? Let's wait for user confirmation
});
submitBtn.classList.remove('hidden');
submitBtn.onclick = () => handleQuizAttempt(input.value);
// Focus input after a short delay for modal transition
setTimeout(() => input.focus(), 100);
}
modal.classList.remove('hidden');
toggleMainContent(true);
setModalFocus(modal.id);
```
--------------------------------
### Initialize Ritual Mode Overlay
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Initializes the ritual mode by filtering room items for those past their due date and rendering a full-screen overlay. If no items are due, it alerts the user.
```javascript
import { stateManager } from './state_manager.js';
import { settingsManager } from './settings_manager.js';
export class RitualMode {
constructor() {
this.overlay = null;
this.currentItems = [];
this.currentIndex = 0;
}
start() {
const roomId = 'foyer'; // Currently only one room
const room = stateManager.getRoom(roomId);
if (!room) return;
// Filter for Due Items
// "Dusty" = Due Date <= Now
const now = Date.now();
this.currentItems = room.items.filter(item => {
const dueDate = item.dueDate || 0;
return now >= dueDate;
});
if (this.currentItems.length === 0) {
alert("โจ The Palace is pristine. No rituals required at this time.");
return;
}
this.currentIndex = 0;
this.renderOverlay();
this.showItem(this.currentIndex);
}
renderOverlay() {
// Create full screen overlay if not exists
if (!this.overlay) {
this.overlay = document.createElement('div');
this.overlay.id = 'ritual-overlay';
this.overlay.className = 'ritual-overlay';
document.body.appendChild(this.overlay);
}
this.overlay.innerHTML = `
```
--------------------------------
### DOM Content Loaded Initialization
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Initializes the application by calling the `init` function once the DOM is fully loaded.
```javascript
// Initialize
document.addEventListener('DOMContentLoaded', init);
```
--------------------------------
### Theme Testing Suite
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Tests the application of dark, light, and matrix themes by mocking the document object and verifying CSS variable changes. Exits on failure.
```javascript
import { settingsManager } from './src/settings_manager.js';
console.log("๐งช Starting Theme Tests...");
// Mock document and documentElement
const styleMap = new Map();
global.document = {
documentElement: {
style: {
setProperty: (key, value) => {
styleMap.set(key, value);
}
}
},
body: {
dataset: {}
}
};
// Test 1: Apply Dark Theme (Default)
console.log("\nTest 1: Apply Dark Theme (Default)");
settingsManager.update('visuals', 'theme', 'dark');
settingsManager.applySettings();
const bgMain = styleMap.get('--bg-main');
if (bgMain === '#121212') {
console.log("โ
Dark theme applied correctly.");
} else {
console.error(`โ Failed to apply dark theme. --bg-main: ${bgMain}`);
process.exit(1);
}
// Test 2: Apply Light Theme
console.log("\nTest 2: Apply Light Theme");
settingsManager.update('visuals', 'theme', 'light');
settingsManager.applySettings();
const bgMainLight = styleMap.get('--bg-main');
if (bgMainLight === '#f5f5f5') {
console.log("โ
Light theme applied correctly.");
} else {
console.error(`โ Failed to apply light theme. --bg-main: ${bgMainLight}`);
process.exit(1);
}
// Test 3: Apply Matrix Theme
console.log("\nTest 3: Apply Matrix Theme");
settingsManager.update('visuals', 'theme', 'matrix');
settingsManager.applySettings();
const textMainMatrix = styleMap.get('--text-main');
if (textMainMatrix === '#00ff00') {
console.log("โ
Matrix theme applied correctly.");
} else {
console.error(`โ Failed to apply matrix theme. --text-main: ${textMainMatrix}`);
process.exit(1);
}
console.log("\n๐ All Theme Tests Passed!");
```
--------------------------------
### Default Application Settings
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Defines the default configuration for various application modules, including audio, gatekeeper, AI, and visuals.
```javascript
const DEFAULT_SETTINGS = {
audio: {
engine: 'browser', // 'browser', 'openai'
openAIKey: '',
recordingMode: 'toggle', // 'toggle', 'hold'
voiceId: 'default',
volume: 0.8
},
gatekeeper: {
mode: 'dynamic', // 'input', 'choice', 'dynamic'
scalingThreshold: 3
},
ai: {
geminiKey: '',
generationMode: 'text' // 'text' or 'image' (future)
},
visuals: {
decayStyle: 'both', // 'filter', 'texture', 'both', 'none'
dualCoding: 'hover', // 'always', 'hover'
theme: 'dark'
}
};
```
--------------------------------
### State Management Class
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Manages the application state, including rooms and items. Includes methods for updating, creating, and adding items, as well as resetting the state.
```javascript
class StateManager {
constructor() {
this.state = { rooms: DEFAULT_ROOMS };
this.saveState();
}
saveState() {
// Placeholder for actual save logic
console.log("State saved:", this.state);
}
/**
* Updates item details (Editing).
*/
updateItemDetails(roomId, itemId, newConcept, newVisualURL) {
const room = this.state.rooms[roomId];
if (!room) return;
const itemIndex = room.items.findIndex(i => i.id == itemId);
if (itemIndex === -1) return;
const item = room.items[itemIndex];
if (newConcept) item.concept = newConcept;
if (newVisualURL) item.visualURL = newVisualURL;
this.state.rooms[roomId].items[itemIndex] = item;
this.saveState();
return item;
}
/**
* Creates a new room.
*/
createRoom(name) {
const id = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)+/g, '');
if (this.state.rooms[id]) return null; // Already exists
const newRoom = {
id: id,
name: name,
items: []
};
this.state.rooms[id] = newRoom;
this.saveState();
return newRoom;
}
/**
* Adds a new item to a room.
*/
addItem(roomId, concept, visualURL) {
const room = this.state.rooms[roomId];
if (!room) return null;
const newItem = {
id: Date.now(),
concept: concept,
visualURL: visualURL || 'https://placehold.co/200x200?text=?',
visual: concept,
streak: 0,
lastReviewed: 0,
...DEFAULT_SRS_STATS,
dueDate: 0
};
room.items.push(newItem);
this.saveState();
return newItem;
}
/**
* Reset everything (Debug/Ritual)
*/
reset() {
this.state = { rooms: DEFAULT_ROOMS };
this.saveState();
}
}
export const stateManager = new StateManager();
```
--------------------------------
### Python Script for Codebase Ingestion
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
This script walks through a directory, filters out binary files and excluded directories/extensions, and generates a snapshot of the codebase. It also calculates and saves the differences (delta) between the current and previous snapshots.
```python
import os
import difflib
# Configuration
INGEST_DIR = "ingest"
SNAPSHOT_FILE = os.path.join(INGEST_DIR, "codebase_snapshot.txt")
DELTA_FILE = os.path.join(INGEST_DIR, "delta_snapshot.txt")
EXCLUDED_DIRS = {'.git', 'node_modules', '__pycache__', 'dist', 'build', 'ingest', '.github'}
EXCLUDED_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.ico', '.pyc', '.DS_Store', '.woff', '.woff2', '.ttf', '.eot'}
def is_text_file(filepath):
"""Simple check to avoid reading binary files based on extension."""
_, ext = os.path.splitext(filepath)
if ext.lower() in EXCLUDED_EXTENSIONS:
return False
# Additional check: try reading a small chunk
try:
with open(filepath, 'r', encoding='utf-8') as f:
f.read(1024)
return True
except UnicodeDecodeError:
return False
def generate_codebase_string(root_dir):
"""Walks the directory and generates the snapshot string."""
codebase_content = []
for root, dirs, files in os.walk(root_dir):
# Modify dirs in-place to skip ignored directories
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
# Sort for deterministic output
dirs.sort()
files.sort()
for file in files:
filepath = os.path.join(root, file)
# Skip excluded files
if any(filepath.endswith(ext) for ext in EXCLUDED_EXTENSIONS):
continue
# Skip the script itself if it's in the list (optional, but good practice)
if filepath.endswith("generate_ingest.py"):
pass # We include it usually, unless specified otherwise.
if is_text_file(filepath):
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
codebase_content.append("=" * 64)
codebase_content.append(f"File: {filepath}")
codebase_content.append("=" * 64)
codebase_content.append(content)
codebase_content.append("\n") # Add newline between files
except Exception as e:
print(f"Skipping file {filepath} due to error: {e}")
return "\n".join(codebase_content)
def main():
# Ensure ingest directory exists
if not os.path.exists(INGEST_DIR):
os.makedirs(INGEST_DIR)
print(f"Created directory: {INGEST_DIR}")
# Generate current state
current_state = generate_codebase_string(".")
# Read previous state if exists
previous_state = ""
if os.path.exists(SNAPSHOT_FILE):
try:
with open(SNAPSHOT_FILE, 'r', encoding='utf-8') as f:
previous_state = f.read()
except Exception as e:
print(f"Could not read previous snapshot: {e}")
# Calculate Diff
if previous_state:
print("Calculating diff...")
diff = difflib.unified_diff(
previous_state.splitlines(),
current_state.splitlines(),
fromfile='Previous Snapshot',
tofile='Current Snapshot',
lineterm=''
)
delta_content = "\n".join(diff)
if not delta_content:
delta_content = "No changes detected."
else:
delta_content = "Initial Snapshot - No previous state to diff against."
# Write Snapshot
print(f"Writing snapshot to {SNAPSHOT_FILE}...")
with open(SNAPSHOT_FILE, 'w', encoding='utf-8') as f:
f.write(current_state)
# Write Delta
print(f"Writing delta to {DELTA_FILE}...")
with open(DELTA_FILE, 'w', encoding='utf-8') as f:
f.write(delta_content)
print("Ingest generation complete.")
if __name__ == "__main__":
main()
```
--------------------------------
### Launch Quiz Modal
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/delta_snapshot.txt
Generates and displays a quiz in a modal dialog. Closes the modal after a short delay once the quiz is completed.
```javascript
function launchQuiz() {
const currentRoomId = stateManager.getCurrentRoomId();
const room = stateManager.getRoom(currentRoomId);
const quiz = activeGatekeeper.generateQuiz(room.items);
launchQuizModal(quiz);
}
function launchQuizModal(quiz) {
// ... (quiz modal setup code) ...
modal.classList.remove('hidden');
toggleMainContent(true);
setModalFocus(modal.id);
}
```
--------------------------------
### Wire Up Voice Input Button
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Sets up a microphone button to capture voice input and populate a specified input field with the transcribed text.
```javascript
// Wire up Mic
const micBtn = document.getElementById('btn-mic-concept');
setupMicButton(micBtn, (text) => {
document.getElementById('edit-concept').value = text;
});
```
--------------------------------
### Test Settings Persistence
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Confirms that settings updated via the manager are correctly persisted to localStorage and can be retrieved.
```javascript
console.log('Test 3: Persistence');
const saved = JSON.parse(localStorage.getItem('dome_settings'));
assert.strictEqual(saved.visuals.theme, 'light', 'LocalStorage should have the updated theme');
console.log('โ
Persistence verified.');
console.log('๐ All Settings Tests Passed!');
```
--------------------------------
### Complete Ritual Walk UI
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Displays a completion message and a button to return.
```javascript
this.overlay.innerHTML = `
โจ Ritual Complete
Your mind palace is fortified.
`;
document.getElementById('ritual-finish').onclick = () => this.end();
```
--------------------------------
### Open HTML File Based on OS
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
This shell script opens an 'index.html' file in the default web browser, adapting the command based on the detected operating system (Linux, macOS, Cygwin, MSYS). If the OS is not recognized, it prompts the user to open the file manually.
```shell
# Open based on OS
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
xdg-open index.html
elif [[ "$OSTYPE" == "darwin"* ]]; then
open index.html
elif [[ "$OSTYPE" == "cygwin" ]]; then
start index.html
elif [[ "$OSTYPE" == "msys" ]]; then
start index.html
else
echo "Could not detect OS to open browser automatically. Please open index.html manually."
fi
```
--------------------------------
### Handle Card Click for Gatekeeper Initialization
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Initiates the Gatekeeper for a specific item when its card is clicked, provided the card is marked as 'dusty'. It sets up the Gatekeeper with the item's concept and streak, then launches the quiz modal.
```javascript
function handleCardClick(item, cardElement) {
// If not dusty, do nothing (already clean)
if (!cardElement.classList.contains('dusty')) return;
const gkMode = settingsManager.get('gatekeeper', 'mode');
// Initialize Gatekeeper for this specific battle
activeGatekeeper = new Gatekeeper({
type: gkMode,
answer: item.concept,
history: { successes: item.streak || 0 }
});
currentTargetItem = { item, element: cardElement };
// Generate Quiz
const room = stateManager.getRoom(currentRoomId);
const quiz = activeGatekeeper.generateQuiz(room.items);
launchQuizModal(quiz);
}
```
--------------------------------
### Mock localStorage for Testing
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
A JavaScript mock implementation of the localStorage API, providing `getItem`, `setItem`, and `clear` methods. This is useful for testing JavaScript code that relies on localStorage without depending on the browser environment.
```javascript
// Mock localStorage
const localStorageMock = (function() {
let store = {};
return {
getItem: function(key) {
return store[key] || null;
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
}
};
})();
// Assign to global
global.localStorage = localStorageMock;
```
--------------------------------
### Microphone Button Event Handling
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Sets up event listeners for microphone buttons to handle 'hold' and 'toggle' recording modes. It manages recording start/stop, UI updates, and calls the onTranscript callback with the recognized text. Ensures existing signal controllers are aborted to prevent duplicate events.
```javascript
function setupMicButton(buttonElement, onTranscript) {
if (!buttonElement) return;
// Remove any existing signal to prevent duplicated events
if (buttonElement._abortController) {
buttonElement._abortController.abort();
}
buttonElement._abortController = new AbortController();
const signal = buttonElement._abortController.signal;
let isRecording = false;
buttonElement.addEventListener('mousedown', async () => {
const mode = settingsManager.get('audio', 'recordingMode');
if (mode === 'hold') {
console.log("๐ค Hold Mode: Start");
const result = await audioEngine.startRecording();
if (result.success) {
buttonElement.classList.add('recording');
buttonElement.innerText = '๐ด Listening...';
} else {
alert(`Cannot record: ${result.error}`);
}
}
}, { signal });
buttonElement.addEventListener('mouseup', async () => {
const mode = settingsManager.get('audio', 'recordingMode');
if (mode === 'hold') {
console.log("๐ค Hold Mode: Stop");
buttonElement.classList.remove('recording');
buttonElement.innerText = 'โณ Processing...';
try {
const text = await audioEngine.stopRecording();
buttonElement.innerText = '๐๏ธ';
onTranscript(text);
} catch (err) {
console.error(err);
buttonElement.innerText = 'โ ๏ธ Error';
}
}
}, { signal });
buttonElement.addEventListener('click', async () => {
const mode = settingsManager.get('audio', 'recordingMode');
if (mode === 'toggle') {
if (!isRecording) {
console.log("๐ค Toggle Mode: Start");
const result = await audioEngine.startRecording();
if (result.success) {
isRecording = true;
buttonElement.classList.add('recording');
buttonElement.innerText = '๐ด Stop';
} else {
alert(`Cannot record: ${result.error}`);
}
} else {
console.log("๐ค Toggle Mode: Stop");
isRecording = false;
buttonElement.classList.remove('recording');
buttonElement.innerText = 'โณ Processing...';
try {
const text = await audioEngine.stopRecording();
buttonElement.innerText = '๐๏ธ';
onTranscript(text);
} catch (err) {
console.error(err);
buttonElement.innerText = 'โ ๏ธ Error';
}
}
}
}, { signal });
}
```
--------------------------------
### Default SRS Statistics Initialization
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Defines the initial state for SuperMemo-2 (SM-2) spaced repetition algorithm statistics. These values are used for new or learning items.
```javascript
/**
* srs_engine.js
* Implements the SuperMemo-2 (SM-2) Spaced Repetition Algorithm.
* Adapted for the "Dome" (Mind Palace) context.
*/
// Default starting values
export const DEFAULT_SRS_STATS = {
interval: 0, // Days (0 means new/learning)
repetition: 0, // Successive successful reviews
```
--------------------------------
### SettingsManager Class for Application Configuration
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Manages application settings, including loading from localStorage, saving changes, and applying visual themes and decay styles to the DOM. It ensures settings are persisted and updated in real-time.
```javascript
class SettingsManager {
constructor() {
this.settings = this.loadSettings();
// In case we run in an environment without DOM (like tests), we skip applySettings
if (typeof document !== 'undefined') {
this.applySettings(); // Apply on load
}
}
loadSettings() {
if (typeof localStorage === 'undefined') return DEFAULT_SETTINGS;
const saved = localStorage.getItem('dome_settings');
return saved ? { ...DEFAULT_SETTINGS, ...JSON.parse(saved) } : DEFAULT_SETTINGS;
}
saveSettings(newSettings) {
this.settings = { ...this.settings, ...newSettings };
if (typeof localStorage !== 'undefined') {
localStorage.setItem('dome_settings', JSON.stringify(this.settings));
}
if (typeof document !== 'undefined') {
this.applySettings(); // Real-time update
}
console.log("โ๏ธ Settings saved & applied.");
}
// Apply logic to the DOM (CSS Variables, Classes, etc.)
applySettings() {
const root = document.documentElement;
const v = this.settings.visuals;
// 1. Apply Theme
const theme = THEMES[v.theme] || THEMES['dark'];
Object.entries(theme).forEach(([key, value]) => {
root.style.setProperty(key, value);
});
// 2. Apply Decay Styles via CSS Variables
if (v.decayStyle === 'filter' || v.decayStyle === 'both') {
root.style.setProperty('--decay-filter', '0.4');
root.style.setProperty('--decay-blur', '1px');
} else {
root.style.setProperty('--decay-filter', '0');
root.style.setProperty('--decay-blur', '0');
}
if (v.decayStyle === 'texture' || v.decayStyle === 'both') {
root.style.setProperty('--decay-texture-opacity', '0.3');
} else {
root.style.setProperty('--decay-texture-opacity', '0');
}
// 3. Dual Coding Mode
if (document.body) {
document.body.dataset.dualCoding = v.dualCoding; // Used by CSS to toggle visibility
}
}
// Helper to get a specific setting safely
get(category, key) {
return this.settings[category]?.[key];
}
// Update a specific setting and save
update(category, key, value) {
const currentCat = this.settings[category] || {};
const newCat = { ...currentCat, [key]: value };
this.saveSettings({ [category]: newCat });
}
}
export const settingsManager = new SettingsManager();
```
--------------------------------
### Spaced Repetition System (SRS) Algorithm Formulas
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Mathematical formulas for calculating the next review interval, repetition count, and ease factor based on user feedback.
```latex
n = n_{prev} + 1
```
```latex
I = \begin{cases}
1 & \text{if } n = 1 \\
6 & \text{if } n = 2 \\
\text{round}(I_{prev} \times EF_{prev}) & \text{if } n > 2
\end{cases}
```
```latex
n = 0
I = 1
```
```latex
EF' = EF_{prev} + (0.1 - (5 - q) \times (0.08 + (5 - q) \times 0.02))
```
```latex
\text{DueDate} = \text{Date.now()} + (I \times 24 \times 60 \times 60 \times 1000)
```
--------------------------------
### State Manager Data Model
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/dev/architecture/system_overview.md
Illustrates the JSON structure for storing room and item data within the State Manager.
```json
{
"rooms": {
"foyer": {
"id": "foyer",
"items": [ ... ]
}
}
}
```
--------------------------------
### Test Add Item Functionality
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Tests the functionality of adding a new item to a room and verifies its properties and presence in the room's item list.
```javascript
console.log('Test 5: Add Item');
const newItem = stateManager.addItem('the-library', 'Book of Secrets', 'http://book.img');
assert.ok(newItem, 'Item should be added');
assert.strictEqual(newItem.concept, 'Book of Secrets', 'Concept should match');
assert.strictEqual(newItem.streak, 0, 'Default streak should be 0');
assert.strictEqual(newItem.interval, 0, 'Default interval should be 0');
assert.ok(newItem.id > 0, 'ID should be generated');
const roomItems = stateManager.getRoom('the-library').items;
assert.strictEqual(roomItems.length, 1, 'Room should have 1 item');
assert.strictEqual(roomItems[0].id, newItem.id, 'Item should be in the room');
console.log('โ
Add Item verified.');
console.log('๐ All Tests Passed!');
```
--------------------------------
### Initialize and Attach Event Listeners for Modal
Source: https://github.com/mnemonice/mind-palace-dome/blob/main/ingest/codebase_snapshot.txt
Attaches static event listeners to modal elements once. Handles closing the modal and file uploads via FileReader for base64 encoding.
```javascript
// Attach static event listeners ONCE
modal.querySelector('.close-btn').addEventListener('click', () => {
modal.classList.add('hidden');
toggleMainContent(false);
});
// File Upload Handler
const fileInput = document.getElementById('edit-file-upload');
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (readerEvent) => {
document.getElementById('edit-url').value = readerEvent.target.result; // Base64
};
reader.readAsDataURL(file);
}
});
```