Undo deletes rows in place instead of reloading the forecast

Perspective table is now created with index: 'pf_id'. Delete endpoints
return the pf_ids they removed; the client calls table.remove(pf_ids)
in undoEntry. Avoids the full /data refetch that dominated undo time.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-04-28 20:21:13 -04:00
parent bf85f11b5f
commit 6b69b00645
3 changed files with 19 additions and 6 deletions

View File

@ -49,7 +49,11 @@ module.exports = function(pool) {
await client.query(`DELETE FROM pf.log WHERE id = $1`, [logid]);
await client.query('COMMIT');
res.json({ message: 'Operation undone', rows_deleted: del.rowCount });
res.json({
message: 'Operation undone',
rows_deleted: del.rowCount,
pf_ids: del.rows.map(r => r.pf_id)
});
} catch (err) {
await client.query('ROLLBACK');
console.error(err);

View File

@ -164,7 +164,11 @@ module.exports = function(pool) {
[versionId]
);
await client.query('COMMIT');
res.json({ rows_deleted: delRows.rowCount, log_entries_deleted: delLog.rowCount });
res.json({
rows_deleted: delRows.rowCount,
log_entries_deleted: delLog.rowCount,
pf_ids: delRows.rows.map(r => r.pf_id)
});
} catch (err) {
await client.query('ROLLBACK');
throw err;
@ -377,7 +381,10 @@ module.exports = function(pool) {
);
await client.query('DELETE FROM pf.log WHERE id = $1', [logId]);
await client.query('COMMIT');
res.json({ rows_deleted: deleted.rowCount });
res.json({
rows_deleted: deleted.rowCount,
pf_ids: deleted.rows.map(r => r.pf_id)
});
} catch (err) {
await client.query('ROLLBACK');
throw err;

View File

@ -192,8 +192,8 @@ export default function Forecast() {
const worker = await perspective.worker()
workerRef.current = worker
tableRef.current = rowCount > 0
? await worker.table(buffer, { name: tableName })
: await worker.table([], { name: tableName })
? await worker.table(buffer, { name: tableName, index: 'pf_id' })
: await worker.table([], { name: tableName, index: 'pf_id' })
await viewer.load(worker)
@ -386,8 +386,10 @@ export default function Forecast() {
const data = await res.json()
if (!res.ok) { flash(data.error, 'error'); return }
setLogEntries(prev => prev.filter(e => e.id !== logId))
if (data.pf_ids?.length && tableRef.current) {
await tableRef.current.remove(data.pf_ids)
}
flash(`Undone — ${data.rows_deleted} rows removed`)
initViewer(versionId, sourceId)
} catch (err) {
flash(err.message, 'error')
} finally {