Skip to content

Commit 13b4630

Browse files
Merge pull request #29 from New1Direction/feat/scan-ledger
feat: Scan Ledger — versioned scan history + trajectory UI
2 parents 36eb03b + 08ce263 commit 13b4630

16 files changed

Lines changed: 1649 additions & 33 deletions

backup.js

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@
88
// scan cache — round-trips through one human-readable JSON file.
99

1010
export const BACKUP_FORMAT = 'repolens-backup';
11-
export const BACKUP_VERSION = 1;
11+
export const BACKUP_VERSION = 2;
1212

1313
// Upper bounds on how much a single import may write, so a hostile or corrupt
1414
// file can't pin the IndexedDB write lock or blow the storage quota. Anything
1515
// past these is dropped with a surfaced warning (never silently).
16-
export const MAX_ROWS = { repos: 5000, nodes: 20000, edges: 50000, cache: 5000, collections: 2000, decisions: 5000 };
16+
export const MAX_ROWS = { repos: 5000, nodes: 20000, edges: 50000, cache: 5000, collections: 2000, decisions: 5000, snapshots: 5000 };
17+
18+
// Per-repo snapshot ring-buffer cap (mirrors SNAPSHOT_CAP in snapshots.js); each
19+
// imported snapshots row is trimmed to its most recent SNAP_CAP entries.
20+
const SNAP_CAP = 30;
1721

1822
const arr = (x) => (Array.isArray(x) ? x : []);
1923
const rowHasRepo = (r) => !!(r && r.id != null && r.payload && r.payload.repoId);
@@ -22,10 +26,11 @@ const edgeOk = (e) => !!(e && e.id != null && e.source != null && e.target != nu
2226
const cacheOk = (c) => !!(c && c.repoId && c.platform);
2327
const collectionOk = (c) => !!(c && c.id != null && c.payload && typeof c.payload.name === 'string');
2428
const decisionOk = (d) => !!(d && d.id != null && d.payload && d.payload.repoId && d.payload.decision);
29+
const snapshotOk = (r) => !!(r && r.id != null && r.repoId && Array.isArray(r.snaps));
2530

2631
/** Empty normalized shape — the safe fallback when a file can't be parsed. */
2732
function emptyValue() {
28-
return { repos: [], nodes: [], edges: [], cache: [], collections: [], decisions: [] };
33+
return { repos: [], nodes: [], edges: [], cache: [], collections: [], decisions: [], snapshots: [] };
2934
}
3035

3136
/**
@@ -34,19 +39,14 @@ function emptyValue() {
3439
* @param {{ repos?: object[], nodes?: object[], edges?: object[], cache?: object[], exportedAt?: string }} [parts]
3540
* @returns {object}
3641
*/
37-
export function buildBackup({ repos, nodes, edges, cache, collections, decisions, exportedAt } = {}) {
38-
const r = arr(repos), n = arr(nodes), e = arr(edges), c = arr(cache), col = arr(collections), dec = arr(decisions);
42+
export function buildBackup({ repos, nodes, edges, cache, collections, decisions, snapshots, exportedAt } = {}) {
43+
const r = arr(repos), n = arr(nodes), e = arr(edges), c = arr(cache), col = arr(collections), dec = arr(decisions), snap = arr(snapshots);
3944
return {
4045
format: BACKUP_FORMAT,
4146
version: BACKUP_VERSION,
4247
exportedAt: exportedAt || new Date().toISOString(),
43-
counts: { repos: r.length, nodes: n.length, edges: e.length, cache: c.length, collections: col.length, decisions: dec.length },
44-
repos: r,
45-
nodes: n,
46-
edges: e,
47-
cache: c,
48-
collections: col,
49-
decisions: dec,
48+
counts: { repos: r.length, nodes: n.length, edges: e.length, cache: c.length, collections: col.length, decisions: dec.length, snapshots: snap.length },
49+
repos: r, nodes: n, edges: e, cache: c, collections: col, decisions: dec, snapshots: snap,
5050
};
5151
}
5252

@@ -87,6 +87,7 @@ export function validateBackup(obj) {
8787
cache: clamp('cache', arr(obj.cache).filter(cacheOk)),
8888
collections: clamp('collections', arr(obj.collections).filter(collectionOk)),
8989
decisions: clamp('decisions', arr(obj.decisions).filter(decisionOk)),
90+
snapshots: clamp('snapshots', arr(obj.snapshots).filter(snapshotOk).map((r) => ({ ...r, snaps: arr(r.snaps).slice(-SNAP_CAP) }))),
9091
};
9192
return { ok: errors.length === 0, errors, warnings, value };
9293
}
@@ -99,7 +100,7 @@ export function validateBackup(obj) {
99100
*/
100101
export function summarizeBackup(obj) {
101102
const { value } = validateBackup(obj);
102-
return { repos: value.repos.length, nodes: value.nodes.length, edges: value.edges.length, cache: value.cache.length, collections: value.collections.length, decisions: value.decisions.length };
103+
return { repos: value.repos.length, nodes: value.nodes.length, edges: value.edges.length, cache: value.cache.length, collections: value.collections.length, decisions: value.decisions.length, snapshots: value.snapshots.length };
103104
}
104105

105106
/**

diff-analysis.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Pure helpers for "Diff Since I Last Looked" — compares two cached analysis snapshots.
22
// No DOM, no chrome APIs, unit-testable.
33

4-
const FIT_ORDER = ['strong', 'solid', 'care', 'risky'];
4+
export const FIT_ORDER = ['strong', 'solid', 'care', 'risky'];
55
const FIT_RANK = Object.fromEntries(FIT_ORDER.map((f, i) => [f, i]));
66

77
export function daysSince(isoTs) {

0 commit comments

Comments
 (0)