Commit ebff2c2
authored
feat: add record diff command for replayed screen diffs (#170)
* chore: bump overrides to clear high-severity audit advisories
aube audit --audit-level high (run by CI) reports 8 high advisories in
transitive dev dependencies: brace-expansion (<5.0.9), js-yaml (<4.3.1),
nanoid (<3.3.18), and postcss (<=8.5.17). Bump the existing overrides
block to the fixed versions; each package resolves to a single version
in the tree, so the bumps are mechanical.
* feat: add record diff command for replayed screen diffs
computeScreenHash can only say whether two screens differ. record diff
replays two session event logs offline, extracts the canonical visible
screens, and emits an LCS line diff (plus per-side screen hashes) as a
JSON envelope and a unified-style human diff. --at-seq-a/--at-seq-b
target intermediate Event Log sequences, so a session can be diffed
against its own earlier state.
* fix: address record diff review findings
- Synthesize the valid initial blank screen for sessions whose event
log is still empty (backends reject snapshot() before the first
replayed event), so record diff works on freshly created sessions.
- Bound the LCS DP table by total cell count (4M) in addition to the
per-side line cap, keeping worst-case memory bounded.
- Parse --at-seq-a/--at-seq-b with full-token number parsing so
malformed values like '1.5' or '2junk' are rejected as INVALID_INPUT
instead of being silently truncated.
* chore: relock aube after upstream repo transfer
The aube repository moved from endevco/aube to jdx/aube. GitHub
attestation lookups via the old path now find nothing, so mise
2026.8.x fails 'mise install --locked' with 'Lockfile requires
github-attestations provenance ... but verification was not performed',
breaking every CI job at setup. Regenerate the aube lockfile entries
with current mise (mise lock aube): URLs now point at jdx/aube and the
unverifiable provenance pins are dropped. All artifact sha256 checksums
are unchanged, so the pinned binaries are identical.
* fix: address record diff round-2 review findings
- Reuse the first replay when both diff selectors are identical, so
'record diff <id> <id>' on an actively printing session cannot race
between two event-log reads and report spurious differences.
- Replace the hard DP-table cell bound with common prefix/suffix
trimming plus a non-minimal delete-then-add fallback for oversized
middles: every valid screen pair now diffs successfully instead of
crashing with an uncaught AssertionError.
* fix: append large diff middles iteratively
Spreading the fallback middle into push() exceeds V8's function-argument
limit for ~125k-entry middles, throwing RangeError instead of returning
the JSON envelope. Append iteratively.
* fix: tighten record diff schema contract
- Model diff entries as a strict discriminated union keyed by op:
equal requires both row indices, delete only aRow, add only bRow.
- Refine the result schema so identical is true exactly when both
screen hashes are equal, identical results carry an empty diff, and
non-identical results carry at least one delete or add entry.
* fix: report capturedAtSeq -1 for empty-event-log diff sides
The blank-screen fallback claimed capturedAtSeq 0, fabricating an event
sequence that was never replayed (and that --at-seq 0 cannot target
while the log is empty). Report -1, mirroring ReplayInput.targetSeq
semantics for an empty log, and widen the side schema accordingly.
* fix: address record diff round-6 review findings
- Parse --at-seq-a/--at-seq-b with a strict integer-token parser so
empty or whitespace-only values (e.g. an unset shell variable) are
rejected as INVALID_INPUT instead of resolving to sequence 0.
- Add an explicit phase boundary to the scrollback-demo fixture so the
completion marker lands in its own PTY chunk, keeping the record diff
e2e assertion deterministic regardless of PTY chunk coalescing.
* fix: bound record diff row coordinates by side dimensions
Diff entries index the padded visible screens, so aRow/bRow are always
less than the corresponding side's rows; refine the schema to reject
out-of-bounds coordinates.
* fix: require record diff rows to enumerate both screens in order
The diff is a complete traversal of both padded visible screens, so the
schema now requires participating A and B coordinates to each enumerate
0..rows-1 exactly once in order, letting consumers reconstruct either
side by filtering operations.
* fix: accept every input size in diffLines
Remove the per-side line cap: prefix/suffix trimming and the cell-
budget fallback already bound the quadratic DP table, and all other
work is linear, so no valid screen dimensions can crash record diff
with an uncaught AssertionError.
* fix: verify record diff hashes against reconstructed screens
- The result schema now reconstructs both sides from the diff traversal
and requires each declared screenHash to match the reconstruction, so
the diff, hashes, and identical flag can never contradict each other
after successful validation.
- Reject --at-seq tokens beyond Number.MAX_SAFE_INTEGER instead of
silently rounding them to a different sequence.
* fix: require equal row counts for identical record diff results
Equal screen hashes imply equal canonical line sequences, which have
one line per padded visible row, so identical results cannot report
different side dimensions.
* fix: address record diff round-12 review findings
- Refine the schema so a pre-event side (capturedAtSeq -1) must hash to
a blank screen of its declared row count.
- Replace the scrollback-demo wall-clock phase pause with an opt-in
stdin handshake (--wait-input-before-complete): the record diff e2e
now waits for an observed pre-completion Event Log state, releases
the fixture, and diffs against that sequence, eliminating the PTY
chunk-coalescing race entirely.
* fix: bound record diff side dimensions in the schema
Cap cols/rows at a generous ceiling so validation-time work (such as
hashing the blank pre-event screen) is proportional to a schema-checked
limit instead of attacker-controlled input.
* fix: keep record diff dimensions unrestricted, bound only hash work
Sessions may be created or resized to any positive dimensions, so the
side schema must not narrow the public contract. Drop the cols/rows
cap and instead skip the pre-event blank-hash equality check above a
100k-row work bound, keeping safeParse cost bounded without rejecting
contract-valid results.
* docs: state the record diff minimality bound explicitly
The LCS is exact for any realistic screen; when the trimmed differing
region exceeds the cell budget on both sides the diff degrades to a
delete-then-add block. Document that public semantics boundary —
identity, hashes, and reconstruction remain exact.
* docs: describe the diff minimality budget by its cell product
* fix: reject missing event logs before synthesizing blank screens
The host creates events.jsonl at startup, so an empty replay is only
authoritative when the zero-length log exists. A deleted or never-
written log now fails with REPLAY_ERROR instead of fabricating an
identical blank-screen result for lost recordings.
* fix: skip the LCS table for one-sided diff middles
An empty middle side needs no comparisons; construct the delete/add
entries directly so huge one-sided middles stay linear in memory
instead of allocating millions of one-element table rows.1 parent 2bc8370 commit ebff2c2
12 files changed
Lines changed: 1904 additions & 11 deletions
File tree
- docs
- src
- cli
- commands
- protocol
- util
- test
- e2e
- fixtures/apps/scrollback-demo
- integration
- unit
- commands
- protocol
- util
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
228 | 228 | | |
229 | 229 | | |
230 | 230 | | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
231 | 246 | | |
232 | 247 | | |
233 | 248 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
| |||
59 | 60 | | |
60 | 61 | | |
61 | 62 | | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
62 | 75 | | |
63 | 76 | | |
64 | 77 | | |
| |||
800 | 813 | | |
801 | 814 | | |
802 | 815 | | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
803 | 855 | | |
804 | 856 | | |
805 | 857 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
4 | 7 | | |
5 | 8 | | |
6 | 9 | | |
| |||
20 | 23 | | |
21 | 24 | | |
22 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
23 | 29 | | |
24 | 30 | | |
25 | 31 | | |
| |||
213 | 219 | | |
214 | 220 | | |
215 | 221 | | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
216 | 228 | | |
217 | 229 | | |
218 | 230 | | |
| |||
0 commit comments