jettypod 4.4.81 → 4.4.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -203,6 +203,8 @@ export function DragProvider({ children, renderDragOverlay }: DragProviderProps)
203
203
  pointerEvents: 'none',
204
204
  transform: 'scale(1.02)',
205
205
  boxShadow: '0 10px 30px rgba(0, 0, 0, 0.2)',
206
+ borderRadius: 8,
207
+ overflow: 'hidden',
206
208
  }}
207
209
  >
208
210
  {renderDragOverlay(draggedItem)}
package/lib/database.js CHANGED
@@ -390,27 +390,151 @@ async function waitForMigrations() {
390
390
  }
391
391
  }
392
392
 
393
+ /**
394
+ * Attempt to recover work.db from JSON snapshots
395
+ * Used when database is missing or corrupted
396
+ * @returns {Promise<{recovered: boolean, itemCount: number}>} Recovery result
397
+ */
398
+ async function recoverFromSnapshots() {
399
+ const snapshotsDir = path.join(getJettypodDir(), 'snapshots');
400
+ const jsonPath = path.join(snapshotsDir, 'work.json');
401
+
402
+ // Check if snapshots exist
403
+ if (!fs.existsSync(jsonPath)) {
404
+ return { recovered: false, itemCount: 0, reason: 'No snapshot file found' };
405
+ }
406
+
407
+ // Read and parse snapshot
408
+ let data;
409
+ try {
410
+ const jsonContent = fs.readFileSync(jsonPath, 'utf8');
411
+ data = JSON.parse(jsonContent);
412
+ } catch (err) {
413
+ return { recovered: false, itemCount: 0, reason: `Failed to read snapshot: ${err.message}` };
414
+ }
415
+
416
+ // Close existing connection and delete corrupted file
417
+ await closeDb();
418
+ const dbFilePath = getDbPath();
419
+
420
+ // Remove corrupted database files (including WAL files)
421
+ const filesToRemove = [dbFilePath, `${dbFilePath}-wal`, `${dbFilePath}-shm`];
422
+ for (const file of filesToRemove) {
423
+ if (fs.existsSync(file)) {
424
+ fs.unlinkSync(file);
425
+ }
426
+ }
427
+
428
+ // Reset singleton to force fresh connection
429
+ resetDb();
430
+
431
+ // Create fresh database (this will create schema)
432
+ const database = getDb();
433
+ await waitForMigrations();
434
+
435
+ // Import data from snapshot
436
+ const tableNames = Object.keys(data);
437
+ let totalItems = 0;
438
+
439
+ for (const tableName of tableNames) {
440
+ const rows = data[tableName];
441
+ if (!rows || rows.length === 0) continue;
442
+
443
+ // Get column names from first row
444
+ const columns = Object.keys(rows[0]);
445
+ const placeholders = columns.map(() => '?').join(', ');
446
+ const columnNames = columns.join(', ');
447
+ const insertSql = `INSERT INTO ${tableName} (${columnNames}) VALUES (${placeholders})`;
448
+
449
+ for (const row of rows) {
450
+ const values = columns.map(col => row[col]);
451
+ await new Promise((resolve, reject) => {
452
+ database.run(insertSql, values, (err) => {
453
+ if (err) reject(err);
454
+ else resolve();
455
+ });
456
+ });
457
+ totalItems++;
458
+ }
459
+ }
460
+
461
+ return { recovered: true, itemCount: totalItems };
462
+ }
463
+
393
464
  /**
394
465
  * Run startup validation checks on the database
395
466
  * Call this early in application startup to detect corruption before operations fail
396
- * @returns {Promise<void>} Resolves if database is healthy, rejects with details if corrupted
397
- * @throws {Error} If database is corrupted or schema is invalid
467
+ * Automatically attempts recovery from snapshots if corruption is detected
468
+ * @returns {Promise<void>} Resolves if database is healthy (or recovered), rejects if unrecoverable
469
+ * @throws {Error} If database is corrupted and recovery fails
398
470
  */
399
471
  async function validateOnStartup() {
400
- const database = getDb();
401
- await waitForMigrations();
472
+ // Check if database file is missing
473
+ const dbFilePath = getDbPath();
474
+ const dbMissing = !fs.existsSync(dbFilePath);
475
+
476
+ if (dbMissing) {
477
+ // Try to recover from snapshots
478
+ console.log('⚠️ Database file missing, attempting recovery from snapshots...');
479
+ const result = await recoverFromSnapshots();
480
+ if (result.recovered) {
481
+ console.log(`✅ Recovered ${result.itemCount} items from snapshots`);
482
+ return;
483
+ } else {
484
+ // No snapshots - just create fresh DB (getDb will do this)
485
+ console.log('ℹ️ No snapshots found, creating fresh database');
486
+ getDb();
487
+ await waitForMigrations();
488
+ return;
489
+ }
490
+ }
491
+
492
+ // Database exists - check integrity
493
+ let database;
494
+ try {
495
+ database = getDb();
496
+ await waitForMigrations();
497
+ } catch (err) {
498
+ // Failed to even open the database - try recovery
499
+ console.log('⚠️ Database failed to open, attempting recovery from snapshots...');
500
+ const result = await recoverFromSnapshots();
501
+ if (result.recovered) {
502
+ console.log(`✅ Recovered ${result.itemCount} items from snapshots`);
503
+ return;
504
+ }
505
+ throw new Error(
506
+ `Database failed to open and recovery failed.\n` +
507
+ `Original error: ${err.message}\n` +
508
+ `Recovery error: ${result.reason}\n\n` +
509
+ `Manual recovery options:\n` +
510
+ ` 1. Restore from backup: jettypod work restore-backup latest\n` +
511
+ ` 2. Check ~/.jettypod-backups/ for global backups`
512
+ );
513
+ }
402
514
 
403
- // Check file integrity first
515
+ // Check file integrity
404
516
  const integrity = await checkIntegrity(database);
405
517
  if (!integrity.ok) {
518
+ console.log('⚠️ Database corruption detected, attempting recovery from snapshots...');
519
+ const result = await recoverFromSnapshots();
520
+ if (result.recovered) {
521
+ console.log(`✅ Recovered ${result.itemCount} items from snapshots`);
522
+ // Verify the recovered database
523
+ const newDb = getDb();
524
+ const newIntegrity = await checkIntegrity(newDb);
525
+ if (newIntegrity.ok) {
526
+ return;
527
+ }
528
+ }
529
+
530
+ // Recovery failed or still corrupted
406
531
  const errorList = integrity.errors.join('\n - ');
407
532
  throw new Error(
408
- `Database integrity check failed. The database file may be corrupted.\n` +
533
+ `Database integrity check failed and automatic recovery was unsuccessful.\n` +
409
534
  `Errors found:\n - ${errorList}\n\n` +
410
- `Recovery options:\n` +
535
+ `Manual recovery options:\n` +
411
536
  ` 1. Restore from backup: jettypod work restore-backup latest\n` +
412
- ` 2. Rebuild from snapshots: The JSON snapshots in .jettypod/snapshots/ can restore your data\n` +
413
- ` 3. Check ~/.jettypod-backups/ for global backups`
537
+ ` 2. Check ~/.jettypod-backups/ for global backups`
414
538
  );
415
539
  }
416
540
 
@@ -428,6 +552,7 @@ module.exports = {
428
552
  validateSchema,
429
553
  checkIntegrity,
430
554
  validateOnStartup,
555
+ recoverFromSnapshots,
431
556
  dbPath, // Deprecated: use getDbPath() for dynamic path
432
557
  jettypodDir // Deprecated: use getJettypodDir() for dynamic path
433
558
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.81",
3
+ "version": "4.4.82",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {