Implement configuration-based mod management and fix navigation spacing

## Major Features Added

### Configuration-Based Mod Management
- Implement proper Luanti mod system using load_mod_* entries in world.mt
- Add mod enable/disable via configuration instead of file copying
- Support both global mods (config-enabled) and world mods (physically installed)
- Clear UI distinction with badges: "Global (Enabled)", "World Copy", "Missing"
- Automatic registry verification to sync database with filesystem state

### Game ID Alias System
- Fix minetest_game/minetest technical debt with proper alias mapping
- Map minetest_game → minetest for world.mt files (matches Luanti internal behavior)
- Reference: c9d4c33174/src/content/subgames.cpp (L21)

### Navigation Improvements
- Fix navigation menu spacing and text overflow issues
- Change "Configuration" to "Config" for better fit
- Implement responsive font sizing with clamp() for better scaling
- Even distribution of nav buttons across full width

### Package Registry Enhancements
- Add verifyAndCleanRegistry() to automatically remove stale package entries
- Periodic verification (every 5 minutes) to keep registry in sync with filesystem
- Fix "already installed" errors for manually deleted packages
- Integration across dashboard, ContentDB, and installation workflows

## Technical Improvements

### Mod System Architecture
- Enhanced ConfigParser to handle load_mod_* entries in world.mt files
- Support for both configuration-based and file-based mod installations
- Proper mod type detection and management workflows
- Updated world details to show comprehensive mod information

### UI/UX Enhancements
- Responsive navigation with proper text scaling
- Improved mod management interface with clear action buttons
- Better visual hierarchy and status indicators
- Enhanced error handling and user feedback

### Code Quality
- Clean up gitignore to properly exclude runtime files
- Add package-lock.json for consistent dependency management
- Remove excess runtime database and log files
- Add .claude/ directory to gitignore

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Nathan Schneider
2025-09-08 10:31:08 -06:00
parent 2d3b1166fe
commit 8aadab1b50
12 changed files with 4047 additions and 92 deletions

View File

@@ -7,6 +7,8 @@ class PackageRegistry {
// If no dbPath provided, we'll set it during init based on current data directory
this.dbPath = dbPath;
this.db = null;
this.lastVerificationTime = 0;
this.verificationInterval = 5 * 60 * 1000; // 5 minutes
}
async init() {
@@ -277,6 +279,83 @@ class PackageRegistry {
});
}
}
async verifyAndCleanRegistry() {
if (!this.db) {
await this.init();
}
const fs = require('fs').promises;
return new Promise((resolve, reject) => {
this.db.all('SELECT * FROM installed_packages', async (err, rows) => {
if (err) {
reject(err);
return;
}
const toRemove = [];
for (const row of rows) {
try {
// Check if the package still exists at the recorded path
await fs.access(row.install_path);
// Additional verification for games/mods - check for key files
if (row.package_type === 'game') {
// Check for game.conf
const gameConfPath = require('path').join(row.install_path, 'game.conf');
await fs.access(gameConfPath);
} else if (row.package_type === 'mod') {
// Check for mod.conf or init.lua
const modConfPath = require('path').join(row.install_path, 'mod.conf');
const initLuaPath = require('path').join(row.install_path, 'init.lua');
try {
await fs.access(modConfPath);
} catch {
await fs.access(initLuaPath);
}
}
} catch (accessError) {
// Package directory or key files don't exist - mark for removal
console.log(`Package registry cleanup: Removing stale entry for ${row.author}/${row.name} (path not found: ${row.install_path})`);
toRemove.push(row.id);
}
}
// Remove stale entries
if (toRemove.length > 0) {
const placeholders = toRemove.map(() => '?').join(',');
this.db.run(`DELETE FROM installed_packages WHERE id IN (${placeholders})`, toRemove, (deleteErr) => {
if (deleteErr) {
console.error('Error cleaning up registry:', deleteErr);
reject(deleteErr);
} else {
console.log(`Package registry cleanup: Removed ${toRemove.length} stale entries`);
resolve(toRemove.length);
}
});
} else {
resolve(0);
}
});
});
}
async verifyIfNeeded() {
const now = Date.now();
if (now - this.lastVerificationTime > this.verificationInterval) {
try {
const cleaned = await this.verifyAndCleanRegistry();
this.lastVerificationTime = now;
return cleaned;
} catch (error) {
console.warn('Periodic registry verification failed:', error);
return 0;
}
}
return 0;
}
}
module.exports = PackageRegistry;