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
|
-
|
|
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
|
-
*
|
|
397
|
-
* @
|
|
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
|
-
|
|
401
|
-
|
|
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
|
|
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
|
|
533
|
+
`Database integrity check failed and automatic recovery was unsuccessful.\n` +
|
|
409
534
|
`Errors found:\n - ${errorList}\n\n` +
|
|
410
|
-
`
|
|
535
|
+
`Manual recovery options:\n` +
|
|
411
536
|
` 1. Restore from backup: jettypod work restore-backup latest\n` +
|
|
412
|
-
` 2.
|
|
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
|
};
|