jettypod 4.4.80 → 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.
@@ -117,11 +117,9 @@ export function DragProvider({ children, renderDragOverlay }: DragProviderProps)
117
117
  }, []);
118
118
 
119
119
  const startDrag = useCallback((item: WorkItem, cardRect: DOMRect, pointerX: number, pointerY: number) => {
120
- console.log('[DragContext] startDrag called', { item: item.id, cardRect, pointerX, pointerY });
121
120
  // Calculate offset from pointer to card's top-left corner
122
121
  const offsetX = pointerX - cardRect.left;
123
122
  const offsetY = pointerY - cardRect.top;
124
- console.log('[DragContext] Setting drag state', { offsetX, offsetY, width: cardRect.width });
125
123
  setDragOffset({ x: offsetX, y: offsetY });
126
124
  setDragPosition({ x: pointerX, y: pointerY });
127
125
  setDraggedCardWidth(cardRect.width);
@@ -171,16 +169,6 @@ export function DragProvider({ children, renderDragOverlay }: DragProviderProps)
171
169
  const overlayX = dragPosition.x - dragOffset.x;
172
170
  const overlayY = dragPosition.y - dragOffset.y;
173
171
 
174
- // Debug: log portal render conditions
175
- console.log('[DragContext] Portal render check', {
176
- hasDraggedItem: !!draggedItem,
177
- hasRenderOverlay: !!renderDragOverlay,
178
- hasDocument: typeof document !== 'undefined',
179
- overlayX,
180
- overlayY,
181
- draggedCardWidth,
182
- });
183
-
184
172
  return (
185
173
  <DragContext.Provider
186
174
  value={{
@@ -215,32 +203,11 @@ export function DragProvider({ children, renderDragOverlay }: DragProviderProps)
215
203
  pointerEvents: 'none',
216
204
  transform: 'scale(1.02)',
217
205
  boxShadow: '0 10px 30px rgba(0, 0, 0, 0.2)',
218
- border: '4px solid red',
219
- background: 'rgba(255, 0, 0, 0.1)',
220
- }}
221
- >
222
- {renderDragOverlay(draggedItem)}
223
- </div>,
224
- document.body
225
- )
226
- }
227
- {/* TEST: Always-visible portal to verify createPortal works */}
228
- {typeof document !== 'undefined' &&
229
- createPortal(
230
- <div
231
- style={{
232
- position: 'fixed',
233
- bottom: 20,
234
- right: 20,
235
- padding: '10px 20px',
236
- background: 'green',
237
- color: 'white',
238
- fontWeight: 'bold',
239
- zIndex: 9999,
240
206
  borderRadius: 8,
207
+ overflow: 'hidden',
241
208
  }}
242
209
  >
243
- Portal Works! Dragging: {draggedItem ? `#${draggedItem.id}` : 'none'}
210
+ {renderDragOverlay(draggedItem)}
244
211
  </div>,
245
212
  document.body
246
213
  )
@@ -21,14 +21,10 @@ export function DraggableCard({ item, children, disabled = false }: DraggableCar
21
21
  }
22
22
 
23
23
  const handleDragStart = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
24
- console.log('[DraggableCard] handleDragStart fired', { itemId: item.id, point: info.point });
25
24
  wasDraggingRef.current = true;
26
25
  if (cardRef.current) {
27
26
  const rect = cardRef.current.getBoundingClientRect();
28
- console.log('[DraggableCard] Card rect:', rect);
29
27
  startDrag(item, rect, info.point.x, info.point.y);
30
- } else {
31
- console.warn('[DraggableCard] cardRef.current is null!');
32
28
  }
33
29
  };
34
30
 
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.80",
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": {